Refactored Neutron tempest plugin directory structure
* switch from neutron.tests.tempest to neutron_tempest_plugin
* Cleaned up README.rst and setup.cfg
* Use neutron_tempest_plugin as a tempest plugin package
* Fixed gitreview
* Keeping flake8 Ignores in tox.ini as tempest plugin is
imported from neutron codebase.
Change-Id: I42d389836e72813fdeebc797a577f4a8ac2ee603
diff --git a/neutron_tempest_plugin/api/__init__.py b/neutron_tempest_plugin/api/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/neutron_tempest_plugin/api/__init__.py
diff --git a/neutron_tempest_plugin/api/admin/__init__.py b/neutron_tempest_plugin/api/admin/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/neutron_tempest_plugin/api/admin/__init__.py
diff --git a/neutron_tempest_plugin/api/admin/test_agent_management.py b/neutron_tempest_plugin/api/admin/test_agent_management.py
new file mode 100644
index 0000000..72cba62
--- /dev/null
+++ b/neutron_tempest_plugin/api/admin/test_agent_management.py
@@ -0,0 +1,90 @@
+# Copyright 2013 IBM Corp.
+#
+# 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 neutron_tempest_plugin.common import tempest_fixtures
+from tempest.lib import decorators
+
+from neutron_tempest_plugin.api import base
+
+
+class AgentManagementTestJSON(base.BaseAdminNetworkTest):
+
+ required_extensions = ['agent']
+
+ @classmethod
+ def resource_setup(cls):
+ super(AgentManagementTestJSON, cls).resource_setup()
+ body = cls.admin_client.list_agents()
+ agents = body['agents']
+ cls.agent = agents[0] # don't modify this agent
+
+ @decorators.idempotent_id('9c80f04d-11f3-44a4-8738-ed2f879b0ff4')
+ def test_list_agent(self):
+ body = self.admin_client.list_agents()
+ agents = body['agents']
+ # Heartbeats must be excluded from comparison
+ self.agent.pop('heartbeat_timestamp', None)
+ self.agent.pop('configurations', None)
+ for agent in agents:
+ agent.pop('heartbeat_timestamp', None)
+ agent.pop('configurations', None)
+ self.assertIn(self.agent, agents)
+
+ @decorators.idempotent_id('e335be47-b9a1-46fd-be30-0874c0b751e6')
+ def test_list_agents_non_admin(self):
+ body = self.client.list_agents()
+ self.assertEqual(len(body["agents"]), 0)
+
+ @decorators.idempotent_id('869bc8e8-0fda-4a30-9b71-f8a7cf58ca9f')
+ def test_show_agent(self):
+ body = self.admin_client.show_agent(self.agent['id'])
+ agent = body['agent']
+ self.assertEqual(agent['id'], self.agent['id'])
+
+ @decorators.idempotent_id('371dfc5b-55b9-4cb5-ac82-c40eadaac941')
+ def test_update_agent_status(self):
+ origin_status = self.agent['admin_state_up']
+ # Try to update the 'admin_state_up' to the original
+ # one to avoid the negative effect.
+ agent_status = {'admin_state_up': origin_status}
+ body = self.admin_client.update_agent(agent_id=self.agent['id'],
+ agent_info=agent_status)
+ updated_status = body['agent']['admin_state_up']
+ self.assertEqual(origin_status, updated_status)
+
+ @decorators.idempotent_id('68a94a14-1243-46e6-83bf-157627e31556')
+ def test_update_agent_description(self):
+ agents = self.admin_client.list_agents()['agents']
+ try:
+ dyn_agent = agents[1]
+ except IndexError:
+ raise self.skipException("This test requires at least two agents.")
+
+ self.useFixture(tempest_fixtures.LockFixture('agent_description'))
+ description = 'description for update agent.'
+ agent_description = {'description': description}
+ body = self.admin_client.update_agent(agent_id=dyn_agent['id'],
+ agent_info=agent_description)
+ self.addCleanup(self._restore_agent, dyn_agent)
+ updated_description = body['agent']['description']
+ self.assertEqual(updated_description, description)
+
+ def _restore_agent(self, dyn_agent):
+ """
+ Restore the agent description after update test.
+ """
+ description = dyn_agent['description']
+ origin_agent = {'description': description}
+ self.admin_client.update_agent(agent_id=dyn_agent['id'],
+ agent_info=origin_agent)
diff --git a/neutron_tempest_plugin/api/admin/test_dhcp_agent_scheduler.py b/neutron_tempest_plugin/api/admin/test_dhcp_agent_scheduler.py
new file mode 100644
index 0000000..d0adcb8
--- /dev/null
+++ b/neutron_tempest_plugin/api/admin/test_dhcp_agent_scheduler.py
@@ -0,0 +1,108 @@
+# Copyright 2013 IBM Corp.
+#
+# 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 neutron_lib import constants
+from tempest.lib import decorators
+
+from neutron_tempest_plugin.api import base
+from neutron_tempest_plugin.common import utils
+
+
+class DHCPAgentSchedulersTestJSON(base.BaseAdminNetworkTest):
+
+ required_extensions = ['dhcp_agent_scheduler']
+
+ @classmethod
+ def resource_setup(cls):
+ super(DHCPAgentSchedulersTestJSON, cls).resource_setup()
+ # Create a network and make sure it will be hosted by a
+ # dhcp agent: this is done by creating a regular port
+ cls.network = cls.create_network()
+ cls.subnet = cls.create_subnet(cls.network)
+ cls.cidr = cls.subnet['cidr']
+ cls.port = cls.create_port(cls.network)
+
+ @decorators.idempotent_id('f164801e-1dd8-4b8b-b5d3-cc3ac77cfaa5')
+ def test_dhcp_port_status_active(self):
+
+ def dhcp_port_active():
+ for p in self.client.list_ports(
+ network_id=self.network['id'])['ports']:
+ if (p['device_owner'] == constants.DEVICE_OWNER_DHCP and
+ p['status'] == constants.PORT_STATUS_ACTIVE):
+ return True
+ return False
+ utils.wait_until_true(dhcp_port_active)
+
+ @decorators.idempotent_id('5032b1fe-eb42-4a64-8f3b-6e189d8b5c7d')
+ def test_list_dhcp_agent_hosting_network(self):
+ self.admin_client.list_dhcp_agent_hosting_network(
+ self.network['id'])
+
+ @decorators.idempotent_id('30c48f98-e45d-4ffb-841c-b8aad57c7587')
+ def test_list_networks_hosted_by_one_dhcp(self):
+ body = self.admin_client.list_dhcp_agent_hosting_network(
+ self.network['id'])
+ agents = body['agents']
+ self.assertIsNotNone(agents)
+ agent = agents[0]
+ self.assertTrue(self._check_network_in_dhcp_agent(
+ self.network['id'], agent))
+
+ def _check_network_in_dhcp_agent(self, network_id, agent):
+ network_ids = []
+ body = self.admin_client.list_networks_hosted_by_one_dhcp_agent(
+ agent['id'])
+ networks = body['networks']
+ for network in networks:
+ network_ids.append(network['id'])
+ return network_id in network_ids
+
+ @decorators.idempotent_id('a0856713-6549-470c-a656-e97c8df9a14d')
+ def test_add_remove_network_from_dhcp_agent(self):
+ # The agent is now bound to the network, we can free the port
+ self.client.delete_port(self.port['id'])
+ self.ports.remove(self.port)
+ agent = dict()
+ agent['agent_type'] = None
+ body = self.admin_client.list_agents()
+ agents = body['agents']
+ for a in agents:
+ if a['agent_type'] == 'DHCP agent':
+ agent = a
+ break
+ self.assertEqual(agent['agent_type'], 'DHCP agent', 'Could not find '
+ 'DHCP agent in agent list though dhcp_agent_scheduler'
+ ' is enabled.')
+ network = self.create_network()
+ network_id = network['id']
+ if self._check_network_in_dhcp_agent(network_id, agent):
+ self._remove_network_from_dhcp_agent(network_id, agent)
+ self._add_dhcp_agent_to_network(network_id, agent)
+ else:
+ self._add_dhcp_agent_to_network(network_id, agent)
+ self._remove_network_from_dhcp_agent(network_id, agent)
+
+ def _remove_network_from_dhcp_agent(self, network_id, agent):
+ self.admin_client.remove_network_from_dhcp_agent(
+ agent_id=agent['id'],
+ network_id=network_id)
+ self.assertFalse(self._check_network_in_dhcp_agent(
+ network_id, agent))
+
+ def _add_dhcp_agent_to_network(self, network_id, agent):
+ self.admin_client.add_dhcp_agent_to_network(agent['id'],
+ network_id)
+ self.assertTrue(self._check_network_in_dhcp_agent(
+ network_id, agent))
diff --git a/neutron_tempest_plugin/api/admin/test_extension_driver_port_security_admin.py b/neutron_tempest_plugin/api/admin/test_extension_driver_port_security_admin.py
new file mode 100644
index 0000000..60af89e
--- /dev/null
+++ b/neutron_tempest_plugin/api/admin/test_extension_driver_port_security_admin.py
@@ -0,0 +1,35 @@
+# Copyright 2015 Cisco Systems, Inc.
+# All Rights Reserved.
+#
+# 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 decorators
+from tempest.lib import exceptions as lib_exc
+
+from neutron_tempest_plugin.api import base
+from neutron_tempest_plugin.api import base_security_groups as base_security
+
+
+class PortSecurityAdminTests(base_security.BaseSecGroupTest,
+ base.BaseAdminNetworkTest):
+
+ required_extensions = ['port-security']
+
+ @decorators.attr(type='negative')
+ @decorators.idempotent_id('d39a96e2-2dea-4feb-8093-e7ac991ce6f8')
+ def test_create_port_security_false_on_shared_network(self):
+ network = self.create_shared_network()
+ self.assertTrue(network['shared'])
+ self.create_subnet(network, client=self.admin_client)
+ self.assertRaises(lib_exc.Forbidden, self.create_port,
+ network, port_security_enabled=False)
diff --git a/neutron_tempest_plugin/api/admin/test_external_network_extension.py b/neutron_tempest_plugin/api/admin/test_external_network_extension.py
new file mode 100644
index 0000000..cc1b2c2
--- /dev/null
+++ b/neutron_tempest_plugin/api/admin/test_external_network_extension.py
@@ -0,0 +1,196 @@
+# 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 oslo_config import cfg
+from tempest.lib.common.utils import data_utils
+from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
+import testtools
+
+from neutron_tempest_plugin.api import base
+
+
+class ExternalNetworksRBACTestJSON(base.BaseAdminNetworkTest):
+
+ credentials = ['primary', 'alt', 'admin']
+ required_extensions = ['rbac-policies']
+
+ @classmethod
+ def resource_setup(cls):
+ super(ExternalNetworksRBACTestJSON, cls).resource_setup()
+ cls.client2 = cls.os_alt.network_client
+
+ def _create_network(self, external=True):
+ post_body = {'name': data_utils.rand_name('network-')}
+ if external:
+ post_body['router:external'] = external
+ body = self.admin_client.create_network(**post_body)
+ network = body['network']
+ self.addCleanup(self.admin_client.delete_network, network['id'])
+ return network
+
+ @decorators.idempotent_id('afd8f1b7-a81e-4629-bca8-a367b3a144bb')
+ def test_regular_client_shares_with_another(self):
+ net = self.create_network()
+ self.client.create_rbac_policy(
+ object_type='network', object_id=net['id'],
+ action='access_as_external',
+ target_tenant=self.client2.tenant_id)
+ body = self.client2.list_networks()
+ networks_list = [n['id'] for n in body['networks']]
+ self.assertIn(net['id'], networks_list)
+ r = self.client2.create_router(
+ data_utils.rand_name('router-'),
+ external_gateway_info={'network_id': net['id']})['router']
+ self.addCleanup(self.admin_client.delete_router, r['id'])
+
+ @decorators.idempotent_id('eff9443a-2d04-48ee-840e-d955ac564bcd')
+ def test_regular_client_blocked_from_creating_external_wild_policies(self):
+ net = self.create_network()
+ with testtools.ExpectedException(lib_exc.Forbidden):
+ self.client.create_rbac_policy(
+ object_type='network', object_id=net['id'],
+ action='access_as_external',
+ target_tenant='*')
+
+ @decorators.idempotent_id('a2e19f06-48a9-4e4c-b717-08cb2008707d')
+ def test_wildcard_policy_created_from_external_network_api(self):
+ # create external makes wildcard
+ net_id = self._create_network(external=True)['id']
+ self.assertEqual(1, len(self.admin_client.list_rbac_policies(
+ object_id=net_id, action='access_as_external',
+ target_tenant='*')['rbac_policies']))
+ # update to non-external clears wildcard
+ self.admin_client.update_network(net_id, **{'router:external': False})
+ self.assertEqual(0, len(self.admin_client.list_rbac_policies(
+ object_id=net_id, action='access_as_external',
+ target_tenant='*')['rbac_policies']))
+ # create non-external has no wildcard
+ net_id = self._create_network(external=False)['id']
+ self.assertEqual(0, len(self.admin_client.list_rbac_policies(
+ object_id=net_id, action='access_as_external',
+ target_tenant='*')['rbac_policies']))
+ # update to external makes wildcard
+ self.admin_client.update_network(net_id, **{'router:external': True})
+ self.assertEqual(1, len(self.admin_client.list_rbac_policies(
+ object_id=net_id, action='access_as_external',
+ target_tenant='*')['rbac_policies']))
+
+ @decorators.idempotent_id('a5539002-5bdb-48b5-b124-abcd12347865')
+ def test_external_update_policy_from_wildcard_to_specific_tenant(self):
+ net_id = self._create_network(external=True)['id']
+ rbac_pol = self.admin_client.list_rbac_policies(
+ object_id=net_id, action='access_as_external',
+ target_tenant='*')['rbac_policies'][0]
+ r = self.client2.create_router(
+ data_utils.rand_name('router-'),
+ external_gateway_info={'network_id': net_id})['router']
+ self.addCleanup(self.admin_client.delete_router, r['id'])
+ # changing wildcard to specific tenant should be okay since its the
+ # only one using the network
+ self.admin_client.update_rbac_policy(
+ rbac_pol['id'], target_tenant=self.client2.tenant_id)
+
+ @decorators.idempotent_id('a5539002-5bdb-48b5-b124-e9eedd5975e6')
+ def test_external_conversion_on_policy_create(self):
+ net_id = self._create_network(external=False)['id']
+ self.admin_client.create_rbac_policy(
+ object_type='network', object_id=net_id,
+ action='access_as_external',
+ target_tenant=self.client2.tenant_id)
+ body = self.admin_client.show_network(net_id)['network']
+ self.assertTrue(body['router:external'])
+
+ @decorators.idempotent_id('01364c50-bfb6-46c4-b44c-edc4564d61cf')
+ def test_policy_allows_tenant_to_allocate_floatingip(self):
+ net = self._create_network(external=False)
+ # share to the admin client so it gets converted to external but
+ # not shared to everyone
+ self.admin_client.create_rbac_policy(
+ object_type='network', object_id=net['id'],
+ action='access_as_external',
+ target_tenant=self.admin_client.tenant_id)
+ self.create_subnet(net, client=self.admin_client, enable_dhcp=False)
+ with testtools.ExpectedException(lib_exc.NotFound):
+ self.client2.create_floatingip(
+ floating_network_id=net['id'])
+ self.admin_client.create_rbac_policy(
+ object_type='network', object_id=net['id'],
+ action='access_as_external',
+ target_tenant=self.client2.tenant_id)
+ self.client2.create_floatingip(
+ floating_network_id=net['id'])
+
+ @decorators.idempotent_id('476be1e0-f72e-47dc-9a14-4435926bbe82')
+ def test_policy_allows_tenant_to_attach_ext_gw(self):
+ net = self._create_network(external=False)
+ self.create_subnet(net, client=self.admin_client, enable_dhcp=False)
+ self.admin_client.create_rbac_policy(
+ object_type='network', object_id=net['id'],
+ action='access_as_external',
+ target_tenant=self.client2.tenant_id)
+ r = self.client2.create_router(
+ data_utils.rand_name('router-'),
+ external_gateway_info={'network_id': net['id']})['router']
+ self.addCleanup(self.admin_client.delete_router, r['id'])
+
+ @decorators.idempotent_id('d54decee-4203-4ced-91a2-ea42ca63e154')
+ def test_delete_policies_while_tenant_attached_to_net(self):
+ net = self._create_network(external=False)
+ self.create_subnet(net, client=self.admin_client, enable_dhcp=False)
+ wildcard = self.admin_client.create_rbac_policy(
+ object_type='network', object_id=net['id'],
+ action='access_as_external',
+ target_tenant='*')['rbac_policy']
+ r = self.client2.create_router(
+ data_utils.rand_name('router-'),
+ external_gateway_info={'network_id': net['id']})['router']
+ # delete should fail because the wildcard is required for the tenant's
+ # access
+ with testtools.ExpectedException(lib_exc.Conflict):
+ self.admin_client.delete_rbac_policy(wildcard['id'])
+ tenant = self.admin_client.create_rbac_policy(
+ object_type='network', object_id=net['id'],
+ action='access_as_external',
+ target_tenant=self.client2.tenant_id)['rbac_policy']
+ # now we can delete the policy because the tenant has its own policy
+ # to allow it access
+ self.admin_client.delete_rbac_policy(wildcard['id'])
+ # but now we can't delete the tenant's policy without the wildcard
+ with testtools.ExpectedException(lib_exc.Conflict):
+ self.admin_client.delete_rbac_policy(tenant['id'])
+ wildcard = self.admin_client.create_rbac_policy(
+ object_type='network', object_id=net['id'],
+ action='access_as_external',
+ target_tenant='*')['rbac_policy']
+ # with the wildcard added back we can delete the tenant's policy
+ self.admin_client.delete_rbac_policy(tenant['id'])
+ self.admin_client.delete_router(r['id'])
+ # now without the tenant attached, the wildcard can be deleted
+ self.admin_client.delete_rbac_policy(wildcard['id'])
+ # finally we ensure that the tenant can't attach to the network since
+ # there are no policies allowing it
+ with testtools.ExpectedException(lib_exc.NotFound):
+ self.client2.create_router(
+ data_utils.rand_name('router-'),
+ external_gateway_info={'network_id': net['id']})
+
+ @decorators.idempotent_id('7041cec7-d8fe-4c78-9b04-b51b2fd49dc9')
+ def test_wildcard_policy_delete_blocked_on_default_ext(self):
+ public_net_id = cfg.CONF.network.public_network_id
+ # ensure it is default before so we don't wipe out the policy
+ self.admin_client.update_network(public_net_id, is_default=True)
+ policy = self.admin_client.list_rbac_policies(
+ object_id=public_net_id, action='access_as_external',
+ target_tenant='*')['rbac_policies'][0]
+ with testtools.ExpectedException(lib_exc.Conflict):
+ self.admin_client.delete_rbac_policy(policy['id'])
diff --git a/neutron_tempest_plugin/api/admin/test_floating_ips_admin_actions.py b/neutron_tempest_plugin/api/admin/test_floating_ips_admin_actions.py
new file mode 100644
index 0000000..b0c5d41
--- /dev/null
+++ b/neutron_tempest_plugin/api/admin/test_floating_ips_admin_actions.py
@@ -0,0 +1,88 @@
+# Copyright 2014 OpenStack Foundation
+# All Rights Reserved.
+#
+# 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.common.utils import data_utils
+from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
+import testtools
+
+from neutron_tempest_plugin.api import base
+from neutron_tempest_plugin import config
+
+CONF = config.CONF
+
+
+class FloatingIPAdminTestJSON(base.BaseAdminNetworkTest):
+ force_tenant_isolation = True
+ credentials = ['primary', 'alt', 'admin']
+
+ @classmethod
+ def resource_setup(cls):
+ super(FloatingIPAdminTestJSON, cls).resource_setup()
+ cls.ext_net_id = CONF.network.public_network_id
+ cls.floating_ip = cls.create_floatingip(cls.ext_net_id)
+ cls.alt_client = cls.os_alt.network_client
+ cls.network = cls.create_network()
+ cls.subnet = cls.create_subnet(cls.network)
+ cls.router = cls.create_router(data_utils.rand_name('router-'),
+ external_network_id=cls.ext_net_id)
+ cls.create_router_interface(cls.router['id'], cls.subnet['id'])
+ cls.port = cls.create_port(cls.network)
+
+ @decorators.attr(type='negative')
+ @decorators.idempotent_id('11116ee9-4e99-5b15-b8e1-aa7df92ca589')
+ def test_associate_floating_ip_with_port_from_another_project(self):
+ body = self.client.create_floatingip(
+ floating_network_id=self.ext_net_id)
+ floating_ip = body['floatingip']
+ test_project = data_utils.rand_name('test_project_')
+ test_description = data_utils.rand_name('desc_')
+ project = self.identity_admin_client.create_project(
+ name=test_project, description=test_description)['project']
+ project_id = project['id']
+ self.addCleanup(self.identity_admin_client.delete_project, project_id)
+
+ port = self.admin_client.create_port(network_id=self.network['id'],
+ project_id=project_id)
+ self.addCleanup(self.admin_client.delete_port, port['port']['id'])
+ self.assertRaises(lib_exc.BadRequest,
+ self.client.update_floatingip,
+ floating_ip['id'], port_id=port['port']['id'])
+
+ @testtools.skipUnless(
+ CONF.neutron_plugin_options.specify_floating_ip_address_available,
+ "Feature for specifying floating IP address is disabled")
+ @decorators.idempotent_id('332a8ae4-402e-4b98-bb6f-532e5a87b8e0')
+ def test_create_floatingip_with_specified_ip_address(self):
+ # other tests may end up stealing the IP before we can use it
+ # since it's on the external network so we need to retry if it's
+ # in use.
+ for i in range(100):
+ fip = self.get_unused_ip(self.ext_net_id, ip_version=4)
+ try:
+ body = self.admin_client.create_floatingip(
+ floating_network_id=self.ext_net_id,
+ floating_ip_address=fip)
+ break
+ except lib_exc.Conflict:
+ pass
+ else:
+ self.fail("Could not get an unused IP after 100 attempts")
+ created_floating_ip = body['floatingip']
+ self.addCleanup(self.admin_client.delete_floatingip,
+ created_floating_ip['id'])
+ self.assertIsNotNone(created_floating_ip['id'])
+ self.assertIsNotNone(created_floating_ip['tenant_id'])
+ self.assertEqual(created_floating_ip['floating_ip_address'], fip)
diff --git a/neutron_tempest_plugin/api/admin/test_l3_agent_scheduler.py b/neutron_tempest_plugin/api/admin/test_l3_agent_scheduler.py
new file mode 100644
index 0000000..3981dfb
--- /dev/null
+++ b/neutron_tempest_plugin/api/admin/test_l3_agent_scheduler.py
@@ -0,0 +1,85 @@
+# Copyright 2013 IBM Corp.
+#
+# 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.common.utils import data_utils
+from tempest.lib import decorators
+
+from neutron_tempest_plugin.api import base
+from neutron_tempest_plugin import exceptions
+
+AGENT_TYPE = 'L3 agent'
+AGENT_MODES = (
+ 'legacy',
+ 'dvr_snat'
+)
+
+
+class L3AgentSchedulerTestJSON(base.BaseAdminNetworkTest):
+ _agent_mode = 'legacy'
+
+ """
+ Tests the following operations in the Neutron API using the REST client for
+ Neutron:
+
+ List routers that the given L3 agent is hosting.
+ List L3 agents hosting the given router.
+ Add and Remove Router to L3 agent
+
+ v2.0 of the Neutron API is assumed.
+
+ The l3_agent_scheduler extension is required for these tests.
+ """
+
+ required_extensions = ['l3_agent_scheduler']
+
+ @classmethod
+ def resource_setup(cls):
+ super(L3AgentSchedulerTestJSON, cls).resource_setup()
+ body = cls.admin_client.list_agents()
+ agents = body['agents']
+ for agent in agents:
+ # TODO(armax): falling back on default _agent_mode can be
+ # dropped as soon as Icehouse is dropped.
+ agent_mode = (
+ agent['configurations'].get('agent_mode', cls._agent_mode))
+ if agent['agent_type'] == AGENT_TYPE and agent_mode in AGENT_MODES:
+ cls.agent = agent
+ break
+ else:
+ msg = "L3 Agent Scheduler enabled in conf, but L3 Agent not found"
+ raise exceptions.InvalidConfiguration(msg)
+ cls.router = cls.create_router(data_utils.rand_name('router'))
+
+ @decorators.idempotent_id('b7ce6e89-e837-4ded-9b78-9ed3c9c6a45a')
+ def test_list_routers_on_l3_agent(self):
+ self.admin_client.list_routers_on_l3_agent(self.agent['id'])
+
+ @decorators.idempotent_id('9464e5e7-8625-49c3-8fd1-89c52be59d66')
+ def test_add_list_remove_router_on_l3_agent(self):
+ l3_agent_ids = list()
+ self.admin_client.add_router_to_l3_agent(
+ self.agent['id'],
+ self.router['id'])
+ body = (
+ self.admin_client.list_l3_agents_hosting_router(self.router['id']))
+ for agent in body['agents']:
+ l3_agent_ids.append(agent['id'])
+ self.assertIn('agent_type', agent)
+ self.assertEqual('L3 agent', agent['agent_type'])
+ self.assertIn(self.agent['id'], l3_agent_ids)
+ body = self.admin_client.remove_router_from_l3_agent(
+ self.agent['id'],
+ self.router['id'])
+ # NOTE(afazekas): The deletion not asserted, because neutron
+ # is not forbidden to reschedule the router to the same agent
diff --git a/neutron_tempest_plugin/api/admin/test_networks.py b/neutron_tempest_plugin/api/admin/test_networks.py
new file mode 100644
index 0000000..e57a7e8
--- /dev/null
+++ b/neutron_tempest_plugin/api/admin/test_networks.py
@@ -0,0 +1,87 @@
+# 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 testtools
+
+from oslo_utils import uuidutils
+from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
+from tempest import test
+
+from neutron_tempest_plugin.api import base
+from neutron_tempest_plugin import config
+
+
+class NetworksTestAdmin(base.BaseAdminNetworkTest):
+
+ @decorators.idempotent_id('d3c76044-d067-4cb0-ae47-8cdd875c7f67')
+ @test.requires_ext(extension="project-id", service="network")
+ def test_admin_create_network_keystone_v3(self):
+ project_id = self.client.tenant_id # non-admin
+
+ name = 'admin-created-with-project_id'
+ new_net = self.create_network_keystone_v3(name, project_id,
+ client=self.admin_client)
+ self.assertEqual(name, new_net['name'])
+ self.assertEqual(project_id, new_net['project_id'])
+ self.assertEqual(project_id, new_net['tenant_id'])
+
+ body = self.client.list_networks(id=new_net['id'])
+ lookup_net = body['networks'][0]
+ self.assertEqual(name, lookup_net['name'])
+ self.assertEqual(project_id, lookup_net['project_id'])
+ self.assertEqual(project_id, lookup_net['tenant_id'])
+
+ @decorators.idempotent_id('8d21aaca-4364-4eb9-8b79-44b4fff6373b')
+ @test.requires_ext(extension="project-id", service="network")
+ def test_admin_create_network_keystone_v3_and_tenant(self):
+ project_id = self.client.tenant_id # non-admin
+
+ name = 'created-with-project-and-tenant'
+ new_net = self.create_network_keystone_v3(
+ name, project_id, tenant_id=project_id, client=self.admin_client)
+ self.assertEqual(name, new_net['name'])
+ self.assertEqual(project_id, new_net['project_id'])
+ self.assertEqual(project_id, new_net['tenant_id'])
+
+ body = self.client.list_networks(id=new_net['id'])
+ lookup_net = body['networks'][0]
+ self.assertEqual(name, lookup_net['name'])
+ self.assertEqual(project_id, lookup_net['project_id'])
+ self.assertEqual(project_id, lookup_net['tenant_id'])
+
+ @decorators.idempotent_id('08b92179-669d-45ee-8233-ef6611190809')
+ @test.requires_ext(extension="project-id", service="network")
+ def test_admin_create_network_keystone_v3_and_other_tenant(self):
+ project_id = self.client.tenant_id # non-admin
+ other_tenant = uuidutils.generate_uuid()
+
+ name = 'created-with-project-and-other-tenant'
+ e = self.assertRaises(lib_exc.BadRequest,
+ self.create_network_keystone_v3, name,
+ project_id, tenant_id=other_tenant,
+ client=self.admin_client)
+ expected_message = "'project_id' and 'tenant_id' do not match"
+ self.assertEqual(expected_message, e.resp_body['message'])
+
+ @decorators.idempotent_id('571d0dde-0f84-11e7-b565-fa163e4fa634')
+ @testtools.skipUnless("vxlan" in config.CONF.neutron_plugin_options.
+ available_type_drivers,
+ 'VXLAN type_driver is not enabled')
+ @test.requires_ext(extension="provider", service="network")
+ def test_create_tenant_network_vxlan(self):
+ network = self.admin_client.create_network(
+ **{"provider:network_type": "vxlan"})['network']
+ self.addCleanup(self.admin_client.delete_network,
+ network['id'])
+ network = self.admin_client.show_network(
+ network['id'])['network']
+ self.assertEqual('vxlan', network['provider:network_type'])
diff --git a/neutron_tempest_plugin/api/admin/test_quotas.py b/neutron_tempest_plugin/api/admin/test_quotas.py
new file mode 100644
index 0000000..5c92be0
--- /dev/null
+++ b/neutron_tempest_plugin/api/admin/test_quotas.py
@@ -0,0 +1,162 @@
+# Copyright 2013 OpenStack Foundation
+# All Rights Reserved.
+#
+# 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 six
+from tempest.lib.common.utils import data_utils
+from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
+from tempest import test
+
+from neutron_tempest_plugin.api import base
+from neutron_tempest_plugin import config
+
+CONF = config.CONF
+
+
+class QuotasTestBase(base.BaseAdminNetworkTest):
+
+ required_extensions = ['quotas']
+
+ @classmethod
+ def resource_setup(cls):
+ super(QuotasTestBase, cls).resource_setup()
+
+ def _create_tenant(self):
+ # Add a tenant to conduct the test
+ test_tenant = data_utils.rand_name('test_tenant_')
+ test_description = data_utils.rand_name('desc_')
+ project = self.identity_admin_client.create_project(
+ name=test_tenant,
+ description=test_description)['project']
+ self.addCleanup(
+ self.identity_admin_client.delete_project, project['id'])
+ return project
+
+ def _setup_quotas(self, project_id, **new_quotas):
+ # Change quotas for tenant
+ quota_set = self.admin_client.update_quotas(project_id,
+ **new_quotas)
+ self.addCleanup(self._cleanup_quotas, project_id)
+ return quota_set
+
+ def _cleanup_quotas(self, project_id):
+ # Try to clean up the resources. If it fails, then
+ # assume that everything was already deleted, so
+ # it is OK to continue.
+ try:
+ self.admin_client.reset_quotas(project_id)
+ except lib_exc.NotFound:
+ pass
+
+ def _create_network(self, project_id):
+ network = self.create_network(client=self.admin_client,
+ tenant_id=project_id)
+ self.addCleanup(self.admin_client.delete_network,
+ network['id'])
+ return network
+
+ def _create_port(self, **kwargs):
+ port = self.admin_client.create_port(**kwargs)['port']
+ self.addCleanup(self.admin_client.delete_port,
+ port['id'])
+ return port
+
+
+class QuotasTest(QuotasTestBase):
+ """Test the Neutron API of Quotas.
+
+ Tests the following operations in the Neutron API using the REST client for
+ Neutron:
+
+ list quotas for tenants who have non-default quota values
+ show quotas for a specified tenant
+ show detail quotas for a specified tenant
+ update quotas for a specified tenant
+ reset quotas to default values for a specified tenant
+
+ v2.0 of the API is assumed.
+ It is also assumed that the per-tenant quota extension API is configured
+ in /etc/neutron/neutron.conf as follows:
+
+ quota_driver = neutron.db.driver.DbQuotaDriver
+ """
+
+ @decorators.attr(type='gate')
+ @decorators.idempotent_id('2390f766-836d-40ef-9aeb-e810d78207fb')
+ def test_quotas(self):
+ tenant_id = self._create_tenant()['id']
+ new_quotas = {'network': 0, 'security_group': 0}
+
+ # Change quotas for tenant
+ quota_set = self._setup_quotas(tenant_id, **new_quotas)
+ for key, value in new_quotas.items():
+ self.assertEqual(value, quota_set[key])
+
+ # Confirm our tenant is listed among tenants with non default quotas
+ non_default_quotas = self.admin_client.list_quotas()
+ found = False
+ for qs in non_default_quotas['quotas']:
+ if qs['tenant_id'] == tenant_id:
+ self.assertEqual(tenant_id, qs['project_id'])
+ found = True
+ self.assertTrue(found)
+
+ # Confirm from API quotas were changed as requested for tenant
+ quota_set = self.admin_client.show_quotas(tenant_id)
+ quota_set = quota_set['quota']
+ for key, value in new_quotas.items():
+ self.assertEqual(value, quota_set[key])
+
+ # Reset quotas to default and confirm
+ self.admin_client.reset_quotas(tenant_id)
+ non_default_quotas = self.admin_client.list_quotas()
+ for q in non_default_quotas['quotas']:
+ self.assertNotEqual(tenant_id, q['tenant_id'])
+
+ @decorators.idempotent_id('e974b5ba-090a-452c-a578-f9710151d9fc')
+ @decorators.attr(type='gate')
+ @test.requires_ext(extension="quota_details", service="network")
+ def test_detail_quotas(self):
+ tenant_id = self._create_tenant()['id']
+ new_quotas = {'network': {'used': 1, 'limit': 2, 'reserved': 0},
+ 'port': {'used': 1, 'limit': 2, 'reserved': 0}}
+
+ # update quota limit for tenant
+ new_quota = {'network': new_quotas['network']['limit'], 'port':
+ new_quotas['port']['limit']}
+ quota_set = self._setup_quotas(tenant_id, **new_quota)
+
+ # create test resources
+ network = self._create_network(tenant_id)
+ post_body = {"network_id": network['id'],
+ "tenant_id": tenant_id}
+ self._create_port(**post_body)
+
+ # confirm from extended API quotas were changed
+ # as requested for tenant
+ quota_set = self.admin_client.show_details_quota(tenant_id)
+ quota_set = quota_set['quota']
+ for key, value in six.iteritems(new_quotas):
+ self.assertEqual(new_quotas[key]['limit'],
+ quota_set[key]['limit'])
+ self.assertEqual(new_quotas[key]['reserved'],
+ quota_set[key]['reserved'])
+ self.assertEqual(new_quotas[key]['used'],
+ quota_set[key]['used'])
+
+ # validate 'default' action for old extension
+ quota_limit = self.admin_client.show_quotas(tenant_id)['quota']
+ for key, value in six.iteritems(new_quotas):
+ self.assertEqual(new_quotas[key]['limit'], quota_limit[key])
diff --git a/neutron_tempest_plugin/api/admin/test_quotas_negative.py b/neutron_tempest_plugin/api/admin/test_quotas_negative.py
new file mode 100644
index 0000000..8960f5f
--- /dev/null
+++ b/neutron_tempest_plugin/api/admin/test_quotas_negative.py
@@ -0,0 +1,175 @@
+# 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.common.utils import data_utils
+from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
+from tempest import test
+
+from neutron_tempest_plugin.api.admin import test_quotas
+from neutron_tempest_plugin import config
+
+CONF = config.CONF
+
+
+class QuotasAdminNegativeTestJSON(test_quotas.QuotasTestBase):
+
+ @decorators.attr(type='negative')
+ @decorators.idempotent_id('952f9b24-9156-4bdc-90f3-682a3d4302f0')
+ def test_create_network_when_quotas_is_full(self):
+ tenant_id = self._create_tenant()['id']
+ new_quotas = {'network': 1}
+ self._setup_quotas(tenant_id, **new_quotas)
+
+ net_args = {'tenant_id': tenant_id}
+ net = self.admin_client.create_network(**net_args)['network']
+ self.addCleanup(self.admin_client.delete_network, net['id'])
+
+ self.assertRaises(lib_exc.Conflict,
+ self.admin_client.create_network, **net_args)
+
+ @decorators.attr(type='negative')
+ @decorators.idempotent_id('0b7f99e3-9f77-45ce-9a89-b39a184de618')
+ def test_create_subnet_when_quotas_is_full(self):
+ tenant_id = self._create_tenant()['id']
+ new_quotas = {'subnet': 1}
+ self._setup_quotas(tenant_id, **new_quotas)
+
+ net_args = {'tenant_id': tenant_id}
+ net = self.admin_client.create_network(**net_args)['network']
+ self.addCleanup(self.admin_client.delete_network, net['id'])
+
+ subnet_args = {'tenant_id': tenant_id,
+ 'network_id': net['id'],
+ 'cidr': '10.0.0.0/24',
+ 'ip_version': '4'}
+ subnet = self.admin_client.create_subnet(**subnet_args)['subnet']
+ self.addCleanup(self.admin_client.delete_subnet, subnet['id'])
+
+ subnet_args['cidr'] = '10.1.0.0/24'
+ self.assertRaises(lib_exc.Conflict,
+ self.admin_client.create_subnet, **subnet_args)
+
+ @decorators.attr(type='negative')
+ @decorators.idempotent_id('fe20d9f9-346c-4a20-bbfa-d9ca390f4dc6')
+ def test_create_port_when_quotas_is_full(self):
+ tenant_id = self._create_tenant()['id']
+ new_quotas = {'port': 1}
+ self._setup_quotas(tenant_id, **new_quotas)
+
+ net_args = {'tenant_id': tenant_id}
+ net = self.admin_client.create_network(**net_args)['network']
+ self.addCleanup(self.admin_client.delete_network, net['id'])
+
+ subnet_args = {'tenant_id': tenant_id,
+ 'network_id': net['id'],
+ 'enable_dhcp': False,
+ 'cidr': '10.0.0.0/24',
+ 'ip_version': '4'}
+ subnet = self.admin_client.create_subnet(**subnet_args)['subnet']
+ self.addCleanup(self.admin_client.delete_subnet, subnet['id'])
+
+ port_args = {'tenant_id': tenant_id,
+ 'network_id': net['id']}
+ port = self.admin_client.create_port(**port_args)['port']
+ self.addCleanup(self.admin_client.delete_port, port['id'])
+
+ self.assertRaises(lib_exc.Conflict,
+ self.admin_client.create_port, **port_args)
+
+ @decorators.attr(type='negative')
+ @decorators.idempotent_id('bb1e9c3c-7e6f-41f1-b579-63dbc655ecb7')
+ @test.requires_ext(extension="router", service="network")
+ def test_create_router_when_quotas_is_full(self):
+ tenant_id = self._create_tenant()['id']
+ new_quotas = {'router': 1}
+ self._setup_quotas(tenant_id, **new_quotas)
+
+ name = data_utils.rand_name('test_router_')
+ router_args = {'tenant_id': tenant_id}
+ router = self.admin_client.create_router(
+ name, True, **router_args)['router']
+ self.addCleanup(self.admin_client.delete_router, router['id'])
+
+ self.assertRaises(lib_exc.Conflict,
+ self.admin_client.create_router,
+ name, True, **router_args)
+
+ @decorators.attr(type='negative')
+ @decorators.idempotent_id('5c924ff7-b7a9-474f-92a3-dbe0f976ec13')
+ @test.requires_ext(extension="security-group", service="network")
+ def test_create_security_group_when_quotas_is_full(self):
+ tenant_id = self._create_tenant()['id']
+ sg_args = {'tenant_id': tenant_id}
+ # avoid a number that is made by default
+ sg_list = self.admin_client.list_security_groups(
+ tenant_id=tenant_id)['security_groups']
+ num = len(sg_list) + 1
+
+ new_quotas = {'security_group': num}
+ self._setup_quotas(tenant_id, **new_quotas)
+
+ sg = self.admin_client.create_security_group(
+ **sg_args)['security_group']
+ self.addCleanup(self.admin_client.delete_security_group, sg['id'])
+
+ self.assertRaises(lib_exc.Conflict,
+ self.admin_client.create_security_group, **sg_args)
+
+ @decorators.attr(type='negative')
+ @decorators.idempotent_id('b7143480-6118-4ed4-be38-1b6f15f30d05')
+ @test.requires_ext(extension="security-group", service="network")
+ def test_create_security_group_rule_when_quotas_is_full(self):
+ tenant_id = self._create_tenant()['id']
+ sg_args = {'tenant_id': tenant_id}
+
+ sg = self.admin_client.create_security_group(
+ **sg_args)['security_group']
+ self.addCleanup(self.admin_client.delete_security_group, sg['id'])
+
+ # avoid a number that is made by default
+ sg_rule_list = self.admin_client.list_security_group_rules(
+ tenant_id=tenant_id)['security_group_rules']
+ num = len(sg_rule_list) + 1
+
+ new_quotas = {'security_group_rule': num}
+ self._setup_quotas(tenant_id, **new_quotas)
+
+ sg_rule_args = {'tenant_id': tenant_id,
+ 'security_group_id': sg['id'],
+ 'direction': 'ingress'}
+ sg_rule = self.admin_client.create_security_group_rule(
+ **sg_rule_args)['security_group_rule']
+ self.addCleanup(
+ self.admin_client.delete_security_group_rule, sg_rule['id'])
+
+ sg_rule_args['direction'] = 'egress'
+ self.assertRaises(lib_exc.Conflict,
+ self.admin_client.create_security_group_rule,
+ **sg_rule_args)
+
+ @decorators.attr(type='negative')
+ @decorators.idempotent_id('d00fe5bb-9db8-4e1a-9c31-490f52897e6f')
+ @test.requires_ext(extension="router", service="network")
+ def test_create_floatingip_when_quotas_is_full(self):
+ tenant_id = self._create_tenant()['id']
+ new_quotas = {'floatingip': 1}
+ self._setup_quotas(tenant_id, **new_quotas)
+
+ ext_net_id = CONF.network.public_network_id
+ fip_args = {'tenant_id': tenant_id,
+ 'floating_network_id': ext_net_id}
+ fip = self.admin_client.create_floatingip(**fip_args)['floatingip']
+ self.addCleanup(self.admin_client.delete_floatingip, fip['id'])
+
+ self.assertRaises(lib_exc.Conflict,
+ self.admin_client.create_floatingip, **fip_args)
diff --git a/neutron_tempest_plugin/api/admin/test_routers_dvr.py b/neutron_tempest_plugin/api/admin/test_routers_dvr.py
new file mode 100644
index 0000000..2313d1b
--- /dev/null
+++ b/neutron_tempest_plugin/api/admin/test_routers_dvr.py
@@ -0,0 +1,101 @@
+# Copyright 2015 OpenStack Foundation
+# All Rights Reserved.
+#
+# 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.common.utils import data_utils
+from tempest.lib import decorators
+
+from neutron_tempest_plugin.api import base_routers as base
+
+
+class RoutersTestDVR(base.BaseRouterTest):
+
+ required_extensions = ['router', 'dvr']
+
+ @classmethod
+ def resource_setup(cls):
+ # The check above will pass if api_extensions=all, which does
+ # not mean DVR extension itself is present.
+ # Instead, we have to check whether DVR is actually present by using
+ # admin credentials to create router with distributed=True attribute
+ # and checking for BadRequest exception and that the resulting router
+ # has a distributed attribute.
+ super(RoutersTestDVR, cls).resource_setup()
+ name = data_utils.rand_name('pretest-check')
+ router = cls.admin_client.create_router(name)
+ if 'distributed' not in router['router']:
+ msg = "'distributed' attribute not found. DVR Possibly not enabled"
+ raise cls.skipException(msg)
+ cls.admin_client.delete_router(router['router']['id'])
+
+ @decorators.idempotent_id('08a2a0a8-f1e4-4b34-8e30-e522e836c44e')
+ def test_distributed_router_creation(self):
+ """
+ Test uses administrative credentials to creates a
+ DVR (Distributed Virtual Routing) router using the
+ distributed=True.
+
+ Acceptance
+ The router is created and the "distributed" attribute is
+ set to True
+ """
+ name = data_utils.rand_name('router')
+ router = self.admin_client.create_router(name, distributed=True)
+ self.addCleanup(self.admin_client.delete_router,
+ router['router']['id'])
+ self.assertTrue(router['router']['distributed'])
+
+ @decorators.idempotent_id('8a0a72b4-7290-4677-afeb-b4ffe37bc352')
+ def test_centralized_router_creation(self):
+ """
+ Test uses administrative credentials to creates a
+ CVR (Centralized Virtual Routing) router using the
+ distributed=False.
+
+ Acceptance
+ The router is created and the "distributed" attribute is
+ set to False, thus making it a "Centralized Virtual Router"
+ as opposed to a "Distributed Virtual Router"
+ """
+ name = data_utils.rand_name('router')
+ router = self.admin_client.create_router(name, distributed=False)
+ self.addCleanup(self.admin_client.delete_router,
+ router['router']['id'])
+ self.assertFalse(router['router']['distributed'])
+
+ @decorators.idempotent_id('acd43596-c1fb-439d-ada8-31ad48ae3c2e')
+ def test_centralized_router_update_to_dvr(self):
+ """
+ Test uses administrative credentials to creates a
+ CVR (Centralized Virtual Routing) router using the
+ distributed=False.Then it will "update" the router
+ distributed attribute to True
+
+ Acceptance
+ The router is created and the "distributed" attribute is
+ set to False. Once the router is updated, the distributed
+ attribute will be set to True
+ """
+ name = data_utils.rand_name('router')
+ # router needs to be in admin state down in order to be upgraded to DVR
+ router = self.admin_client.create_router(name, distributed=False,
+ ha=False,
+ admin_state_up=False)
+ self.addCleanup(self.admin_client.delete_router,
+ router['router']['id'])
+ self.assertFalse(router['router']['distributed'])
+ self.assertFalse(router['router']['ha'])
+ router = self.admin_client.update_router(router['router']['id'],
+ distributed=True)
+ self.assertTrue(router['router']['distributed'])
diff --git a/neutron_tempest_plugin/api/admin/test_routers_flavors.py b/neutron_tempest_plugin/api/admin/test_routers_flavors.py
new file mode 100644
index 0000000..86a993a
--- /dev/null
+++ b/neutron_tempest_plugin/api/admin/test_routers_flavors.py
@@ -0,0 +1,104 @@
+#
+# 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 neutron_lib.plugins import constants
+from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
+import testtools
+
+from neutron_tempest_plugin.api import base_routers as base
+
+
+class RoutersFlavorTestCase(base.BaseRouterTest):
+
+ required_extensions = ['router', 'flavors', 'l3-flavors']
+
+ @classmethod
+ def resource_setup(cls):
+ super(RoutersFlavorTestCase, cls).resource_setup()
+ cls.service_profiles = []
+ cls.flavor_service_profiles = []
+ # make a flavor based on legacy router for regular tenant to use
+ driver = ('neutron.services.l3_router.service_providers.'
+ 'single_node.SingleNodeDriver')
+ try:
+ sp = cls.admin_client.create_service_profile(driver=driver)
+ except lib_exc.NotFound as e:
+ if e.resp_body['type'] == 'ServiceProfileDriverNotFound':
+ raise cls.skipException("%s is not available" % driver)
+ raise
+ cls.service_profiles.append(sp['service_profile'])
+ cls.flavor = cls.create_flavor(
+ name='special_flavor',
+ description='econonomy class',
+ service_type=constants.L3)
+ cls.admin_client.create_flavor_service_profile(
+ cls.flavor['id'], sp['service_profile']['id'])
+ cls.flavor_service_profiles.append((cls.flavor['id'],
+ sp['service_profile']['id']))
+ # make another with a different driver
+ driver = ('neutron.services.l3_router.service_providers.'
+ 'dvr.DvrDriver')
+ try:
+ sp = cls.admin_client.create_service_profile(driver=driver)
+ except lib_exc.NotFound as e:
+ if e.resp_body['type'] == 'ServiceProfileDriverNotFound':
+ raise cls.skipException("%s is not available" % driver)
+ raise
+ cls.service_profiles.append(sp['service_profile'])
+ cls.prem_flavor = cls.create_flavor(
+ name='better_special_flavor',
+ description='econonomy comfort',
+ service_type=constants.L3)
+ cls.admin_client.create_flavor_service_profile(
+ cls.prem_flavor['id'], sp['service_profile']['id'])
+ cls.flavor_service_profiles.append((cls.prem_flavor['id'],
+ sp['service_profile']['id']))
+
+ @classmethod
+ def resource_cleanup(cls):
+ for flavor_id, service_profile_id in cls.flavor_service_profiles:
+ cls.admin_client.delete_flavor_service_profile(flavor_id,
+ service_profile_id)
+ for service_profile in cls.service_profiles:
+ cls.admin_client.delete_service_profile(
+ service_profile['id'])
+ super(RoutersFlavorTestCase, cls).resource_cleanup()
+
+ @decorators.idempotent_id('a4d01977-e968-4983-b4d9-824ea6c33f4b')
+ def test_create_router_with_flavor(self):
+ # ensure regular client can see flavor
+ flavors = self.client.list_flavors(id=self.flavor['id'])
+ flavor = flavors['flavors'][0]
+ self.assertEqual('special_flavor', flavor['name'])
+ flavors = self.client.list_flavors(id=self.prem_flavor['id'])
+ prem_flavor = flavors['flavors'][0]
+ self.assertEqual('better_special_flavor', prem_flavor['name'])
+
+ # ensure client can create router with both flavors
+ router = self.create_router('name', flavor_id=flavor['id'])
+ self.assertEqual(flavor['id'], router['flavor_id'])
+ router = self.create_router('name', flavor_id=prem_flavor['id'])
+ self.assertEqual(prem_flavor['id'], router['flavor_id'])
+
+ @decorators.idempotent_id('30e73858-a0fc-409c-a2e0-e9cd2826f6a2')
+ def test_delete_router_flavor_in_use(self):
+ self.create_router('name', flavor_id=self.flavor['id'])
+ with testtools.ExpectedException(lib_exc.Conflict):
+ self.admin_client.delete_flavor(self.flavor['id'])
+
+ @decorators.idempotent_id('83939cf7-5070-41bc-9a3e-cd9f22df2186')
+ def test_badrequest_on_requesting_flags_and_flavor(self):
+ with testtools.ExpectedException(lib_exc.BadRequest):
+ self.admin_client.create_router(
+ 'name', flavor_id=self.flavor['id'], distributed=True)
diff --git a/neutron_tempest_plugin/api/admin/test_routers_ha.py b/neutron_tempest_plugin/api/admin/test_routers_ha.py
new file mode 100644
index 0000000..fafe209
--- /dev/null
+++ b/neutron_tempest_plugin/api/admin/test_routers_ha.py
@@ -0,0 +1,92 @@
+# 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.common.utils import data_utils
+from tempest.lib import decorators
+
+from neutron_tempest_plugin.api import base_routers as base
+
+
+class RoutersTestHA(base.BaseRouterTest):
+
+ required_extensions = ['router', 'l3-ha']
+
+ @classmethod
+ def resource_setup(cls):
+ # The check above will pass if api_extensions=all, which does
+ # not mean "l3-ha" extension itself is present.
+ # Instead, we have to check whether "ha" is actually present by using
+ # admin credentials to create router with ha=True attribute
+ # and checking for BadRequest exception and that the resulting router
+ # has a high availability attribute.
+ super(RoutersTestHA, cls).resource_setup()
+ name = data_utils.rand_name('pretest-check')
+ router = cls.admin_client.create_router(name)
+ if 'ha' not in router['router']:
+ cls.admin_client.delete_router(router['router']['id'])
+ msg = "'ha' attribute not found. HA Possibly not enabled"
+ raise cls.skipException(msg)
+
+ @decorators.idempotent_id('8abc177d-14f1-4018-9f01-589b299cbee1')
+ def test_ha_router_creation(self):
+ """
+ Test uses administrative credentials to create a
+ HA (High Availability) router using the ha=True.
+
+ Acceptance
+ The router is created and the "ha" attribute is set to True
+ """
+ name = data_utils.rand_name('router')
+ router = self.admin_client.create_router(name, ha=True)
+ self.addCleanup(self.admin_client.delete_router,
+ router['router']['id'])
+ self.assertTrue(router['router']['ha'])
+
+ @decorators.idempotent_id('97b5f7ef-2192-4fa3-901e-979cd5c1097a')
+ def test_legacy_router_creation(self):
+ """
+ Test uses administrative credentials to create a
+ SF (Single Failure) router using the ha=False.
+
+ Acceptance
+ The router is created and the "ha" attribute is
+ set to False, thus making it a "Single Failure Router"
+ as opposed to a "High Availability Router"
+ """
+ name = data_utils.rand_name('router')
+ router = self.admin_client.create_router(name, ha=False)
+ self.addCleanup(self.admin_client.delete_router,
+ router['router']['id'])
+ self.assertFalse(router['router']['ha'])
+
+ @decorators.idempotent_id('5a6bfe82-5b23-45a4-b027-5160997d4753')
+ def test_legacy_router_update_to_ha(self):
+ """
+ Test uses administrative credentials to create a
+ SF (Single Failure) router using the ha=False.
+ Then it will "update" the router ha attribute to True
+
+ Acceptance
+ The router is created and the "ha" attribute is
+ set to False. Once the router is updated, the ha
+ attribute will be set to True
+ """
+ name = data_utils.rand_name('router')
+ # router needs to be in admin state down in order to be upgraded to HA
+ router = self.admin_client.create_router(name, ha=False,
+ admin_state_up=False)
+ self.addCleanup(self.admin_client.delete_router,
+ router['router']['id'])
+ self.assertFalse(router['router']['ha'])
+ router = self.admin_client.update_router(router['router']['id'],
+ ha=True)
+ self.assertTrue(router['router']['ha'])
diff --git a/neutron_tempest_plugin/api/admin/test_shared_network_extension.py b/neutron_tempest_plugin/api/admin/test_shared_network_extension.py
new file mode 100644
index 0000000..e2198bd
--- /dev/null
+++ b/neutron_tempest_plugin/api/admin/test_shared_network_extension.py
@@ -0,0 +1,464 @@
+# Copyright 2015 Hewlett-Packard Development Company, L.P.dsvsv
+# Copyright 2015 OpenStack Foundation
+# All Rights Reserved.
+#
+# 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 oslo_utils import uuidutils
+from tempest.lib.common.utils import data_utils
+from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
+from tempest import test
+import testtools
+
+from neutron_tempest_plugin.api import base
+
+
+class SharedNetworksTest(base.BaseAdminNetworkTest):
+
+ @classmethod
+ def resource_setup(cls):
+ super(SharedNetworksTest, cls).resource_setup()
+ cls.shared_network = cls.create_shared_network()
+
+ @decorators.idempotent_id('6661d219-b96d-4597-ad10-55766123421a')
+ def test_filtering_shared_networks(self):
+ # this test is necessary because the 'shared' column does not actually
+ # exist on networks so the filter function has to translate it into
+ # queries against the RBAC table
+ self.create_network()
+ self._check_shared_correct(
+ self.client.list_networks(shared=True)['networks'], True)
+ self._check_shared_correct(
+ self.admin_client.list_networks(shared=True)['networks'], True)
+ self._check_shared_correct(
+ self.client.list_networks(shared=False)['networks'], False)
+ self._check_shared_correct(
+ self.admin_client.list_networks(shared=False)['networks'], False)
+
+ def _check_shared_correct(self, items, shared):
+ self.assertNotEmpty(items)
+ self.assertTrue(all(n['shared'] == shared for n in items))
+
+ def _list_subnets_ids(self, client, shared):
+ body = client.list_subnets(shared=shared)
+ return [subnet['id'] for subnet in body['subnets']]
+
+ @decorators.idempotent_id('6661d219-b96d-4597-ad10-51672353421a')
+ def test_filtering_shared_subnets(self):
+ # shared subnets need to be tested because their shared status isn't
+ # visible as a regular API attribute and it's solely dependent on the
+ # parent network
+ reg = self.create_network()
+ priv = self.create_subnet(reg, client=self.client)
+ shared = self.create_subnet(self.shared_network,
+ client=self.admin_client)
+ self.assertIn(shared['id'],
+ self._list_subnets_ids(self.client, shared=True))
+ self.assertIn(shared['id'],
+ self._list_subnets_ids(self.admin_client, shared=True))
+ self.assertNotIn(priv['id'],
+ self._list_subnets_ids(self.client, shared=True))
+ self.assertNotIn(
+ priv['id'],
+ self._list_subnets_ids(self.admin_client, shared=True))
+ self.assertIn(priv['id'],
+ self._list_subnets_ids(self.client, shared=False))
+ self.assertIn(priv['id'],
+ self._list_subnets_ids(self.admin_client, shared=False))
+ self.assertNotIn(shared['id'],
+ self._list_subnets_ids(self.client, shared=False))
+ self.assertNotIn(
+ shared['id'],
+ self._list_subnets_ids(self.admin_client, shared=False))
+
+ @decorators.idempotent_id('6661d219-b96d-4597-ad10-55766ce4abf7')
+ def test_create_update_shared_network(self):
+ shared_network = self.create_shared_network()
+ net_id = shared_network['id']
+ self.assertEqual('ACTIVE', shared_network['status'])
+ self.assertIsNotNone(shared_network['id'])
+ self.assertTrue(self.shared_network['shared'])
+ new_name = "New_shared_network"
+ body = self.admin_client.update_network(net_id, name=new_name,
+ admin_state_up=False,
+ shared=False)
+ updated_net = body['network']
+ self.assertEqual(new_name, updated_net['name'])
+ self.assertFalse(updated_net['shared'])
+ self.assertFalse(updated_net['admin_state_up'])
+
+ @decorators.idempotent_id('9c31fabb-0181-464f-9ace-95144fe9ca77')
+ def test_create_port_shared_network_as_non_admin_tenant(self):
+ # create a port as non admin
+ body = self.client.create_port(network_id=self.shared_network['id'])
+ port = body['port']
+ self.addCleanup(self.admin_client.delete_port, port['id'])
+ # verify the tenant id of admin network and non admin port
+ self.assertNotEqual(self.shared_network['tenant_id'],
+ port['tenant_id'])
+
+ @decorators.idempotent_id('3e39c4a6-9caf-4710-88f1-d20073c6dd76')
+ def test_create_bulk_shared_network(self):
+ # Creates 2 networks in one request
+ net_nm = [data_utils.rand_name('network'),
+ data_utils.rand_name('network')]
+ body = self.admin_client.create_bulk_network(net_nm, shared=True)
+ created_networks = body['networks']
+ for net in created_networks:
+ self.addCleanup(self.admin_client.delete_network, net['id'])
+ self.assertIsNotNone(net['id'])
+ self.assertTrue(net['shared'])
+
+ def _list_shared_networks(self, user):
+ body = user.list_networks(shared=True)
+ networks_list = [net['id'] for net in body['networks']]
+ self.assertIn(self.shared_network['id'], networks_list)
+ self.assertTrue(self.shared_network['shared'])
+
+ @decorators.idempotent_id('a064a9fd-e02f-474a-8159-f828cd636a28')
+ def test_list_shared_networks(self):
+ # List the shared networks and confirm that
+ # shared network extension attribute is returned for those networks
+ # that are created as shared
+ self._list_shared_networks(self.admin_client)
+ self._list_shared_networks(self.client)
+
+ def _show_shared_network(self, user):
+ body = user.show_network(self.shared_network['id'])
+ show_shared_net = body['network']
+ self.assertEqual(self.shared_network['name'], show_shared_net['name'])
+ self.assertEqual(self.shared_network['id'], show_shared_net['id'])
+ self.assertTrue(show_shared_net['shared'])
+
+ @decorators.idempotent_id('e03c92a2-638d-4bfa-b50a-b1f66f087e58')
+ def test_show_shared_networks_attribute(self):
+ # Show a shared network and confirm that
+ # shared network extension attribute is returned.
+ self._show_shared_network(self.admin_client)
+ self._show_shared_network(self.client)
+
+
+class AllowedAddressPairSharedNetworkTest(base.BaseAdminNetworkTest):
+ allowed_address_pairs = [{'ip_address': '1.1.1.1'}]
+ required_extensions = ['allowed-address-pairs']
+
+ @classmethod
+ def resource_setup(cls):
+ super(AllowedAddressPairSharedNetworkTest, cls).resource_setup()
+ cls.network = cls.create_shared_network()
+ cls.create_subnet(cls.network, client=cls.admin_client)
+
+ @decorators.idempotent_id('86c3529b-1231-40de-803c-ffffffff1fff')
+ def test_create_with_address_pair_blocked_on_other_network(self):
+ with testtools.ExpectedException(lib_exc.Forbidden):
+ self.create_port(self.network,
+ allowed_address_pairs=self.allowed_address_pairs)
+
+ @decorators.idempotent_id('86c3529b-1231-40de-803c-ffffffff2fff')
+ def test_update_with_address_pair_blocked_on_other_network(self):
+ port = self.create_port(self.network)
+ with testtools.ExpectedException(lib_exc.Forbidden):
+ self.update_port(
+ port, allowed_address_pairs=self.allowed_address_pairs)
+
+
+class RBACSharedNetworksTest(base.BaseAdminNetworkTest):
+
+ force_tenant_isolation = True
+ credentials = ['primary', 'alt', 'admin']
+ required_extensions = ['rbac-policies']
+
+ @classmethod
+ def resource_setup(cls):
+ super(RBACSharedNetworksTest, cls).resource_setup()
+ cls.client2 = cls.os_alt.network_client
+
+ def _make_admin_net_and_subnet_shared_to_tenant_id(self, tenant_id):
+ net = self.admin_client.create_network(
+ name=data_utils.rand_name('test-network-'))['network']
+ self.addCleanup(self.admin_client.delete_network, net['id'])
+ subnet = self.create_subnet(net, client=self.admin_client)
+ # network is shared to first unprivileged client by default
+ pol = self.admin_client.create_rbac_policy(
+ object_type='network', object_id=net['id'],
+ action='access_as_shared', target_tenant=tenant_id
+ )['rbac_policy']
+ return {'network': net, 'subnet': subnet, 'policy': pol}
+
+ @decorators.attr(type='smoke')
+ @decorators.idempotent_id('86c3529b-1231-40de-803c-bfffffff1eee')
+ def test_create_rbac_policy_with_target_tenant_none(self):
+ with testtools.ExpectedException(lib_exc.BadRequest):
+ self._make_admin_net_and_subnet_shared_to_tenant_id(
+ tenant_id=None)
+
+ @decorators.attr(type='smoke')
+ @decorators.idempotent_id('86c3529b-1231-40de-803c-bfffffff1fff')
+ def test_create_rbac_policy_with_target_tenant_too_long_id(self):
+ with testtools.ExpectedException(lib_exc.BadRequest):
+ target_tenant = '1234' * 100
+ self._make_admin_net_and_subnet_shared_to_tenant_id(
+ tenant_id=target_tenant)
+
+ @decorators.attr(type='smoke')
+ @decorators.idempotent_id('86c3529b-1231-40de-803c-afffffff1fff')
+ def test_network_only_visible_to_policy_target(self):
+ net = self._make_admin_net_and_subnet_shared_to_tenant_id(
+ self.client.tenant_id)['network']
+ self.client.show_network(net['id'])
+ with testtools.ExpectedException(lib_exc.NotFound):
+ # client2 has not been granted access
+ self.client2.show_network(net['id'])
+
+ @decorators.idempotent_id('86c3529b-1231-40de-803c-afffffff2fff')
+ def test_subnet_on_network_only_visible_to_policy_target(self):
+ sub = self._make_admin_net_and_subnet_shared_to_tenant_id(
+ self.client.tenant_id)['subnet']
+ self.client.show_subnet(sub['id'])
+ with testtools.ExpectedException(lib_exc.NotFound):
+ # client2 has not been granted access
+ self.client2.show_subnet(sub['id'])
+
+ @decorators.idempotent_id('86c3529b-1231-40de-803c-afffffff2eee')
+ def test_policy_target_update(self):
+ res = self._make_admin_net_and_subnet_shared_to_tenant_id(
+ self.client.tenant_id)
+ # change to client2
+ update_res = self.admin_client.update_rbac_policy(
+ res['policy']['id'], target_tenant=self.client2.tenant_id)
+ self.assertEqual(self.client2.tenant_id,
+ update_res['rbac_policy']['target_tenant'])
+ # make sure everything else stayed the same
+ res['policy'].pop('target_tenant')
+ update_res['rbac_policy'].pop('target_tenant')
+ self.assertEqual(res['policy'], update_res['rbac_policy'])
+
+ @decorators.idempotent_id('86c3529b-1231-40de-803c-affefefef321')
+ def test_duplicate_policy_error(self):
+ res = self._make_admin_net_and_subnet_shared_to_tenant_id(
+ self.client.tenant_id)
+ with testtools.ExpectedException(lib_exc.Conflict):
+ self.admin_client.create_rbac_policy(
+ object_type='network', object_id=res['network']['id'],
+ action='access_as_shared', target_tenant=self.client.tenant_id)
+
+ @decorators.idempotent_id('86c3529b-1231-40de-803c-afffffff3fff')
+ def test_port_presence_prevents_network_rbac_policy_deletion(self):
+ res = self._make_admin_net_and_subnet_shared_to_tenant_id(
+ self.client.tenant_id)
+ port = self.client.create_port(network_id=res['network']['id'])['port']
+ # a port on the network should prevent the deletion of a policy
+ # required for it to exist
+ with testtools.ExpectedException(lib_exc.Conflict):
+ self.admin_client.delete_rbac_policy(res['policy']['id'])
+
+ # a wildcard policy should allow the specific policy to be deleted
+ # since it allows the remaining port
+ wild = self.admin_client.create_rbac_policy(
+ object_type='network', object_id=res['network']['id'],
+ action='access_as_shared', target_tenant='*')['rbac_policy']
+ self.admin_client.delete_rbac_policy(res['policy']['id'])
+
+ # now that wildcard is the only remaining, it should be subjected to
+ # to the same restriction
+ with testtools.ExpectedException(lib_exc.Conflict):
+ self.admin_client.delete_rbac_policy(wild['id'])
+ # similarly, we can't update the policy to a different tenant
+ with testtools.ExpectedException(lib_exc.Conflict):
+ self.admin_client.update_rbac_policy(
+ wild['id'], target_tenant=self.client2.tenant_id)
+
+ self.client.delete_port(port['id'])
+ # anchor is gone, delete should pass
+ self.admin_client.delete_rbac_policy(wild['id'])
+
+ @decorators.idempotent_id('34d627da-a732-68c0-2e1a-bc4a19246698')
+ def test_delete_self_share_rule(self):
+ net = self.create_network()
+ self_share = self.client.create_rbac_policy(
+ object_type='network', object_id=net['id'],
+ action='access_as_shared',
+ target_tenant=net['tenant_id'])['rbac_policy']
+ port = self.client.create_port(network_id=net['id'])['port']
+ self.client.delete_rbac_policy(self_share['id'])
+ self.client.delete_port(port['id'])
+
+ @decorators.idempotent_id('86c3529b-1231-40de-803c-beefbeefbeef')
+ def test_tenant_can_delete_port_on_own_network(self):
+ net = self.create_network() # owned by self.client
+ self.client.create_rbac_policy(
+ object_type='network', object_id=net['id'],
+ action='access_as_shared', target_tenant=self.client2.tenant_id)
+ port = self.client2.create_port(network_id=net['id'])['port']
+ self.client.delete_port(port['id'])
+
+ @decorators.idempotent_id('f7539232-389a-4e9c-9e37-e42a129eb541')
+ def test_tenant_cant_delete_other_tenants_ports(self):
+ net = self.create_network()
+ port = self.client.create_port(network_id=net['id'])['port']
+ self.addCleanup(self.client.delete_port, port['id'])
+ with testtools.ExpectedException(lib_exc.NotFound):
+ self.client2.delete_port(port['id'])
+
+ @decorators.idempotent_id('86c3529b-1231-40de-803c-afffffff4fff')
+ def test_regular_client_shares_to_another_regular_client(self):
+ net = self.create_network() # owned by self.client
+ with testtools.ExpectedException(lib_exc.NotFound):
+ self.client2.show_network(net['id'])
+ pol = self.client.create_rbac_policy(
+ object_type='network', object_id=net['id'],
+ action='access_as_shared', target_tenant=self.client2.tenant_id)
+ self.client2.show_network(net['id'])
+
+ self.assertIn(pol['rbac_policy'],
+ self.client.list_rbac_policies()['rbac_policies'])
+ # ensure that 'client2' can't see the policy sharing the network to it
+ # because the policy belongs to 'client'
+ self.assertNotIn(pol['rbac_policy']['id'],
+ [p['id']
+ for p in self.client2.list_rbac_policies()['rbac_policies']])
+
+ @decorators.idempotent_id('bf5052b8-b11e-407c-8e43-113447404d3e')
+ def test_filter_fields(self):
+ net = self.create_network()
+ self.client.create_rbac_policy(
+ object_type='network', object_id=net['id'],
+ action='access_as_shared', target_tenant=self.client2.tenant_id)
+ field_args = (('id',), ('id', 'action'), ('object_type', 'object_id'),
+ ('tenant_id', 'target_tenant'))
+ for fields in field_args:
+ res = self.client.list_rbac_policies(fields=fields)
+ self.assertEqual(set(fields), set(res['rbac_policies'][0].keys()))
+
+ @decorators.idempotent_id('86c3529b-1231-40de-803c-afffffff5fff')
+ def test_policy_show(self):
+ res = self._make_admin_net_and_subnet_shared_to_tenant_id(
+ self.client.tenant_id)
+ p1 = res['policy']
+ p2 = self.admin_client.create_rbac_policy(
+ object_type='network', object_id=res['network']['id'],
+ action='access_as_shared',
+ target_tenant='*')['rbac_policy']
+
+ self.assertEqual(
+ p1, self.admin_client.show_rbac_policy(p1['id'])['rbac_policy'])
+ self.assertEqual(
+ p2, self.admin_client.show_rbac_policy(p2['id'])['rbac_policy'])
+
+ @decorators.idempotent_id('e7bcb1ea-4877-4266-87bb-76f68b421f31')
+ def test_filter_policies(self):
+ net = self.create_network()
+ pol1 = self.client.create_rbac_policy(
+ object_type='network', object_id=net['id'],
+ action='access_as_shared',
+ target_tenant=self.client2.tenant_id)['rbac_policy']
+ pol2 = self.client.create_rbac_policy(
+ object_type='network', object_id=net['id'],
+ action='access_as_shared',
+ target_tenant=self.client.tenant_id)['rbac_policy']
+ res1 = self.client.list_rbac_policies(id=pol1['id'])['rbac_policies']
+ res2 = self.client.list_rbac_policies(id=pol2['id'])['rbac_policies']
+ self.assertEqual(1, len(res1))
+ self.assertEqual(1, len(res2))
+ self.assertEqual(pol1['id'], res1[0]['id'])
+ self.assertEqual(pol2['id'], res2[0]['id'])
+
+ @decorators.idempotent_id('86c3529b-1231-40de-803c-afffffff6fff')
+ def test_regular_client_blocked_from_sharing_anothers_network(self):
+ net = self._make_admin_net_and_subnet_shared_to_tenant_id(
+ self.client.tenant_id)['network']
+ with testtools.ExpectedException(lib_exc.BadRequest):
+ self.client.create_rbac_policy(
+ object_type='network', object_id=net['id'],
+ action='access_as_shared', target_tenant=self.client.tenant_id)
+
+ @decorators.idempotent_id('c5f8f785-ce8d-4430-af7e-a236205862fb')
+ @test.requires_ext(extension="quotas", service="network")
+ def test_rbac_policy_quota(self):
+ quota = self.client.show_quotas(self.client.tenant_id)['quota']
+ max_policies = quota['rbac_policy']
+ self.assertGreater(max_policies, 0)
+ net = self.client.create_network(
+ name=data_utils.rand_name('test-network-'))['network']
+ self.addCleanup(self.client.delete_network, net['id'])
+ with testtools.ExpectedException(lib_exc.Conflict):
+ for i in range(0, max_policies + 1):
+ self.admin_client.create_rbac_policy(
+ object_type='network', object_id=net['id'],
+ action='access_as_shared',
+ target_tenant=uuidutils.generate_uuid().replace('-', ''))
+
+ @decorators.idempotent_id('86c3529b-1231-40de-803c-afffffff7fff')
+ def test_regular_client_blocked_from_sharing_with_wildcard(self):
+ net = self.create_network()
+ with testtools.ExpectedException(lib_exc.Forbidden):
+ self.client.create_rbac_policy(
+ object_type='network', object_id=net['id'],
+ action='access_as_shared', target_tenant='*')
+ # ensure it works on update as well
+ pol = self.client.create_rbac_policy(
+ object_type='network', object_id=net['id'],
+ action='access_as_shared', target_tenant=self.client2.tenant_id)
+ with testtools.ExpectedException(lib_exc.Forbidden):
+ self.client.update_rbac_policy(pol['rbac_policy']['id'],
+ target_tenant='*')
+
+ @decorators.idempotent_id('34d627da-869f-68c0-2e1a-bc4a19246698')
+ def test_update_self_share_rule(self):
+ net = self.create_network()
+ self_share = self.client.create_rbac_policy(
+ object_type='network', object_id=net['id'],
+ action='access_as_shared',
+ target_tenant=net['tenant_id'])['rbac_policy']
+ port = self.client.create_port(network_id=net['id'])['port']
+ self.client.update_rbac_policy(self_share['id'],
+ target_tenant=self.client2.tenant_id)
+ self.client.delete_port(port['id'])
+
+ @test.requires_ext(extension="standard-attr-revisions", service="network")
+ @decorators.idempotent_id('86c3529b-1231-40de-1234-89664291a4cb')
+ def test_rbac_bumps_network_revision(self):
+ resp = self._make_admin_net_and_subnet_shared_to_tenant_id(
+ self.client.tenant_id)
+ net_id = resp['network']['id']
+ rev = self.client.show_network(net_id)['network']['revision_number']
+ self.admin_client.create_rbac_policy(
+ object_type='network', object_id=net_id,
+ action='access_as_shared', target_tenant='*')
+ self.assertGreater(
+ self.client.show_network(net_id)['network']['revision_number'],
+ rev
+ )
+
+ @decorators.idempotent_id('86c3529b-1231-40de-803c-aeeeeeee7fff')
+ def test_filtering_works_with_rbac_records_present(self):
+ resp = self._make_admin_net_and_subnet_shared_to_tenant_id(
+ self.client.tenant_id)
+ net = resp['network']['id']
+ sub = resp['subnet']['id']
+ self.admin_client.create_rbac_policy(
+ object_type='network', object_id=net,
+ action='access_as_shared', target_tenant='*')
+ self._assert_shared_object_id_listing_presence('subnets', False, sub)
+ self._assert_shared_object_id_listing_presence('subnets', True, sub)
+ self._assert_shared_object_id_listing_presence('networks', False, net)
+ self._assert_shared_object_id_listing_presence('networks', True, net)
+
+ def _assert_shared_object_id_listing_presence(self, resource, shared, oid):
+ lister = getattr(self.admin_client, 'list_%s' % resource)
+ objects = [o['id'] for o in lister(shared=shared)[resource]]
+ if shared:
+ self.assertIn(oid, objects)
+ else:
+ self.assertNotIn(oid, objects)
diff --git a/neutron_tempest_plugin/api/base.py b/neutron_tempest_plugin/api/base.py
new file mode 100644
index 0000000..b122ce8
--- /dev/null
+++ b/neutron_tempest_plugin/api/base.py
@@ -0,0 +1,809 @@
+# Copyright 2012 OpenStack Foundation
+# All Rights Reserved.
+#
+# 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 functools
+import math
+
+import netaddr
+from tempest.lib.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
+from tempest import test
+
+from neutron_tempest_plugin.api import clients
+from neutron_tempest_plugin.common import constants
+from neutron_tempest_plugin.common import utils
+from neutron_tempest_plugin import config
+from neutron_tempest_plugin import exceptions
+
+CONF = config.CONF
+
+
+class BaseNetworkTest(test.BaseTestCase):
+
+ """
+ Base class for the Neutron tests that use the Tempest Neutron REST client
+
+ Per the Neutron API Guide, API v1.x was removed from the source code tree
+ (docs.openstack.org/api/openstack-network/2.0/content/Overview-d1e71.html)
+ Therefore, v2.x of the Neutron API is assumed. It is also assumed that the
+ following options are defined in the [network] section of etc/tempest.conf:
+
+ project_network_cidr with a block of cidr's from which smaller blocks
+ can be allocated for tenant networks
+
+ project_network_mask_bits with the mask bits to be used to partition
+ the block defined by tenant-network_cidr
+
+ Finally, it is assumed that the following option is defined in the
+ [service_available] section of etc/tempest.conf
+
+ neutron as True
+ """
+
+ force_tenant_isolation = False
+ credentials = ['primary']
+
+ # Default to ipv4.
+ _ip_version = 4
+
+ @classmethod
+ def get_client_manager(cls, credential_type=None, roles=None,
+ force_new=None):
+ manager = super(BaseNetworkTest, cls).get_client_manager(
+ credential_type=credential_type,
+ roles=roles,
+ force_new=force_new
+ )
+ # Neutron uses a different clients manager than the one in the Tempest
+ return clients.Manager(manager.credentials)
+
+ @classmethod
+ def skip_checks(cls):
+ super(BaseNetworkTest, cls).skip_checks()
+ if not CONF.service_available.neutron:
+ raise cls.skipException("Neutron support is required")
+ if cls._ip_version == 6 and not CONF.network_feature_enabled.ipv6:
+ raise cls.skipException("IPv6 Tests are disabled.")
+ for req_ext in getattr(cls, 'required_extensions', []):
+ if not test.is_extension_enabled(req_ext, 'network'):
+ msg = "%s extension not enabled." % req_ext
+ raise cls.skipException(msg)
+
+ @classmethod
+ def setup_credentials(cls):
+ # Create no network resources for these test.
+ cls.set_network_resources()
+ super(BaseNetworkTest, cls).setup_credentials()
+
+ @classmethod
+ def setup_clients(cls):
+ super(BaseNetworkTest, cls).setup_clients()
+ cls.client = cls.os_primary.network_client
+
+ @classmethod
+ def resource_setup(cls):
+ super(BaseNetworkTest, cls).resource_setup()
+
+ cls.networks = []
+ cls.admin_networks = []
+ cls.subnets = []
+ cls.admin_subnets = []
+ cls.ports = []
+ cls.routers = []
+ cls.floating_ips = []
+ cls.metering_labels = []
+ cls.service_profiles = []
+ cls.flavors = []
+ cls.metering_label_rules = []
+ cls.qos_rules = []
+ cls.qos_policies = []
+ cls.ethertype = "IPv" + str(cls._ip_version)
+ cls.address_scopes = []
+ cls.admin_address_scopes = []
+ cls.subnetpools = []
+ cls.admin_subnetpools = []
+ cls.security_groups = []
+
+ @classmethod
+ def resource_cleanup(cls):
+ if CONF.service_available.neutron:
+ # Clean up floating IPs
+ for floating_ip in cls.floating_ips:
+ cls._try_delete_resource(cls.client.delete_floatingip,
+ floating_ip['id'])
+ # Clean up routers
+ for router in cls.routers:
+ cls._try_delete_resource(cls.delete_router,
+ router)
+ # Clean up metering label rules
+ for metering_label_rule in cls.metering_label_rules:
+ cls._try_delete_resource(
+ cls.admin_client.delete_metering_label_rule,
+ metering_label_rule['id'])
+ # Clean up metering labels
+ for metering_label in cls.metering_labels:
+ cls._try_delete_resource(
+ cls.admin_client.delete_metering_label,
+ metering_label['id'])
+ # Clean up flavors
+ for flavor in cls.flavors:
+ cls._try_delete_resource(
+ cls.admin_client.delete_flavor,
+ flavor['id'])
+ # Clean up service profiles
+ for service_profile in cls.service_profiles:
+ cls._try_delete_resource(
+ cls.admin_client.delete_service_profile,
+ service_profile['id'])
+ # Clean up ports
+ for port in cls.ports:
+ cls._try_delete_resource(cls.client.delete_port,
+ port['id'])
+ # Clean up subnets
+ for subnet in cls.subnets:
+ cls._try_delete_resource(cls.client.delete_subnet,
+ subnet['id'])
+ # Clean up admin subnets
+ for subnet in cls.admin_subnets:
+ cls._try_delete_resource(cls.admin_client.delete_subnet,
+ subnet['id'])
+ # Clean up networks
+ for network in cls.networks:
+ cls._try_delete_resource(cls.client.delete_network,
+ network['id'])
+
+ # Clean up admin networks
+ for network in cls.admin_networks:
+ cls._try_delete_resource(cls.admin_client.delete_network,
+ network['id'])
+
+ # Clean up security groups
+ for secgroup in cls.security_groups:
+ cls._try_delete_resource(cls.client.delete_security_group,
+ secgroup['id'])
+
+ for subnetpool in cls.subnetpools:
+ cls._try_delete_resource(cls.client.delete_subnetpool,
+ subnetpool['id'])
+
+ for subnetpool in cls.admin_subnetpools:
+ cls._try_delete_resource(cls.admin_client.delete_subnetpool,
+ subnetpool['id'])
+
+ for address_scope in cls.address_scopes:
+ cls._try_delete_resource(cls.client.delete_address_scope,
+ address_scope['id'])
+
+ for address_scope in cls.admin_address_scopes:
+ cls._try_delete_resource(
+ cls.admin_client.delete_address_scope,
+ address_scope['id'])
+
+ # Clean up QoS rules
+ for qos_rule in cls.qos_rules:
+ cls._try_delete_resource(cls.admin_client.delete_qos_rule,
+ qos_rule['id'])
+ # Clean up QoS policies
+ # as all networks and ports are already removed, QoS policies
+ # shouldn't be "in use"
+ for qos_policy in cls.qos_policies:
+ cls._try_delete_resource(cls.admin_client.delete_qos_policy,
+ qos_policy['id'])
+
+ super(BaseNetworkTest, cls).resource_cleanup()
+
+ @classmethod
+ def _try_delete_resource(cls, delete_callable, *args, **kwargs):
+ """Cleanup resources in case of test-failure
+
+ Some resources are explicitly deleted by the test.
+ If the test failed to delete a resource, this method will execute
+ the appropriate delete methods. Otherwise, the method ignores NotFound
+ exceptions thrown for resources that were correctly deleted by the
+ test.
+
+ :param delete_callable: delete method
+ :param args: arguments for delete method
+ :param kwargs: keyword arguments for delete method
+ """
+ try:
+ delete_callable(*args, **kwargs)
+ # if resource is not found, this means it was deleted in the test
+ except lib_exc.NotFound:
+ pass
+
+ @classmethod
+ def create_network(cls, network_name=None, client=None, **kwargs):
+ """Wrapper utility that returns a test network."""
+ network_name = network_name or data_utils.rand_name('test-network-')
+
+ client = client or cls.client
+ body = client.create_network(name=network_name, **kwargs)
+ network = body['network']
+ if client is cls.client:
+ cls.networks.append(network)
+ else:
+ cls.admin_networks.append(network)
+ return network
+
+ @classmethod
+ def create_shared_network(cls, network_name=None, **post_body):
+ network_name = network_name or data_utils.rand_name('sharednetwork-')
+ post_body.update({'name': network_name, 'shared': True})
+ body = cls.admin_client.create_network(**post_body)
+ network = body['network']
+ cls.admin_networks.append(network)
+ return network
+
+ @classmethod
+ def create_network_keystone_v3(cls, network_name=None, project_id=None,
+ tenant_id=None, client=None):
+ """Wrapper utility that creates a test network with project_id."""
+ client = client or cls.client
+ network_name = network_name or data_utils.rand_name(
+ 'test-network-with-project_id')
+ project_id = cls.client.tenant_id
+ body = client.create_network_keystone_v3(network_name, project_id,
+ tenant_id)
+ network = body['network']
+ if client is cls.client:
+ cls.networks.append(network)
+ else:
+ cls.admin_networks.append(network)
+ return network
+
+ @classmethod
+ def create_subnet(cls, network, gateway='', cidr=None, mask_bits=None,
+ ip_version=None, client=None, **kwargs):
+ """Wrapper utility that returns a test subnet."""
+
+ # allow tests to use admin client
+ if not client:
+ client = cls.client
+
+ # The cidr and mask_bits depend on the ip version.
+ ip_version = ip_version if ip_version is not None else cls._ip_version
+ gateway_not_set = gateway == ''
+ if ip_version == 4:
+ cidr = cidr or netaddr.IPNetwork(
+ config.safe_get_config_value(
+ 'network', 'project_network_cidr'))
+ mask_bits = (
+ mask_bits or config.safe_get_config_value(
+ 'network', 'project_network_mask_bits'))
+ elif ip_version == 6:
+ cidr = (
+ cidr or netaddr.IPNetwork(
+ config.safe_get_config_value(
+ 'network', 'project_network_v6_cidr')))
+ mask_bits = (
+ mask_bits or config.safe_get_config_value(
+ 'network', 'project_network_v6_mask_bits'))
+ # Find a cidr that is not in use yet and create a subnet with it
+ for subnet_cidr in cidr.subnet(mask_bits):
+ if gateway_not_set:
+ gateway_ip = str(netaddr.IPAddress(subnet_cidr) + 1)
+ else:
+ gateway_ip = gateway
+ try:
+ body = client.create_subnet(
+ network_id=network['id'],
+ cidr=str(subnet_cidr),
+ ip_version=ip_version,
+ gateway_ip=gateway_ip,
+ **kwargs)
+ break
+ except lib_exc.BadRequest as e:
+ is_overlapping_cidr = 'overlaps with another subnet' in str(e)
+ if not is_overlapping_cidr:
+ raise
+ else:
+ message = 'Available CIDR for subnet creation could not be found'
+ raise ValueError(message)
+ subnet = body['subnet']
+ if client is cls.client:
+ cls.subnets.append(subnet)
+ else:
+ cls.admin_subnets.append(subnet)
+ return subnet
+
+ @classmethod
+ def create_port(cls, network, **kwargs):
+ """Wrapper utility that returns a test port."""
+ body = cls.client.create_port(network_id=network['id'],
+ **kwargs)
+ port = body['port']
+ cls.ports.append(port)
+ return port
+
+ @classmethod
+ def update_port(cls, port, **kwargs):
+ """Wrapper utility that updates a test port."""
+ body = cls.client.update_port(port['id'],
+ **kwargs)
+ return body['port']
+
+ @classmethod
+ def _create_router_with_client(
+ cls, client, router_name=None, admin_state_up=False,
+ external_network_id=None, enable_snat=None, **kwargs
+ ):
+ ext_gw_info = {}
+ if external_network_id:
+ ext_gw_info['network_id'] = external_network_id
+ if enable_snat is not None:
+ ext_gw_info['enable_snat'] = enable_snat
+ body = client.create_router(
+ router_name, external_gateway_info=ext_gw_info,
+ admin_state_up=admin_state_up, **kwargs)
+ router = body['router']
+ cls.routers.append(router)
+ return router
+
+ @classmethod
+ def create_router(cls, *args, **kwargs):
+ return cls._create_router_with_client(cls.client, *args, **kwargs)
+
+ @classmethod
+ def create_admin_router(cls, *args, **kwargs):
+ return cls._create_router_with_client(cls.os_admin.network_client,
+ *args, **kwargs)
+
+ @classmethod
+ def create_floatingip(cls, external_network_id):
+ """Wrapper utility that returns a test floating IP."""
+ body = cls.client.create_floatingip(
+ floating_network_id=external_network_id)
+ fip = body['floatingip']
+ cls.floating_ips.append(fip)
+ return fip
+
+ @classmethod
+ def create_router_interface(cls, router_id, subnet_id):
+ """Wrapper utility that returns a router interface."""
+ interface = cls.client.add_router_interface_with_subnet_id(
+ router_id, subnet_id)
+ return interface
+
+ @classmethod
+ def get_supported_qos_rule_types(cls):
+ body = cls.client.list_qos_rule_types()
+ return [rule_type['type'] for rule_type in body['rule_types']]
+
+ @classmethod
+ def create_qos_policy(cls, name, description=None, shared=False,
+ tenant_id=None, is_default=False):
+ """Wrapper utility that returns a test QoS policy."""
+ body = cls.admin_client.create_qos_policy(
+ name, description, shared, tenant_id, is_default)
+ qos_policy = body['policy']
+ cls.qos_policies.append(qos_policy)
+ return qos_policy
+
+ @classmethod
+ def create_qos_bandwidth_limit_rule(cls, policy_id, max_kbps,
+ max_burst_kbps,
+ direction=constants.EGRESS_DIRECTION):
+ """Wrapper utility that returns a test QoS bandwidth limit rule."""
+ body = cls.admin_client.create_bandwidth_limit_rule(
+ policy_id, max_kbps, max_burst_kbps, direction)
+ qos_rule = body['bandwidth_limit_rule']
+ cls.qos_rules.append(qos_rule)
+ return qos_rule
+
+ @classmethod
+ def delete_router(cls, router):
+ body = cls.client.list_router_interfaces(router['id'])
+ interfaces = body['ports']
+ for i in interfaces:
+ try:
+ cls.client.remove_router_interface_with_subnet_id(
+ router['id'], i['fixed_ips'][0]['subnet_id'])
+ except lib_exc.NotFound:
+ pass
+ cls.client.delete_router(router['id'])
+
+ @classmethod
+ def create_address_scope(cls, name, is_admin=False, **kwargs):
+ if is_admin:
+ body = cls.admin_client.create_address_scope(name=name, **kwargs)
+ cls.admin_address_scopes.append(body['address_scope'])
+ else:
+ body = cls.client.create_address_scope(name=name, **kwargs)
+ cls.address_scopes.append(body['address_scope'])
+ return body['address_scope']
+
+ @classmethod
+ def create_subnetpool(cls, name, is_admin=False, **kwargs):
+ if is_admin:
+ body = cls.admin_client.create_subnetpool(name, **kwargs)
+ cls.admin_subnetpools.append(body['subnetpool'])
+ else:
+ body = cls.client.create_subnetpool(name, **kwargs)
+ cls.subnetpools.append(body['subnetpool'])
+ return body['subnetpool']
+
+
+class BaseAdminNetworkTest(BaseNetworkTest):
+
+ credentials = ['primary', 'admin']
+
+ @classmethod
+ def setup_clients(cls):
+ super(BaseAdminNetworkTest, cls).setup_clients()
+ cls.admin_client = cls.os_admin.network_client
+ cls.identity_admin_client = cls.os_admin.projects_client
+
+ @classmethod
+ def create_metering_label(cls, name, description):
+ """Wrapper utility that returns a test metering label."""
+ body = cls.admin_client.create_metering_label(
+ description=description,
+ name=data_utils.rand_name("metering-label"))
+ metering_label = body['metering_label']
+ cls.metering_labels.append(metering_label)
+ return metering_label
+
+ @classmethod
+ def create_metering_label_rule(cls, remote_ip_prefix, direction,
+ metering_label_id):
+ """Wrapper utility that returns a test metering label rule."""
+ body = cls.admin_client.create_metering_label_rule(
+ remote_ip_prefix=remote_ip_prefix, direction=direction,
+ metering_label_id=metering_label_id)
+ metering_label_rule = body['metering_label_rule']
+ cls.metering_label_rules.append(metering_label_rule)
+ return metering_label_rule
+
+ @classmethod
+ def create_flavor(cls, name, description, service_type):
+ """Wrapper utility that returns a test flavor."""
+ body = cls.admin_client.create_flavor(
+ description=description, service_type=service_type,
+ name=name)
+ flavor = body['flavor']
+ cls.flavors.append(flavor)
+ return flavor
+
+ @classmethod
+ def create_service_profile(cls, description, metainfo, driver):
+ """Wrapper utility that returns a test service profile."""
+ body = cls.admin_client.create_service_profile(
+ driver=driver, metainfo=metainfo, description=description)
+ service_profile = body['service_profile']
+ cls.service_profiles.append(service_profile)
+ return service_profile
+
+ @classmethod
+ def get_unused_ip(cls, net_id, ip_version=None):
+ """Get an unused ip address in a allocation pool of net"""
+ body = cls.admin_client.list_ports(network_id=net_id)
+ ports = body['ports']
+ used_ips = []
+ for port in ports:
+ used_ips.extend(
+ [fixed_ip['ip_address'] for fixed_ip in port['fixed_ips']])
+ body = cls.admin_client.list_subnets(network_id=net_id)
+ subnets = body['subnets']
+
+ for subnet in subnets:
+ if ip_version and subnet['ip_version'] != ip_version:
+ continue
+ cidr = subnet['cidr']
+ allocation_pools = subnet['allocation_pools']
+ iterators = []
+ if allocation_pools:
+ for allocation_pool in allocation_pools:
+ iterators.append(netaddr.iter_iprange(
+ allocation_pool['start'], allocation_pool['end']))
+ else:
+ net = netaddr.IPNetwork(cidr)
+
+ def _iterip():
+ for ip in net:
+ if ip not in (net.network, net.broadcast):
+ yield ip
+ iterators.append(iter(_iterip()))
+
+ for iterator in iterators:
+ for ip in iterator:
+ if str(ip) not in used_ips:
+ return str(ip)
+
+ message = (
+ "net(%s) has no usable IP address in allocation pools" % net_id)
+ raise exceptions.InvalidConfiguration(message)
+
+
+def require_qos_rule_type(rule_type):
+ def decorator(f):
+ @functools.wraps(f)
+ def wrapper(self, *func_args, **func_kwargs):
+ if rule_type not in self.get_supported_qos_rule_types():
+ raise self.skipException(
+ "%s rule type is required." % rule_type)
+ return f(self, *func_args, **func_kwargs)
+ return wrapper
+ return decorator
+
+
+def _require_sorting(f):
+ @functools.wraps(f)
+ def inner(self, *args, **kwargs):
+ if not test.is_extension_enabled("sorting", "network"):
+ 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 test.is_extension_enabled("pagination", "network"):
+ 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
+
+ field = 'name'
+
+ # 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. Also test a case when there are multiple resources with the
+ # same name
+ resource_names = ('test1', 'abc1', 'test10', '123test') + ('test1',)
+
+ force_tenant_isolation = True
+
+ list_kwargs = {}
+
+ list_as_admin = False
+
+ def assertSameOrder(self, original, actual):
+ # gracefully handle iterators passed
+ original = list(original)
+ actual = list(actual)
+ self.assertEqual(len(original), len(actual))
+ for expected, res in zip(original, actual):
+ self.assertEqual(expected[self.field], res[self.field])
+
+ @utils.classproperty
+ def plural_name(self):
+ return '%ss' % self.resource
+
+ @property
+ def list_client(self):
+ return self.admin_client if self.list_as_admin else self.client
+
+ def list_method(self, *args, **kwargs):
+ method = getattr(self.list_client, 'list_%s' % self.plural_name)
+ kwargs.update(self.list_kwargs)
+ return method(*args, **kwargs)
+
+ def get_bare_url(self, url):
+ base_url = self.client.base_url
+ self.assertTrue(url.startswith(base_url))
+ return url[len(base_url):]
+
+ @classmethod
+ def _extract_resources(cls, body):
+ return body[cls.plural_name]
+
+ def _test_list_sorts(self, direction):
+ sort_args = {
+ 'sort_dir': direction,
+ 'sort_key': self.field
+ }
+ body = self.list_method(**sort_args)
+ resources = self._extract_resources(body)
+ self.assertNotEmpty(
+ resources, "%s list returned is empty" % self.resource)
+ retrieved_names = [res[self.field] 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.assertGreaterEqual(len(resources), len(self.resource_names))
+
+ def _test_list_pagination_iteratively(self, lister):
+ # first, collect all resources for later comparison
+ sort_args = {
+ 'sort_dir': constants.SORT_DIRECTION_ASC,
+ 'sort_key': self.field
+ }
+ body = self.list_method(**sort_args)
+ expected_resources = self._extract_resources(body)
+ self.assertNotEmpty(expected_resources)
+
+ resources = lister(
+ len(expected_resources), sort_args
+ )
+
+ # finally, compare that the list retrieved in one go is identical to
+ # the one containing pagination results
+ self.assertSameOrder(expected_resources, resources)
+
+ def _list_all_with_marker(self, niterations, sort_args):
+ # paginate resources one by one, using last fetched resource as a
+ # marker
+ resources = []
+ for i in range(niterations):
+ 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_)
+ return resources
+
+ @_require_pagination
+ @_require_sorting
+ def _test_list_pagination_with_marker(self):
+ self._test_list_pagination_iteratively(self._list_all_with_marker)
+
+ def _list_all_with_hrefs(self, niterations, sort_args):
+ # paginate resources one by one, using next href links
+ resources = []
+ prev_links = {}
+
+ for i in range(niterations):
+ if prev_links:
+ uri = self.get_bare_url(prev_links['next'])
+ else:
+ sort_args.update(self.list_kwargs)
+ uri = self.list_client.build_uri(
+ self.plural_name, limit=1, **sort_args)
+ prev_links, body = self.list_client.get_uri_with_links(
+ self.plural_name, uri
+ )
+ resources_ = self._extract_resources(body)
+ self.assertEqual(1, len(resources_))
+ resources.extend(resources_)
+
+ # The last element is empty and does not contain 'next' link
+ uri = self.get_bare_url(prev_links['next'])
+ prev_links, body = self.client.get_uri_with_links(
+ self.plural_name, uri
+ )
+ self.assertNotIn('next', prev_links)
+
+ # Now walk backwards and compare results
+ resources2 = []
+ for i in range(niterations):
+ uri = self.get_bare_url(prev_links['previous'])
+ prev_links, body = self.list_client.get_uri_with_links(
+ self.plural_name, uri
+ )
+ resources_ = self._extract_resources(body)
+ self.assertEqual(1, len(resources_))
+ resources2.extend(resources_)
+
+ self.assertSameOrder(resources, reversed(resources2))
+
+ return resources
+
+ @_require_pagination
+ @_require_sorting
+ def _test_list_pagination_with_href_links(self):
+ self._test_list_pagination_iteratively(self._list_all_with_hrefs)
+
+ @_require_pagination
+ @_require_sorting
+ def _test_list_pagination_page_reverse_with_href_links(
+ self, direction=constants.SORT_DIRECTION_ASC):
+ pagination_args = {
+ 'sort_dir': direction,
+ 'sort_key': self.field,
+ }
+ body = self.list_method(**pagination_args)
+ expected_resources = self._extract_resources(body)
+
+ page_size = 2
+ pagination_args['limit'] = page_size
+
+ prev_links = {}
+ resources = []
+ num_resources = len(expected_resources)
+ niterations = int(math.ceil(float(num_resources) / page_size))
+ for i in range(niterations):
+ if prev_links:
+ uri = self.get_bare_url(prev_links['previous'])
+ else:
+ pagination_args.update(self.list_kwargs)
+ uri = self.list_client.build_uri(
+ self.plural_name, page_reverse=True, **pagination_args)
+ prev_links, body = self.list_client.get_uri_with_links(
+ self.plural_name, uri
+ )
+ resources_ = self._extract_resources(body)
+ self.assertGreaterEqual(page_size, len(resources_))
+ resources.extend(reversed(resources_))
+
+ self.assertSameOrder(expected_resources, reversed(resources))
+
+ @_require_pagination
+ @_require_sorting
+ def _test_list_pagination_page_reverse_asc(self):
+ self._test_list_pagination_page_reverse(
+ direction=constants.SORT_DIRECTION_ASC)
+
+ @_require_pagination
+ @_require_sorting
+ def _test_list_pagination_page_reverse_desc(self):
+ self._test_list_pagination_page_reverse(
+ direction=constants.SORT_DIRECTION_DESC)
+
+ def _test_list_pagination_page_reverse(self, direction):
+ pagination_args = {
+ 'sort_dir': direction,
+ 'sort_key': self.field,
+ 'limit': 3,
+ }
+ body = self.list_method(**pagination_args)
+ expected_resources = self._extract_resources(body)
+
+ pagination_args['limit'] -= 1
+ pagination_args['marker'] = expected_resources[-1]['id']
+ pagination_args['page_reverse'] = True
+ body = self.list_method(**pagination_args)
+
+ self.assertSameOrder(
+ # the last entry is not included in 2nd result when used as a
+ # marker
+ expected_resources[:-1],
+ self._extract_resources(body))
+
+ def _test_list_validation_filters(self):
+ validation_args = {
+ 'unknown_filter': 'value',
+ }
+ body = self.list_method(**validation_args)
+ resources = self._extract_resources(body)
+ for resource in resources:
+ self.assertIn(resource['name'], self.resource_names)
diff --git a/neutron_tempest_plugin/api/base_routers.py b/neutron_tempest_plugin/api/base_routers.py
new file mode 100644
index 0000000..c8d3783
--- /dev/null
+++ b/neutron_tempest_plugin/api/base_routers.py
@@ -0,0 +1,45 @@
+# Copyright 2013 OpenStack Foundation
+# All Rights Reserved.
+#
+# 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 neutron_tempest_plugin.api import base
+
+
+class BaseRouterTest(base.BaseAdminNetworkTest):
+ # NOTE(salv-orlando): This class inherits from BaseAdminNetworkTest
+ # as some router operations, such as enabling or disabling SNAT
+ # require admin credentials by default
+
+ def _cleanup_router(self, router):
+ self.delete_router(router)
+ self.routers.remove(router)
+
+ def _create_router(self, name, admin_state_up=False,
+ external_network_id=None, enable_snat=None):
+ # associate a cleanup with created routers to avoid quota limits
+ router = self.create_router(name, admin_state_up,
+ external_network_id, enable_snat)
+ self.addCleanup(self._cleanup_router, router)
+ return router
+
+ def _delete_router(self, router_id, network_client=None):
+ client = network_client or self.client
+ client.delete_router(router_id)
+ # Asserting that the router is not found in the list
+ # after deletion
+ list_body = self.client.list_routers()
+ routers_list = list()
+ for router in list_body['routers']:
+ routers_list.append(router['id'])
+ self.assertNotIn(router_id, routers_list)
diff --git a/neutron_tempest_plugin/api/base_security_groups.py b/neutron_tempest_plugin/api/base_security_groups.py
new file mode 100644
index 0000000..e2736f3
--- /dev/null
+++ b/neutron_tempest_plugin/api/base_security_groups.py
@@ -0,0 +1,41 @@
+# Copyright 2013 OpenStack Foundation
+# All Rights Reserved.
+#
+# 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.common.utils import data_utils
+
+from neutron_tempest_plugin.api import base
+
+
+class BaseSecGroupTest(base.BaseNetworkTest):
+
+ def _create_security_group(self, **kwargs):
+ # Create a security group
+ name = data_utils.rand_name('secgroup-')
+ group_create_body = self.client.create_security_group(name=name,
+ **kwargs)
+ self.addCleanup(self._delete_security_group,
+ group_create_body['security_group']['id'])
+ self.assertEqual(group_create_body['security_group']['name'], name)
+ return group_create_body, name
+
+ def _delete_security_group(self, secgroup_id):
+ self.client.delete_security_group(secgroup_id)
+ # Asserting that the security group is not found in the list
+ # after deletion
+ list_body = self.client.list_security_groups()
+ secgroup_list = list()
+ for secgroup in list_body['security_groups']:
+ secgroup_list.append(secgroup['id'])
+ self.assertNotIn(secgroup_id, secgroup_list)
diff --git a/neutron_tempest_plugin/api/clients.py b/neutron_tempest_plugin/api/clients.py
new file mode 100644
index 0000000..272f5be
--- /dev/null
+++ b/neutron_tempest_plugin/api/clients.py
@@ -0,0 +1,91 @@
+# Copyright 2012 OpenStack Foundation
+# All Rights Reserved.
+#
+# 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.services.compute import keypairs_client
+from tempest.lib.services.compute import servers_client
+from tempest.lib.services.identity.v2 import tenants_client
+from tempest.lib.services.identity.v3 import projects_client
+from tempest import manager
+
+from neutron_tempest_plugin import config
+from neutron_tempest_plugin.services.network.json import network_client
+
+CONF = config.CONF
+
+
+class Manager(manager.Manager):
+ """
+ Top level manager for OpenStack tempest clients
+ """
+ default_params = {
+ 'disable_ssl_certificate_validation':
+ CONF.identity.disable_ssl_certificate_validation,
+ 'ca_certs': CONF.identity.ca_certificates_file,
+ 'trace_requests': CONF.debug.trace_requests
+ }
+
+ # NOTE: Tempest uses timeout values of compute API if project specific
+ # timeout values don't exist.
+ default_params_with_timeout_values = {
+ 'build_interval': CONF.compute.build_interval,
+ 'build_timeout': CONF.compute.build_timeout
+ }
+ default_params_with_timeout_values.update(default_params)
+
+ def __init__(self, credentials=None, service=None):
+ super(Manager, self).__init__(credentials=credentials)
+
+ self._set_identity_clients()
+
+ self.network_client = network_client.NetworkClientJSON(
+ self.auth_provider,
+ CONF.network.catalog_type,
+ CONF.network.region or CONF.identity.region,
+ endpoint_type=CONF.network.endpoint_type,
+ build_interval=CONF.network.build_interval,
+ build_timeout=CONF.network.build_timeout,
+ **self.default_params)
+
+ params = {
+ 'service': CONF.compute.catalog_type,
+ 'region': CONF.compute.region or CONF.identity.region,
+ 'endpoint_type': CONF.compute.endpoint_type,
+ 'build_interval': CONF.compute.build_interval,
+ 'build_timeout': CONF.compute.build_timeout
+ }
+ params.update(self.default_params)
+
+ self.servers_client = servers_client.ServersClient(
+ self.auth_provider,
+ enable_instance_password=CONF.compute_feature_enabled
+ .enable_instance_password,
+ **params)
+ self.keypairs_client = keypairs_client.KeyPairsClient(
+ self.auth_provider, **params)
+
+ def _set_identity_clients(self):
+ params = {
+ 'service': CONF.identity.catalog_type,
+ 'region': CONF.identity.region
+ }
+ params.update(self.default_params_with_timeout_values)
+ params_v2_admin = params.copy()
+ params_v2_admin['endpoint_type'] = CONF.identity.v2_admin_endpoint_type
+ # Client uses admin endpoint type of Keystone API v2
+ self.tenants_client = tenants_client.TenantsClient(self.auth_provider,
+ **params_v2_admin)
+ # Client uses admin endpoint type of Keystone API v3
+ self.projects_client = projects_client.ProjectsClient(
+ self.auth_provider, **params_v2_admin)
diff --git a/neutron_tempest_plugin/api/test_address_scopes.py b/neutron_tempest_plugin/api/test_address_scopes.py
new file mode 100644
index 0000000..e9575b4
--- /dev/null
+++ b/neutron_tempest_plugin/api/test_address_scopes.py
@@ -0,0 +1,117 @@
+# Copyright (c) 2015 Red Hat, Inc.
+#
+# 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.common.utils import data_utils
+from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
+from tempest import test
+
+from neutron_tempest_plugin.api import base
+
+
+ADDRESS_SCOPE_NAME = 'smoke-address-scope'
+
+
+class AddressScopeTestBase(base.BaseAdminNetworkTest):
+
+ required_extensions = ['address-scope']
+
+ def _create_address_scope(self, is_admin=False, **kwargs):
+ name = data_utils.rand_name(ADDRESS_SCOPE_NAME)
+ return self.create_address_scope(name=name, is_admin=is_admin,
+ **kwargs)
+
+ def _test_update_address_scope_helper(self, is_admin=False, shared=None):
+ address_scope = self._create_address_scope(is_admin=is_admin,
+ ip_version=4)
+
+ if is_admin:
+ client = self.admin_client
+ else:
+ client = self.client
+
+ kwargs = {'name': 'new_name'}
+ if shared is not None:
+ kwargs['shared'] = shared
+
+ client.update_address_scope(address_scope['id'], **kwargs)
+ body = client.show_address_scope(address_scope['id'])
+ address_scope = body['address_scope']
+ self.assertEqual('new_name', address_scope['name'])
+ return address_scope
+
+
+class AddressScopeTest(AddressScopeTestBase):
+
+ @decorators.idempotent_id('045f9294-8b1a-4848-b6a8-edf1b41e9d06')
+ def test_tenant_create_list_address_scope(self):
+ address_scope = self._create_address_scope(ip_version=4)
+ body = self.client.list_address_scopes()
+ returned_address_scopes = body['address_scopes']
+ self.assertIn(address_scope['id'],
+ [a_s['id'] for a_s in returned_address_scopes],
+ "Created address scope id should be in the list")
+ self.assertIn(address_scope['name'],
+ [a_s['name'] for a_s in returned_address_scopes],
+ "Created address scope name should be in the list")
+
+ @decorators.idempotent_id('85e0326b-4c75-4b92-bd6e-7c7de6aaf05c')
+ def test_show_address_scope(self):
+ address_scope = self._create_address_scope(ip_version=4)
+ body = self.client.show_address_scope(
+ address_scope['id'])
+ returned_address_scope = body['address_scope']
+ self.assertEqual(address_scope['id'], returned_address_scope['id'])
+ self.assertEqual(address_scope['name'],
+ returned_address_scope['name'])
+ self.assertFalse(returned_address_scope['shared'])
+
+ @decorators.idempotent_id('bbd57364-6d57-48e4-b0f1-8b9a998f5e06')
+ @test.requires_ext(extension="project-id", service="network")
+ def test_show_address_scope_project_id(self):
+ address_scope = self._create_address_scope(ip_version=4)
+ body = self.client.show_address_scope(address_scope['id'])
+ show_addr_scope = body['address_scope']
+ self.assertIn('project_id', show_addr_scope)
+ self.assertIn('tenant_id', show_addr_scope)
+ self.assertEqual(self.client.tenant_id, show_addr_scope['project_id'])
+ self.assertEqual(self.client.tenant_id, show_addr_scope['tenant_id'])
+
+ @decorators.idempotent_id('85a259b2-ace6-4e32-9657-a9a392b452aa')
+ def test_tenant_update_address_scope(self):
+ self._test_update_address_scope_helper()
+
+ @decorators.idempotent_id('22b3b600-72a8-4b60-bc94-0f29dd6271df')
+ def test_delete_address_scope(self):
+ address_scope = self._create_address_scope(ip_version=4)
+ self.client.delete_address_scope(address_scope['id'])
+ self.assertRaises(lib_exc.NotFound, self.client.show_address_scope,
+ address_scope['id'])
+
+ @decorators.idempotent_id('5a06c287-8036-4d04-9d78-def8e06d43df')
+ def test_admin_create_shared_address_scope(self):
+ address_scope = self._create_address_scope(is_admin=True, shared=True,
+ ip_version=4)
+ body = self.admin_client.show_address_scope(
+ address_scope['id'])
+ returned_address_scope = body['address_scope']
+ self.assertEqual(address_scope['name'],
+ returned_address_scope['name'])
+ self.assertTrue(returned_address_scope['shared'])
+
+ @decorators.idempotent_id('e9e1ccdd-9ccd-4076-9503-71820529508b')
+ def test_admin_update_shared_address_scope(self):
+ address_scope = self._test_update_address_scope_helper(is_admin=True,
+ shared=True)
+ self.assertTrue(address_scope['shared'])
diff --git a/neutron_tempest_plugin/api/test_address_scopes_negative.py b/neutron_tempest_plugin/api/test_address_scopes_negative.py
new file mode 100644
index 0000000..13c5aac
--- /dev/null
+++ b/neutron_tempest_plugin/api/test_address_scopes_negative.py
@@ -0,0 +1,92 @@
+# Copyright (c) 2015 Red Hat, Inc.
+#
+# 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.common.utils import data_utils
+from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
+
+from neutron_tempest_plugin.api import test_address_scopes
+
+
+class AddressScopeTestNegative(test_address_scopes.AddressScopeTestBase):
+
+ @decorators.attr(type='negative')
+ @decorators.idempotent_id('9c92ec34-0c50-4104-aa47-9ce98d5088df')
+ def test_tenant_create_shared_address_scope(self):
+ self.assertRaises(lib_exc.Forbidden, self._create_address_scope,
+ shared=True, ip_version=4)
+
+ @decorators.attr(type='negative')
+ @decorators.idempotent_id('a857b61e-bf53-4fab-b21a-b0daaf81b5bd')
+ def test_tenant_update_address_scope_shared_true(self):
+ self.assertRaises(lib_exc.Forbidden,
+ self._test_update_address_scope_helper, shared=True)
+
+ @decorators.attr(type='negative')
+ @decorators.idempotent_id('a859ef2f-9c76-4e2e-ba0f-e0339a489e8c')
+ def test_tenant_update_address_scope_shared_false(self):
+ self.assertRaises(lib_exc.Forbidden,
+ self._test_update_address_scope_helper, shared=False)
+
+ @decorators.attr(type='negative')
+ @decorators.idempotent_id('9b6dd7ad-cabb-4f55-bd5e-e61176ef41f6')
+ def test_get_non_existent_address_scope(self):
+ non_exist_id = data_utils.rand_name('address_scope')
+ self.assertRaises(lib_exc.NotFound, self.client.show_address_scope,
+ non_exist_id)
+
+ @decorators.attr(type='negative')
+ @decorators.idempotent_id('ef213552-f2da-487d-bf4a-e1705d115ff1')
+ def test_tenant_get_not_shared_admin_address_scope(self):
+ address_scope = self._create_address_scope(is_admin=True,
+ ip_version=4)
+ # None-shared admin address scope cannot be retrieved by tenant user.
+ self.assertRaises(lib_exc.NotFound, self.client.show_address_scope,
+ address_scope['id'])
+
+ @decorators.attr(type='negative')
+ @decorators.idempotent_id('5c25dc6a-1e92-467a-9cc7-cda74b6003db')
+ def test_delete_non_existent_address_scope(self):
+ non_exist_id = data_utils.rand_name('address_scope')
+ self.assertRaises(lib_exc.NotFound, self.client.delete_address_scope,
+ non_exist_id)
+
+ @decorators.attr(type='negative')
+ @decorators.idempotent_id('47c25dc5-e886-4a84-88c3-ac5031969661')
+ def test_update_non_existent_address_scope(self):
+ non_exist_id = data_utils.rand_name('address_scope')
+ self.assertRaises(lib_exc.NotFound, self.client.update_address_scope,
+ non_exist_id, name='foo-name')
+
+ @decorators.attr(type='negative')
+ @decorators.idempotent_id('702d0515-82cb-4207-b0d9-703336e54665')
+ def test_update_shared_address_scope_to_unshare(self):
+ address_scope = self._create_address_scope(is_admin=True, shared=True,
+ ip_version=4)
+ self.assertRaises(lib_exc.BadRequest,
+ self.admin_client.update_address_scope,
+ address_scope['id'], name='new-name', shared=False)
+
+ @decorators.attr(type='negative')
+ @decorators.idempotent_id('1e471e5c-6f9c-437a-9257-fd9bc4b6f0fb')
+ def test_delete_address_scope_associated_with_subnetpool(self):
+ address_scope = self._create_address_scope(ip_version=4)
+ prefixes = [u'10.11.12.0/24']
+ subnetpool_data = {
+ 'name': 'foo-subnetpool',
+ 'min_prefixlen': '29', 'prefixes': prefixes,
+ 'address_scope_id': address_scope['id']}
+ self.create_subnetpool(**subnetpool_data)
+ self.assertRaises(lib_exc.Conflict, self.client.delete_address_scope,
+ address_scope['id'])
diff --git a/neutron_tempest_plugin/api/test_allowed_address_pair.py b/neutron_tempest_plugin/api/test_allowed_address_pair.py
new file mode 100644
index 0000000..1c6abcc
--- /dev/null
+++ b/neutron_tempest_plugin/api/test_allowed_address_pair.py
@@ -0,0 +1,128 @@
+# Copyright 2014 OpenStack Foundation
+# All Rights Reserved.
+#
+# 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 netaddr
+from tempest.lib import decorators
+
+from neutron_tempest_plugin.api import base
+from neutron_tempest_plugin import config
+
+
+class AllowedAddressPairTestJSON(base.BaseNetworkTest):
+
+ """
+ Tests the Neutron Allowed Address Pair API extension using the Tempest
+ REST client. The following API operations are tested with this extension:
+
+ create port
+ list ports
+ update port
+ show port
+
+ v2.0 of the Neutron API is assumed. It is also assumed that the following
+ options are defined in the [network-feature-enabled] section of
+ etc/tempest.conf
+
+ api_extensions
+ """
+
+ required_extensions = ['allowed-address-pairs']
+
+ @classmethod
+ def resource_setup(cls):
+ super(AllowedAddressPairTestJSON, cls).resource_setup()
+ cls.network = cls.create_network()
+ cls.create_subnet(cls.network)
+ port = cls.create_port(cls.network)
+ cls.ip_address = port['fixed_ips'][0]['ip_address']
+ cls.mac_address = port['mac_address']
+
+ @decorators.idempotent_id('86c3529b-1231-40de-803c-00e40882f043')
+ def test_create_list_port_with_address_pair(self):
+ # Create port with allowed address pair attribute
+ allowed_address_pairs = [{'ip_address': self.ip_address,
+ 'mac_address': self.mac_address}]
+ body = self.client.create_port(
+ network_id=self.network['id'],
+ allowed_address_pairs=allowed_address_pairs)
+ port_id = body['port']['id']
+ self.addCleanup(self.client.delete_port, port_id)
+
+ # Confirm port was created with allowed address pair attribute
+ body = self.client.list_ports()
+ ports = body['ports']
+ port = [p for p in ports if p['id'] == port_id]
+ msg = 'Created port not found in list of ports returned by Neutron'
+ self.assertTrue(port, msg)
+ self._confirm_allowed_address_pair(port[0], self.ip_address)
+
+ def _update_port_with_address(self, address, mac_address=None, **kwargs):
+ # Create a port without allowed address pair
+ body = self.client.create_port(network_id=self.network['id'])
+ port_id = body['port']['id']
+ self.addCleanup(self.client.delete_port, port_id)
+ if mac_address is None:
+ mac_address = self.mac_address
+
+ # Update allowed address pair attribute of port
+ allowed_address_pairs = [{'ip_address': address,
+ 'mac_address': mac_address}]
+ if kwargs:
+ allowed_address_pairs.append(kwargs['allowed_address_pairs'])
+ body = self.client.update_port(
+ port_id, allowed_address_pairs=allowed_address_pairs)
+ allowed_address_pair = body['port']['allowed_address_pairs']
+ self.assertItemsEqual(allowed_address_pair, allowed_address_pairs)
+
+ @decorators.idempotent_id('9599b337-272c-47fd-b3cf-509414414ac4')
+ def test_update_port_with_address_pair(self):
+ # Update port with allowed address pair
+ self._update_port_with_address(self.ip_address)
+
+ @decorators.idempotent_id('4d6d178f-34f6-4bff-a01c-0a2f8fe909e4')
+ def test_update_port_with_cidr_address_pair(self):
+ # Update allowed address pair with cidr
+ cidr = str(
+ netaddr.IPNetwork(config.safe_get_config_value(
+ 'network', 'project_network_cidr')))
+ self._update_port_with_address(cidr)
+
+ @decorators.idempotent_id('b3f20091-6cd5-472b-8487-3516137df933')
+ def test_update_port_with_multiple_ip_mac_address_pair(self):
+ # Create an ip _address and mac_address through port create
+ resp = self.client.create_port(network_id=self.network['id'])
+ newportid = resp['port']['id']
+ self.addCleanup(self.client.delete_port, newportid)
+ ipaddress = resp['port']['fixed_ips'][0]['ip_address']
+ macaddress = resp['port']['mac_address']
+
+ # Update allowed address pair port with multiple ip and mac
+ allowed_address_pairs = {'ip_address': ipaddress,
+ 'mac_address': macaddress}
+ self._update_port_with_address(
+ self.ip_address, self.mac_address,
+ allowed_address_pairs=allowed_address_pairs)
+
+ def _confirm_allowed_address_pair(self, port, ip):
+ msg = 'Port allowed address pairs should not be empty'
+ self.assertTrue(port['allowed_address_pairs'], msg)
+ ip_address = port['allowed_address_pairs'][0]['ip_address']
+ mac_address = port['allowed_address_pairs'][0]['mac_address']
+ self.assertEqual(ip_address, ip)
+ self.assertEqual(mac_address, self.mac_address)
+
+
+class AllowedAddressPairIpV6TestJSON(AllowedAddressPairTestJSON):
+ _ip_version = 6
diff --git a/neutron_tempest_plugin/api/test_auto_allocated_topology.py b/neutron_tempest_plugin/api/test_auto_allocated_topology.py
new file mode 100644
index 0000000..37f9ad1
--- /dev/null
+++ b/neutron_tempest_plugin/api/test_auto_allocated_topology.py
@@ -0,0 +1,117 @@
+# Copyright 2016 IBM
+# All Rights Reserved.
+#
+# 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 neutron_lib.api.definitions import auto_allocated_topology
+from oslo_config import cfg
+from tempest.lib import decorators
+
+from neutron_tempest_plugin.api import base
+
+
+class TestAutoAllocatedTopology(base.BaseAdminNetworkTest):
+
+ """
+ Tests the Get-Me-A-Network operations in the Neutron API
+ using the REST client for Neutron.
+ """
+ # NOTE(armax): this is a precaution to avoid interference
+ # from other tests exercising this extension. So long as
+ # all tests are added under TestAutoAllocatedTopology,
+ # nothing bad should happen.
+ force_tenant_isolation = True
+ required_extensions = [auto_allocated_topology.ALIAS]
+
+ @classmethod
+ def resource_setup(cls):
+ super(TestAutoAllocatedTopology, cls).resource_setup()
+
+ # The deployment must contain a default subnetpool
+ body = cls.client.list_subnetpools(is_default=True)
+ # The deployment may contain one or two default subnetpools:
+ # one ipv4 pool, or one ipv6 pool, or one of each.
+ # This run-time dependency should be revisited if the test is
+ # moved over to tempest.
+ cls.num_subnetpools = len(body['subnetpools'])
+ if cls.num_subnetpools == 0:
+ raise cls.skipException("No default subnetpool")
+
+ # Ensure the public external network is the default external network
+ public_net_id = cfg.CONF.network.public_network_id
+ cls.admin_client.update_network(public_net_id, is_default=True)
+ # Ensure that is_default does not accidentally flip back to False
+ # because of network_update requests that do not contain is_default.
+ cls.admin_client.update_network(public_net_id, description="gman")
+
+ def _count_topology_resources(self):
+ '''Count the resources whose names begin with 'auto_allocated_'.'''
+
+ def _count(resources):
+ return len([resource['id'] for resource in resources
+ if resource['name'].startswith('auto_allocated_')])
+
+ up = {'admin_state_up': True}
+ networks = _count(self.client.list_networks(**up)['networks'])
+ subnets = _count(self.client.list_subnets(**up)['subnets'])
+ routers = _count(self.client.list_routers(**up)['routers'])
+ return networks, subnets, routers
+
+ def _add_topology_cleanup(self, client):
+ '''Add the auto-allocated resources to the cleanup lists.'''
+
+ body = client.list_routers(name='auto_allocated_router')
+ self.routers.extend(body['routers'])
+ body = client.list_subnets(name='auto_allocated_subnet_v4')
+ self.subnets.extend(body['subnets'])
+ body = client.list_subnets(name='auto_allocated_subnet_v6')
+ self.subnets.extend(body['subnets'])
+ body = client.list_networks(name='auto_allocated_network')
+ self.networks.extend(body['networks'])
+
+ @decorators.idempotent_id('64bc0b02-cee4-11e5-9f3c-080027605a2b')
+ def test_get_allocated_net_topology_as_tenant(self):
+ resources_before = self._count_topology_resources()
+ self.assertEqual((0, 0, 0), resources_before)
+
+ body = self.client.get_auto_allocated_topology()
+ topology = body[auto_allocated_topology.RESOURCE_NAME]
+ self.assertIsNotNone(topology)
+ self._add_topology_cleanup(self.client)
+
+ network_id1 = topology['id']
+ self.assertIsNotNone(network_id1)
+ network = self.client.show_network(topology['id'])['network']
+ self.assertTrue(network['admin_state_up'])
+ resources_after1 = self._count_topology_resources()
+ # One network, two subnets (v4 and v6) and one router
+ self.assertEqual((1, self.num_subnetpools, 1), resources_after1)
+
+ body = self.client.get_auto_allocated_topology()
+ topology = body[auto_allocated_topology.RESOURCE_NAME]
+ network_id2 = topology['id']
+ resources_after2 = self._count_topology_resources()
+ # After the initial GET, the API should be idempotent
+ self.assertEqual(network_id1, network_id2)
+ self.assertEqual(resources_after1, resources_after2)
+
+ @decorators.idempotent_id('aabc0b02-cee4-11e5-9f3c-091127605a2b')
+ def test_delete_allocated_net_topology_as_tenant(self):
+ resources_before = self._count_topology_resources()
+ self.assertEqual((0, 0, 0), resources_before)
+ body = self.client.get_auto_allocated_topology()
+ topology = body[auto_allocated_topology.RESOURCE_NAME]
+ self.assertIsNotNone(topology)
+ self.client.delete_auto_allocated_topology()
+ resources_after = self._count_topology_resources()
+ self.assertEqual((0, 0, 0), resources_after)
diff --git a/neutron_tempest_plugin/api/test_dhcp_ipv6.py b/neutron_tempest_plugin/api/test_dhcp_ipv6.py
new file mode 100644
index 0000000..f408c97
--- /dev/null
+++ b/neutron_tempest_plugin/api/test_dhcp_ipv6.py
@@ -0,0 +1,98 @@
+# Copyright 2014 OpenStack Foundation
+# All Rights Reserved.
+#
+# 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 netaddr
+from neutron_lib import constants
+from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
+
+from neutron_tempest_plugin.api import base
+from neutron_tempest_plugin import config
+
+CONF = config.CONF
+
+
+class NetworksTestDHCPv6(base.BaseNetworkTest):
+ _ip_version = 6
+
+ def setUp(self):
+ super(NetworksTestDHCPv6, self).setUp()
+ self.addCleanup(self._clean_network)
+
+ @classmethod
+ def skip_checks(cls):
+ msg = None
+ if not CONF.network_feature_enabled.ipv6:
+ msg = "IPv6 is not enabled"
+ elif not CONF.network_feature_enabled.ipv6_subnet_attributes:
+ msg = "DHCPv6 attributes are not enabled."
+ if msg:
+ raise cls.skipException(msg)
+
+ @classmethod
+ def resource_setup(cls):
+ super(NetworksTestDHCPv6, cls).resource_setup()
+ cls.network = cls.create_network()
+
+ def _remove_from_list_by_index(self, things_list, elem):
+ for index, i in enumerate(things_list):
+ if i['id'] == elem['id']:
+ break
+ del things_list[index]
+
+ def _clean_network(self):
+ body = self.client.list_ports()
+ ports = body['ports']
+ for port in ports:
+ if (port['device_owner'].startswith(
+ constants.DEVICE_OWNER_ROUTER_INTF)
+ and port['device_id'] in [r['id'] for r in self.routers]):
+ self.client.remove_router_interface_with_port_id(
+ port['device_id'], port['id']
+ )
+ else:
+ if port['id'] in [p['id'] for p in self.ports]:
+ self.client.delete_port(port['id'])
+ self._remove_from_list_by_index(self.ports, port)
+ body = self.client.list_subnets()
+ subnets = body['subnets']
+ for subnet in subnets:
+ if subnet['id'] in [s['id'] for s in self.subnets]:
+ self.client.delete_subnet(subnet['id'])
+ self._remove_from_list_by_index(self.subnets, subnet)
+ body = self.client.list_routers()
+ routers = body['routers']
+ for router in routers:
+ if router['id'] in [r['id'] for r in self.routers]:
+ self.client.delete_router(router['id'])
+ self._remove_from_list_by_index(self.routers, router)
+
+ @decorators.idempotent_id('98244d88-d990-4570-91d4-6b25d70d08af')
+ def test_dhcp_stateful_fixedips_outrange(self):
+ """When port gets IP address from fixed IP range it
+ shall be checked if it's from subnets range.
+ """
+ kwargs = {'ipv6_ra_mode': 'dhcpv6-stateful',
+ 'ipv6_address_mode': 'dhcpv6-stateful'}
+ subnet = self.create_subnet(self.network, **kwargs)
+ ip_range = netaddr.IPRange(subnet["allocation_pools"][0]["start"],
+ subnet["allocation_pools"][0]["end"])
+ for i in range(1, 3):
+ ip = netaddr.IPAddress(ip_range.last + i).format()
+ self.assertRaises(lib_exc.BadRequest,
+ self.create_port,
+ self.network,
+ fixed_ips=[{'subnet_id': subnet['id'],
+ 'ip_address': ip}])
diff --git a/neutron_tempest_plugin/api/test_extension_driver_port_security.py b/neutron_tempest_plugin/api/test_extension_driver_port_security.py
new file mode 100644
index 0000000..7a8cf0e
--- /dev/null
+++ b/neutron_tempest_plugin/api/test_extension_driver_port_security.py
@@ -0,0 +1,150 @@
+# Copyright 2015 OpenStack Foundation
+# All Rights Reserved.
+#
+# 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 ddt
+from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
+from tempest import test
+
+from neutron_tempest_plugin.api import base
+from neutron_tempest_plugin.api import base_security_groups as base_security
+
+FAKE_IP = '10.0.0.1'
+FAKE_MAC = '00:25:64:e8:19:dd'
+
+
+@ddt.ddt
+class PortSecTest(base_security.BaseSecGroupTest,
+ base.BaseNetworkTest):
+
+ @decorators.idempotent_id('7c338ddf-e64e-4118-bd33-e49a1f2f1495')
+ @test.requires_ext(extension='port-security', service='network')
+ def test_port_sec_default_value(self):
+ # Default port-sec value is True, and the attr of the port will inherit
+ # from the port-sec of the network when it not be specified in API
+ network = self.create_network()
+ self.assertTrue(network['port_security_enabled'])
+ self.create_subnet(network)
+ port = self.create_port(network)
+ self.assertTrue(port['port_security_enabled'])
+
+ @decorators.idempotent_id('e60eafd2-31de-4c38-8106-55447d033b57')
+ @test.requires_ext(extension='port-security', service='network')
+ @ddt.unpack
+ @ddt.data({'port_sec_net': False, 'port_sec_port': True, 'expected': True},
+ {'port_sec_net': True, 'port_sec_port': False,
+ 'expected': False})
+ def test_port_sec_specific_value(self, port_sec_net, port_sec_port,
+ expected):
+ network = self.create_network(port_security_enabled=port_sec_net)
+ self.create_subnet(network)
+ port = self.create_port(network, port_security_enabled=port_sec_port)
+ self.assertEqual(network['port_security_enabled'], port_sec_net)
+ self.assertEqual(port['port_security_enabled'], expected)
+
+ @decorators.idempotent_id('fe7c27b9-f320-4daf-b977-b1547c43daf6')
+ @test.requires_ext(extension='port-security', service='network')
+ def test_create_port_sec_with_security_group(self):
+ network = self.create_network(port_security_enabled=True)
+ self.create_subnet(network)
+
+ port = self.create_port(network, security_groups=[])
+ self.assertTrue(port['port_security_enabled'])
+ self.client.delete_port(port['id'])
+
+ port = self.create_port(network, security_groups=[],
+ port_security_enabled=False)
+ self.assertFalse(port['port_security_enabled'])
+ self.assertEmpty(port['security_groups'])
+
+ @decorators.attr(type='negative')
+ @decorators.idempotent_id('ff11226c-a5ff-4ad4-8480-0840e36e47a9')
+ @test.requires_ext(extension='port-security', service='network')
+ def test_port_sec_update_port_failed(self):
+ network = self.create_network()
+ self.create_subnet(network)
+
+ sec_group_body, _ = self._create_security_group()
+ port = self.create_port(network)
+
+ # Exception when set port-sec to False with sec-group defined
+ self.assertRaises(lib_exc.Conflict, self.update_port, port,
+ port_security_enabled=False)
+
+ port = self.update_port(port, security_groups=[],
+ port_security_enabled=False)
+ self.assertEmpty(port['security_groups'])
+ self.assertFalse(port['port_security_enabled'])
+ port = self.update_port(
+ port, security_groups=[sec_group_body['security_group']['id']],
+ port_security_enabled=True)
+
+ self.assertNotEmpty(port['security_groups'])
+ self.assertTrue(port['port_security_enabled'])
+
+ # Remove security group from port before deletion on resource_cleanup
+ self.update_port(port, security_groups=[])
+
+ @decorators.idempotent_id('05642059-1bfc-4581-9bc9-aaa5db08dd60')
+ @test.requires_ext(extension='port-security', service='network')
+ def test_port_sec_update_pass(self):
+ network = self.create_network()
+ self.create_subnet(network)
+ sec_group, _ = self._create_security_group()
+ sec_group_id = sec_group['security_group']['id']
+ port = self.create_port(network, security_groups=[sec_group_id],
+ port_security_enabled=True)
+
+ self.assertNotEmpty(port['security_groups'])
+ self.assertTrue(port['port_security_enabled'])
+
+ port = self.update_port(port, security_groups=[])
+ self.assertEmpty(port['security_groups'])
+ self.assertTrue(port['port_security_enabled'])
+
+ port = self.update_port(port, security_groups=[sec_group_id])
+ self.assertNotEmpty(port['security_groups'])
+ port = self.update_port(port, security_groups=[],
+ port_security_enabled=False)
+ self.assertEmpty(port['security_groups'])
+ self.assertFalse(port['port_security_enabled'])
+
+ @decorators.idempotent_id('2df6114b-b8c3-48a1-96e8-47f08159d35c')
+ @test.requires_ext(extension='port-security', service='network')
+ def test_delete_with_port_sec(self):
+ network = self.create_network(port_security_enabled=True)
+ port = self.create_port(network=network,
+ port_security_enabled=True)
+ self.client.delete_port(port['id'])
+ self.assertTrue(self.client.is_resource_deleted('port', port['id']))
+ self.client.delete_network(network['id'])
+ self.assertTrue(
+ self.client.is_resource_deleted('network', network['id']))
+
+ @decorators.attr(type='negative')
+ @decorators.idempotent_id('ed93e453-3f8d-495e-8e7e-b0e268c2ebd9')
+ @test.requires_ext(extension='port-security', service='network')
+ @test.requires_ext(extension='allowed-address-pairs', service='network')
+ def test_allow_address_pairs(self):
+ network = self.create_network()
+ self.create_subnet(network)
+ port = self.create_port(network=network, port_security_enabled=False)
+ allowed_address_pairs = [{'ip_address': FAKE_IP,
+ 'mac_address': FAKE_MAC}]
+
+ # Exception when set address-pairs with port-sec is False
+ self.assertRaises(lib_exc.Conflict,
+ self.update_port, port,
+ allowed_address_pairs=allowed_address_pairs)
diff --git a/neutron_tempest_plugin/api/test_extensions.py b/neutron_tempest_plugin/api/test_extensions.py
new file mode 100644
index 0000000..4659ba9
--- /dev/null
+++ b/neutron_tempest_plugin/api/test_extensions.py
@@ -0,0 +1,41 @@
+# 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 decorators
+from tempest import test
+
+from neutron_tempest_plugin.api import base
+
+
+class ExtensionsTest(base.BaseNetworkTest):
+
+ def _test_list_extensions_includes(self, ext):
+ body = self.client.list_extensions()
+ extensions = {ext_['alias'] for ext_ in body['extensions']}
+ self.assertNotEmpty(extensions, "Extension list returned is empty")
+ ext_enabled = test.is_extension_enabled(ext, "network")
+ if ext_enabled:
+ self.assertIn(ext, extensions)
+ else:
+ self.assertNotIn(ext, extensions)
+
+ @decorators.idempotent_id('262420b7-a4bb-4a3e-b4b5-e73bad18df8c')
+ def test_list_extensions_sorting(self):
+ self._test_list_extensions_includes('sorting')
+
+ @decorators.idempotent_id('19db409e-a23f-445d-8bc8-ca3d64c84706')
+ def test_list_extensions_pagination(self):
+ self._test_list_extensions_includes('pagination')
+
+ @decorators.idempotent_id('155b7bc2-e358-4dd8-bf3e-1774c084567f')
+ def test_list_extensions_project_id(self):
+ self._test_list_extensions_includes('project-id')
diff --git a/neutron_tempest_plugin/api/test_extra_dhcp_options.py b/neutron_tempest_plugin/api/test_extra_dhcp_options.py
new file mode 100644
index 0000000..e5f73b2
--- /dev/null
+++ b/neutron_tempest_plugin/api/test_extra_dhcp_options.py
@@ -0,0 +1,98 @@
+# Copyright 2013 OpenStack Foundation
+# All Rights Reserved.
+#
+# 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.common.utils import data_utils
+from tempest.lib import decorators
+
+from neutron_tempest_plugin.api import base
+
+
+class ExtraDHCPOptionsTestJSON(base.BaseNetworkTest):
+
+ """
+ Tests the following operations with the Extra DHCP Options Neutron API
+ extension:
+
+ port create
+ port list
+ port show
+ port update
+
+ v2.0 of the Neutron API is assumed. It is also assumed that the Extra
+ DHCP Options extension is enabled in the [network-feature-enabled]
+ section of etc/tempest.conf
+ """
+
+ required_extensions = ['extra_dhcp_opt']
+
+ @classmethod
+ def resource_setup(cls):
+ super(ExtraDHCPOptionsTestJSON, cls).resource_setup()
+ cls.network = cls.create_network()
+ cls.subnet = cls.create_subnet(cls.network)
+ cls.port = cls.create_port(cls.network)
+ cls.ip_tftp = ('123.123.123.123' if cls._ip_version == 4
+ else '2015::dead')
+ cls.ip_server = ('123.123.123.45' if cls._ip_version == 4
+ else '2015::badd')
+ cls.extra_dhcp_opts = [
+ {'opt_value': 'pxelinux.0', 'opt_name': 'bootfile-name'},
+ {'opt_value': cls.ip_tftp, 'opt_name': 'tftp-server'},
+ {'opt_value': cls.ip_server, 'opt_name': 'server-ip-address'}
+ ]
+
+ @decorators.idempotent_id('d2c17063-3767-4a24-be4f-a23dbfa133c9')
+ def test_create_list_port_with_extra_dhcp_options(self):
+ # Create a port with Extra DHCP Options
+ body = self.client.create_port(
+ network_id=self.network['id'],
+ extra_dhcp_opts=self.extra_dhcp_opts)
+ port_id = body['port']['id']
+ self.addCleanup(self.client.delete_port, port_id)
+
+ # Confirm port created has Extra DHCP Options
+ body = self.client.list_ports()
+ ports = body['ports']
+ port = [p for p in ports if p['id'] == port_id]
+ self.assertTrue(port)
+ self._confirm_extra_dhcp_options(port[0], self.extra_dhcp_opts)
+
+ @decorators.idempotent_id('9a6aebf4-86ee-4f47-b07a-7f7232c55607')
+ def test_update_show_port_with_extra_dhcp_options(self):
+ # Update port with extra dhcp options
+ name = data_utils.rand_name('new-port-name')
+ body = self.client.update_port(
+ self.port['id'],
+ name=name,
+ extra_dhcp_opts=self.extra_dhcp_opts)
+ # Confirm extra dhcp options were added to the port
+ body = self.client.show_port(self.port['id'])
+ self._confirm_extra_dhcp_options(body['port'], self.extra_dhcp_opts)
+
+ def _confirm_extra_dhcp_options(self, port, extra_dhcp_opts):
+ retrieved = port['extra_dhcp_opts']
+ self.assertEqual(len(retrieved), len(extra_dhcp_opts))
+ for retrieved_option in retrieved:
+ for option in extra_dhcp_opts:
+ if (retrieved_option['opt_value'] == option['opt_value'] and
+ retrieved_option['opt_name'] == option['opt_name']):
+ break
+ else:
+ self.fail('Extra DHCP option not found in port %s' %
+ str(retrieved_option))
+
+
+class ExtraDHCPOptionsIpV6TestJSON(ExtraDHCPOptionsTestJSON):
+ _ip_version = 6
diff --git a/neutron_tempest_plugin/api/test_flavors_extensions.py b/neutron_tempest_plugin/api/test_flavors_extensions.py
new file mode 100644
index 0000000..30f1eb6
--- /dev/null
+++ b/neutron_tempest_plugin/api/test_flavors_extensions.py
@@ -0,0 +1,155 @@
+# Copyright 2015 Hewlett-Packard Development Company, L.P.
+#
+# 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 decorators
+from tempest.lib import exceptions as lib_exc
+
+from neutron_tempest_plugin.api import base
+
+
+class TestFlavorsJson(base.BaseAdminNetworkTest):
+
+ """
+ Tests the following operations in the Neutron API using the REST client for
+ Neutron:
+
+ List, Show, Create, Update, Delete Flavors
+ List, Show, Create, Update, Delete service profiles
+ """
+
+ required_extensions = ['flavors']
+
+ @classmethod
+ def resource_setup(cls):
+ super(TestFlavorsJson, cls).resource_setup()
+
+ # Use flavors service type as know this is loaded
+ service_type = "FLAVORS"
+ description_flavor = "flavor is created by tempest"
+ name_flavor = "Best flavor created by tempest"
+
+ # The check above will pass if api_extensions=all, which does
+ # not mean flavors extension itself is present.
+ try:
+ cls.flavor = cls.create_flavor(name_flavor, description_flavor,
+ service_type)
+ except lib_exc.NotFound:
+ msg = "flavors plugin not enabled."
+ raise cls.skipException(msg)
+
+ description_sp = "service profile created by tempest"
+ # Drivers are supported as is an empty driver field. Use an
+ # empty field for now since otherwise driver is validated against the
+ # servicetype configuration which may differ in test scenarios.
+ driver = ""
+ metainfo = '{"data": "value"}'
+ cls.service_profile = cls.create_service_profile(
+ description=description_sp, metainfo=metainfo, driver=driver)
+
+ def _delete_service_profile(self, service_profile_id):
+ # Deletes a service profile and verifies if it is deleted or not
+ self.admin_client.delete_service_profile(service_profile_id)
+ # Asserting that service profile is not found in list after deletion
+ labels = self.admin_client.list_service_profiles(id=service_profile_id)
+ self.assertEqual(len(labels['service_profiles']), 0)
+
+ @decorators.idempotent_id('b12a9487-b6a2-4cff-a69a-fe2a0b64fae6')
+ def test_create_update_delete_service_profile(self):
+ # Creates a service profile
+ description = "service_profile created by tempest"
+ driver = ""
+ metainfo = '{"data": "value"}'
+ body = self.admin_client.create_service_profile(
+ description=description, driver=driver, metainfo=metainfo)
+ service_profile = body['service_profile']
+ # Updates a service profile
+ self.admin_client.update_service_profile(service_profile['id'],
+ enabled=False)
+ self.assertTrue(service_profile['enabled'])
+ # Deletes a service profile
+ self.addCleanup(self._delete_service_profile,
+ service_profile['id'])
+ # Assert whether created service profiles are found in service profile
+ # lists or fail if created service profiles are not found in service
+ # profiles list
+ labels = (self.admin_client.list_service_profiles(
+ id=service_profile['id']))
+ self.assertEqual(len(labels['service_profiles']), 1)
+
+ @decorators.idempotent_id('136bcf09-00af-4da7-9b7f-174735d4aebd')
+ def test_create_update_delete_flavor(self):
+ # Creates a flavor
+ description = "flavor created by tempest"
+ service = "FLAVORS"
+ name = "Best flavor created by tempest"
+ body = self.admin_client.create_flavor(name=name, service_type=service,
+ description=description)
+ flavor = body['flavor']
+ # Updates a flavor
+ self.admin_client.update_flavor(flavor['id'], enabled=False)
+ self.assertTrue(flavor['enabled'])
+ # Deletes a flavor
+ self.addCleanup(self._delete_flavor, flavor['id'])
+ # Assert whether created flavors are found in flavor lists or fail
+ # if created flavors are not found in flavors list
+ labels = (self.admin_client.list_flavors(id=flavor['id']))
+ self.assertEqual(len(labels['flavors']), 1)
+
+ @decorators.idempotent_id('30abb445-0eea-472e-bd02-8649f54a5968')
+ def test_show_service_profile(self):
+ # Verifies the details of a service profile
+ body = self.admin_client.show_service_profile(
+ self.service_profile['id'])
+ service_profile = body['service_profile']
+ self.assertEqual(self.service_profile['id'], service_profile['id'])
+ self.assertEqual(self.service_profile['description'],
+ service_profile['description'])
+ self.assertEqual(self.service_profile['metainfo'],
+ service_profile['metainfo'])
+ self.assertTrue(service_profile['enabled'])
+
+ @decorators.idempotent_id('362f9658-164b-44dd-8356-151bc9b7be72')
+ def test_show_flavor(self):
+ # Verifies the details of a flavor
+ body = self.admin_client.show_flavor(self.flavor['id'])
+ flavor = body['flavor']
+ self.assertEqual(self.flavor['id'], flavor['id'])
+ self.assertEqual(self.flavor['description'], flavor['description'])
+ self.assertEqual(self.flavor['name'], flavor['name'])
+ self.assertTrue(flavor['enabled'])
+
+ @decorators.idempotent_id('eb3dd12e-6dfd-45f4-8393-46e0fa19860e')
+ def test_list_flavors(self):
+ # Verify flavor lists
+ body = self.admin_client.list_flavors(id=33)
+ flavors = body['flavors']
+ self.assertEqual(0, len(flavors))
+
+ @decorators.idempotent_id('e2fb2f8c-45bf-429a-9f17-171c70444612')
+ def test_list_service_profiles(self):
+ # Verify service profiles lists
+ body = self.admin_client.list_service_profiles(id=33)
+ service_profiles = body['service_profiles']
+ self.assertEqual(0, len(service_profiles))
+
+ def _delete_flavor(self, flavor_id):
+ # Deletes a flavor and verifies if it is deleted or not
+ self.admin_client.delete_flavor(flavor_id)
+ # Asserting that the flavor is not found in list after deletion
+ labels = self.admin_client.list_flavors(id=flavor_id)
+ self.assertEqual(len(labels['flavors']), 0)
+
+
+class TestFlavorsIpV6TestJSON(TestFlavorsJson):
+ _ip_version = 6
diff --git a/neutron_tempest_plugin/api/test_floating_ips.py b/neutron_tempest_plugin/api/test_floating_ips.py
new file mode 100644
index 0000000..3b283cb
--- /dev/null
+++ b/neutron_tempest_plugin/api/test_floating_ips.py
@@ -0,0 +1,107 @@
+# Copyright 2013 OpenStack Foundation
+# All Rights Reserved.
+#
+# 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.common.utils import data_utils
+from tempest.lib import decorators
+from tempest import test
+
+from neutron_tempest_plugin.api import base
+from neutron_tempest_plugin import config
+
+CONF = config.CONF
+
+
+class FloatingIPTestJSON(base.BaseNetworkTest):
+
+ required_extensions = ['router']
+
+ @classmethod
+ def resource_setup(cls):
+ super(FloatingIPTestJSON, cls).resource_setup()
+ cls.ext_net_id = CONF.network.public_network_id
+
+ # Create network, subnet, router and add interface
+ cls.network = cls.create_network()
+ cls.subnet = cls.create_subnet(cls.network)
+ cls.router = cls.create_router(data_utils.rand_name('router-'),
+ external_network_id=cls.ext_net_id)
+ cls.create_router_interface(cls.router['id'], cls.subnet['id'])
+ cls.port = list()
+ # Create two ports one each for Creation and Updating of floatingIP
+ for i in range(2):
+ cls.create_port(cls.network)
+
+ @decorators.idempotent_id('f6a0fb6c-cb64-4b81-b0d5-f41d8f69d22d')
+ def test_blank_update_clears_association(self):
+ # originally the floating IP had no attributes other than its
+ # association, so an update with an empty body was a signal to
+ # clear the association. This test ensures we maintain that behavior.
+ body = self.client.create_floatingip(
+ floating_network_id=self.ext_net_id,
+ port_id=self.ports[0]['id'],
+ )['floatingip']
+ self.floating_ips.append(body)
+ self.assertEqual(self.ports[0]['id'], body['port_id'])
+ body = self.client.update_floatingip(body['id'])['floatingip']
+ self.assertFalse(body['port_id'])
+
+ @decorators.idempotent_id('c72c1c0c-2193-4aca-eeee-b1442641ffff')
+ @test.requires_ext(extension="standard-attr-description",
+ service="network")
+ def test_create_update_floatingip_description(self):
+ body = self.client.create_floatingip(
+ floating_network_id=self.ext_net_id,
+ port_id=self.ports[0]['id'],
+ description='d1'
+ )['floatingip']
+ self.floating_ips.append(body)
+ self.assertEqual('d1', body['description'])
+ body = self.client.show_floatingip(body['id'])['floatingip']
+ self.assertEqual('d1', body['description'])
+ body = self.client.update_floatingip(body['id'], description='d2')
+ self.assertEqual('d2', body['floatingip']['description'])
+ body = self.client.show_floatingip(body['floatingip']['id'])
+ self.assertEqual('d2', body['floatingip']['description'])
+ # disassociate
+ body = self.client.update_floatingip(body['floatingip']['id'],
+ port_id=None)
+ self.assertEqual('d2', body['floatingip']['description'])
+
+ @decorators.idempotent_id('fd7161e1-2167-4686-a6ff-0f3df08001bb')
+ @test.requires_ext(extension="standard-attr-description",
+ service="network")
+ def test_floatingip_update_extra_attributes_port_id_not_changed(self):
+ port_id = self.ports[1]['id']
+ body = self.client.create_floatingip(
+ floating_network_id=self.ext_net_id,
+ port_id=port_id,
+ description='d1'
+ )['floatingip']
+ self.floating_ips.append(body)
+ self.assertEqual('d1', body['description'])
+ body = self.client.show_floatingip(body['id'])['floatingip']
+ self.assertEqual(port_id, body['port_id'])
+ # Update description
+ body = self.client.update_floatingip(body['id'], description='d2')
+ self.assertEqual('d2', body['floatingip']['description'])
+ # Floating IP association is not changed.
+ self.assertEqual(port_id, body['floatingip']['port_id'])
+ body = self.client.show_floatingip(body['floatingip']['id'])
+ self.assertEqual('d2', body['floatingip']['description'])
+ self.assertEqual(port_id, body['floatingip']['port_id'])
+ # disassociate
+ body = self.client.update_floatingip(body['floatingip']['id'],
+ port_id=None)
+ self.assertIsNone(body['floatingip']['port_id'])
diff --git a/neutron_tempest_plugin/api/test_floating_ips_negative.py b/neutron_tempest_plugin/api/test_floating_ips_negative.py
new file mode 100644
index 0000000..453af71
--- /dev/null
+++ b/neutron_tempest_plugin/api/test_floating_ips_negative.py
@@ -0,0 +1,67 @@
+# Copyright 2014 Hewlett-Packard Development Company, L.P.
+# Copyright 2014 OpenStack Foundation
+# All Rights Reserved.
+#
+# 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.common.utils import data_utils
+from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
+
+from neutron_tempest_plugin.api import base
+from neutron_tempest_plugin import config
+
+CONF = config.CONF
+
+
+class FloatingIPNegativeTestJSON(base.BaseNetworkTest):
+
+ required_extensions = ['router']
+
+ @classmethod
+ def resource_setup(cls):
+ super(FloatingIPNegativeTestJSON, cls).resource_setup()
+ cls.ext_net_id = CONF.network.public_network_id
+ # Create a network with a subnet connected to a router.
+ cls.network = cls.create_network()
+ cls.subnet = cls.create_subnet(cls.network)
+ cls.router = cls.create_router(data_utils.rand_name('router'))
+ cls.create_router_interface(cls.router['id'], cls.subnet['id'])
+ cls.port = cls.create_port(cls.network)
+
+ @decorators.attr(type='negative')
+ @decorators.idempotent_id('0b5b8797-6de7-4191-905c-a48b888eb429')
+ def test_associate_floatingip_with_port_with_floatingip(self):
+ net = self.create_network()
+ subnet = self.create_subnet(net)
+ r = self.create_router('test')
+ self.create_router_interface(r['id'], subnet['id'])
+ self.client.update_router(
+ r['id'],
+ external_gateway_info={
+ 'network_id': self.ext_net_id})
+ self.addCleanup(self.client.update_router, self.router['id'],
+ external_gateway_info={})
+ port = self.create_port(net)
+ body1 = self.client.create_floatingip(
+ floating_network_id=self.ext_net_id)
+ floating_ip1 = body1['floatingip']
+ self.addCleanup(self.client.delete_floatingip, floating_ip1['id'])
+ body2 = self.client.create_floatingip(
+ floating_network_id=self.ext_net_id)
+ floating_ip2 = body2['floatingip']
+ self.addCleanup(self.client.delete_floatingip, floating_ip2['id'])
+ self.client.update_floatingip(floating_ip1['id'],
+ port_id=port['id'])
+ self.assertRaises(lib_exc.Conflict, self.client.update_floatingip,
+ floating_ip2['id'], port_id=port['id'])
diff --git a/neutron_tempest_plugin/api/test_metering_extensions.py b/neutron_tempest_plugin/api/test_metering_extensions.py
new file mode 100644
index 0000000..186b024
--- /dev/null
+++ b/neutron_tempest_plugin/api/test_metering_extensions.py
@@ -0,0 +1,154 @@
+# Copyright (C) 2014 eNovance SAS <licensing@enovance.com>
+#
+# 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 neutron_lib.db import constants as db_const
+from tempest.lib.common.utils import data_utils
+from tempest.lib import decorators
+
+from neutron_tempest_plugin.api import base
+
+LONG_NAME_OK = 'x' * db_const.NAME_FIELD_SIZE
+
+
+class MeteringTestJSON(base.BaseAdminNetworkTest):
+
+ """
+ Tests the following operations in the Neutron API using the REST client for
+ Neutron:
+
+ List, Show, Create, Delete Metering labels
+ List, Show, Create, Delete Metering labels rules
+ """
+
+ required_extensions = ['metering']
+
+ @classmethod
+ def resource_setup(cls):
+ super(MeteringTestJSON, cls).resource_setup()
+ description = "metering label created by tempest"
+ name = data_utils.rand_name("metering-label")
+ cls.metering_label = cls.create_metering_label(name, description)
+ remote_ip_prefix = ("10.0.0.0/24" if cls._ip_version == 4
+ else "fd02::/64")
+ direction = "ingress"
+ cls.metering_label_rule = cls.create_metering_label_rule(
+ remote_ip_prefix, direction,
+ metering_label_id=cls.metering_label['id'])
+
+ def _delete_metering_label(self, metering_label_id):
+ # Deletes a label and verifies if it is deleted or not
+ self.admin_client.delete_metering_label(metering_label_id)
+ # Asserting that the label is not found in list after deletion
+ labels = self.admin_client.list_metering_labels(id=metering_label_id)
+ self.assertEqual(len(labels['metering_labels']), 0)
+
+ def _delete_metering_label_rule(self, metering_label_rule_id):
+ # Deletes a rule and verifies if it is deleted or not
+ self.admin_client.delete_metering_label_rule(
+ metering_label_rule_id)
+ # Asserting that the rule is not found in list after deletion
+ rules = (self.admin_client.list_metering_label_rules(
+ id=metering_label_rule_id))
+ self.assertEqual(len(rules['metering_label_rules']), 0)
+
+ @decorators.idempotent_id('05d7c750-6d26-44d6-82f3-c9dd1f81f358')
+ def test_list_metering_labels(self):
+ # Verify label filtering
+ body = self.admin_client.list_metering_labels(id=33)
+ metering_labels = body['metering_labels']
+ self.assertEqual(0, len(metering_labels))
+
+ @decorators.idempotent_id('ec8e15ff-95d0-433b-b8a6-b466bddb1e50')
+ def test_create_delete_metering_label_with_filters(self):
+ # Creates a label
+ name = data_utils.rand_name('metering-label-')
+ description = "label created by tempest"
+ body = self.admin_client.create_metering_label(name=name,
+ description=description)
+ metering_label = body['metering_label']
+ self.addCleanup(self._delete_metering_label,
+ metering_label['id'])
+ # Assert whether created labels are found in labels list or fail
+ # if created labels are not found in labels list
+ labels = (self.admin_client.list_metering_labels(
+ id=metering_label['id']))
+ self.assertEqual(len(labels['metering_labels']), 1)
+
+ @decorators.idempotent_id('46608f8d-2e27-4eb6-a0b4-dbe405144c4d')
+ def test_create_delete_metering_label_with_name_max_length(self):
+ name = LONG_NAME_OK
+ body = self.admin_client.create_metering_label(name=name)
+ metering_label = body['metering_label']
+ self.addCleanup(self._delete_metering_label,
+ metering_label['id'])
+ labels = (self.admin_client.list_metering_labels(
+ id=metering_label['id']))
+ self.assertEqual(len(labels['metering_labels']), 1)
+
+ @decorators.idempotent_id('cfc500d9-9de6-4847-8803-62889c097d45')
+ def test_show_metering_label(self):
+ # Verifies the details of a label
+ body = self.admin_client.show_metering_label(self.metering_label['id'])
+ metering_label = body['metering_label']
+ self.assertEqual(self.metering_label['id'], metering_label['id'])
+ self.assertEqual(self.metering_label['tenant_id'],
+ metering_label['tenant_id'])
+ self.assertEqual(self.metering_label['name'], metering_label['name'])
+ self.assertEqual(self.metering_label['description'],
+ metering_label['description'])
+
+ @decorators.idempotent_id('cc832399-6681-493b-9d79-0202831a1281')
+ def test_list_metering_label_rules(self):
+ # Verify rule filtering
+ body = self.admin_client.list_metering_label_rules(id=33)
+ metering_label_rules = body['metering_label_rules']
+ self.assertEqual(0, len(metering_label_rules))
+
+ @decorators.idempotent_id('f4d547cd-3aee-408f-bf36-454f8825e045')
+ def test_create_delete_metering_label_rule_with_filters(self):
+ # Creates a rule
+ remote_ip_prefix = ("10.0.1.0/24" if self._ip_version == 4
+ else "fd03::/64")
+ body = (self.admin_client.create_metering_label_rule(
+ remote_ip_prefix=remote_ip_prefix,
+ direction="ingress",
+ metering_label_id=self.metering_label['id']))
+ metering_label_rule = body['metering_label_rule']
+ self.addCleanup(self._delete_metering_label_rule,
+ metering_label_rule['id'])
+ # Assert whether created rules are found in rules list or fail
+ # if created rules are not found in rules list
+ rules = (self.admin_client.list_metering_label_rules(
+ id=metering_label_rule['id']))
+ self.assertEqual(len(rules['metering_label_rules']), 1)
+
+ @decorators.idempotent_id('b7354489-96ea-41f3-9452-bace120fb4a7')
+ def test_show_metering_label_rule(self):
+ # Verifies the details of a rule
+ body = (self.admin_client.show_metering_label_rule(
+ self.metering_label_rule['id']))
+ metering_label_rule = body['metering_label_rule']
+ self.assertEqual(self.metering_label_rule['id'],
+ metering_label_rule['id'])
+ self.assertEqual(self.metering_label_rule['remote_ip_prefix'],
+ metering_label_rule['remote_ip_prefix'])
+ self.assertEqual(self.metering_label_rule['direction'],
+ metering_label_rule['direction'])
+ self.assertEqual(self.metering_label_rule['metering_label_id'],
+ metering_label_rule['metering_label_id'])
+ self.assertFalse(metering_label_rule['excluded'])
+
+
+class MeteringIpV6TestJSON(MeteringTestJSON):
+ _ip_version = 6
diff --git a/neutron_tempest_plugin/api/test_metering_negative.py b/neutron_tempest_plugin/api/test_metering_negative.py
new file mode 100644
index 0000000..175f314
--- /dev/null
+++ b/neutron_tempest_plugin/api/test_metering_negative.py
@@ -0,0 +1,33 @@
+# Copyright 2016 FUJITSU LIMITED
+#
+# 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 neutron_lib.db import constants as db_const
+from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
+
+from neutron_tempest_plugin.api import base
+
+LONG_NAME_NG = 'x' * (db_const.NAME_FIELD_SIZE + 1)
+
+
+class MeteringNegativeTestJSON(base.BaseAdminNetworkTest):
+
+ required_extensions = ['metering']
+
+ @decorators.attr(type='negative')
+ @decorators.idempotent_id('8b3f7c84-9d37-4771-8681-bfd2c07f3c2d')
+ def test_create_metering_label_with_too_long_name(self):
+ self.assertRaises(lib_exc.BadRequest,
+ self.admin_client.create_metering_label,
+ name=LONG_NAME_NG)
diff --git a/neutron_tempest_plugin/api/test_network_ip_availability.py b/neutron_tempest_plugin/api/test_network_ip_availability.py
new file mode 100644
index 0000000..fe83a77
--- /dev/null
+++ b/neutron_tempest_plugin/api/test_network_ip_availability.py
@@ -0,0 +1,166 @@
+# Copyright 2016 OpenStack Foundation
+# All Rights Reserved.
+#
+# 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 netaddr
+
+from tempest.lib.common.utils import data_utils
+from tempest.lib.common.utils import test_utils
+from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
+from tempest import test
+
+from neutron_tempest_plugin.api import base
+from neutron_tempest_plugin import config
+
+from neutron_lib import constants as lib_constants
+
+# 3 IP addresses are taken from every total for IPv4 these are reserved
+DEFAULT_IP4_RESERVED = 3
+# 2 IP addresses are taken from every total for IPv6 these are reserved
+# I assume the reason for having one less than IPv4 is it does not have
+# broadcast address
+DEFAULT_IP6_RESERVED = 2
+
+DELETE_TIMEOUT = 10
+DELETE_SLEEP = 2
+
+
+class NetworksIpAvailabilityTest(base.BaseAdminNetworkTest):
+
+ """
+ Tests the following operations in the Neutron API using the REST client for
+ Neutron:
+
+ test total and used ips for net create
+ test total and ips for net after subnet create
+ test total and used ips for net after subnet and port create
+
+ """
+
+ @classmethod
+ @test.requires_ext(extension="network-ip-availability", service="network")
+ def skip_checks(cls):
+ super(NetworksIpAvailabilityTest, cls).skip_checks()
+
+ def _get_used_ips(self, network, net_availability):
+ if network:
+ for availability in net_availability['network_ip_availabilities']:
+ if availability['network_id'] == network['id']:
+ return availability['used_ips']
+
+ def _cleanUp_port(self, port_id):
+ # delete port, any way to avoid race
+ try:
+ self.client.delete_port(port_id)
+ # if port is not found, this means it was deleted in the test
+ except lib_exc.NotFound:
+ pass
+
+ def _assert_total_and_used_ips(self, expected_used, expected_total,
+ network, net_availability):
+ if network:
+ for availability in net_availability['network_ip_availabilities']:
+ if availability['network_id'] == network['id']:
+ self.assertEqual(expected_total, availability['total_ips'])
+ self.assertEqual(expected_used, availability['used_ips'])
+
+ def _create_subnet(self, network, ip_version):
+ if ip_version == lib_constants.IP_VERSION_4:
+ cidr = netaddr.IPNetwork('20.0.0.0/24')
+ mask_bits = config.safe_get_config_value(
+ 'network', 'project_network_mask_bits')
+ elif ip_version == lib_constants.IP_VERSION_6:
+ cidr = netaddr.IPNetwork('20:db8::/64')
+ mask_bits = config.safe_get_config_value(
+ 'network', 'project_network_v6_mask_bits')
+
+ subnet_cidr = next(cidr.subnet(mask_bits))
+ prefix_len = subnet_cidr.prefixlen
+ subnet = self.create_subnet(network,
+ cidr=subnet_cidr,
+ enable_dhcp=False,
+ mask_bits=mask_bits,
+ ip_version=ip_version)
+ return subnet, prefix_len
+
+
+def calc_total_ips(prefix, ip_version):
+ # will calculate total ips after removing reserved.
+ if ip_version == lib_constants.IP_VERSION_4:
+ total_ips = 2 ** (lib_constants.IPv4_BITS
+ - prefix) - DEFAULT_IP4_RESERVED
+ elif ip_version == lib_constants.IP_VERSION_6:
+ total_ips = 2 ** (lib_constants.IPv6_BITS
+ - prefix) - DEFAULT_IP6_RESERVED
+ return total_ips
+
+
+class NetworksIpAvailabilityIPv4Test(NetworksIpAvailabilityTest):
+
+ @decorators.idempotent_id('0f33cc8c-1bf6-47d1-9ce1-010618240599')
+ def test_admin_network_availability_before_subnet(self):
+ net_name = data_utils.rand_name('network-')
+ network = self.create_network(network_name=net_name)
+ self.addCleanup(self.client.delete_network, network['id'])
+ net_availability = self.admin_client.list_network_ip_availabilities()
+ self._assert_total_and_used_ips(0, 0, network, net_availability)
+
+ @decorators.idempotent_id('3aecd3b2-16ed-4b87-a54a-91d7b3c2986b')
+ def test_net_ip_availability_after_subnet_and_ports(self):
+ net_name = data_utils.rand_name('network-')
+ network = self.create_network(network_name=net_name)
+ self.addCleanup(self.client.delete_network, network['id'])
+ subnet, prefix = self._create_subnet(network, self._ip_version)
+ self.addCleanup(self.client.delete_subnet, subnet['id'])
+ body = self.admin_client.list_network_ip_availabilities()
+ used_ip = self._get_used_ips(network, body)
+ port1 = self.client.create_port(network_id=network['id'])
+ self.addCleanup(self.client.delete_port, port1['port']['id'])
+ port2 = self.client.create_port(network_id=network['id'])
+ self.addCleanup(self.client.delete_port, port2['port']['id'])
+ net_availability = self.admin_client.list_network_ip_availabilities()
+ self._assert_total_and_used_ips(
+ used_ip + 2,
+ calc_total_ips(prefix, self._ip_version),
+ network, net_availability)
+
+ @decorators.idempotent_id('9f11254d-757b-492e-b14b-f52144e4ee7b')
+ def test_net_ip_availability_after_port_delete(self):
+ net_name = data_utils.rand_name('network-')
+ network = self.create_network(network_name=net_name)
+ self.addCleanup(self.client.delete_network, network['id'])
+ subnet, prefix = self._create_subnet(network, self._ip_version)
+ self.addCleanup(self.client.delete_subnet, subnet['id'])
+ port = self.client.create_port(network_id=network['id'])
+ self.addCleanup(self._cleanUp_port, port['port']['id'])
+ net_availability = self.admin_client.list_network_ip_availabilities()
+ used_ip = self._get_used_ips(network, net_availability)
+ self.client.delete_port(port['port']['id'])
+
+ def get_net_availability():
+ availabilities = self.admin_client.list_network_ip_availabilities()
+ used_ip_after_port_delete = self._get_used_ips(network,
+ availabilities)
+ return used_ip - 1 == used_ip_after_port_delete
+
+ self.assertTrue(
+ test_utils.call_until_true(
+ get_net_availability, DELETE_TIMEOUT, DELETE_SLEEP),
+ msg="IP address did not become available after port delete")
+
+
+class NetworksIpAvailabilityIPv6Test(NetworksIpAvailabilityIPv4Test):
+
+ _ip_version = lib_constants.IP_VERSION_6
diff --git a/neutron_tempest_plugin/api/test_network_ip_availability_negative.py b/neutron_tempest_plugin/api/test_network_ip_availability_negative.py
new file mode 100644
index 0000000..5ba4937
--- /dev/null
+++ b/neutron_tempest_plugin/api/test_network_ip_availability_negative.py
@@ -0,0 +1,29 @@
+# All Rights Reserved.
+#
+# 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 oslo_utils import uuidutils
+from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
+
+from neutron_tempest_plugin.api import test_network_ip_availability as net_ip
+
+
+class NetworksIpAvailabilityNegativeTest(net_ip.NetworksIpAvailabilityTest):
+
+ @decorators.attr(type='negative')
+ @decorators.idempotent_id('3b8693eb-6c57-4ea1-ab84-3730c9ee9c84')
+ def test_network_availability_nonexistent_network_id(self):
+ self.assertRaises(lib_exc.NotFound,
+ self.admin_client.show_network_ip_availability,
+ uuidutils.generate_uuid())
diff --git a/neutron_tempest_plugin/api/test_networks.py b/neutron_tempest_plugin/api/test_networks.py
new file mode 100644
index 0000000..b991993
--- /dev/null
+++ b/neutron_tempest_plugin/api/test_networks.py
@@ -0,0 +1,213 @@
+# Copyright 2012 OpenStack Foundation
+# All Rights Reserved.
+#
+# 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 decorators
+from tempest import test
+import testtools
+
+from neutron_tempest_plugin.api import base
+from neutron_tempest_plugin import config
+
+
+class NetworksTestJSON(base.BaseNetworkTest):
+
+ """
+ Tests the following operations in the Neutron API using the REST client for
+ Neutron:
+
+ list tenant's networks
+ show a network
+ show a tenant network details
+
+ v2.0 of the Neutron API is assumed.
+ """
+
+ @classmethod
+ def resource_setup(cls):
+ super(NetworksTestJSON, cls).resource_setup()
+ cls.network = cls.create_network()
+
+ @decorators.idempotent_id('2bf13842-c93f-4a69-83ed-717d2ec3b44e')
+ def test_show_network(self):
+ # Verify the details of a network
+ body = self.client.show_network(self.network['id'])
+ network = body['network']
+ fields = ['id', 'name']
+ if test.is_extension_enabled('net-mtu', 'network'):
+ fields.append('mtu')
+ for key in fields:
+ self.assertEqual(network[key], self.network[key])
+ project_id = self.client.tenant_id
+ self.assertEqual(project_id, network['tenant_id'])
+ if test.is_extension_enabled('project-id', 'network'):
+ self.assertEqual(project_id, network['project_id'])
+
+ @decorators.idempotent_id('26f2b7a5-2cd1-4f3a-b11f-ad259b099b11')
+ @test.requires_ext(extension="project-id", service="network")
+ def test_show_network_fields_keystone_v3(self):
+
+ def _check_show_network_fields(fields, expect_project_id,
+ expect_tenant_id):
+ params = {}
+ if fields:
+ params['fields'] = fields
+ body = self.client.show_network(self.network['id'], **params)
+ network = body['network']
+ self.assertEqual(expect_project_id, 'project_id' in network)
+ self.assertEqual(expect_tenant_id, 'tenant_id' in network)
+
+ _check_show_network_fields(None, True, True)
+ _check_show_network_fields(['tenant_id'], False, True)
+ _check_show_network_fields(['project_id'], True, False)
+ _check_show_network_fields(['project_id', 'tenant_id'], True, True)
+
+ @decorators.idempotent_id('0cc0552f-afaf-4231-b7a7-c2a1774616da')
+ @test.requires_ext(extension="project-id", service="network")
+ def test_create_network_keystone_v3(self):
+ project_id = self.client.tenant_id
+
+ name = 'created-with-project_id'
+ new_net = self.create_network_keystone_v3(name, project_id)
+ self.assertEqual(name, new_net['name'])
+ self.assertEqual(project_id, new_net['project_id'])
+ self.assertEqual(project_id, new_net['tenant_id'])
+
+ body = self.client.list_networks(id=new_net['id'])['networks'][0]
+ self.assertEqual(name, body['name'])
+
+ new_name = 'create-with-project_id-2'
+ body = self.client.update_network(new_net['id'], name=new_name)
+ new_net = body['network']
+ self.assertEqual(new_name, new_net['name'])
+ self.assertEqual(project_id, new_net['project_id'])
+ self.assertEqual(project_id, new_net['tenant_id'])
+
+ @decorators.idempotent_id('94e2a44c-3367-4253-8c2a-22deaf59e96c')
+ @test.requires_ext(extension="dns-integration",
+ service="network")
+ def test_create_update_network_dns_domain(self):
+ domain1 = 'test.org.'
+ body = self.create_network(dns_domain=domain1)
+ self.assertEqual(domain1, body['dns_domain'])
+ net_id = body['id']
+ body = self.client.list_networks(id=net_id)['networks'][0]
+ self.assertEqual(domain1, body['dns_domain'])
+ domain2 = 'd.org.'
+ body = self.client.update_network(net_id, dns_domain=domain2)
+ self.assertEqual(domain2, body['network']['dns_domain'])
+ body = self.client.show_network(net_id)['network']
+ self.assertEqual(domain2, body['dns_domain'])
+
+ @decorators.idempotent_id('a23186b9-aa6f-4b08-b877-35ca3b9cd54c')
+ @test.requires_ext(extension="project-id", service="network")
+ def test_list_networks_fields_keystone_v3(self):
+ def _check_list_networks_fields(fields, expect_project_id,
+ expect_tenant_id):
+ params = {}
+ if fields:
+ params['fields'] = fields
+ body = self.client.list_networks(**params)
+ networks = body['networks']
+ self.assertNotEmpty(networks, "Network list returned is empty")
+ for network in networks:
+ self.assertEqual(expect_project_id, 'project_id' in network)
+ self.assertEqual(expect_tenant_id, 'tenant_id' in network)
+
+ _check_list_networks_fields(None, True, True)
+ _check_list_networks_fields(['tenant_id'], False, True)
+ _check_list_networks_fields(['project_id'], True, False)
+ _check_list_networks_fields(['project_id', 'tenant_id'], True, True)
+
+
+# TODO(ihrachys): check that bad mtu is not allowed; current API extension
+# definition doesn't enforce values
+# TODO(ihrachys): check that new segment reservation updates mtu, once
+# https://review.openstack.org/#/c/353115/ is merged
+class NetworksMtuTestJSON(base.BaseNetworkTest):
+ required_extensions = ['net-mtu', 'net-mtu-writable']
+
+ @decorators.idempotent_id('c79dbf94-ee26-420f-a56f-382aaccb1a41')
+ def test_create_network_custom_mtu(self):
+ # 68 should be supported by all implementations, as per api-ref
+ network = self.create_network(mtu=68)
+ body = self.client.show_network(network['id'])['network']
+ self.assertEqual(68, body['mtu'])
+
+ @decorators.idempotent_id('2d35d49d-9d16-465c-92c7-4768eb717688')
+ @testtools.skipUnless(config.CONF.network_feature_enabled.ipv6,
+ 'IPv6 is not enabled')
+ def test_update_network_custom_mtu(self):
+ # 68 should be supported by all implementations, as per api-ref
+ network = self.create_network(mtu=68)
+ body = self.client.show_network(network['id'])['network']
+ self.assertEqual(68, body['mtu'])
+
+ # 1280 should be supported by all ipv6 compliant implementations
+ self.client.update_network(network['id'], mtu=1280)
+ body = self.client.show_network(network['id'])['network']
+ self.assertEqual(1280, body['mtu'])
+
+
+class NetworksSearchCriteriaTest(base.BaseSearchCriteriaTest):
+
+ resource = 'network'
+
+ list_kwargs = {'shared': False, 'router:external': False}
+
+ @classmethod
+ def resource_setup(cls):
+ super(NetworksSearchCriteriaTest, cls).resource_setup()
+ for name in cls.resource_names:
+ cls.create_network(network_name=name)
+
+ @decorators.idempotent_id('de27d34a-bd9d-4516-83d6-81ef723f7d0d')
+ def test_list_sorts_asc(self):
+ self._test_list_sorts_asc()
+
+ @decorators.idempotent_id('e767a160-59f9-4c4b-8dc1-72124a68640a')
+ def test_list_sorts_desc(self):
+ self._test_list_sorts_desc()
+
+ @decorators.idempotent_id('71389852-f57b-49f2-b109-77b705e9e8af')
+ def test_list_pagination(self):
+ self._test_list_pagination()
+
+ @decorators.idempotent_id('b7e153d2-37c3-48d4-8390-ec13498fee3d')
+ def test_list_pagination_with_marker(self):
+ self._test_list_pagination_with_marker()
+
+ @decorators.idempotent_id('8a9c89df-0ee7-4c0d-8f1d-ec8f27cf362f')
+ def test_list_pagination_with_href_links(self):
+ self._test_list_pagination_with_href_links()
+
+ @decorators.idempotent_id('79a52810-2156-4ab6-b577-9e46e58d4b58')
+ def test_list_pagination_page_reverse_asc(self):
+ self._test_list_pagination_page_reverse_asc()
+
+ @decorators.idempotent_id('36a4671f-a542-442f-bc44-a8873ee778d1')
+ def test_list_pagination_page_reverse_desc(self):
+ self._test_list_pagination_page_reverse_desc()
+
+ @decorators.idempotent_id('13eb066c-aa90-406d-b4c3-39595bf8f910')
+ def test_list_pagination_page_reverse_with_href_links(self):
+ self._test_list_pagination_page_reverse_with_href_links()
+
+ @decorators.idempotent_id('f1867fc5-e1d6-431f-bc9f-8b882e43a7f9')
+ def test_list_no_pagination_limit_0(self):
+ self._test_list_no_pagination_limit_0()
+
+ @decorators.idempotent_id('3574ec9b-a8b8-43e3-9c11-98f5875df6a9')
+ def test_list_validation_filters(self):
+ self._test_list_validation_filters()
diff --git a/neutron_tempest_plugin/api/test_networks_negative.py b/neutron_tempest_plugin/api/test_networks_negative.py
new file mode 100644
index 0000000..93f32f7
--- /dev/null
+++ b/neutron_tempest_plugin/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 decorators
+from tempest.lib import exceptions as lib_exc
+import testtools
+
+from neutron_tempest_plugin.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)
+
+ @decorators.attr(type='negative')
+ @decorators.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_tempest_plugin/api/test_ports.py b/neutron_tempest_plugin/api/test_ports.py
new file mode 100644
index 0000000..c68f4e3
--- /dev/null
+++ b/neutron_tempest_plugin/api/test_ports.py
@@ -0,0 +1,183 @@
+# Copyright 2014 OpenStack Foundation
+# All Rights Reserved.
+#
+# 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 decorators
+from tempest import test
+
+from neutron_tempest_plugin.api import base
+
+
+class PortsTestJSON(base.BaseNetworkTest):
+
+ @classmethod
+ def resource_setup(cls):
+ super(PortsTestJSON, cls).resource_setup()
+ cls.network = cls.create_network()
+
+ def _confirm_dns_assignment(self, port):
+ # NOTE(manjeets) port created with single subnet
+ # would have only one dns_assignment
+ dns_assignment = port['dns_assignment'][0]
+ ip = port['fixed_ips'][0]['ip_address']
+ if port['dns_name']:
+ hostname = port['dns_name']
+ else:
+ hostname = 'host-%s' % ip.replace('.', '-')
+ self.assertEqual(hostname, dns_assignment['hostname'])
+
+ # To avoid hard coding the expected dns_domain value
+ # in neutron.conf we just check that the fqdn starts
+ # with correct hostname
+ self.assertTrue(dns_assignment['fqdn'].startswith(hostname))
+ self.assertEqual(ip, dns_assignment['ip_address'])
+
+ @decorators.idempotent_id('c72c1c0c-2193-4aca-bbb4-b1442640bbbb')
+ @test.requires_ext(extension="standard-attr-description",
+ service="network")
+ def test_create_update_port_description(self):
+ body = self.create_port(self.network,
+ description='d1')
+ self.assertEqual('d1', body['description'])
+ body = self.client.list_ports(id=body['id'])['ports'][0]
+ self.assertEqual('d1', body['description'])
+ body = self.client.update_port(body['id'],
+ description='d2')
+ self.assertEqual('d2', body['port']['description'])
+ body = self.client.list_ports(id=body['port']['id'])['ports'][0]
+ self.assertEqual('d2', body['description'])
+
+ @decorators.idempotent_id('539fbefe-fb36-48aa-9a53-8c5fbd44e492')
+ @test.requires_ext(extension="dns-integration",
+ service="network")
+ def test_create_update_port_with_dns_name(self):
+ # NOTE(manjeets) dns_domain is set to openstackgate.local
+ # so dns_name for port can be set
+ self.create_subnet(self.network)
+ body = self.create_port(self.network, dns_name='d1')
+ self.assertEqual('d1', body['dns_name'])
+ self._confirm_dns_assignment(body)
+ body = self.client.list_ports(id=body['id'])['ports'][0]
+ self._confirm_dns_assignment(body)
+ self.assertEqual('d1', body['dns_name'])
+ body = self.client.update_port(body['id'],
+ dns_name='d2')
+ self.assertEqual('d2', body['port']['dns_name'])
+ self._confirm_dns_assignment(body['port'])
+ body = self.client.show_port(body['port']['id'])['port']
+ self.assertEqual('d2', body['dns_name'])
+ self._confirm_dns_assignment(body)
+
+ @decorators.idempotent_id('435e89df-a8bb-4b41-801a-9f20d362d777')
+ @test.requires_ext(extension="dns-integration",
+ service="network")
+ def test_create_update_port_with_no_dns_name(self):
+ self.create_subnet(self.network)
+ body = self.create_port(self.network)
+ self.assertFalse(body['dns_name'])
+ self._confirm_dns_assignment(body)
+ port_body = self.client.show_port(body['id'])
+ self.assertFalse(port_body['port']['dns_name'])
+ self._confirm_dns_assignment(port_body['port'])
+
+ @decorators.idempotent_id('dfe8cc79-18d9-4ae8-acef-3ec6bb719aa7')
+ @test.requires_ext(extension="dns-domain-ports",
+ service="network")
+ def test_create_update_port_with_dns_domain(self):
+ self.create_subnet(self.network)
+ body = self.create_port(self.network, dns_name='d1',
+ dns_domain='test.org.')
+ self.assertEqual('d1', body['dns_name'])
+ self.assertEqual('test.org.', body['dns_domain'])
+ self._confirm_dns_assignment(body)
+ body = self.client.list_ports(id=body['id'])['ports'][0]
+ self._confirm_dns_assignment(body)
+ self.assertEqual('d1', body['dns_name'])
+ self.assertEqual('test.org.', body['dns_domain'])
+ body = self.client.update_port(body['id'],
+ dns_name='d2', dns_domain='d.org.')
+ self.assertEqual('d2', body['port']['dns_name'])
+ self.assertEqual('d.org.', body['dns_domain'])
+ self._confirm_dns_assignment(body['port'])
+ body = self.client.show_port(body['port']['id'])['port']
+ self.assertEqual('d2', body['dns_name'])
+ self.assertEqual('d.org.', body['dns_domain'])
+ self._confirm_dns_assignment(body)
+
+ @decorators.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)
+
+ @decorators.idempotent_id('1d6d8683-8691-43c6-a7ba-c69723258726')
+ def test_add_ips_to_port(self):
+ s = self.create_subnet(self.network)
+ port = self.create_port(self.network)
+ # request another IP on the same subnet
+ port['fixed_ips'].append({'subnet_id': s['id']})
+ updated = self.client.update_port(port['id'],
+ fixed_ips=port['fixed_ips'])
+ subnets = [ip['subnet_id'] for ip in updated['port']['fixed_ips']]
+ expected = [s['id'], s['id']]
+ self.assertEqual(expected, subnets)
+
+
+class PortsSearchCriteriaTest(base.BaseSearchCriteriaTest):
+
+ resource = 'port'
+
+ @classmethod
+ def resource_setup(cls):
+ super(PortsSearchCriteriaTest, cls).resource_setup()
+ net = cls.create_network(network_name='port-search-test-net')
+ for name in cls.resource_names:
+ cls.create_port(net, name=name)
+
+ @decorators.idempotent_id('9ab73df4-960a-4ae3-87d3-60992b8d3e2d')
+ def test_list_sorts_asc(self):
+ self._test_list_sorts_asc()
+
+ @decorators.idempotent_id('b426671d-7270-430f-82ff-8f33eec93010')
+ def test_list_sorts_desc(self):
+ self._test_list_sorts_desc()
+
+ @decorators.idempotent_id('a202fdc8-6616-45df-b6a0-463932de6f94')
+ def test_list_pagination(self):
+ self._test_list_pagination()
+
+ @decorators.idempotent_id('f4723b8e-8186-4b9a-bf9e-57519967e048')
+ def test_list_pagination_with_marker(self):
+ self._test_list_pagination_with_marker()
+
+ @decorators.idempotent_id('fcd02a7a-f07e-4d5e-b0ca-b58e48927a9b')
+ def test_list_pagination_with_href_links(self):
+ self._test_list_pagination_with_href_links()
+
+ @decorators.idempotent_id('3afe7024-77ab-4cfe-824b-0b2bf4217727')
+ def test_list_no_pagination_limit_0(self):
+ self._test_list_no_pagination_limit_0()
+
+ @decorators.idempotent_id('b8857391-dc44-40cc-89b7-2800402e03ce')
+ def test_list_pagination_page_reverse_asc(self):
+ self._test_list_pagination_page_reverse_asc()
+
+ @decorators.idempotent_id('4e51e9c9-ceae-4ec0-afd4-147569247699')
+ def test_list_pagination_page_reverse_desc(self):
+ self._test_list_pagination_page_reverse_desc()
+
+ @decorators.idempotent_id('74293e59-d794-4a93-be09-38667199ef68')
+ def test_list_pagination_page_reverse_with_href_links(self):
+ self._test_list_pagination_page_reverse_with_href_links()
diff --git a/neutron_tempest_plugin/api/test_qos.py b/neutron_tempest_plugin/api/test_qos.py
new file mode 100644
index 0000000..a075b67
--- /dev/null
+++ b/neutron_tempest_plugin/api/test_qos.py
@@ -0,0 +1,1194 @@
+# Copyright 2015 Red Hat, Inc.
+#
+# 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.common.utils import data_utils
+from tempest.lib import decorators
+from tempest.lib import exceptions
+from tempest import test
+
+import testscenarios
+import testtools
+
+from neutron_tempest_plugin.api import base
+from neutron_tempest_plugin.common import qos_consts
+
+
+load_tests = testscenarios.load_tests_apply_scenarios
+
+
+class QosTestJSON(base.BaseAdminNetworkTest):
+
+ required_extensions = ['qos']
+
+ @staticmethod
+ def _get_driver_details(rule_type_details, driver_name):
+ for driver in rule_type_details['drivers']:
+ if driver['name'] == driver_name:
+ return driver
+
+ def _create_project(self):
+ # Add a project to conduct the test
+ test_project = data_utils.rand_name('test_project_')
+ test_description = data_utils.rand_name('desc_')
+ project = self.identity_admin_client.create_project(
+ name=test_project,
+ description=test_description)['project']
+ self.addCleanup(
+ self.identity_admin_client.delete_project, project['id'])
+ return project
+
+ @decorators.idempotent_id('108fbdf7-3463-4e47-9871-d07f3dcf5bbb')
+ def test_create_policy(self):
+ policy = self.create_qos_policy(name='test-policy',
+ description='test policy desc1',
+ shared=False)
+
+ # Test 'show policy'
+ retrieved_policy = self.admin_client.show_qos_policy(policy['id'])
+ retrieved_policy = retrieved_policy['policy']
+ self.assertEqual('test-policy', retrieved_policy['name'])
+ self.assertEqual('test policy desc1', retrieved_policy['description'])
+ self.assertFalse(retrieved_policy['shared'])
+
+ # Test 'list policies'
+ policies = self.admin_client.list_qos_policies()['policies']
+ policies_ids = [p['id'] for p in policies]
+ self.assertIn(policy['id'], policies_ids)
+
+ @decorators.idempotent_id('606a48e2-5403-4052-b40f-4d54b855af76')
+ @test.requires_ext(extension="project-id", service="network")
+ def test_show_policy_has_project_id(self):
+ policy = self.create_qos_policy(name='test-policy', shared=False)
+ body = self.admin_client.show_qos_policy(policy['id'])
+ show_policy = body['policy']
+ self.assertIn('project_id', show_policy)
+ self.assertIn('tenant_id', show_policy)
+ self.assertEqual(self.admin_client.tenant_id,
+ show_policy['project_id'])
+ self.assertEqual(self.admin_client.tenant_id,
+ show_policy['tenant_id'])
+
+ @decorators.idempotent_id('f8d20e92-f06d-4805-b54f-230f77715815')
+ def test_list_policy_filter_by_name(self):
+ self.create_qos_policy(name='test', description='test policy',
+ shared=False)
+ self.create_qos_policy(name='test2', description='test policy',
+ shared=False)
+
+ policies = (self.admin_client.
+ list_qos_policies(name='test')['policies'])
+ self.assertEqual(1, len(policies))
+
+ retrieved_policy = policies[0]
+ self.assertEqual('test', retrieved_policy['name'])
+
+ @decorators.idempotent_id('8e88a54b-f0b2-4b7d-b061-a15d93c2c7d6')
+ def test_policy_update(self):
+ policy = self.create_qos_policy(name='test-policy',
+ description='',
+ shared=False,
+ tenant_id=self.admin_client.tenant_id)
+ self.admin_client.update_qos_policy(policy['id'],
+ description='test policy desc2',
+ shared=True)
+
+ retrieved_policy = self.admin_client.show_qos_policy(policy['id'])
+ retrieved_policy = retrieved_policy['policy']
+ self.assertEqual('test policy desc2', retrieved_policy['description'])
+ self.assertTrue(retrieved_policy['shared'])
+ self.assertEqual([], retrieved_policy['rules'])
+
+ @decorators.idempotent_id('6e880e0f-bbfc-4e54-87c6-680f90e1b618')
+ def test_policy_update_forbidden_for_regular_tenants_own_policy(self):
+ policy = self.create_qos_policy(name='test-policy',
+ description='',
+ shared=False,
+ tenant_id=self.client.tenant_id)
+ self.assertRaises(
+ exceptions.Forbidden,
+ self.client.update_qos_policy,
+ policy['id'], description='test policy')
+
+ @decorators.idempotent_id('4ecfd7e7-47b6-4702-be38-be9235901a87')
+ def test_policy_update_forbidden_for_regular_tenants_foreign_policy(self):
+ policy = self.create_qos_policy(name='test-policy',
+ description='',
+ shared=False,
+ tenant_id=self.admin_client.tenant_id)
+ self.assertRaises(
+ exceptions.NotFound,
+ self.client.update_qos_policy,
+ policy['id'], description='test policy')
+
+ @decorators.idempotent_id('ee263db4-009a-4641-83e5-d0e83506ba4c')
+ def test_shared_policy_update(self):
+ policy = self.create_qos_policy(name='test-policy',
+ description='',
+ shared=True,
+ tenant_id=self.admin_client.tenant_id)
+
+ self.admin_client.update_qos_policy(policy['id'],
+ description='test policy desc2')
+ retrieved_policy = self.admin_client.show_qos_policy(policy['id'])
+ retrieved_policy = retrieved_policy['policy']
+ self.assertTrue(retrieved_policy['shared'])
+
+ self.admin_client.update_qos_policy(policy['id'],
+ shared=False)
+ retrieved_policy = self.admin_client.show_qos_policy(policy['id'])
+ retrieved_policy = retrieved_policy['policy']
+ self.assertFalse(retrieved_policy['shared'])
+
+ @decorators.idempotent_id('1cb42653-54bd-4a9a-b888-c55e18199201')
+ def test_delete_policy(self):
+ policy = self.admin_client.create_qos_policy(
+ 'test-policy', 'desc', True)['policy']
+
+ retrieved_policy = self.admin_client.show_qos_policy(policy['id'])
+ retrieved_policy = retrieved_policy['policy']
+ self.assertEqual('test-policy', retrieved_policy['name'])
+
+ self.admin_client.delete_qos_policy(policy['id'])
+ self.assertRaises(exceptions.NotFound,
+ self.admin_client.show_qos_policy, policy['id'])
+
+ @decorators.idempotent_id('cf776f77-8d3d-49f2-8572-12d6a1557224')
+ def test_list_admin_rule_types(self):
+ self._test_list_rule_types(self.admin_client)
+
+ @decorators.idempotent_id('49c8ea35-83a9-453a-bd23-239cf3b13929')
+ def test_list_regular_rule_types(self):
+ self._test_list_rule_types(self.client)
+
+ def _test_list_rule_types(self, client):
+ # List supported rule types
+ # Since returned rule types depends on loaded backend drivers this test
+ # is checking only if returned keys are same as expected keys
+ #
+ # In theory, we could make the test conditional on which ml2 drivers
+ # are enabled in gate (or more specifically, on which supported qos
+ # rules are claimed by core plugin), but that option doesn't seem to be
+ # available through tempest.lib framework
+ expected_rule_keys = ['type']
+
+ rule_types = client.list_qos_rule_types()
+ actual_list_rule_types = rule_types['rule_types']
+
+ # Verify that only required fields present in rule details
+ for rule in actual_list_rule_types:
+ self.assertEqual(tuple(expected_rule_keys), tuple(rule.keys()))
+
+ @decorators.idempotent_id('8ececa21-ef97-4904-a152-9f04c90f484d')
+ def test_show_rule_type_details_as_user(self):
+ self.assertRaises(
+ exceptions.Forbidden,
+ self.client.show_qos_rule_type,
+ qos_consts.RULE_TYPE_BANDWIDTH_LIMIT)
+
+ @decorators.idempotent_id('d0a2460b-7325-481f-a531-050bd96ab25e')
+ def test_show_rule_type_details_as_admin(self):
+ # Since returned rule types depend on loaded backend drivers this test
+ # is checking only if returned keys are same as expected keys
+
+ # In theory, we could make the test conditional on which ml2 drivers
+ # are enabled in gate, but that option doesn't seem to be
+ # available through tempest.lib framework
+ expected_rule_type_details_keys = ['type', 'drivers']
+
+ rule_type_details = self.admin_client.show_qos_rule_type(
+ qos_consts.RULE_TYPE_BANDWIDTH_LIMIT).get("rule_type")
+
+ # Verify that only required fields present in rule details
+ self.assertEqual(
+ sorted(tuple(expected_rule_type_details_keys)),
+ sorted(tuple(rule_type_details.keys())))
+
+ @decorators.idempotent_id('65b9ef75-1911-406a-bbdb-ca1d68d528b0')
+ def test_policy_association_with_admin_network(self):
+ policy = self.create_qos_policy(name='test-policy',
+ description='test policy',
+ shared=False)
+ network = self.create_shared_network('test network',
+ qos_policy_id=policy['id'])
+
+ retrieved_network = self.admin_client.show_network(network['id'])
+ self.assertEqual(
+ policy['id'], retrieved_network['network']['qos_policy_id'])
+
+ @decorators.idempotent_id('1738de5d-0476-4163-9022-5e1b548c208e')
+ def test_policy_association_with_tenant_network(self):
+ policy = self.create_qos_policy(name='test-policy',
+ description='test policy',
+ shared=True)
+ network = self.create_network('test network',
+ qos_policy_id=policy['id'])
+
+ retrieved_network = self.admin_client.show_network(network['id'])
+ self.assertEqual(
+ policy['id'], retrieved_network['network']['qos_policy_id'])
+
+ @decorators.idempotent_id('9efe63d0-836f-4cc2-b00c-468e63aa614e')
+ def test_policy_association_with_network_nonexistent_policy(self):
+ self.assertRaises(
+ exceptions.NotFound,
+ self.create_network,
+ 'test network',
+ qos_policy_id='9efe63d0-836f-4cc2-b00c-468e63aa614e')
+
+ @decorators.idempotent_id('1aa55a79-324f-47d9-a076-894a8fc2448b')
+ def test_policy_association_with_network_non_shared_policy(self):
+ policy = self.create_qos_policy(name='test-policy',
+ description='test policy',
+ shared=False)
+ self.assertRaises(
+ exceptions.NotFound,
+ self.create_network,
+ 'test network', qos_policy_id=policy['id'])
+
+ @decorators.idempotent_id('09a9392c-1359-4cbb-989f-fb768e5834a8')
+ def test_policy_update_association_with_admin_network(self):
+ policy = self.create_qos_policy(name='test-policy',
+ description='test policy',
+ shared=False)
+ network = self.create_shared_network('test network')
+ retrieved_network = self.admin_client.show_network(network['id'])
+ self.assertIsNone(retrieved_network['network']['qos_policy_id'])
+
+ self.admin_client.update_network(network['id'],
+ qos_policy_id=policy['id'])
+ retrieved_network = self.admin_client.show_network(network['id'])
+ self.assertEqual(
+ policy['id'], retrieved_network['network']['qos_policy_id'])
+
+ @decorators.idempotent_id('98fcd95e-84cf-4746-860e-44692e674f2e')
+ def test_policy_association_with_port_shared_policy(self):
+ policy = self.create_qos_policy(name='test-policy',
+ description='test policy',
+ shared=True)
+ network = self.create_shared_network('test network')
+ port = self.create_port(network, qos_policy_id=policy['id'])
+
+ retrieved_port = self.admin_client.show_port(port['id'])
+ self.assertEqual(
+ policy['id'], retrieved_port['port']['qos_policy_id'])
+
+ @decorators.idempotent_id('49e02f5a-e1dd-41d5-9855-cfa37f2d195e')
+ def test_policy_association_with_port_nonexistent_policy(self):
+ network = self.create_shared_network('test network')
+ self.assertRaises(
+ exceptions.NotFound,
+ self.create_port,
+ network,
+ qos_policy_id='49e02f5a-e1dd-41d5-9855-cfa37f2d195e')
+
+ @decorators.idempotent_id('f53d961c-9fe5-4422-8b66-7add972c6031')
+ def test_policy_association_with_port_non_shared_policy(self):
+ policy = self.create_qos_policy(name='test-policy',
+ description='test policy',
+ shared=False)
+ network = self.create_shared_network('test network')
+ self.assertRaises(
+ exceptions.NotFound,
+ self.create_port,
+ network, qos_policy_id=policy['id'])
+
+ @decorators.idempotent_id('f8163237-fba9-4db5-9526-bad6d2343c76')
+ def test_policy_update_association_with_port_shared_policy(self):
+ policy = self.create_qos_policy(name='test-policy',
+ description='test policy',
+ shared=True)
+ network = self.create_shared_network('test network')
+ port = self.create_port(network)
+ retrieved_port = self.admin_client.show_port(port['id'])
+ self.assertIsNone(retrieved_port['port']['qos_policy_id'])
+
+ self.client.update_port(port['id'], qos_policy_id=policy['id'])
+ retrieved_port = self.admin_client.show_port(port['id'])
+ self.assertEqual(
+ policy['id'], retrieved_port['port']['qos_policy_id'])
+
+ @decorators.idempotent_id('18163237-8ba9-4db5-9525-bad6d2343c75')
+ def test_delete_not_allowed_if_policy_in_use_by_network(self):
+ policy = self.create_qos_policy(name='test-policy',
+ description='test policy',
+ shared=True)
+ self.create_shared_network('test network', qos_policy_id=policy['id'])
+ self.assertRaises(
+ exceptions.Conflict,
+ self.admin_client.delete_qos_policy, policy['id'])
+
+ @decorators.idempotent_id('24153230-84a9-4dd5-9525-bad6d2343c75')
+ def test_delete_not_allowed_if_policy_in_use_by_port(self):
+ policy = self.create_qos_policy(name='test-policy',
+ description='test policy',
+ shared=True)
+ network = self.create_shared_network('test network')
+ self.create_port(network, qos_policy_id=policy['id'])
+ self.assertRaises(
+ exceptions.Conflict,
+ self.admin_client.delete_qos_policy, policy['id'])
+
+ @decorators.idempotent_id('a2a5849b-dd06-4b18-9664-0b6828a1fc27')
+ def test_qos_policy_delete_with_rules(self):
+ policy = self.create_qos_policy(name='test-policy',
+ description='test policy',
+ shared=False)
+ self.admin_client.create_bandwidth_limit_rule(
+ policy['id'], 200, 1337)['bandwidth_limit_rule']
+
+ self.admin_client.delete_qos_policy(policy['id'])
+
+ with testtools.ExpectedException(exceptions.NotFound):
+ self.admin_client.show_qos_policy(policy['id'])
+
+ @decorators.idempotent_id('fb384bde-a973-41c3-a542-6f77a092155f')
+ def test_get_policy_that_is_shared(self):
+ policy = self.create_qos_policy(
+ name='test-policy-shared',
+ description='shared policy',
+ shared=True,
+ tenant_id=self.admin_client.tenant_id)
+ obtained_policy = self.client.show_qos_policy(policy['id'])['policy']
+ self.assertEqual(obtained_policy, policy)
+
+ @decorators.idempotent_id('aed8e2a6-22da-421b-89b9-935a2c1a1b50')
+ def test_policy_create_forbidden_for_regular_tenants(self):
+ self.assertRaises(
+ exceptions.Forbidden,
+ self.client.create_qos_policy,
+ 'test-policy', 'test policy', False)
+
+ @decorators.idempotent_id('18d94f22-b9d5-4390-af12-d30a0cfc4cd3')
+ def test_default_policy_creating_network_without_policy(self):
+ project_id = self._create_project()['id']
+ policy = self.create_qos_policy(name='test-policy',
+ tenant_id=project_id,
+ is_default=True)
+ network = self.create_network('test network', client=self.admin_client,
+ project_id=project_id)
+ retrieved_network = self.admin_client.show_network(network['id'])
+ self.assertEqual(
+ policy['id'], retrieved_network['network']['qos_policy_id'])
+
+ @decorators.idempotent_id('807cce45-38e5-482d-94db-36e1796aba73')
+ def test_default_policy_creating_network_with_policy(self):
+ project_id = self._create_project()['id']
+ self.create_qos_policy(name='test-policy',
+ tenant_id=project_id,
+ is_default=True)
+ policy = self.create_qos_policy(name='test-policy',
+ tenant_id=project_id)
+ network = self.create_network('test network', client=self.admin_client,
+ project_id=project_id,
+ qos_policy_id=policy['id'])
+ retrieved_network = self.admin_client.show_network(network['id'])
+ self.assertEqual(
+ policy['id'], retrieved_network['network']['qos_policy_id'])
+
+
+class QosBandwidthLimitRuleTestJSON(base.BaseAdminNetworkTest):
+
+ direction = None
+ required_extensions = ['qos']
+
+ @classmethod
+ @base.require_qos_rule_type(qos_consts.RULE_TYPE_BANDWIDTH_LIMIT)
+ def resource_setup(cls):
+ super(QosBandwidthLimitRuleTestJSON, cls).resource_setup()
+
+ @property
+ def opposite_direction(self):
+ if self.direction == "ingress":
+ return "egress"
+ elif self.direction == "egress":
+ return "ingress"
+ else:
+ return None
+
+ @decorators.idempotent_id('8a59b00b-3e9c-4787-92f8-93a5cdf5e378')
+ def test_rule_create(self):
+ policy = self.create_qos_policy(name='test-policy',
+ description='test policy',
+ shared=False)
+ rule = self.create_qos_bandwidth_limit_rule(
+ policy_id=policy['id'],
+ max_kbps=200,
+ max_burst_kbps=1337,
+ direction=self.direction)
+
+ # Test 'show rule'
+ retrieved_rule = self.admin_client.show_bandwidth_limit_rule(
+ policy['id'], rule['id'])
+ retrieved_rule = retrieved_rule['bandwidth_limit_rule']
+ self.assertEqual(rule['id'], retrieved_rule['id'])
+ self.assertEqual(200, retrieved_rule['max_kbps'])
+ self.assertEqual(1337, retrieved_rule['max_burst_kbps'])
+ if self.direction:
+ self.assertEqual(self.direction, retrieved_rule['direction'])
+
+ # Test 'list rules'
+ rules = self.admin_client.list_bandwidth_limit_rules(policy['id'])
+ rules = rules['bandwidth_limit_rules']
+ rules_ids = [r['id'] for r in rules]
+ self.assertIn(rule['id'], rules_ids)
+
+ # Test 'show policy'
+ retrieved_policy = self.admin_client.show_qos_policy(policy['id'])
+ policy_rules = retrieved_policy['policy']['rules']
+ self.assertEqual(1, len(policy_rules))
+ self.assertEqual(rule['id'], policy_rules[0]['id'])
+ self.assertEqual(qos_consts.RULE_TYPE_BANDWIDTH_LIMIT,
+ policy_rules[0]['type'])
+
+ @decorators.idempotent_id('8a59b00b-ab01-4787-92f8-93a5cdf5e378')
+ def test_rule_create_fail_for_the_same_type(self):
+ policy = self.create_qos_policy(name='test-policy',
+ description='test policy',
+ shared=False)
+ self.create_qos_bandwidth_limit_rule(policy_id=policy['id'],
+ max_kbps=200,
+ max_burst_kbps=1337,
+ direction=self.direction)
+
+ self.assertRaises(exceptions.Conflict,
+ self.create_qos_bandwidth_limit_rule,
+ policy_id=policy['id'],
+ max_kbps=201, max_burst_kbps=1338,
+ direction=self.direction)
+
+ @decorators.idempotent_id('149a6988-2568-47d2-931e-2dbc858943b3')
+ def test_rule_update(self):
+ policy = self.create_qos_policy(name='test-policy',
+ description='test policy',
+ shared=False)
+ rule = self.create_qos_bandwidth_limit_rule(policy_id=policy['id'],
+ max_kbps=1,
+ max_burst_kbps=1,
+ direction=self.direction)
+
+ self.admin_client.update_bandwidth_limit_rule(
+ policy['id'],
+ rule['id'],
+ max_kbps=200,
+ max_burst_kbps=1337,
+ direction=self.opposite_direction)
+
+ retrieved_policy = self.admin_client.show_bandwidth_limit_rule(
+ policy['id'], rule['id'])
+ retrieved_policy = retrieved_policy['bandwidth_limit_rule']
+ self.assertEqual(200, retrieved_policy['max_kbps'])
+ self.assertEqual(1337, retrieved_policy['max_burst_kbps'])
+ if self.opposite_direction:
+ self.assertEqual(self.opposite_direction,
+ retrieved_policy['direction'])
+
+ @decorators.idempotent_id('67ee6efd-7b33-4a68-927d-275b4f8ba958')
+ def test_rule_delete(self):
+ policy = self.create_qos_policy(name='test-policy',
+ description='test policy',
+ shared=False)
+ rule = self.admin_client.create_bandwidth_limit_rule(
+ policy['id'], 200, 1337, self.direction)['bandwidth_limit_rule']
+
+ retrieved_policy = self.admin_client.show_bandwidth_limit_rule(
+ policy['id'], rule['id'])
+ retrieved_policy = retrieved_policy['bandwidth_limit_rule']
+ self.assertEqual(rule['id'], retrieved_policy['id'])
+
+ self.admin_client.delete_bandwidth_limit_rule(policy['id'], rule['id'])
+ self.assertRaises(exceptions.NotFound,
+ self.admin_client.show_bandwidth_limit_rule,
+ policy['id'], rule['id'])
+
+ @decorators.idempotent_id('f211222c-5808-46cb-a961-983bbab6b852')
+ def test_rule_create_rule_nonexistent_policy(self):
+ self.assertRaises(
+ exceptions.NotFound,
+ self.create_qos_bandwidth_limit_rule,
+ 'policy', 200, 1337, self.direction)
+
+ @decorators.idempotent_id('a4a2e7ad-786f-4927-a85a-e545a93bd274')
+ def test_rule_create_forbidden_for_regular_tenants(self):
+ self.assertRaises(
+ exceptions.Forbidden,
+ self.client.create_bandwidth_limit_rule,
+ 'policy', 1, 2, self.direction)
+
+ @decorators.idempotent_id('1bfc55d9-6fd8-4293-ab3a-b1d69bf7cd2e')
+ def test_rule_update_forbidden_for_regular_tenants_own_policy(self):
+ policy = self.create_qos_policy(name='test-policy',
+ description='test policy',
+ shared=False,
+ tenant_id=self.client.tenant_id)
+ rule = self.create_qos_bandwidth_limit_rule(policy_id=policy['id'],
+ max_kbps=1,
+ max_burst_kbps=1,
+ direction=self.direction)
+ self.assertRaises(
+ exceptions.Forbidden,
+ self.client.update_bandwidth_limit_rule,
+ policy['id'], rule['id'], max_kbps=2, max_burst_kbps=4)
+
+ @decorators.idempotent_id('9a607936-4b6f-4c2f-ad21-bd5b3d4fc91f')
+ def test_rule_update_forbidden_for_regular_tenants_foreign_policy(self):
+ policy = self.create_qos_policy(name='test-policy',
+ description='test policy',
+ shared=False,
+ tenant_id=self.admin_client.tenant_id)
+ rule = self.create_qos_bandwidth_limit_rule(policy_id=policy['id'],
+ max_kbps=1,
+ max_burst_kbps=1,
+ direction=self.direction)
+ self.assertRaises(
+ exceptions.NotFound,
+ self.client.update_bandwidth_limit_rule,
+ policy['id'], rule['id'], max_kbps=2, max_burst_kbps=4)
+
+ @decorators.idempotent_id('ce0bd0c2-54d9-4e29-85f1-cfb36ac3ebe2')
+ def test_get_rules_by_policy(self):
+ policy1 = self.create_qos_policy(name='test-policy1',
+ description='test policy1',
+ shared=False)
+ rule1 = self.create_qos_bandwidth_limit_rule(policy_id=policy1['id'],
+ max_kbps=200,
+ max_burst_kbps=1337,
+ direction=self.direction)
+
+ policy2 = self.create_qos_policy(name='test-policy2',
+ description='test policy2',
+ shared=False)
+ rule2 = self.create_qos_bandwidth_limit_rule(policy_id=policy2['id'],
+ max_kbps=5000,
+ max_burst_kbps=2523,
+ direction=self.direction)
+
+ # Test 'list rules'
+ rules = self.admin_client.list_bandwidth_limit_rules(policy1['id'])
+ rules = rules['bandwidth_limit_rules']
+ rules_ids = [r['id'] for r in rules]
+ self.assertIn(rule1['id'], rules_ids)
+ self.assertNotIn(rule2['id'], rules_ids)
+
+
+class QosBandwidthLimitRuleWithDirectionTestJSON(
+ QosBandwidthLimitRuleTestJSON):
+
+ required_extensions = (
+ QosBandwidthLimitRuleTestJSON.required_extensions +
+ ['qos-bw-limit-direction']
+ )
+ scenarios = [
+ ('ingress', {'direction': 'ingress'}),
+ ('egress', {'direction': 'egress'}),
+ ]
+
+
+class RbacSharedQosPoliciesTest(base.BaseAdminNetworkTest):
+
+ force_tenant_isolation = True
+ credentials = ['primary', 'alt', 'admin']
+ required_extensions = ['qos']
+
+ @classmethod
+ def resource_setup(cls):
+ super(RbacSharedQosPoliciesTest, cls).resource_setup()
+ cls.client2 = cls.os_alt.network_client
+
+ def _create_qos_policy(self, tenant_id=None):
+ args = {'name': data_utils.rand_name('test-policy'),
+ 'description': 'test policy',
+ 'shared': False,
+ 'tenant_id': tenant_id}
+ qos_policy = self.admin_client.create_qos_policy(**args)['policy']
+ self.addCleanup(self.admin_client.delete_qos_policy, qos_policy['id'])
+
+ return qos_policy
+
+ def _make_admin_policy_shared_to_tenant_id(self, tenant_id):
+ policy = self._create_qos_policy()
+ rbac_policy = self.admin_client.create_rbac_policy(
+ object_type='qos_policy',
+ object_id=policy['id'],
+ action='access_as_shared',
+ target_tenant=tenant_id,
+ )['rbac_policy']
+
+ return {'policy': policy, 'rbac_policy': rbac_policy}
+
+ def _create_network(self, qos_policy_id, client, should_cleanup=True):
+ net = client.create_network(
+ name=data_utils.rand_name('test-network'),
+ qos_policy_id=qos_policy_id)['network']
+ if should_cleanup:
+ self.addCleanup(client.delete_network, net['id'])
+
+ return net
+
+ @decorators.idempotent_id('b9dcf582-d3b3-11e5-950a-54ee756c66df')
+ def test_policy_sharing_with_wildcard(self):
+ qos_pol = self.create_qos_policy(
+ name=data_utils.rand_name('test-policy'),
+ description='test-shared-policy', shared=False,
+ tenant_id=self.admin_client.tenant_id)
+ self.assertNotIn(qos_pol, self.client2.list_qos_policies()['policies'])
+
+ # test update shared False -> True
+ self.admin_client.update_qos_policy(qos_pol['id'], shared=True)
+ qos_pol['shared'] = True
+ self.client2.show_qos_policy(qos_pol['id'])
+ rbac_pol = {'target_tenant': '*',
+ 'tenant_id': self.admin_client.tenant_id,
+ 'project_id': self.admin_client.tenant_id,
+ 'object_type': 'qos_policy',
+ 'object_id': qos_pol['id'],
+ 'action': 'access_as_shared'}
+
+ rbac_policies = self.admin_client.list_rbac_policies()['rbac_policies']
+ rbac_policies = [r for r in rbac_policies if r.pop('id')]
+ self.assertIn(rbac_pol, rbac_policies)
+
+ # update shared True -> False should fail because the policy is bound
+ # to a network
+ net = self._create_network(qos_pol['id'], self.admin_client, False)
+ with testtools.ExpectedException(exceptions.Conflict):
+ self.admin_client.update_qos_policy(qos_pol['id'], shared=False)
+
+ # delete the network, and update shared True -> False should pass now
+ self.admin_client.delete_network(net['id'])
+ self.admin_client.update_qos_policy(qos_pol['id'], shared=False)
+ qos_pol['shared'] = False
+ self.assertNotIn(qos_pol, self.client2.list_qos_policies()['policies'])
+
+ def _create_net_bound_qos_rbacs(self):
+ res = self._make_admin_policy_shared_to_tenant_id(
+ self.client.tenant_id)
+ qos_policy, rbac_for_client_tenant = res['policy'], res['rbac_policy']
+
+ # add a wildcard rbac rule - now the policy globally shared
+ rbac_wildcard = self.admin_client.create_rbac_policy(
+ object_type='qos_policy',
+ object_id=qos_policy['id'],
+ action='access_as_shared',
+ target_tenant='*',
+ )['rbac_policy']
+
+ # tenant1 now uses qos policy for net
+ self._create_network(qos_policy['id'], self.client)
+
+ return rbac_for_client_tenant, rbac_wildcard
+
+ @decorators.idempotent_id('328b1f70-d424-11e5-a57f-54ee756c66df')
+ def test_net_bound_shared_policy_wildcard_and_tenant_id_wild_remove(self):
+ client_rbac, wildcard_rbac = self._create_net_bound_qos_rbacs()
+ # globally unshare the qos-policy, the specific share should remain
+ self.admin_client.delete_rbac_policy(wildcard_rbac['id'])
+ self.client.list_rbac_policies(id=client_rbac['id'])
+
+ @decorators.idempotent_id('1997b00c-0c75-4e43-8ce2-999f9fa555ee')
+ def test_net_bound_shared_policy_wildcard_and_tenant_id_wild_remains(self):
+ client_rbac, wildcard_rbac = self._create_net_bound_qos_rbacs()
+ # remove client_rbac policy the wildcard share should remain
+ self.admin_client.delete_rbac_policy(client_rbac['id'])
+ self.client.list_rbac_policies(id=wildcard_rbac['id'])
+
+ @decorators.idempotent_id('2ace9adc-da6e-11e5-aafe-54ee756c66df')
+ def test_policy_sharing_with_wildcard_and_tenant_id(self):
+ res = self._make_admin_policy_shared_to_tenant_id(
+ self.client.tenant_id)
+ qos_policy, rbac = res['policy'], res['rbac_policy']
+ qos_pol = self.client.show_qos_policy(qos_policy['id'])['policy']
+ self.assertTrue(qos_pol['shared'])
+ with testtools.ExpectedException(exceptions.NotFound):
+ self.client2.show_qos_policy(qos_policy['id'])
+
+ # make the qos-policy globally shared
+ self.admin_client.update_qos_policy(qos_policy['id'], shared=True)
+ qos_pol = self.client2.show_qos_policy(qos_policy['id'])['policy']
+ self.assertTrue(qos_pol['shared'])
+
+ # globally unshare the qos-policy, the specific share should remain
+ self.admin_client.update_qos_policy(qos_policy['id'], shared=False)
+ self.client.show_qos_policy(qos_policy['id'])
+ with testtools.ExpectedException(exceptions.NotFound):
+ self.client2.show_qos_policy(qos_policy['id'])
+ self.assertIn(rbac,
+ self.admin_client.list_rbac_policies()['rbac_policies'])
+
+ @decorators.idempotent_id('9f85c76a-a350-11e5-8ae5-54ee756c66df')
+ def test_policy_target_update(self):
+ res = self._make_admin_policy_shared_to_tenant_id(
+ self.client.tenant_id)
+ # change to client2
+ update_res = self.admin_client.update_rbac_policy(
+ res['rbac_policy']['id'], target_tenant=self.client2.tenant_id)
+ self.assertEqual(self.client2.tenant_id,
+ update_res['rbac_policy']['target_tenant'])
+ # make sure everything else stayed the same
+ res['rbac_policy'].pop('target_tenant')
+ update_res['rbac_policy'].pop('target_tenant')
+ self.assertEqual(res['rbac_policy'], update_res['rbac_policy'])
+
+ @decorators.idempotent_id('a9b39f46-a350-11e5-97c7-54ee756c66df')
+ def test_network_presence_prevents_policy_rbac_policy_deletion(self):
+ res = self._make_admin_policy_shared_to_tenant_id(
+ self.client2.tenant_id)
+ qos_policy_id = res['policy']['id']
+ self._create_network(qos_policy_id, self.client2)
+ # a network with shared qos-policy should prevent the deletion of an
+ # rbac-policy required for it to be shared
+ with testtools.ExpectedException(exceptions.Conflict):
+ self.admin_client.delete_rbac_policy(res['rbac_policy']['id'])
+
+ # a wildcard policy should allow the specific policy to be deleted
+ # since it allows the remaining port
+ wild = self.admin_client.create_rbac_policy(
+ object_type='qos_policy', object_id=res['policy']['id'],
+ action='access_as_shared', target_tenant='*')['rbac_policy']
+ self.admin_client.delete_rbac_policy(res['rbac_policy']['id'])
+
+ # now that wildcard is the only remaining, it should be subjected to
+ # the same restriction
+ with testtools.ExpectedException(exceptions.Conflict):
+ self.admin_client.delete_rbac_policy(wild['id'])
+
+ # we can't update the policy to a different tenant
+ with testtools.ExpectedException(exceptions.Conflict):
+ self.admin_client.update_rbac_policy(
+ wild['id'], target_tenant=self.client2.tenant_id)
+
+ @decorators.idempotent_id('b0fe87e8-a350-11e5-9f08-54ee756c66df')
+ def test_regular_client_shares_to_another_regular_client(self):
+ # owned by self.admin_client
+ policy = self._create_qos_policy()
+ with testtools.ExpectedException(exceptions.NotFound):
+ self.client.show_qos_policy(policy['id'])
+ rbac_policy = self.admin_client.create_rbac_policy(
+ object_type='qos_policy', object_id=policy['id'],
+ action='access_as_shared',
+ target_tenant=self.client.tenant_id)['rbac_policy']
+ self.client.show_qos_policy(policy['id'])
+
+ self.assertIn(rbac_policy,
+ self.admin_client.list_rbac_policies()['rbac_policies'])
+ # ensure that 'client2' can't see the rbac-policy sharing the
+ # qos-policy to it because the rbac-policy belongs to 'client'
+ self.assertNotIn(rbac_policy['id'], [p['id'] for p in
+ self.client2.list_rbac_policies()['rbac_policies']])
+
+ @decorators.idempotent_id('ba88d0ca-a350-11e5-a06f-54ee756c66df')
+ def test_filter_fields(self):
+ policy = self._create_qos_policy()
+ self.admin_client.create_rbac_policy(
+ object_type='qos_policy', object_id=policy['id'],
+ action='access_as_shared', target_tenant=self.client2.tenant_id)
+ field_args = (('id',), ('id', 'action'), ('object_type', 'object_id'),
+ ('tenant_id', 'target_tenant'))
+ for fields in field_args:
+ res = self.admin_client.list_rbac_policies(fields=fields)
+ self.assertEqual(set(fields), set(res['rbac_policies'][0].keys()))
+
+ @decorators.idempotent_id('c10d993a-a350-11e5-9c7a-54ee756c66df')
+ def test_rbac_policy_show(self):
+ res = self._make_admin_policy_shared_to_tenant_id(
+ self.client.tenant_id)
+ p1 = res['rbac_policy']
+ p2 = self.admin_client.create_rbac_policy(
+ object_type='qos_policy', object_id=res['policy']['id'],
+ action='access_as_shared',
+ target_tenant='*')['rbac_policy']
+
+ self.assertEqual(
+ p1, self.admin_client.show_rbac_policy(p1['id'])['rbac_policy'])
+ self.assertEqual(
+ p2, self.admin_client.show_rbac_policy(p2['id'])['rbac_policy'])
+
+ @decorators.idempotent_id('c7496f86-a350-11e5-b380-54ee756c66df')
+ def test_filter_rbac_policies(self):
+ policy = self._create_qos_policy()
+ rbac_pol1 = self.admin_client.create_rbac_policy(
+ object_type='qos_policy', object_id=policy['id'],
+ action='access_as_shared',
+ target_tenant=self.client2.tenant_id)['rbac_policy']
+ rbac_pol2 = self.admin_client.create_rbac_policy(
+ object_type='qos_policy', object_id=policy['id'],
+ action='access_as_shared',
+ target_tenant=self.admin_client.tenant_id)['rbac_policy']
+ res1 = self.admin_client.list_rbac_policies(id=rbac_pol1['id'])[
+ 'rbac_policies']
+ res2 = self.admin_client.list_rbac_policies(id=rbac_pol2['id'])[
+ 'rbac_policies']
+ self.assertEqual(1, len(res1))
+ self.assertEqual(1, len(res2))
+ self.assertEqual(rbac_pol1['id'], res1[0]['id'])
+ self.assertEqual(rbac_pol2['id'], res2[0]['id'])
+
+ @decorators.idempotent_id('cd7d755a-a350-11e5-a344-54ee756c66df')
+ def test_regular_client_blocked_from_sharing_anothers_policy(self):
+ qos_policy = self._make_admin_policy_shared_to_tenant_id(
+ self.client.tenant_id)['policy']
+ with testtools.ExpectedException(exceptions.BadRequest):
+ self.client.create_rbac_policy(
+ object_type='qos_policy', object_id=qos_policy['id'],
+ action='access_as_shared',
+ target_tenant=self.client2.tenant_id)
+
+ # make sure the rbac-policy is invisible to the tenant for which it's
+ # being shared
+ self.assertFalse(self.client.list_rbac_policies()['rbac_policies'])
+
+
+class QosDscpMarkingRuleTestJSON(base.BaseAdminNetworkTest):
+ VALID_DSCP_MARK1 = 56
+ VALID_DSCP_MARK2 = 48
+
+ required_extensions = ['qos']
+
+ @classmethod
+ @base.require_qos_rule_type(qos_consts.RULE_TYPE_DSCP_MARKING)
+ def resource_setup(cls):
+ super(QosDscpMarkingRuleTestJSON, cls).resource_setup()
+
+ @decorators.idempotent_id('f5cbaceb-5829-497c-9c60-ad70969e9a08')
+ def test_rule_create(self):
+ policy = self.create_qos_policy(name='test-policy',
+ description='test policy',
+ shared=False)
+ rule = self.admin_client.create_dscp_marking_rule(
+ policy['id'], self.VALID_DSCP_MARK1)['dscp_marking_rule']
+
+ # Test 'show rule'
+ retrieved_rule = self.admin_client.show_dscp_marking_rule(
+ policy['id'], rule['id'])
+ retrieved_rule = retrieved_rule['dscp_marking_rule']
+ self.assertEqual(rule['id'], retrieved_rule['id'])
+ self.assertEqual(self.VALID_DSCP_MARK1, retrieved_rule['dscp_mark'])
+
+ # Test 'list rules'
+ rules = self.admin_client.list_dscp_marking_rules(policy['id'])
+ rules = rules['dscp_marking_rules']
+ rules_ids = [r['id'] for r in rules]
+ self.assertIn(rule['id'], rules_ids)
+
+ # Test 'show policy'
+ retrieved_policy = self.admin_client.show_qos_policy(policy['id'])
+ policy_rules = retrieved_policy['policy']['rules']
+ self.assertEqual(1, len(policy_rules))
+ self.assertEqual(rule['id'], policy_rules[0]['id'])
+ self.assertEqual(qos_consts.RULE_TYPE_DSCP_MARKING,
+ policy_rules[0]['type'])
+
+ @decorators.idempotent_id('08553ffe-030f-4037-b486-7e0b8fb9385a')
+ def test_rule_create_fail_for_the_same_type(self):
+ policy = self.create_qos_policy(name='test-policy',
+ description='test policy',
+ shared=False)
+ self.admin_client.create_dscp_marking_rule(
+ policy['id'], self.VALID_DSCP_MARK1)['dscp_marking_rule']
+
+ self.assertRaises(exceptions.Conflict,
+ self.admin_client.create_dscp_marking_rule,
+ policy_id=policy['id'],
+ dscp_mark=self.VALID_DSCP_MARK2)
+
+ @decorators.idempotent_id('76f632e5-3175-4408-9a32-3625e599c8a2')
+ def test_rule_update(self):
+ policy = self.create_qos_policy(name='test-policy',
+ description='test policy',
+ shared=False)
+ rule = self.admin_client.create_dscp_marking_rule(
+ policy['id'], self.VALID_DSCP_MARK1)['dscp_marking_rule']
+
+ self.admin_client.update_dscp_marking_rule(
+ policy['id'], rule['id'], dscp_mark=self.VALID_DSCP_MARK2)
+
+ retrieved_policy = self.admin_client.show_dscp_marking_rule(
+ policy['id'], rule['id'])
+ retrieved_policy = retrieved_policy['dscp_marking_rule']
+ self.assertEqual(self.VALID_DSCP_MARK2, retrieved_policy['dscp_mark'])
+
+ @decorators.idempotent_id('74f81904-c35f-48a3-adae-1f5424cb3c18')
+ def test_rule_delete(self):
+ policy = self.create_qos_policy(name='test-policy',
+ description='test policy',
+ shared=False)
+ rule = self.admin_client.create_dscp_marking_rule(
+ policy['id'], self.VALID_DSCP_MARK1)['dscp_marking_rule']
+
+ retrieved_policy = self.admin_client.show_dscp_marking_rule(
+ policy['id'], rule['id'])
+ retrieved_policy = retrieved_policy['dscp_marking_rule']
+ self.assertEqual(rule['id'], retrieved_policy['id'])
+
+ self.admin_client.delete_dscp_marking_rule(policy['id'], rule['id'])
+ self.assertRaises(exceptions.NotFound,
+ self.admin_client.show_dscp_marking_rule,
+ policy['id'], rule['id'])
+
+ @decorators.idempotent_id('9cb8ef5c-96fc-4978-9ee0-e3b02bab628a')
+ def test_rule_create_rule_nonexistent_policy(self):
+ self.assertRaises(
+ exceptions.NotFound,
+ self.admin_client.create_dscp_marking_rule,
+ 'policy', self.VALID_DSCP_MARK1)
+
+ @decorators.idempotent_id('bf6002ea-29de-486f-b65d-08aea6d4c4e2')
+ def test_rule_create_forbidden_for_regular_tenants(self):
+ self.assertRaises(
+ exceptions.Forbidden,
+ self.client.create_dscp_marking_rule,
+ 'policy', self.VALID_DSCP_MARK1)
+
+ @decorators.idempotent_id('33646b08-4f05-4493-a48a-bde768a18533')
+ def test_invalid_rule_create(self):
+ policy = self.create_qos_policy(name='test-policy',
+ description='test policy',
+ shared=False)
+ self.assertRaises(
+ exceptions.BadRequest,
+ self.admin_client.create_dscp_marking_rule,
+ policy['id'], 58)
+
+ @decorators.idempotent_id('c565131d-4c80-4231-b0f3-9ae2be4de129')
+ def test_get_rules_by_policy(self):
+ policy1 = self.create_qos_policy(name='test-policy1',
+ description='test policy1',
+ shared=False)
+ rule1 = self.admin_client.create_dscp_marking_rule(
+ policy1['id'], self.VALID_DSCP_MARK1)['dscp_marking_rule']
+
+ policy2 = self.create_qos_policy(name='test-policy2',
+ description='test policy2',
+ shared=False)
+ rule2 = self.admin_client.create_dscp_marking_rule(
+ policy2['id'], self.VALID_DSCP_MARK2)['dscp_marking_rule']
+
+ # Test 'list rules'
+ rules = self.admin_client.list_dscp_marking_rules(policy1['id'])
+ rules = rules['dscp_marking_rules']
+ rules_ids = [r['id'] for r in rules]
+ self.assertIn(rule1['id'], rules_ids)
+ self.assertNotIn(rule2['id'], rules_ids)
+
+
+class QosMinimumBandwidthRuleTestJSON(base.BaseAdminNetworkTest):
+ DIRECTION_EGRESS = "egress"
+ DIRECTION_INGRESS = "ingress"
+ RULE_NAME = qos_consts.RULE_TYPE_MINIMUM_BANDWIDTH + "_rule"
+ RULES_NAME = RULE_NAME + "s"
+ required_extensions = ['qos']
+
+ @classmethod
+ @base.require_qos_rule_type(qos_consts.RULE_TYPE_MINIMUM_BANDWIDTH)
+ def resource_setup(cls):
+ super(QosMinimumBandwidthRuleTestJSON, cls).resource_setup()
+
+ @decorators.idempotent_id('aa59b00b-3e9c-4787-92f8-93a5cdf5e378')
+ def test_rule_create(self):
+ policy = self.create_qos_policy(name='test-policy',
+ description='test policy',
+ shared=False)
+ rule = self.admin_client.create_minimum_bandwidth_rule(
+ policy_id=policy['id'],
+ direction=self.DIRECTION_EGRESS,
+ min_kbps=1138)[self.RULE_NAME]
+
+ # Test 'show rule'
+ retrieved_rule = self.admin_client.show_minimum_bandwidth_rule(
+ policy['id'], rule['id'])
+ retrieved_rule = retrieved_rule[self.RULE_NAME]
+ self.assertEqual(rule['id'], retrieved_rule['id'])
+ self.assertEqual(1138, retrieved_rule['min_kbps'])
+ self.assertEqual(self.DIRECTION_EGRESS, retrieved_rule['direction'])
+
+ # Test 'list rules'
+ rules = self.admin_client.list_minimum_bandwidth_rules(policy['id'])
+ rules = rules[self.RULES_NAME]
+ rules_ids = [r['id'] for r in rules]
+ self.assertIn(rule['id'], rules_ids)
+
+ # Test 'show policy'
+ retrieved_policy = self.admin_client.show_qos_policy(policy['id'])
+ policy_rules = retrieved_policy['policy']['rules']
+ self.assertEqual(1, len(policy_rules))
+ self.assertEqual(rule['id'], policy_rules[0]['id'])
+ self.assertEqual(qos_consts.RULE_TYPE_MINIMUM_BANDWIDTH,
+ policy_rules[0]['type'])
+
+ @decorators.idempotent_id('266d9b87-e51c-48bd-9aa7-8269573621be')
+ def test_rule_create_fail_for_missing_min_kbps(self):
+ policy = self.create_qos_policy(name='test-policy',
+ description='test policy',
+ shared=False)
+ self.assertRaises(exceptions.BadRequest,
+ self.admin_client.create_minimum_bandwidth_rule,
+ policy_id=policy['id'],
+ direction=self.DIRECTION_EGRESS)
+
+ @decorators.idempotent_id('aa59b00b-ab01-4787-92f8-93a5cdf5e378')
+ def test_rule_create_fail_for_the_same_type(self):
+ policy = self.create_qos_policy(name='test-policy',
+ description='test policy',
+ shared=False)
+ self.admin_client.create_minimum_bandwidth_rule(
+ policy_id=policy['id'],
+ direction=self.DIRECTION_EGRESS, min_kbps=200)
+
+ self.assertRaises(exceptions.Conflict,
+ self.admin_client.create_minimum_bandwidth_rule,
+ policy_id=policy['id'],
+ direction=self.DIRECTION_EGRESS, min_kbps=201)
+
+ @decorators.idempotent_id('d6fce764-e511-4fa6-9f86-f4b41cf142cf')
+ def test_rule_create_fail_for_direction_ingress(self):
+ policy = self.create_qos_policy(name='test-policy',
+ description='test policy',
+ shared=False)
+ self.assertRaises(exceptions.BadRequest,
+ self.admin_client.create_minimum_bandwidth_rule,
+ policy_id=policy['id'],
+ direction=self.DIRECTION_INGRESS,
+ min_kbps=201)
+
+ @decorators.idempotent_id('a49a6988-2568-47d2-931e-2dbc858943b3')
+ def test_rule_update(self):
+ policy = self.create_qos_policy(name='test-policy',
+ description='test policy',
+ shared=False)
+ rule = self.admin_client.create_minimum_bandwidth_rule(
+ policy_id=policy['id'],
+ direction=self.DIRECTION_EGRESS,
+ min_kbps=300)[self.RULE_NAME]
+
+ self.admin_client.update_minimum_bandwidth_rule(policy['id'],
+ rule['id'], min_kbps=350, direction=self.DIRECTION_EGRESS)
+
+ retrieved_policy = self.admin_client.show_minimum_bandwidth_rule(
+ policy['id'], rule['id'])
+ retrieved_policy = retrieved_policy[self.RULE_NAME]
+ self.assertEqual(350, retrieved_policy['min_kbps'])
+ self.assertEqual(self.DIRECTION_EGRESS, retrieved_policy['direction'])
+
+ @decorators.idempotent_id('a7ee6efd-7b33-4a68-927d-275b4f8ba958')
+ def test_rule_delete(self):
+ policy = self.create_qos_policy(name='test-policy',
+ description='test policy',
+ shared=False)
+ rule = self.admin_client.create_minimum_bandwidth_rule(
+ policy['id'], self.DIRECTION_EGRESS, min_kbps=200)[self.RULE_NAME]
+
+ retrieved_policy = self.admin_client.show_minimum_bandwidth_rule(
+ policy['id'], rule['id'])
+ retrieved_policy = retrieved_policy[self.RULE_NAME]
+ self.assertEqual(rule['id'], retrieved_policy['id'])
+
+ self.admin_client.delete_minimum_bandwidth_rule(policy['id'],
+ rule['id'])
+ self.assertRaises(exceptions.NotFound,
+ self.admin_client.show_minimum_bandwidth_rule,
+ policy['id'], rule['id'])
+
+ @decorators.idempotent_id('a211222c-5808-46cb-a961-983bbab6b852')
+ def test_rule_create_rule_nonexistent_policy(self):
+ self.assertRaises(
+ exceptions.NotFound,
+ self.admin_client.create_minimum_bandwidth_rule,
+ 'policy', self.DIRECTION_EGRESS, min_kbps=200)
+
+ @decorators.idempotent_id('b4a2e7ad-786f-4927-a85a-e545a93bd274')
+ def test_rule_create_forbidden_for_regular_tenants(self):
+ self.assertRaises(
+ exceptions.Forbidden,
+ self.client.create_minimum_bandwidth_rule,
+ 'policy', self.DIRECTION_EGRESS, min_kbps=300)
+
+ @decorators.idempotent_id('de0bd0c2-54d9-4e29-85f1-cfb36ac3ebe2')
+ def test_get_rules_by_policy(self):
+ policy1 = self.create_qos_policy(name='test-policy1',
+ description='test policy1',
+ shared=False)
+ rule1 = self.admin_client.create_minimum_bandwidth_rule(
+ policy_id=policy1['id'],
+ direction=self.DIRECTION_EGRESS,
+ min_kbps=200)[self.RULE_NAME]
+
+ policy2 = self.create_qos_policy(name='test-policy2',
+ description='test policy2',
+ shared=False)
+ rule2 = self.admin_client.create_minimum_bandwidth_rule(
+ policy_id=policy2['id'],
+ direction=self.DIRECTION_EGRESS,
+ min_kbps=5000)[self.RULE_NAME]
+
+ # Test 'list rules'
+ rules = self.admin_client.list_minimum_bandwidth_rules(policy1['id'])
+ rules = rules[self.RULES_NAME]
+ rules_ids = [r['id'] for r in rules]
+ self.assertIn(rule1['id'], rules_ids)
+ self.assertNotIn(rule2['id'], rules_ids)
+
+
+class QosSearchCriteriaTest(base.BaseSearchCriteriaTest,
+ base.BaseAdminNetworkTest):
+
+ resource = 'policy'
+ plural_name = 'policies'
+
+ # Use unique description to isolate the tests from other QoS tests
+ list_kwargs = {'description': 'search-criteria-test'}
+ list_as_admin = True
+
+ required_extensions = ['qos']
+
+ @classmethod
+ def resource_setup(cls):
+ super(QosSearchCriteriaTest, cls).resource_setup()
+ for name in cls.resource_names:
+ cls.create_qos_policy(
+ name=name, description='search-criteria-test')
+
+ @decorators.idempotent_id('55fc0103-fdc1-4d34-ab62-c579bb739a91')
+ def test_list_sorts_asc(self):
+ self._test_list_sorts_asc()
+
+ @decorators.idempotent_id('13e08ac3-bfed-426b-892a-b3b158560c23')
+ def test_list_sorts_desc(self):
+ self._test_list_sorts_desc()
+
+ @decorators.idempotent_id('719e61cc-e33c-4918-aa4d-1a791e6e0e86')
+ def test_list_pagination(self):
+ self._test_list_pagination()
+
+ @decorators.idempotent_id('3bd8fb58-c0f8-4954-87fb-f286e1eb096a')
+ def test_list_pagination_with_marker(self):
+ self._test_list_pagination_with_marker()
+
+ @decorators.idempotent_id('3bad0747-8082-46e9-be4d-c428a842db41')
+ def test_list_pagination_with_href_links(self):
+ self._test_list_pagination_with_href_links()
+
+ @decorators.idempotent_id('d6a8bacd-d5e8-4ef3-bc55-23ca6998d208')
+ def test_list_pagination_page_reverse_asc(self):
+ self._test_list_pagination_page_reverse_asc()
+
+ @decorators.idempotent_id('0b9aecdc-2b27-421b-b104-53d24e905ae8')
+ def test_list_pagination_page_reverse_desc(self):
+ self._test_list_pagination_page_reverse_desc()
+
+ @decorators.idempotent_id('1a3dc257-dafd-4870-8c71-639ae7ddc6ea')
+ def test_list_pagination_page_reverse_with_href_links(self):
+ self._test_list_pagination_page_reverse_with_href_links()
+
+ @decorators.idempotent_id('40e09b53-4eb8-4526-9181-d438c8005a20')
+ def test_list_no_pagination_limit_0(self):
+ self._test_list_no_pagination_limit_0()
diff --git a/neutron_tempest_plugin/api/test_qos_negative.py b/neutron_tempest_plugin/api/test_qos_negative.py
new file mode 100644
index 0000000..e26a536
--- /dev/null
+++ b/neutron_tempest_plugin/api/test_qos_negative.py
@@ -0,0 +1,48 @@
+# 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 neutron_lib.db import constants as db_const
+from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
+
+from neutron_tempest_plugin.api import base
+
+LONG_NAME_NG = 'z' * (db_const.NAME_FIELD_SIZE + 1)
+LONG_DESCRIPTION_NG = 'z' * (db_const.LONG_DESCRIPTION_FIELD_SIZE + 1)
+LONG_TENANT_ID_NG = 'z' * (db_const.PROJECT_ID_FIELD_SIZE + 1)
+
+
+class QosNegativeTestJSON(base.BaseAdminNetworkTest):
+
+ required_extensions = ['qos']
+
+ @decorators.attr(type='negative')
+ @decorators.idempotent_id('b9dce555-d3b3-11e5-950a-54ee757c77da')
+ def test_add_policy_with_too_long_name(self):
+ self.assertRaises(lib_exc.BadRequest,
+ self.client.create_qos_policy,
+ LONG_NAME_NG, 'test policy desc1', False)
+
+ @decorators.attr(type='negative')
+ @decorators.idempotent_id('b9dce444-d3b3-11e5-950a-54ee747c99db')
+ def test_add_policy_with_too_long_description(self):
+ self.assertRaises(lib_exc.BadRequest,
+ self.client.create_qos_policy,
+ 'test-policy', LONG_DESCRIPTION_NG, False)
+
+ @decorators.attr(type='negative')
+ @decorators.idempotent_id('b9dce444-d3b3-11e5-950a-54ee757c77dc')
+ def test_add_policy_with_too_long_tenant_id(self):
+ self.assertRaises(lib_exc.BadRequest,
+ self.client.create_qos_policy,
+ 'test-policy', 'test policy desc1',
+ False, LONG_TENANT_ID_NG)
diff --git a/neutron_tempest_plugin/api/test_revisions.py b/neutron_tempest_plugin/api/test_revisions.py
new file mode 100644
index 0000000..1d860ca
--- /dev/null
+++ b/neutron_tempest_plugin/api/test_revisions.py
@@ -0,0 +1,398 @@
+# 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 netaddr
+
+from tempest.lib import decorators
+from tempest.lib import exceptions
+from tempest import test
+
+from neutron_tempest_plugin.api import base
+from neutron_tempest_plugin.api import base_security_groups as bsg
+from neutron_tempest_plugin import config
+
+
+class TestRevisions(base.BaseAdminNetworkTest, bsg.BaseSecGroupTest):
+
+ required_extensions = ['standard-attr-revisions']
+
+ @decorators.idempotent_id('4a26a4be-9c53-483c-bc50-b53f1db10ac6')
+ def test_update_network_bumps_revision(self):
+ net = self.create_network()
+ self.addCleanup(self.client.delete_network, net['id'])
+ self.assertIn('revision_number', net)
+ updated = self.client.update_network(net['id'], name='newnet')
+ self.assertGreater(updated['network']['revision_number'],
+ net['revision_number'])
+
+ @decorators.idempotent_id('4a26a4be-9c53-483c-bc50-b11111113333')
+ def test_update_network_constrained_by_revision(self):
+ net = self.create_network()
+ current = net['revision_number']
+ stale = current - 1
+ # using a stale number should fail
+ self.assertRaises(
+ exceptions.PreconditionFailed,
+ self.client.update_network,
+ net['id'], name='newnet',
+ headers={'If-Match': 'revision_number=%s' % stale}
+ )
+
+ # using current should pass. in case something is updating the network
+ # on the server at the same time, we have to re-read and update to be
+ # safe
+ for i in range(100):
+ current = (self.client.show_network(net['id'])
+ ['network']['revision_number'])
+ try:
+ self.client.update_network(
+ net['id'], name='newnet',
+ headers={'If-Match': 'revision_number=%s' % current})
+ except exceptions.UnexpectedResponseCode:
+ continue
+ break
+ else:
+ self.fail("Failed to update network after 100 tries.")
+
+ @decorators.idempotent_id('cac7ecde-12d5-4331-9a03-420899dea077')
+ def test_update_port_bumps_revision(self):
+ net = self.create_network()
+ self.addCleanup(self.client.delete_network, net['id'])
+ port = self.create_port(net)
+ self.addCleanup(self.client.delete_port, port['id'])
+ self.assertIn('revision_number', port)
+ updated = self.client.update_port(port['id'], name='newport')
+ self.assertGreater(updated['port']['revision_number'],
+ port['revision_number'])
+
+ @decorators.idempotent_id('c1c4fa41-8e89-44d0-9bfc-409f3b66dc57')
+ def test_update_subnet_bumps_revision(self):
+ net = self.create_network()
+ self.addCleanup(self.client.delete_network, net['id'])
+ subnet = self.create_subnet(net)
+ self.addCleanup(self.client.delete_subnet, subnet['id'])
+ self.assertIn('revision_number', subnet)
+ updated = self.client.update_subnet(subnet['id'], name='newsub')
+ self.assertGreater(updated['subnet']['revision_number'],
+ subnet['revision_number'])
+
+ @decorators.idempotent_id('e8c5d7db-2b8d-4615-a476-6e537437c4f2')
+ def test_update_subnetpool_bumps_revision(self):
+ sp = self.create_subnetpool('subnetpool', default_prefixlen=24,
+ prefixes=['10.0.0.0/8'])
+ self.addCleanup(self.client.delete_subnetpool, sp['id'])
+ self.assertIn('revision_number', sp)
+ updated = self.admin_client.update_subnetpool(sp['id'], name='sp2')
+ self.assertGreater(updated['subnetpool']['revision_number'],
+ sp['revision_number'])
+
+ @decorators.idempotent_id('e8c5d7db-2b8d-4567-a326-6e123437c4d1')
+ def test_update_subnet_bumps_network_revision(self):
+ net = self.create_network()
+ self.addCleanup(self.client.delete_network, net['id'])
+ subnet = self.create_subnet(net)
+ updated = self.client.show_network(net['id'])
+ self.assertGreater(updated['network']['revision_number'],
+ net['revision_number'])
+ self.client.delete_subnet(subnet['id'])
+ updated2 = self.client.show_network(net['id'])
+ self.assertGreater(updated2['network']['revision_number'],
+ updated['network']['revision_number'])
+
+ @decorators.idempotent_id('6c256f71-c929-4200-b3dc-4e1843506be5')
+ @test.requires_ext(extension="security-group", service="network")
+ def test_update_sg_group_bumps_revision(self):
+ sg, name = self._create_security_group()
+ self.assertIn('revision_number', sg['security_group'])
+ update_body = self.client.update_security_group(
+ sg['security_group']['id'], name='new_sg_name')
+ self.assertGreater(update_body['security_group']['revision_number'],
+ sg['security_group']['revision_number'])
+
+ @decorators.idempotent_id('6489632f-8550-4453-a674-c98849742967')
+ @test.requires_ext(extension="security-group", service="network")
+ def test_update_port_sg_binding_bumps_revision(self):
+ net = self.create_network()
+ self.addCleanup(self.client.delete_network, net['id'])
+ port = self.create_port(net)
+ self.addCleanup(self.client.delete_port, port['id'])
+ sg = self._create_security_group()[0]
+ self.client.update_port(
+ port['id'], security_groups=[sg['security_group']['id']])
+ updated = self.client.show_port(port['id'])
+ updated2 = self.client.update_port(port['id'], security_groups=[])
+ self.assertGreater(updated['port']['revision_number'],
+ port['revision_number'])
+ self.assertGreater(updated2['port']['revision_number'],
+ updated['port']['revision_number'])
+
+ @decorators.idempotent_id('29c7ab2b-d1d8-425d-8cec-fcf632960f22')
+ @test.requires_ext(extension="security-group", service="network")
+ def test_update_sg_rule_bumps_sg_revision(self):
+ sg, name = self._create_security_group()
+ rule = self.client.create_security_group_rule(
+ security_group_id=sg['security_group']['id'],
+ protocol='tcp', direction='ingress', ethertype=self.ethertype,
+ port_range_min=60, port_range_max=70)
+ updated = self.client.show_security_group(sg['security_group']['id'])
+ self.assertGreater(updated['security_group']['revision_number'],
+ sg['security_group']['revision_number'])
+ self.client.delete_security_group_rule(
+ rule['security_group_rule']['id'])
+ updated2 = self.client.show_security_group(sg['security_group']['id'])
+ self.assertGreater(updated2['security_group']['revision_number'],
+ updated['security_group']['revision_number'])
+
+ @decorators.idempotent_id('db70c285-0365-4fac-9f55-2a0ad8cf55a8')
+ @test.requires_ext(extension="allowed-address-pairs", service="network")
+ def test_update_allowed_address_pairs_bumps_revision(self):
+ net = self.create_network()
+ self.addCleanup(self.client.delete_network, net['id'])
+ port = self.create_port(net)
+ self.addCleanup(self.client.delete_port, port['id'])
+ updated = self.client.update_port(
+ port['id'], allowed_address_pairs=[{'ip_address': '1.1.1.1/32'}])
+ self.assertGreater(updated['port']['revision_number'],
+ port['revision_number'])
+ updated2 = self.client.update_port(
+ port['id'], allowed_address_pairs=[])
+ self.assertGreater(updated2['port']['revision_number'],
+ updated['port']['revision_number'])
+
+ @decorators.idempotent_id('a21ec3b4-3569-4b77-bf29-4177edaa2df5')
+ @test.requires_ext(extension="extra_dhcp_opt", service="network")
+ def test_update_extra_dhcp_opt_bumps_revision(self):
+ net = self.create_network()
+ self.addCleanup(self.client.delete_network, net['id'])
+ port = self.create_port(net)
+ self.addCleanup(self.client.delete_port, port['id'])
+ opts = [{'opt_value': 'pxelinux.0', 'opt_name': 'bootfile-name'}]
+ updated = self.client.update_port(port['id'], extra_dhcp_opts=opts)
+ self.assertGreater(updated['port']['revision_number'],
+ port['revision_number'])
+ opts[0]['opt_value'] = 'pxelinux.77'
+ updated2 = self.client.update_port(
+ port['id'], extra_dhcp_opts=opts)
+ self.assertGreater(updated2['port']['revision_number'],
+ updated['port']['revision_number'])
+
+ @decorators.idempotent_id('40ba648f-f374-4c29-a5b7-489dd5a38a4e')
+ @test.requires_ext(extension="dns-integration", service="network")
+ def test_update_dns_domain_bumps_revision(self):
+ net = self.create_network(dns_domain='example.test.')
+ self.addCleanup(self.client.delete_network, net['id'])
+ updated = self.client.update_network(net['id'], dns_domain='exa.test.')
+ self.assertGreater(updated['network']['revision_number'],
+ net['revision_number'])
+ port = self.create_port(net)
+ self.addCleanup(self.client.delete_port, port['id'])
+ updated = self.client.update_port(port['id'], dns_name='port1')
+ if not updated['port']['dns_name']:
+ self.skipTest("Server does not have DNS domain configured.")
+ self.assertGreater(updated['port']['revision_number'],
+ port['revision_number'])
+ updated2 = self.client.update_port(port['id'], dns_name='')
+ self.assertGreater(updated2['port']['revision_number'],
+ updated['port']['revision_number'])
+
+ @decorators.idempotent_id('8482324f-cf59-4d73-b98e-d37119255300')
+ @test.requires_ext(extension="router", service="network")
+ @test.requires_ext(extension="extraroute", service="network")
+ def test_update_router_extra_routes_bumps_revision(self):
+ net = self.create_network()
+ self.addCleanup(self.client.delete_network, net['id'])
+ subnet = self.create_subnet(net)
+ self.addCleanup(self.client.delete_subnet, subnet['id'])
+ subgateway = netaddr.IPAddress(subnet['gateway_ip'])
+ router = self.create_router(router_name='test')
+ self.addCleanup(self.client.delete_router, router['id'])
+ self.create_router_interface(router['id'], subnet['id'])
+ self.addCleanup(
+ self.client.remove_router_interface_with_subnet_id,
+ router['id'],
+ subnet['id'])
+ router = self.client.show_router(router['id'])['router']
+ updated = self.client.update_extra_routes(
+ router['id'], str(subgateway + 1), '2.0.0.0/24')
+ self.assertGreater(updated['router']['revision_number'],
+ router['revision_number'])
+ updated2 = self.client.delete_extra_routes(router['id'])
+ self.assertGreater(updated2['router']['revision_number'],
+ updated['router']['revision_number'])
+
+ @decorators.idempotent_id('6bd18702-e25a-4b4b-8c0c-680113533511')
+ @test.requires_ext(extension="subnet-service-types", service="network")
+ def test_update_subnet_service_types_bumps_revisions(self):
+ net = self.create_network()
+ self.addCleanup(self.client.delete_network, net['id'])
+ subnet = self.create_subnet(net)
+ self.addCleanup(self.client.delete_subnet, subnet['id'])
+ updated = self.client.update_subnet(
+ subnet['id'], service_types=['compute:'])
+ self.assertGreater(updated['subnet']['revision_number'],
+ subnet['revision_number'])
+ updated2 = self.client.update_subnet(
+ subnet['id'], service_types=[])
+ self.assertGreater(updated2['subnet']['revision_number'],
+ updated['subnet']['revision_number'])
+
+ @decorators.idempotent_id('9c83105c-9973-45ff-9ca2-e66d64700abe')
+ @test.requires_ext(extension="port-security", service="network")
+ def test_update_port_security_bumps_revisions(self):
+ net = self.create_network(port_security_enabled=False)
+ self.addCleanup(self.client.delete_network, net['id'])
+ updated = self.client.update_network(net['id'],
+ port_security_enabled=True)
+ self.assertGreater(updated['network']['revision_number'],
+ net['revision_number'])
+ updated2 = self.client.update_network(net['id'],
+ port_security_enabled=False)
+ self.assertGreater(updated2['network']['revision_number'],
+ updated['network']['revision_number'])
+ port = self.create_port(net, port_security_enabled=False)
+ self.addCleanup(self.client.delete_port, port['id'])
+ updated = self.client.update_port(port['id'],
+ port_security_enabled=True)
+ self.assertGreater(updated['port']['revision_number'],
+ port['revision_number'])
+ updated2 = self.client.update_port(port['id'],
+ port_security_enabled=False)
+ self.assertGreater(updated2['port']['revision_number'],
+ updated['port']['revision_number'])
+
+ @decorators.idempotent_id('68d5ac3a-11a1-4847-8e2e-5843c043d89b')
+ @test.requires_ext(extension="binding", service="network")
+ def test_portbinding_bumps_revision(self):
+ net = self.create_network()
+ self.addCleanup(self.client.delete_network, net['id'])
+ port = self.create_port(net)
+ self.addCleanup(self.client.delete_port, port['id'])
+ port = self.admin_client.update_port(
+ port['id'], **{'binding:host_id': 'badhost1'})['port']
+ updated = self.admin_client.update_port(
+ port['id'], **{'binding:host_id': 'badhost2'})['port']
+ self.assertGreater(updated['revision_number'],
+ port['revision_number'])
+
+ @decorators.idempotent_id('4a37bde9-1975-47e0-9b8c-2c9ca36415b0')
+ @test.requires_ext(extension="router", service="network")
+ def test_update_router_bumps_revision(self):
+ net = self.create_network()
+ self.addCleanup(self.client.delete_network, net['id'])
+ subnet = self.create_subnet(net)
+ self.addCleanup(self.client.delete_subnet, subnet['id'])
+ router = self.create_router(router_name='test')
+ self.addCleanup(self.client.delete_router, router['id'])
+ self.assertIn('revision_number', router)
+ rev1 = router['revision_number']
+ router = self.client.update_router(router['id'],
+ name='test2')['router']
+ self.assertGreater(router['revision_number'], rev1)
+ self.create_router_interface(router['id'], subnet['id'])
+ self.addCleanup(
+ self.client.remove_router_interface_with_subnet_id,
+ router['id'],
+ subnet['id'])
+ updated = self.client.show_router(router['id'])['router']
+ self.assertGreater(updated['revision_number'],
+ router['revision_number'])
+
+ @decorators.idempotent_id('9de71ebc-f5df-4cd0-80bc-60299fce3ce9')
+ @test.requires_ext(extension="router", service="network")
+ @test.requires_ext(extension="standard-attr-description",
+ service="network")
+ def test_update_floatingip_bumps_revision(self):
+ ext_id = config.CONF.network.public_network_id
+ net = self.create_network()
+ self.addCleanup(self.client.delete_network, net['id'])
+ subnet = self.create_subnet(net)
+ self.addCleanup(self.client.delete_subnet, subnet['id'])
+ router = self.create_router('test', external_network_id=ext_id)
+ self.addCleanup(self.client.delete_router, router['id'])
+ self.create_router_interface(router['id'], subnet['id'])
+ self.addCleanup(
+ self.client.remove_router_interface_with_subnet_id,
+ router['id'],
+ subnet['id'])
+ port = self.create_port(net)
+ self.addCleanup(self.client.delete_port, port['id'])
+ body = self.client.create_floatingip(
+ floating_network_id=ext_id,
+ port_id=port['id'],
+ description='d1'
+ )['floatingip']
+ self.floating_ips.append(body)
+ self.assertIn('revision_number', body)
+ b2 = self.client.update_floatingip(body['id'], description='d2')
+ self.assertGreater(b2['floatingip']['revision_number'],
+ body['revision_number'])
+ # disassociate
+ self.client.update_floatingip(b2['floatingip']['id'], port_id=None)
+
+ @decorators.idempotent_id('afb6486c-41b5-483e-a500-3c506f4deb49')
+ @test.requires_ext(extension="router", service="network")
+ @test.requires_ext(extension="l3-ha", service="network")
+ def test_update_router_extra_attributes_bumps_revision(self):
+ # updates from CVR to CVR-HA are supported on every release,
+ # but only the admin can forcibly create a non-HA router
+ router_args = {'tenant_id': self.client.tenant_id,
+ 'ha': False}
+ router = self.admin_client.create_router('r1', True,
+ **router_args)['router']
+ self.addCleanup(self.client.delete_router, router['id'])
+ self.assertIn('revision_number', router)
+ rev1 = router['revision_number']
+ router = self.admin_client.update_router(
+ router['id'], admin_state_up=False)['router']
+ self.assertGreater(router['revision_number'], rev1)
+ self.admin_client.update_router(router['id'], ha=True)['router']
+ updated = self.client.show_router(router['id'])['router']
+ self.assertGreater(updated['revision_number'],
+ router['revision_number'])
+
+ @decorators.idempotent_id('90743b00-b0e2-40e4-9524-1c884fe3ef23')
+ @test.requires_ext(extension="external-net", service="network")
+ @test.requires_ext(extension="auto-allocated-topology", service="network")
+ @test.requires_ext(extension="subnet_allocation", service="network")
+ @test.requires_ext(extension="router", service="network")
+ def test_update_external_network_bumps_revision(self):
+ net = self.create_network()
+ self.addCleanup(self.client.delete_network, net['id'])
+ self.assertIn('revision_number', net)
+ updated = self.admin_client.update_network(net['id'],
+ **{'router:external': True})
+ self.assertGreater(updated['network']['revision_number'],
+ net['revision_number'])
+
+ @decorators.idempotent_id('5af6450a-0f61-49c3-b628-38db77c7b856')
+ @test.requires_ext(extension="qos", service="network")
+ def test_update_qos_port_policy_binding_bumps_revision(self):
+ policy = self.create_qos_policy(name='port-policy', shared=False)
+ net = self.create_network()
+ self.addCleanup(self.client.delete_network, net['id'])
+ port = self.create_port(net)
+ self.addCleanup(self.client.delete_port, port['id'])
+ updated = self.admin_client.update_port(
+ port['id'], qos_policy_id=policy['id'])
+ self.assertGreater(updated['port']['revision_number'],
+ port['revision_number'])
+
+ @decorators.idempotent_id('817da343-c6e4-445c-9519-a621f124dfbe')
+ @test.requires_ext(extension="qos", service="network")
+ def test_update_qos_network_policy_binding_bumps_revision(self):
+ policy = self.create_qos_policy(name='network-policy', shared=False)
+ network = self.create_network()
+ self.addCleanup(self.client.delete_network, network['id'])
+ updated = self.admin_client.update_network(
+ network['id'], qos_policy_id=policy['id'])
+ self.assertGreater(updated['network']['revision_number'],
+ network['revision_number'])
diff --git a/neutron_tempest_plugin/api/test_routers.py b/neutron_tempest_plugin/api/test_routers.py
new file mode 100644
index 0000000..11fdecf
--- /dev/null
+++ b/neutron_tempest_plugin/api/test_routers.py
@@ -0,0 +1,326 @@
+# Copyright 2013 OpenStack Foundation
+# All Rights Reserved.
+#
+# 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 netaddr
+from tempest.lib.common.utils import data_utils
+from tempest.lib import decorators
+from tempest import test
+
+from neutron_tempest_plugin.api import base
+from neutron_tempest_plugin.api import base_routers
+from neutron_tempest_plugin.common import utils
+from neutron_tempest_plugin import config
+
+CONF = config.CONF
+
+
+class RoutersTest(base_routers.BaseRouterTest):
+
+ required_extensions = ['router']
+
+ @classmethod
+ def resource_setup(cls):
+ super(RoutersTest, cls).resource_setup()
+ cls.tenant_cidr = (
+ config.safe_get_config_value('network', 'project_network_cidr')
+ if cls._ip_version == 4 else
+ config.safe_get_config_value('network', 'project_network_v6_cidr'))
+
+ @decorators.idempotent_id('c72c1c0c-2193-4aca-eeee-b1442640eeee')
+ @test.requires_ext(extension="standard-attr-description",
+ service="network")
+ def test_create_update_router_description(self):
+ body = self.create_router(description='d1', router_name='test')
+ self.assertEqual('d1', body['description'])
+ body = self.client.show_router(body['id'])['router']
+ self.assertEqual('d1', body['description'])
+ body = self.client.update_router(body['id'], description='d2')
+ self.assertEqual('d2', body['router']['description'])
+ body = self.client.show_router(body['router']['id'])['router']
+ self.assertEqual('d2', body['description'])
+
+ @decorators.idempotent_id('847257cc-6afd-4154-b8fb-af49f5670ce8')
+ @test.requires_ext(extension='ext-gw-mode', service='network')
+ def test_create_router_with_default_snat_value(self):
+ # Create a router with default snat rule
+ name = data_utils.rand_name('router')
+ router = self._create_router(
+ name, external_network_id=CONF.network.public_network_id)
+ self._verify_router_gateway(
+ router['id'], {'network_id': CONF.network.public_network_id,
+ 'enable_snat': True})
+
+ @decorators.idempotent_id('ea74068d-09e9-4fd7-8995-9b6a1ace920f')
+ @test.requires_ext(extension='ext-gw-mode', service='network')
+ def test_create_router_with_snat_explicit(self):
+ name = data_utils.rand_name('snat-router')
+ # Create a router enabling snat attributes
+ enable_snat_states = [False, True]
+ for enable_snat in enable_snat_states:
+ external_gateway_info = {
+ 'network_id': CONF.network.public_network_id,
+ 'enable_snat': enable_snat}
+ create_body = self.admin_client.create_router(
+ name, external_gateway_info=external_gateway_info)
+ self.addCleanup(self.admin_client.delete_router,
+ create_body['router']['id'])
+ # Verify snat attributes after router creation
+ self._verify_router_gateway(create_body['router']['id'],
+ exp_ext_gw_info=external_gateway_info)
+
+ def _verify_router_gateway(self, router_id, exp_ext_gw_info=None):
+ show_body = self.admin_client.show_router(router_id)
+ actual_ext_gw_info = show_body['router']['external_gateway_info']
+ if exp_ext_gw_info is None:
+ self.assertIsNone(actual_ext_gw_info)
+ return
+ # Verify only keys passed in exp_ext_gw_info
+ for k, v in exp_ext_gw_info.items():
+ self.assertEqual(v, actual_ext_gw_info[k])
+
+ def _verify_gateway_port(self, router_id):
+ list_body = self.admin_client.list_ports(
+ network_id=CONF.network.public_network_id,
+ device_id=router_id)
+ self.assertEqual(len(list_body['ports']), 1)
+ gw_port = list_body['ports'][0]
+ fixed_ips = gw_port['fixed_ips']
+ self.assertGreaterEqual(len(fixed_ips), 1)
+ public_net_body = self.admin_client.show_network(
+ CONF.network.public_network_id)
+ public_subnet_id = public_net_body['network']['subnets'][0]
+ self.assertIn(public_subnet_id,
+ [x['subnet_id'] for x in fixed_ips])
+
+ @decorators.idempotent_id('b386c111-3b21-466d-880c-5e72b01e1a33')
+ @test.requires_ext(extension='ext-gw-mode', service='network')
+ def test_update_router_set_gateway_with_snat_explicit(self):
+ router = self._create_router(data_utils.rand_name('router-'))
+ self.admin_client.update_router_with_snat_gw_info(
+ router['id'],
+ external_gateway_info={
+ 'network_id': CONF.network.public_network_id,
+ 'enable_snat': True})
+ self._verify_router_gateway(
+ router['id'],
+ {'network_id': CONF.network.public_network_id,
+ 'enable_snat': True})
+ self._verify_gateway_port(router['id'])
+
+ @decorators.idempotent_id('96536bc7-8262-4fb2-9967-5c46940fa279')
+ @test.requires_ext(extension='ext-gw-mode', service='network')
+ def test_update_router_set_gateway_without_snat(self):
+ router = self._create_router(data_utils.rand_name('router-'))
+ self.admin_client.update_router_with_snat_gw_info(
+ router['id'],
+ external_gateway_info={
+ 'network_id': CONF.network.public_network_id,
+ 'enable_snat': False})
+ self._verify_router_gateway(
+ router['id'],
+ {'network_id': CONF.network.public_network_id,
+ 'enable_snat': False})
+ self._verify_gateway_port(router['id'])
+
+ @decorators.idempotent_id('f2faf994-97f4-410b-a831-9bc977b64374')
+ @test.requires_ext(extension='ext-gw-mode', service='network')
+ def test_update_router_reset_gateway_without_snat(self):
+ router = self._create_router(
+ data_utils.rand_name('router-'),
+ external_network_id=CONF.network.public_network_id)
+ self.admin_client.update_router_with_snat_gw_info(
+ router['id'],
+ external_gateway_info={
+ 'network_id': CONF.network.public_network_id,
+ 'enable_snat': False})
+ self._verify_router_gateway(
+ router['id'],
+ {'network_id': CONF.network.public_network_id,
+ 'enable_snat': False})
+ self._verify_gateway_port(router['id'])
+
+ @decorators.idempotent_id('db3093b1-93b6-4893-be83-c4716c251b3e')
+ def test_router_interface_status(self):
+ network = self.create_network()
+ subnet = self.create_subnet(network)
+ # Add router interface with subnet id
+ router = self._create_router(data_utils.rand_name('router-'), True)
+ intf = self.create_router_interface(router['id'], subnet['id'])
+ status_active = lambda: self.client.show_port(
+ intf['port_id'])['port']['status'] == 'ACTIVE'
+ utils.wait_until_true(status_active, exception=AssertionError)
+
+ @decorators.idempotent_id('c86ac3a8-50bd-4b00-a6b8-62af84a0765c')
+ @test.requires_ext(extension='extraroute', service='network')
+ def test_update_extra_route(self):
+ self.network = self.create_network()
+ self.name = self.network['name']
+ self.subnet = self.create_subnet(self.network)
+ # Add router interface with subnet id
+ self.router = self._create_router(
+ data_utils.rand_name('router-'), True)
+ self.create_router_interface(self.router['id'], self.subnet['id'])
+ self.addCleanup(
+ self._delete_extra_routes,
+ self.router['id'])
+ # Update router extra route, second ip of the range is
+ # used as next hop
+ cidr = netaddr.IPNetwork(self.subnet['cidr'])
+ next_hop = str(cidr[2])
+ destination = str(self.subnet['cidr'])
+ extra_route = self.client.update_extra_routes(self.router['id'],
+ next_hop, destination)
+ self.assertEqual(1, len(extra_route['router']['routes']))
+ self.assertEqual(destination,
+ extra_route['router']['routes'][0]['destination'])
+ self.assertEqual(next_hop,
+ extra_route['router']['routes'][0]['nexthop'])
+ show_body = self.client.show_router(self.router['id'])
+ self.assertEqual(destination,
+ show_body['router']['routes'][0]['destination'])
+ self.assertEqual(next_hop,
+ show_body['router']['routes'][0]['nexthop'])
+
+ def _delete_extra_routes(self, router_id):
+ self.client.delete_extra_routes(router_id)
+
+ @decorators.idempotent_id('01f185d1-d1a6-4cf9-abf7-e0e1384c169c')
+ def test_network_attached_with_two_routers(self):
+ network = self.create_network(data_utils.rand_name('network1'))
+ self.create_subnet(network)
+ port1 = self.create_port(network)
+ port2 = self.create_port(network)
+ router1 = self._create_router(data_utils.rand_name('router1'))
+ router2 = self._create_router(data_utils.rand_name('router2'))
+ self.client.add_router_interface_with_port_id(
+ router1['id'], port1['id'])
+ self.client.add_router_interface_with_port_id(
+ router2['id'], port2['id'])
+ self.addCleanup(self.client.remove_router_interface_with_port_id,
+ router1['id'], port1['id'])
+ self.addCleanup(self.client.remove_router_interface_with_port_id,
+ router2['id'], port2['id'])
+ body = self.client.show_port(port1['id'])
+ port_show1 = body['port']
+ body = self.client.show_port(port2['id'])
+ port_show2 = body['port']
+ self.assertEqual(port_show1['network_id'], network['id'])
+ self.assertEqual(port_show2['network_id'], network['id'])
+ self.assertEqual(port_show1['device_id'], router1['id'])
+ self.assertEqual(port_show2['device_id'], router2['id'])
+
+
+class RoutersIpV6Test(RoutersTest):
+ _ip_version = 6
+
+
+class DvrRoutersTest(base_routers.BaseRouterTest):
+
+ required_extensions = ['dvr']
+
+ @decorators.idempotent_id('141297aa-3424-455d-aa8d-f2d95731e00a')
+ def test_create_distributed_router(self):
+ name = data_utils.rand_name('router')
+ create_body = self.admin_client.create_router(
+ name, distributed=True)
+ self.addCleanup(self._delete_router,
+ create_body['router']['id'],
+ self.admin_client)
+ self.assertTrue(create_body['router']['distributed'])
+
+ @decorators.idempotent_id('644d7a4a-01a1-4b68-bb8d-0c0042cb1729')
+ def test_convert_centralized_router(self):
+ router_args = {'tenant_id': self.client.tenant_id,
+ 'distributed': False, 'ha': False}
+ router = self.admin_client.create_router(
+ data_utils.rand_name('router'), admin_state_up=False,
+ **router_args)['router']
+ self.addCleanup(self.admin_client.delete_router,
+ router['id'])
+ self.assertFalse(router['distributed'])
+ self.assertFalse(router['ha'])
+ update_body = self.admin_client.update_router(router['id'],
+ distributed=True)
+ self.assertTrue(update_body['router']['distributed'])
+ show_body = self.admin_client.show_router(router['id'])
+ self.assertTrue(show_body['router']['distributed'])
+ show_body = self.client.show_router(router['id'])
+ self.assertNotIn('distributed', show_body['router'])
+ self.assertNotIn('ha', show_body['router'])
+
+
+class HaRoutersTest(base_routers.BaseRouterTest):
+
+ required_extensions = ['l3-ha']
+
+ @decorators.idempotent_id('77db8eae-3aa3-4e61-bf2a-e739ce042e53')
+ def test_convert_legacy_router(self):
+ router = self._create_router(data_utils.rand_name('router'))
+ self.assertNotIn('ha', router)
+ update_body = self.admin_client.update_router(router['id'],
+ ha=True)
+ self.assertTrue(update_body['router']['ha'])
+ show_body = self.admin_client.show_router(router['id'])
+ self.assertTrue(show_body['router']['ha'])
+ show_body = self.client.show_router(router['id'])
+ self.assertNotIn('ha', show_body['router'])
+
+
+class RoutersSearchCriteriaTest(base.BaseSearchCriteriaTest):
+
+ required_extensions = ['router']
+ resource = 'router'
+
+ @classmethod
+ def resource_setup(cls):
+ super(RoutersSearchCriteriaTest, cls).resource_setup()
+ for name in cls.resource_names:
+ cls.create_router(router_name=name)
+
+ @decorators.idempotent_id('03a69efb-90a7-435b-bb5c-3add3612085a')
+ def test_list_sorts_asc(self):
+ self._test_list_sorts_asc()
+
+ @decorators.idempotent_id('95913d30-ff41-4b17-9f44-5258c651e78c')
+ def test_list_sorts_desc(self):
+ self._test_list_sorts_desc()
+
+ @decorators.idempotent_id('7f7d40b1-e165-4817-8dc5-02f8e2f0dff3')
+ def test_list_pagination(self):
+ self._test_list_pagination()
+
+ @decorators.idempotent_id('a5b83e83-3d98-45bb-a2c7-0ee179ffd42c')
+ def test_list_pagination_with_marker(self):
+ self._test_list_pagination_with_marker()
+
+ @decorators.idempotent_id('40804af8-c25d-45f8-b8a8-b4c70345215d')
+ def test_list_pagination_with_href_links(self):
+ self._test_list_pagination_with_href_links()
+
+ @decorators.idempotent_id('77b9676c-d3cb-43af-a0e8-a5b8c6099e70')
+ def test_list_pagination_page_reverse_asc(self):
+ self._test_list_pagination_page_reverse_asc()
+
+ @decorators.idempotent_id('3133a2c5-1bb9-4fc7-833e-cf9a1d160255')
+ def test_list_pagination_page_reverse_desc(self):
+ self._test_list_pagination_page_reverse_desc()
+
+ @decorators.idempotent_id('8252e2f0-b3da-4738-8e25-f6f8d878a2da')
+ def test_list_pagination_page_reverse_with_href_links(self):
+ self._test_list_pagination_page_reverse_with_href_links()
+
+ @decorators.idempotent_id('fb102124-20f8-4cb3-8c81-f16f5e41d192')
+ def test_list_no_pagination_limit_0(self):
+ self._test_list_no_pagination_limit_0()
diff --git a/neutron_tempest_plugin/api/test_routers_negative.py b/neutron_tempest_plugin/api/test_routers_negative.py
new file mode 100644
index 0000000..2f4ad44
--- /dev/null
+++ b/neutron_tempest_plugin/api/test_routers_negative.py
@@ -0,0 +1,92 @@
+# Copyright 2013 OpenStack Foundation
+# All Rights Reserved.
+#
+# 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.common.utils import data_utils
+from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
+import testtools
+
+from neutron_tempest_plugin.api import base_routers as base
+
+
+class RoutersNegativeTestBase(base.BaseRouterTest):
+
+ required_extensions = ['router']
+
+ @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):
+
+ @decorators.attr(type='negative')
+ @decorators.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 RoutersNegativePolicyTest(RoutersNegativeTestBase):
+
+ credentials = ['admin', 'primary', 'alt']
+
+ @decorators.attr(type='negative')
+ @decorators.idempotent_id('159f576d-a423-46b5-b501-622694c02f6b')
+ def test_add_interface_wrong_tenant(self):
+ client2 = self.os_alt.network_client
+ network = client2.create_network()['network']
+ self.addCleanup(client2.delete_network, network['id'])
+ subnet = self.create_subnet(network, client=client2)
+ # This port is deleted after a test by remove_router_interface.
+ port = client2.create_port(network_id=network['id'])['port']
+ self.addCleanup(client2.delete_port, port['id'])
+ with testtools.ExpectedException(lib_exc.NotFound):
+ client2.add_router_interface_with_port_id(
+ self.router['id'], port['id'])
+ with testtools.ExpectedException(lib_exc.NotFound):
+ client2.add_router_interface_with_subnet_id(
+ self.router['id'], subnet['id'])
+
+
+class DvrRoutersNegativeTest(RoutersNegativeTestBase):
+
+ required_extensions = ['dvr']
+
+ @decorators.attr(type='negative')
+ @decorators.idempotent_id('4990b055-8fc7-48ab-bba7-aa28beaad0b9')
+ def test_router_create_tenant_distributed_returns_forbidden(self):
+ with testtools.ExpectedException(lib_exc.Forbidden):
+ self.create_router(
+ data_utils.rand_name('router'), distributed=True)
+
+
+class HaRoutersNegativeTest(RoutersNegativeTestBase):
+
+ required_extensions = ['l3-ha']
+
+ @decorators.attr(type='negative')
+ @decorators.idempotent_id('821b85b9-9c51-40f3-831f-bf223a7e0084')
+ def test_router_create_tenant_ha_returns_forbidden(self):
+ with testtools.ExpectedException(lib_exc.Forbidden):
+ self.create_router(
+ data_utils.rand_name('router'), ha=True)
diff --git a/neutron_tempest_plugin/api/test_security_groups.py b/neutron_tempest_plugin/api/test_security_groups.py
new file mode 100644
index 0000000..46e00f7
--- /dev/null
+++ b/neutron_tempest_plugin/api/test_security_groups.py
@@ -0,0 +1,66 @@
+# Copyright 2013 OpenStack Foundation
+# All Rights Reserved.
+#
+# 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.common.utils import data_utils
+from tempest.lib import decorators
+
+from neutron_tempest_plugin.api import base_security_groups as base
+
+
+class SecGroupTest(base.BaseSecGroupTest):
+
+ required_extensions = ['security-group']
+
+ @decorators.idempotent_id('bfd128e5-3c92-44b6-9d66-7fe29d22c802')
+ def test_create_list_update_show_delete_security_group(self):
+ group_create_body, name = self._create_security_group()
+
+ # List security groups and verify if created group is there in response
+ list_body = self.client.list_security_groups()
+ secgroup_list = list()
+ for secgroup in list_body['security_groups']:
+ secgroup_list.append(secgroup['id'])
+ self.assertIn(group_create_body['security_group']['id'], secgroup_list)
+ # Update the security group
+ new_name = data_utils.rand_name('security-')
+ new_description = data_utils.rand_name('security-description')
+ update_body = self.client.update_security_group(
+ group_create_body['security_group']['id'],
+ name=new_name,
+ description=new_description)
+ # Verify if security group is updated
+ self.assertEqual(update_body['security_group']['name'], new_name)
+ self.assertEqual(update_body['security_group']['description'],
+ new_description)
+ # Show details of the updated security group
+ show_body = self.client.show_security_group(
+ group_create_body['security_group']['id'])
+ self.assertEqual(show_body['security_group']['name'], new_name)
+ self.assertEqual(show_body['security_group']['description'],
+ new_description)
+
+ @decorators.idempotent_id('7c0ecb10-b2db-11e6-9b14-000c29248b0d')
+ def test_create_bulk_sec_groups(self):
+ # Creates 2 sec-groups in one request
+ sec_nm = [data_utils.rand_name('secgroup'),
+ data_utils.rand_name('secgroup')]
+ body = self.client.create_bulk_security_groups(sec_nm)
+ created_sec_grps = body['security_groups']
+ self.assertEqual(2, len(created_sec_grps))
+ for secgrp in created_sec_grps:
+ self.addCleanup(self.client.delete_security_group,
+ secgrp['id'])
+ self.assertIn(secgrp['name'], sec_nm)
+ self.assertIsNotNone(secgrp['id'])
diff --git a/neutron_tempest_plugin/api/test_security_groups_negative.py b/neutron_tempest_plugin/api/test_security_groups_negative.py
new file mode 100644
index 0000000..fd54a5c
--- /dev/null
+++ b/neutron_tempest_plugin/api/test_security_groups_negative.py
@@ -0,0 +1,71 @@
+# Copyright 2013 OpenStack Foundation
+# All Rights Reserved.
+#
+# 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 neutron_lib.db import constants as db_const
+from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
+
+from neutron_tempest_plugin.api import base_security_groups as base
+
+LONG_NAME_NG = 'x' * (db_const.NAME_FIELD_SIZE + 1)
+
+
+class NegativeSecGroupTest(base.BaseSecGroupTest):
+
+ required_extensions = ['security-group']
+
+ @decorators.attr(type='negative')
+ @decorators.idempotent_id('594edfa8-9a5b-438e-9344-49aece337d49')
+ def test_create_security_group_with_too_long_name(self):
+ self.assertRaises(lib_exc.BadRequest,
+ self.client.create_security_group,
+ name=LONG_NAME_NG)
+
+ @decorators.attr(type='negative')
+ @decorators.idempotent_id('b6b79838-7430-4d3f-8e07-51dfb61802c2')
+ def test_create_security_group_with_boolean_type_name(self):
+ self.assertRaises(lib_exc.BadRequest,
+ self.client.create_security_group,
+ name=True)
+
+ @decorators.attr(type='negative')
+ @decorators.idempotent_id('55100aa8-b24f-333c-0bef-64eefd85f15c')
+ def test_update_default_security_group_name(self):
+ sg_list = self.client.list_security_groups(name='default')
+ sg = sg_list['security_groups'][0]
+ self.assertRaises(lib_exc.Conflict, self.client.update_security_group,
+ sg['id'], name='test')
+
+ @decorators.attr(type='negative')
+ @decorators.idempotent_id('c8510dd8-c3a8-4df9-ae44-24354db50960')
+ def test_update_security_group_with_too_long_name(self):
+ sg_list = self.client.list_security_groups(name='default')
+ sg = sg_list['security_groups'][0]
+ self.assertRaises(lib_exc.BadRequest,
+ self.client.update_security_group,
+ sg['id'], name=LONG_NAME_NG)
+
+ @decorators.attr(type='negative')
+ @decorators.idempotent_id('d9a14917-f66f-4eca-ab72-018563917f1b')
+ def test_update_security_group_with_boolean_type_name(self):
+ sg_list = self.client.list_security_groups(name='default')
+ sg = sg_list['security_groups'][0]
+ self.assertRaises(lib_exc.BadRequest,
+ self.client.update_security_group,
+ sg['id'], name=True)
+
+
+class NegativeSecGroupIPv6Test(NegativeSecGroupTest):
+ _ip_version = 6
diff --git a/neutron_tempest_plugin/api/test_service_type_management.py b/neutron_tempest_plugin/api/test_service_type_management.py
new file mode 100644
index 0000000..adcbb6c
--- /dev/null
+++ b/neutron_tempest_plugin/api/test_service_type_management.py
@@ -0,0 +1,25 @@
+# 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 decorators
+
+from neutron_tempest_plugin.api import base
+
+
+class ServiceTypeManagementTest(base.BaseNetworkTest):
+
+ required_extensions = ['service-type']
+
+ @decorators.idempotent_id('2cbbeea9-f010-40f6-8df5-4eaa0c918ea6')
+ def test_service_provider_list(self):
+ body = self.client.list_service_providers()
+ self.assertIsInstance(body['service_providers'], list)
diff --git a/neutron_tempest_plugin/api/test_subnetpools.py b/neutron_tempest_plugin/api/test_subnetpools.py
new file mode 100644
index 0000000..5da0ad8
--- /dev/null
+++ b/neutron_tempest_plugin/api/test_subnetpools.py
@@ -0,0 +1,482 @@
+# Copyright 2015 Hewlett-Packard Development Company, L.P.
+# All Rights Reserved.
+#
+# 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.common.utils import data_utils
+from tempest.lib import decorators
+from tempest import test
+
+from neutron_tempest_plugin.api import base
+
+SUBNETPOOL_NAME = 'smoke-subnetpool'
+SUBNET_NAME = 'smoke-subnet'
+
+
+class SubnetPoolsTestBase(base.BaseAdminNetworkTest):
+
+ @classmethod
+ def resource_setup(cls):
+ super(SubnetPoolsTestBase, cls).resource_setup()
+ min_prefixlen = '29'
+ prefixes = [u'10.11.12.0/24']
+ cls._subnetpool_data = {'prefixes': prefixes,
+ 'min_prefixlen': min_prefixlen}
+
+ @classmethod
+ def _create_subnetpool(cls, is_admin=False, **kwargs):
+ if 'name' not in kwargs:
+ name = data_utils.rand_name(SUBNETPOOL_NAME)
+ else:
+ name = kwargs.pop('name')
+
+ if 'prefixes' not in kwargs:
+ kwargs['prefixes'] = cls._subnetpool_data['prefixes']
+
+ if 'min_prefixlen' not in kwargs:
+ kwargs['min_prefixlen'] = cls._subnetpool_data['min_prefixlen']
+
+ return cls.create_subnetpool(name=name, is_admin=is_admin, **kwargs)
+
+
+class SubnetPoolsTest(SubnetPoolsTestBase):
+
+ min_prefixlen = '28'
+ max_prefixlen = '31'
+ _ip_version = 4
+ subnet_cidr = u'10.11.12.0/31'
+ new_prefix = u'10.11.15.0/24'
+ larger_prefix = u'10.11.0.0/16'
+
+ """
+ Tests the following operations in the Neutron API using the REST client for
+ Neutron:
+
+ create a subnetpool for a tenant
+ list tenant's subnetpools
+ show a tenant subnetpool details
+ subnetpool update
+ delete a subnetpool
+
+ All subnetpool tests are run once with ipv4 and once with ipv6.
+
+ v2.0 of the Neutron API is assumed.
+
+ """
+
+ def _new_subnetpool_attributes(self):
+ new_name = data_utils.rand_name(SUBNETPOOL_NAME)
+ return {'name': new_name, 'min_prefixlen': self.min_prefixlen,
+ 'max_prefixlen': self.max_prefixlen}
+
+ def _check_equality_updated_subnetpool(self, expected_values,
+ updated_pool):
+ self.assertEqual(expected_values['name'],
+ updated_pool['name'])
+ self.assertEqual(expected_values['min_prefixlen'],
+ updated_pool['min_prefixlen'])
+ self.assertEqual(expected_values['max_prefixlen'],
+ updated_pool['max_prefixlen'])
+ # expected_values may not contains all subnetpool values
+ if 'prefixes' in expected_values:
+ self.assertEqual(expected_values['prefixes'],
+ updated_pool['prefixes'])
+
+ @decorators.idempotent_id('6e1781ec-b45b-4042-aebe-f485c022996e')
+ def test_create_list_subnetpool(self):
+ created_subnetpool = self._create_subnetpool()
+ body = self.client.list_subnetpools()
+ subnetpools = body['subnetpools']
+ self.assertIn(created_subnetpool['id'],
+ [sp['id'] for sp in subnetpools],
+ "Created subnetpool id should be in the list")
+ self.assertIn(created_subnetpool['name'],
+ [sp['name'] for sp in subnetpools],
+ "Created subnetpool name should be in the list")
+
+ @decorators.idempotent_id('c72c1c0c-2193-4aca-ddd4-b1442640bbbb')
+ @test.requires_ext(extension="standard-attr-description",
+ service="network")
+ def test_create_update_subnetpool_description(self):
+ body = self._create_subnetpool(description='d1')
+ self.assertEqual('d1', body['description'])
+ sub_id = body['id']
+ subnet_pools = [x for x in
+ self.client.list_subnetpools()['subnetpools'] if x['id'] == sub_id]
+ body = subnet_pools[0]
+ self.assertEqual('d1', body['description'])
+ body = self.client.update_subnetpool(sub_id, description='d2')
+ self.assertEqual('d2', body['subnetpool']['description'])
+ subnet_pools = [x for x in
+ self.client.list_subnetpools()['subnetpools'] if x['id'] == sub_id]
+ body = subnet_pools[0]
+ self.assertEqual('d2', body['description'])
+
+ @decorators.idempotent_id('741d08c2-1e3f-42be-99c7-0ea93c5b728c')
+ def test_get_subnetpool(self):
+ created_subnetpool = self._create_subnetpool()
+ prefixlen = self._subnetpool_data['min_prefixlen']
+ body = self.client.show_subnetpool(created_subnetpool['id'])
+ subnetpool = body['subnetpool']
+ self.assertEqual(created_subnetpool['name'], subnetpool['name'])
+ self.assertEqual(created_subnetpool['id'], subnetpool['id'])
+ self.assertEqual(prefixlen, subnetpool['min_prefixlen'])
+ self.assertEqual(prefixlen, subnetpool['default_prefixlen'])
+ self.assertFalse(subnetpool['shared'])
+
+ @decorators.idempotent_id('5bf9f1e2-efc8-4195-acf3-d12b2bd68dd3')
+ @test.requires_ext(extension="project-id", service="network")
+ def test_show_subnetpool_has_project_id(self):
+ subnetpool = self._create_subnetpool()
+ body = self.client.show_subnetpool(subnetpool['id'])
+ show_subnetpool = body['subnetpool']
+ self.assertIn('project_id', show_subnetpool)
+ self.assertIn('tenant_id', show_subnetpool)
+ self.assertEqual(self.client.tenant_id, show_subnetpool['project_id'])
+ self.assertEqual(self.client.tenant_id, show_subnetpool['tenant_id'])
+
+ @decorators.idempotent_id('764f1b93-1c4a-4513-9e7b-6c2fc5e9270c')
+ def test_tenant_update_subnetpool(self):
+ created_subnetpool = self._create_subnetpool()
+ pool_id = created_subnetpool['id']
+ subnetpool_data = self._new_subnetpool_attributes()
+ self.client.update_subnetpool(created_subnetpool['id'],
+ **subnetpool_data)
+
+ body = self.client.show_subnetpool(pool_id)
+ subnetpool = body['subnetpool']
+ self._check_equality_updated_subnetpool(subnetpool_data,
+ subnetpool)
+ self.assertFalse(subnetpool['shared'])
+
+ @decorators.idempotent_id('4b496082-c992-4319-90be-d4a7ce646290')
+ def test_update_subnetpool_prefixes_append(self):
+ # We can append new prefixes to subnetpool
+ create_subnetpool = self._create_subnetpool()
+ pool_id = create_subnetpool['id']
+ old_prefixes = self._subnetpool_data['prefixes']
+ new_prefixes = old_prefixes[:]
+ new_prefixes.append(self.new_prefix)
+ subnetpool_data = {'prefixes': new_prefixes}
+ self.client.update_subnetpool(pool_id, **subnetpool_data)
+ body = self.client.show_subnetpool(pool_id)
+ prefixes = body['subnetpool']['prefixes']
+ self.assertIn(self.new_prefix, prefixes)
+ self.assertIn(old_prefixes[0], prefixes)
+
+ @decorators.idempotent_id('2cae5d6a-9d32-42d8-8067-f13970ae13bb')
+ def test_update_subnetpool_prefixes_extend(self):
+ # We can extend current subnetpool prefixes
+ created_subnetpool = self._create_subnetpool()
+ pool_id = created_subnetpool['id']
+ old_prefixes = self._subnetpool_data['prefixes']
+ subnetpool_data = {'prefixes': [self.larger_prefix]}
+ self.client.update_subnetpool(pool_id, **subnetpool_data)
+ body = self.client.show_subnetpool(pool_id)
+ prefixes = body['subnetpool']['prefixes']
+ self.assertIn(self.larger_prefix, prefixes)
+ self.assertNotIn(old_prefixes[0], prefixes)
+
+ @decorators.idempotent_id('d70c6c35-913b-4f24-909f-14cd0d29b2d2')
+ def test_admin_create_shared_subnetpool(self):
+ created_subnetpool = self._create_subnetpool(is_admin=True,
+ shared=True)
+ pool_id = created_subnetpool['id']
+ # Shared subnetpool can be retrieved by tenant user.
+ body = self.client.show_subnetpool(pool_id)
+ subnetpool = body['subnetpool']
+ self.assertEqual(created_subnetpool['name'], subnetpool['name'])
+ self.assertTrue(subnetpool['shared'])
+
+ def _create_subnet_from_pool(self, subnet_values=None, pool_values=None):
+ if pool_values is None:
+ pool_values = {}
+
+ created_subnetpool = self._create_subnetpool(**pool_values)
+ pool_id = created_subnetpool['id']
+ subnet_name = data_utils.rand_name(SUBNETPOOL_NAME)
+ network = self.create_network()
+ subnet_kwargs = {'name': subnet_name,
+ 'subnetpool_id': pool_id}
+ if subnet_values:
+ subnet_kwargs.update(subnet_values)
+ # not creating the subnet using the base.create_subnet because
+ # that function needs to be enhanced to support subnet_create when
+ # prefixlen and subnetpool_id is specified.
+ body = self.client.create_subnet(
+ network_id=network['id'],
+ ip_version=self._ip_version,
+ **subnet_kwargs)
+ subnet = body['subnet']
+ return pool_id, subnet
+
+ @decorators.idempotent_id('1362ed7d-3089-42eb-b3a5-d6cb8398ee77')
+ def test_create_subnet_from_pool_with_prefixlen(self):
+ subnet_values = {"prefixlen": self.max_prefixlen}
+ pool_id, subnet = self._create_subnet_from_pool(
+ subnet_values=subnet_values)
+ cidr = str(subnet['cidr'])
+ self.assertEqual(pool_id, subnet['subnetpool_id'])
+ self.assertTrue(cidr.endswith(str(self.max_prefixlen)))
+
+ @decorators.idempotent_id('86b86189-9789-4582-9c3b-7e2bfe5735ee')
+ def test_create_subnet_from_pool_with_subnet_cidr(self):
+ subnet_values = {"cidr": self.subnet_cidr}
+ pool_id, subnet = self._create_subnet_from_pool(
+ subnet_values=subnet_values)
+ cidr = str(subnet['cidr'])
+ self.assertEqual(pool_id, subnet['subnetpool_id'])
+ self.assertEqual(cidr, self.subnet_cidr)
+
+ @decorators.idempotent_id('83f76e3a-9c40-40c2-a015-b7c5242178d8')
+ def test_create_subnet_from_pool_with_default_prefixlen(self):
+ # If neither cidr nor prefixlen is specified,
+ # subnet will use subnetpool default_prefixlen for cidr.
+ pool_id, subnet = self._create_subnet_from_pool()
+ cidr = str(subnet['cidr'])
+ self.assertEqual(pool_id, subnet['subnetpool_id'])
+ prefixlen = self._subnetpool_data['min_prefixlen']
+ self.assertTrue(cidr.endswith(str(prefixlen)))
+
+ @decorators.idempotent_id('a64af292-ec52-4bde-b654-a6984acaf477')
+ def test_create_subnet_from_pool_with_quota(self):
+ pool_values = {'default_quota': 4}
+ subnet_values = {"prefixlen": self.max_prefixlen}
+ pool_id, subnet = self._create_subnet_from_pool(
+ subnet_values=subnet_values, pool_values=pool_values)
+ cidr = str(subnet['cidr'])
+ self.assertEqual(pool_id, subnet['subnetpool_id'])
+ self.assertTrue(cidr.endswith(str(self.max_prefixlen)))
+
+ @decorators.idempotent_id('49b44c64-1619-4b29-b527-ffc3c3115dc4')
+ @test.requires_ext(extension='address-scope', service='network')
+ def test_create_subnetpool_associate_address_scope(self):
+ address_scope = self.create_address_scope(
+ name=data_utils.rand_name('smoke-address-scope'),
+ ip_version=self._ip_version)
+ created_subnetpool = self._create_subnetpool(
+ address_scope_id=address_scope['id'])
+ body = self.client.show_subnetpool(created_subnetpool['id'])
+ self.assertEqual(address_scope['id'],
+ body['subnetpool']['address_scope_id'])
+
+ @decorators.idempotent_id('910b6393-db24-4f6f-87dc-b36892ad6c8c')
+ @test.requires_ext(extension='address-scope', service='network')
+ def test_update_subnetpool_associate_address_scope(self):
+ address_scope = self.create_address_scope(
+ name=data_utils.rand_name('smoke-address-scope'),
+ ip_version=self._ip_version)
+ created_subnetpool = self._create_subnetpool()
+ pool_id = created_subnetpool['id']
+ body = self.client.show_subnetpool(pool_id)
+ self.assertIsNone(body['subnetpool']['address_scope_id'])
+ self.client.update_subnetpool(pool_id,
+ address_scope_id=address_scope['id'])
+ body = self.client.show_subnetpool(pool_id)
+ self.assertEqual(address_scope['id'],
+ body['subnetpool']['address_scope_id'])
+
+ @decorators.idempotent_id('18302e80-46a3-4563-82ac-ccd1dd57f652')
+ @test.requires_ext(extension='address-scope', service='network')
+ def test_update_subnetpool_associate_another_address_scope(self):
+ address_scope = self.create_address_scope(
+ name=data_utils.rand_name('smoke-address-scope'),
+ ip_version=self._ip_version)
+ another_address_scope = self.create_address_scope(
+ name=data_utils.rand_name('smoke-address-scope'),
+ ip_version=self._ip_version)
+ created_subnetpool = self._create_subnetpool(
+ address_scope_id=address_scope['id'])
+ pool_id = created_subnetpool['id']
+ body = self.client.show_subnetpool(pool_id)
+ self.assertEqual(address_scope['id'],
+ body['subnetpool']['address_scope_id'])
+ self.client.update_subnetpool(
+ pool_id, address_scope_id=another_address_scope['id'])
+ body = self.client.show_subnetpool(pool_id)
+ self.assertEqual(another_address_scope['id'],
+ body['subnetpool']['address_scope_id'])
+
+ @decorators.idempotent_id('f8970048-e41b-42d6-934b-a1297b07706a')
+ @test.requires_ext(extension='address-scope', service='network')
+ def test_update_subnetpool_disassociate_address_scope(self):
+ address_scope = self.create_address_scope(
+ name=data_utils.rand_name('smoke-address-scope'),
+ ip_version=self._ip_version)
+ created_subnetpool = self._create_subnetpool(
+ address_scope_id=address_scope['id'])
+ pool_id = created_subnetpool['id']
+ body = self.client.show_subnetpool(pool_id)
+ self.assertEqual(address_scope['id'],
+ body['subnetpool']['address_scope_id'])
+ self.client.update_subnetpool(pool_id,
+ address_scope_id=None)
+ body = self.client.show_subnetpool(pool_id)
+ self.assertIsNone(body['subnetpool']['address_scope_id'])
+
+ @decorators.idempotent_id('4c6963c2-f54c-4347-b288-75d18421c4c4')
+ @test.requires_ext(extension='default-subnetpools', service='network')
+ def test_tenant_create_non_default_subnetpool(self):
+ """
+ Test creates a subnetpool, the "is_default" attribute is False.
+ """
+ created_subnetpool = self._create_subnetpool()
+ self.assertFalse(created_subnetpool['is_default'])
+
+
+class DefaultSubnetPoolsTest(SubnetPoolsTestBase):
+
+ def setUp(self):
+ self.addCleanup(self.resource_cleanup)
+ super(DefaultSubnetPoolsTest, self).setUp()
+
+ @classmethod
+ def resource_setup(cls):
+ super(DefaultSubnetPoolsTest, cls).resource_setup()
+ body = cls.admin_client.list_subnetpools()
+ subnetpools = body['subnetpools']
+ for subnetpool in subnetpools:
+ if subnetpool.get('is_default'):
+ msg = 'Default subnetpool already exists. Only one is allowed.'
+ raise cls.skipException(msg)
+
+ @decorators.idempotent_id('cb839106-6184-4332-b292-5d07c074de4f')
+ @test.requires_ext(extension='default-subnetpools', service='network')
+ def test_admin_create_default_subnetpool(self):
+ """
+ Test uses administrative credentials to create a default subnetpool,
+ using the is_default=True.
+ """
+ created_subnetpool = self._create_subnetpool(is_admin=True,
+ is_default=True)
+ self.assertTrue(created_subnetpool['is_default'])
+
+ @decorators.idempotent_id('9e79730c-29b6-44a4-9504-bf3c7cedc56c')
+ @test.requires_ext(extension='default-subnetpools', service='network')
+ def test_convert_subnetpool_to_default_subnetpool(self):
+ """
+ Test creates a subnetpool, which is non default subnetpool.
+ Then it will update to a default subnetpool, by setting "is_default"
+ attribute to True.
+ """
+ created_subnetpool = self._create_subnetpool()
+ subnetpool_id = created_subnetpool['id']
+ self.assertFalse(created_subnetpool['is_default'])
+ subnetpool_data = {'is_default': True}
+ self.admin_client.update_subnetpool(subnetpool_id,
+ **subnetpool_data)
+ show_body = self.client.show_subnetpool(subnetpool_id)
+ self.assertTrue(show_body['subnetpool']['is_default'])
+
+ @decorators.idempotent_id('39687561-7a37-47b8-91ce-f9143ae26969')
+ @test.requires_ext(extension='default-subnetpools', service='network')
+ def test_convert_default_subnetpool_to_non_default(self):
+ """
+ Test uses administrative credentials to create a default subnetpool,
+ using the is_default=True.
+ Then it will update "is_default" attribute to False.
+ """
+ created_subnetpool = self._create_subnetpool(is_admin=True,
+ is_default=True)
+ subnetpool_id = created_subnetpool['id']
+ self.assertTrue(created_subnetpool['is_default'])
+ subnetpool_data = {'is_default': False}
+ self.admin_client.update_subnetpool(subnetpool_id,
+ **subnetpool_data)
+ show_body = self.admin_client.show_subnetpool(subnetpool_id)
+ self.assertFalse(show_body['subnetpool']['is_default'])
+
+
+class SubnetPoolsTestV6(SubnetPoolsTest):
+
+ min_prefixlen = '48'
+ max_prefixlen = '64'
+ _ip_version = 6
+ subnet_cidr = '2001:db8:3::/64'
+ new_prefix = u'2001:db8:5::/64'
+ larger_prefix = u'2001:db8::/32'
+
+ @classmethod
+ def resource_setup(cls):
+ super(SubnetPoolsTestV6, cls).resource_setup()
+ min_prefixlen = '64'
+ prefixes = [u'2001:db8:3::/48']
+ cls._subnetpool_data = {'min_prefixlen': min_prefixlen,
+ 'prefixes': prefixes}
+
+ @decorators.idempotent_id('f62d73dc-cf6f-4879-b94b-dab53982bf3b')
+ def test_create_dual_stack_subnets_from_subnetpools(self):
+ pool_id_v6, subnet_v6 = self._create_subnet_from_pool()
+ pool_values_v4 = {'prefixes': ['192.168.0.0/16'],
+ 'min_prefixlen': 21,
+ 'max_prefixlen': 32}
+ create_v4_subnetpool = self._create_subnetpool(**pool_values_v4)
+ pool_id_v4 = create_v4_subnetpool['id']
+ subnet_v4 = self.client.create_subnet(
+ network_id=subnet_v6['network_id'], ip_version=4,
+ subnetpool_id=pool_id_v4)['subnet']
+ self.assertEqual(subnet_v4['network_id'], subnet_v6['network_id'])
+
+
+class SubnetPoolsSearchCriteriaTest(base.BaseSearchCriteriaTest,
+ SubnetPoolsTestBase):
+
+ resource = 'subnetpool'
+
+ list_kwargs = {'shared': False}
+
+ @classmethod
+ def resource_setup(cls):
+ super(SubnetPoolsSearchCriteriaTest, cls).resource_setup()
+ for name in cls.resource_names:
+ cls._create_subnetpool(name=name)
+
+ @decorators.idempotent_id('6e3f842e-6bfb-49cb-82d3-0026be4e8e04')
+ def test_list_sorts_asc(self):
+ self._test_list_sorts_asc()
+
+ @decorators.idempotent_id('f336859b-b868-438c-a6fc-2c06374115f2')
+ def test_list_sorts_desc(self):
+ self._test_list_sorts_desc()
+
+ @decorators.idempotent_id('1291fae7-c196-4372-ad59-ce7988518f7b')
+ def test_list_pagination(self):
+ self._test_list_pagination()
+
+ @decorators.idempotent_id('ddb20d14-1952-49b4-a17e-231cc2239a52')
+ def test_list_pagination_with_marker(self):
+ self._test_list_pagination_with_marker()
+
+ @decorators.idempotent_id('b3bd9665-2769-4a43-b50c-31b1add12891')
+ def test_list_pagination_with_href_links(self):
+ self._test_list_pagination_with_href_links()
+
+ @decorators.idempotent_id('1ec1f325-43b0-406e-96ce-20539e38a61d')
+ def test_list_pagination_page_reverse_asc(self):
+ self._test_list_pagination_page_reverse_asc()
+
+ @decorators.idempotent_id('f43a293e-4aaa-48f4-aeaf-de63a676357c')
+ def test_list_pagination_page_reverse_desc(self):
+ self._test_list_pagination_page_reverse_desc()
+
+ @decorators.idempotent_id('73511385-839c-4829-8ac1-b5ad992126c4')
+ def test_list_pagination_page_reverse_with_href_links(self):
+ self._test_list_pagination_page_reverse_with_href_links()
+
+ @decorators.idempotent_id('82a13efc-c18f-4249-b8ec-cec7cf26fbd6')
+ def test_list_no_pagination_limit_0(self):
+ self._test_list_no_pagination_limit_0()
+
+ @decorators.idempotent_id('27feb3f8-40f4-4e50-8cd2-7d0096a98682')
+ def test_list_validation_filters(self):
+ self._test_list_validation_filters()
diff --git a/neutron_tempest_plugin/api/test_subnetpools_negative.py b/neutron_tempest_plugin/api/test_subnetpools_negative.py
new file mode 100644
index 0000000..950b57e
--- /dev/null
+++ b/neutron_tempest_plugin/api/test_subnetpools_negative.py
@@ -0,0 +1,288 @@
+# Copyright 2015 Hewlett-Packard Development Company, L.P.
+# All Rights Reserved.
+#
+# 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 netaddr
+from oslo_utils import uuidutils
+from tempest.lib.common.utils import data_utils
+from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
+from tempest import test
+
+from neutron_tempest_plugin.api import test_subnetpools
+
+
+SUBNETPOOL_NAME = 'smoke-subnetpool'
+
+
+class SubnetPoolsNegativeTestJSON(test_subnetpools.SubnetPoolsTestBase):
+
+ smaller_prefix = u'10.11.12.0/26'
+
+ @decorators.attr(type='negative')
+ @decorators.idempotent_id('0212a042-603a-4f46-99e0-e37de9374d30')
+ def test_get_non_existent_subnetpool(self):
+ non_exist_id = data_utils.rand_name('subnetpool')
+ self.assertRaises(lib_exc.NotFound, self.client.show_subnetpool,
+ non_exist_id)
+
+ @decorators.attr(type='negative')
+ @decorators.idempotent_id('dc9336e5-f28f-4658-a0b0-cc79e607007d')
+ def test_tenant_get_not_shared_admin_subnetpool(self):
+ created_subnetpool = self._create_subnetpool(is_admin=True)
+ # None-shared admin subnetpool cannot be retrieved by tenant user.
+ self.assertRaises(lib_exc.NotFound, self.client.show_subnetpool,
+ created_subnetpool['id'])
+
+ @decorators.attr(type='negative')
+ @decorators.idempotent_id('5e1f2f86-d81a-498c-82ed-32a49f4dc4d3')
+ def test_delete_non_existent_subnetpool(self):
+ non_exist_id = data_utils.rand_name('subnetpool')
+ self.assertRaises(lib_exc.NotFound, self.client.delete_subnetpool,
+ non_exist_id)
+
+ @decorators.attr(type='negative')
+ @decorators.idempotent_id('d1143fe2-212b-4e23-a308-d18f7d8d78d6')
+ def test_tenant_create_shared_subnetpool(self):
+ # 'shared' subnetpool can only be created by admin.
+ self.assertRaises(lib_exc.Forbidden, self._create_subnetpool,
+ is_admin=False, shared=True)
+
+ @decorators.attr(type='negative')
+ @decorators.idempotent_id('6ae09d8f-95be-40ed-b1cf-8b850d45bab5')
+ @test.requires_ext(extension='default-subnetpools', service='network')
+ def test_tenant_create_default_subnetpool(self):
+ # 'default' subnetpool can only be created by admin.
+ self.assertRaises(lib_exc.Forbidden, self._create_subnetpool,
+ is_admin=False, is_default=True)
+
+ @decorators.attr(type='negative')
+ @decorators.idempotent_id('4be84d30-60ca-4bd3-8512-db5b36ce1378')
+ def test_update_non_existent_subnetpool(self):
+ non_exist_id = data_utils.rand_name('subnetpool')
+ self.assertRaises(lib_exc.NotFound, self.client.update_subnetpool,
+ non_exist_id, name='foo-name')
+
+ @decorators.attr(type='negative')
+ @decorators.idempotent_id('e6cd6d87-6173-45dd-bf04-c18ea7ec7537')
+ def test_update_subnetpool_not_modifiable_shared(self):
+ # 'shared' attributes can be specified during creation.
+ # But this attribute is not modifiable after creation.
+ created_subnetpool = self._create_subnetpool(is_admin=True)
+ pool_id = created_subnetpool['id']
+ self.assertRaises(lib_exc.BadRequest, self.client.update_subnetpool,
+ pool_id, shared=True)
+
+ @decorators.attr(type='negative')
+ @decorators.idempotent_id('62f7c43b-bff1-4def-8bb7-4754b840aaad')
+ def test_update_subnetpool_prefixes_shrink(self):
+ # Shrink current subnetpool prefixes is not supported
+ created_subnetpool = self._create_subnetpool()
+ self.assertRaises(lib_exc.BadRequest,
+ self.client.update_subnetpool,
+ created_subnetpool['id'],
+ prefixes=[self.smaller_prefix])
+
+ @decorators.attr(type='negative')
+ @decorators.idempotent_id('fc011824-153e-4469-97ad-9808eb88cae1')
+ def test_create_subnet_different_pools_same_network(self):
+ network = self.create_network(network_name='smoke-network')
+ created_subnetpool = self._create_subnetpool(
+ is_admin=True, prefixes=['192.168.0.0/16'])
+ subnet = self.create_subnet(
+ network, cidr=netaddr.IPNetwork('10.10.10.0/24'), ip_version=4,
+ gateway=None, client=self.admin_client)
+ # add the subnet created by admin to the cleanUp because
+ # the base.py doesn't delete it using the admin client
+ self.addCleanup(self.admin_client.delete_subnet, subnet['id'])
+ self.assertRaises(lib_exc.BadRequest, self.create_subnet, network,
+ ip_version=4,
+ subnetpool_id=created_subnetpool['id'],
+ client=self.admin_client)
+
+ @decorators.attr(type='negative')
+ @decorators.idempotent_id('9589e332-638e-476e-81bd-013d964aa3cb')
+ @test.requires_ext(extension='address-scope', service='network')
+ def test_create_subnetpool_associate_invalid_address_scope(self):
+ self.assertRaises(lib_exc.BadRequest, self._create_subnetpool,
+ address_scope_id='foo-addr-scope')
+
+ @decorators.attr(type='negative')
+ @decorators.idempotent_id('3b6c5942-485d-4964-a560-55608af020b5')
+ @test.requires_ext(extension='address-scope', service='network')
+ def test_create_subnetpool_associate_non_exist_address_scope(self):
+ self.assertRaises(lib_exc.NotFound, self._create_subnetpool,
+ address_scope_id=uuidutils.generate_uuid())
+
+ @decorators.attr(type='negative')
+ @decorators.idempotent_id('2dfb4269-8657-485a-a053-b022e911456e')
+ @test.requires_ext(extension='address-scope', service='network')
+ def test_create_subnetpool_associate_address_scope_prefix_intersect(self):
+ address_scope = self.create_address_scope(
+ name=data_utils.rand_name('smoke-address-scope'),
+ ip_version=4)
+ addr_scope_id = address_scope['id']
+ self._create_subnetpool(address_scope_id=addr_scope_id)
+ subnetpool_data = {'name': 'foo-subnetpool',
+ 'prefixes': [u'10.11.12.13/24'],
+ 'min_prefixlen': '29',
+ 'address_scope_id': addr_scope_id}
+ self.assertRaises(lib_exc.Conflict, self._create_subnetpool,
+ **subnetpool_data)
+
+ @decorators.attr(type='negative')
+ @decorators.idempotent_id('83a19a13-5384-42e2-b579-43fc69c80914')
+ @test.requires_ext(extension='address-scope', service='network')
+ def test_create_sp_associate_address_scope_multiple_prefix_intersect(self):
+ address_scope = self.create_address_scope(
+ name=data_utils.rand_name('smoke-address-scope'),
+ ip_version=4)
+ addr_scope_id = address_scope['id']
+ self._create_subnetpool(prefixes=[u'20.0.0.0/18', u'30.0.0.0/18'],
+ address_scope_id=addr_scope_id)
+ prefixes = [u'40.0.0.0/18', u'50.0.0.0/18', u'30.0.0.0/12']
+ subnetpool_data = {'name': 'foo-subnetpool',
+ 'prefixes': prefixes,
+ 'min_prefixlen': '29',
+ 'address_scope_id': addr_scope_id}
+ self.assertRaises(lib_exc.Conflict, self._create_subnetpool,
+ **subnetpool_data)
+
+ @decorators.attr(type='negative')
+ @decorators.idempotent_id('f06d8e7b-908b-4e94-b570-8156be6a4bf1')
+ @test.requires_ext(extension='address-scope', service='network')
+ def test_create_subnetpool_associate_address_scope_of_other_owner(self):
+ address_scope = self.create_address_scope(
+ name=data_utils.rand_name('smoke-address-scope'), is_admin=True,
+ ip_version=4)
+ self.assertRaises(lib_exc.NotFound, self._create_subnetpool,
+ address_scope_id=address_scope['id'])
+
+ @decorators.attr(type='negative')
+ @decorators.idempotent_id('3396ec6c-cb80-4ebe-b897-84e904580bdf')
+ @test.requires_ext(extension='address-scope', service='network')
+ def test_tenant_create_subnetpool_associate_shared_address_scope(self):
+ address_scope = self.create_address_scope(
+ name=data_utils.rand_name('smoke-address-scope'), is_admin=True,
+ shared=True, ip_version=4)
+ self.assertRaises(lib_exc.BadRequest, self._create_subnetpool,
+ address_scope_id=address_scope['id'])
+
+ @decorators.attr(type='negative')
+ @decorators.idempotent_id('6d3d9ad5-32d4-4d63-aa00-8c62f73e2881')
+ @test.requires_ext(extension='address-scope', service='network')
+ def test_update_subnetpool_associate_address_scope_of_other_owner(self):
+ address_scope = self.create_address_scope(
+ name=data_utils.rand_name('smoke-address-scope'), is_admin=True,
+ ip_version=4)
+ address_scope_id = address_scope['id']
+ created_subnetpool = self._create_subnetpool(self.client)
+ self.assertRaises(lib_exc.NotFound, self.client.update_subnetpool,
+ created_subnetpool['id'],
+ address_scope_id=address_scope_id)
+
+ def _test_update_subnetpool_prefix_intersect_helper(
+ self, pool_1_prefixes, pool_2_prefixes, pool_1_updated_prefixes):
+ # create two subnet pools associating to an address scope.
+ # Updating the first subnet pool with the prefix intersecting
+ # with the second one should be a failure
+ address_scope = self.create_address_scope(
+ name=data_utils.rand_name('smoke-address-scope'), ip_version=4)
+ addr_scope_id = address_scope['id']
+ pool_values = {'address_scope_id': addr_scope_id,
+ 'prefixes': pool_1_prefixes}
+ created_subnetpool_1 = self._create_subnetpool(**pool_values)
+ pool_id_1 = created_subnetpool_1['id']
+ pool_values = {'address_scope_id': addr_scope_id,
+ 'prefixes': pool_2_prefixes}
+ self._create_subnetpool(**pool_values)
+ # now update the pool_id_1 with the prefix intersecting with
+ # pool_id_2
+ self.assertRaises(lib_exc.Conflict, self.client.update_subnetpool,
+ pool_id_1, prefixes=pool_1_updated_prefixes)
+
+ @decorators.attr(type='negative')
+ @decorators.idempotent_id('96006292-7214-40e0-a471-153fb76e6b31')
+ @test.requires_ext(extension='address-scope', service='network')
+ def test_update_subnetpool_prefix_intersect(self):
+ pool_1_prefix = [u'20.0.0.0/18']
+ pool_2_prefix = [u'20.10.0.0/24']
+ pool_1_updated_prefix = [u'20.0.0.0/12']
+ self._test_update_subnetpool_prefix_intersect_helper(
+ pool_1_prefix, pool_2_prefix, pool_1_updated_prefix)
+
+ @decorators.attr(type='negative')
+ @decorators.idempotent_id('4d3f8a79-c530-4e59-9acf-6c05968adbfe')
+ @test.requires_ext(extension='address-scope', service='network')
+ def test_update_subnetpool_multiple_prefix_intersect(self):
+ pool_1_prefixes = [u'20.0.0.0/18', u'30.0.0.0/18']
+ pool_2_prefixes = [u'20.10.0.0/24', u'40.0.0.0/18', '50.0.0.0/18']
+ pool_1_updated_prefixes = [u'20.0.0.0/18', u'30.0.0.0/18',
+ u'50.0.0.0/12']
+ self._test_update_subnetpool_prefix_intersect_helper(
+ pool_1_prefixes, pool_2_prefixes, pool_1_updated_prefixes)
+
+ @decorators.attr(type='negative')
+ @decorators.idempotent_id('7438e49e-1351-45d8-937b-892059fb97f5')
+ @test.requires_ext(extension='address-scope', service='network')
+ def test_tenant_update_sp_prefix_associated_with_shared_addr_scope(self):
+ address_scope = self.create_address_scope(
+ name=data_utils.rand_name('smoke-address-scope'), is_admin=True,
+ shared=True, ip_version=4)
+ addr_scope_id = address_scope['id']
+ pool_values = {'prefixes': [u'20.0.0.0/18', u'30.0.0.0/18']}
+
+ created_subnetpool = self._create_subnetpool(**pool_values)
+ pool_id = created_subnetpool['id']
+ # associate the subnetpool to the address scope as an admin
+ self.admin_client.update_subnetpool(pool_id,
+ address_scope_id=addr_scope_id)
+ body = self.admin_client.show_subnetpool(pool_id)
+ self.assertEqual(addr_scope_id,
+ body['subnetpool']['address_scope_id'])
+
+ # updating the subnetpool prefix by the tenant user should fail
+ # since the tenant is not the owner of address scope
+ update_prefixes = [u'20.0.0.0/18', u'30.0.0.0/18', u'40.0.0.0/18']
+ self.assertRaises(lib_exc.BadRequest, self.client.update_subnetpool,
+ pool_id, prefixes=update_prefixes)
+
+ # admin can update the prefixes
+ self.admin_client.update_subnetpool(pool_id, prefixes=update_prefixes)
+ body = self.admin_client.show_subnetpool(pool_id)
+ self.assertEqual(update_prefixes,
+ body['subnetpool']['prefixes'])
+
+ @decorators.attr(type='negative')
+ @decorators.idempotent_id('648fee7d-a909-4ced-bad3-3a169444c0a8')
+ @test.requires_ext(extension='address-scope', service='network')
+ def test_update_subnetpool_associate_address_scope_wrong_ip_version(self):
+ address_scope = self.create_address_scope(
+ name=data_utils.rand_name('smoke-address-scope'),
+ ip_version=6)
+ created_subnetpool = self._create_subnetpool()
+ self.assertRaises(lib_exc.BadRequest, self.client.update_subnetpool,
+ created_subnetpool['id'],
+ address_scope_id=address_scope['id'])
+
+ @decorators.attr(type='negative')
+ @decorators.idempotent_id('2f66dc2f-cc32-4caa-91ec-0c0cd7c46d70')
+ def test_update_subnetpool_tenant_id(self):
+ subnetpool = self._create_subnetpool()
+ self.assertRaises(
+ lib_exc.BadRequest,
+ self.admin_client.update_subnetpool,
+ subnetpool['id'],
+ tenant_id=self.admin_client.tenant_id,
+ )
diff --git a/neutron_tempest_plugin/api/test_subnets.py b/neutron_tempest_plugin/api/test_subnets.py
new file mode 100644
index 0000000..fb2f4d6
--- /dev/null
+++ b/neutron_tempest_plugin/api/test_subnets.py
@@ -0,0 +1,69 @@
+# 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 decorators
+
+from neutron_tempest_plugin.api import base
+
+
+class SubnetsSearchCriteriaTest(base.BaseSearchCriteriaTest):
+
+ resource = 'subnet'
+
+ list_kwargs = {'shared': False}
+
+ @classmethod
+ def resource_setup(cls):
+ super(SubnetsSearchCriteriaTest, cls).resource_setup()
+ net = cls.create_network(network_name='subnet-search-test-net')
+ for name in cls.resource_names:
+ cls.create_subnet(net, name=name)
+
+ @decorators.idempotent_id('d2d61995-5dd5-4b93-bce7-3edefdb79563')
+ def test_list_sorts_asc(self):
+ self._test_list_sorts_asc()
+
+ @decorators.idempotent_id('c3c6b0af-c4ac-4da0-b568-8d08ae550604')
+ def test_list_sorts_desc(self):
+ self._test_list_sorts_desc()
+
+ @decorators.idempotent_id('b93063b3-f713-406e-bf93-e5738e09153c')
+ def test_list_pagination(self):
+ self._test_list_pagination()
+
+ @decorators.idempotent_id('2ddd9aa6-de28-410f-9cbc-ce752893c407')
+ def test_list_pagination_with_marker(self):
+ self._test_list_pagination_with_marker()
+
+ @decorators.idempotent_id('351183ef-6ed9-4d71-a9f2-a5ac049bd7ea')
+ def test_list_pagination_with_href_links(self):
+ self._test_list_pagination_with_href_links()
+
+ @decorators.idempotent_id('dfaa20ca-6d84-4f26-962f-2fee4d247cd9')
+ def test_list_pagination_page_reverse_asc(self):
+ self._test_list_pagination_page_reverse_asc()
+
+ @decorators.idempotent_id('40552213-3e12-4d6a-86f3-dda92f3de88c')
+ def test_list_pagination_page_reverse_desc(self):
+ self._test_list_pagination_page_reverse_desc()
+
+ @decorators.idempotent_id('3cea9053-a731-4480-93ee-19b2c28a9ce4')
+ def test_list_pagination_page_reverse_with_href_links(self):
+ self._test_list_pagination_page_reverse_with_href_links()
+
+ @decorators.idempotent_id('d851937c-9821-4b46-9d18-43e9077ecac0')
+ def test_list_no_pagination_limit_0(self):
+ self._test_list_no_pagination_limit_0()
+
+ @decorators.idempotent_id('c0f9280b-9d81-4728-a967-6be22659d4c8')
+ def test_list_validation_filters(self):
+ self._test_list_validation_filters()
diff --git a/neutron_tempest_plugin/api/test_tag.py b/neutron_tempest_plugin/api/test_tag.py
new file mode 100644
index 0000000..c56d611
--- /dev/null
+++ b/neutron_tempest_plugin/api/test_tag.py
@@ -0,0 +1,351 @@
+# 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 decorators
+from tempest.lib import exceptions as lib_exc
+from tempest import test
+
+from neutron_tempest_plugin.api import base
+
+
+class TagTestJSON(base.BaseAdminNetworkTest):
+
+ required_extensions = ['tag']
+
+ @classmethod
+ def resource_setup(cls):
+ super(TagTestJSON, cls).resource_setup()
+ cls.res_id = cls._create_resource()
+
+ def _get_and_compare_tags(self, tags):
+ res_body = self.client.get_tags(self.resource, self.res_id)
+ self.assertItemsEqual(tags, res_body['tags'])
+
+ def _test_tag_operations(self):
+ # create and get tags
+ tags = ['red', 'blue']
+ res_body = self.client.update_tags(self.resource, self.res_id, tags)
+ self.assertItemsEqual(tags, res_body['tags'])
+ self._get_and_compare_tags(tags)
+
+ # add a tag
+ self.client.update_tag(self.resource, self.res_id, 'green')
+ self._get_and_compare_tags(['red', 'blue', 'green'])
+
+ # update tag exist
+ self.client.update_tag(self.resource, self.res_id, 'red')
+ self._get_and_compare_tags(['red', 'blue', 'green'])
+
+ # add a tag with a dot
+ self.client.update_tag(self.resource, self.res_id, 'black.or.white')
+ self._get_and_compare_tags(['red', 'blue', 'green', 'black.or.white'])
+
+ # replace tags
+ tags = ['red', 'yellow', 'purple']
+ res_body = self.client.update_tags(self.resource, self.res_id, tags)
+ self.assertItemsEqual(tags, res_body['tags'])
+ self._get_and_compare_tags(tags)
+
+ # get tag
+ self.client.get_tag(self.resource, self.res_id, 'red')
+
+ # get tag not exist
+ self.assertRaises(lib_exc.NotFound, self.client.get_tag,
+ self.resource, self.res_id, 'green')
+
+ # delete tag
+ self.client.delete_tag(self.resource, self.res_id, 'red')
+ self._get_and_compare_tags(['yellow', 'purple'])
+
+ # delete tag not exist
+ self.assertRaises(lib_exc.NotFound, self.client.delete_tag,
+ self.resource, self.res_id, 'green')
+
+ # delete tags
+ self.client.delete_tags(self.resource, self.res_id)
+ self._get_and_compare_tags([])
+
+
+class TagNetworkTestJSON(TagTestJSON):
+ resource = 'networks'
+
+ @classmethod
+ def _create_resource(cls):
+ network = cls.create_network()
+ return network['id']
+
+ @decorators.attr(type='smoke')
+ @decorators.idempotent_id('5621062d-fbfb-4437-9d69-138c78ea4188')
+ def test_network_tags(self):
+ self._test_tag_operations()
+
+
+class TagSubnetTestJSON(TagTestJSON):
+ resource = 'subnets'
+
+ @classmethod
+ def _create_resource(cls):
+ network = cls.create_network()
+ subnet = cls.create_subnet(network)
+ return subnet['id']
+
+ @decorators.attr(type='smoke')
+ @decorators.idempotent_id('2805aabf-a94c-4e70-a0b2-9814f06beb03')
+ @test.requires_ext(extension="tag-ext", service="network")
+ def test_subnet_tags(self):
+ self._test_tag_operations()
+
+
+class TagPortTestJSON(TagTestJSON):
+ resource = 'ports'
+
+ @classmethod
+ def _create_resource(cls):
+ network = cls.create_network()
+ port = cls.create_port(network)
+ return port['id']
+
+ @decorators.attr(type='smoke')
+ @decorators.idempotent_id('c7c44f2c-edb0-4ebd-a386-d37cec155c34')
+ @test.requires_ext(extension="tag-ext", service="network")
+ def test_port_tags(self):
+ self._test_tag_operations()
+
+
+class TagSubnetPoolTestJSON(TagTestJSON):
+ resource = 'subnetpools'
+
+ @classmethod
+ def _create_resource(cls):
+ subnetpool = cls.create_subnetpool('subnetpool', default_prefixlen=24,
+ prefixes=['10.0.0.0/8'])
+ return subnetpool['id']
+
+ @decorators.attr(type='smoke')
+ @decorators.idempotent_id('bdc1c24b-c0b5-4835-953c-8f67dc11edfe')
+ @test.requires_ext(extension="tag-ext", service="network")
+ def test_subnetpool_tags(self):
+ self._test_tag_operations()
+
+
+class TagRouterTestJSON(TagTestJSON):
+ resource = 'routers'
+
+ @classmethod
+ def _create_resource(cls):
+ router = cls.create_router(router_name='test')
+ return router['id']
+
+ @decorators.attr(type='smoke')
+ @decorators.idempotent_id('b898ff92-dc33-4232-8ab9-2c6158c80d28')
+ @test.requires_ext(extension="router", service="network")
+ @test.requires_ext(extension="tag-ext", service="network")
+ def test_router_tags(self):
+ self._test_tag_operations()
+
+
+class TagFilterTestJSON(base.BaseAdminNetworkTest):
+ credentials = ['primary', 'alt', 'admin']
+ required_extensions = ['tag']
+
+ @classmethod
+ def resource_setup(cls):
+ super(TagFilterTestJSON, cls).resource_setup()
+
+ res1_id = cls._create_resource('tag-res1')
+ res2_id = cls._create_resource('tag-res2')
+ res3_id = cls._create_resource('tag-res3')
+ res4_id = cls._create_resource('tag-res4')
+ # tag-res5: a resource without tags
+ cls._create_resource('tag-res5')
+
+ cls.client.update_tags(cls.resource, res1_id, ['red'])
+ cls.client.update_tags(cls.resource, res2_id, ['red', 'blue'])
+ cls.client.update_tags(cls.resource, res3_id,
+ ['red', 'blue', 'green'])
+ cls.client.update_tags(cls.resource, res4_id, ['green'])
+
+ @classmethod
+ def setup_clients(cls):
+ super(TagFilterTestJSON, cls).setup_clients()
+ cls.client = cls.os_alt.network_client
+
+ def _assertEqualResources(self, expected, res):
+ actual = [n['name'] for n in res if n['name'].startswith('tag-res')]
+ self.assertEqual(set(expected), set(actual))
+
+ def _test_filter_tags(self):
+ # tags single
+ filters = {'tags': 'red'}
+ res = self._list_resource(filters)
+ self._assertEqualResources(['tag-res1', 'tag-res2', 'tag-res3'], res)
+
+ # tags multi
+ filters = {'tags': 'red,blue'}
+ res = self._list_resource(filters)
+ self._assertEqualResources(['tag-res2', 'tag-res3'], res)
+
+ # tags-any single
+ filters = {'tags-any': 'blue'}
+ res = self._list_resource(filters)
+ self._assertEqualResources(['tag-res2', 'tag-res3'], res)
+
+ # tags-any multi
+ filters = {'tags-any': 'red,blue'}
+ res = self._list_resource(filters)
+ self._assertEqualResources(['tag-res1', 'tag-res2', 'tag-res3'], res)
+
+ # not-tags single
+ filters = {'not-tags': 'red'}
+ res = self._list_resource(filters)
+ self._assertEqualResources(['tag-res4', 'tag-res5'], res)
+
+ # not-tags multi
+ filters = {'not-tags': 'red,blue'}
+ res = self._list_resource(filters)
+ self._assertEqualResources(['tag-res1', 'tag-res4', 'tag-res5'], res)
+
+ # not-tags-any single
+ filters = {'not-tags-any': 'blue'}
+ res = self._list_resource(filters)
+ self._assertEqualResources(['tag-res1', 'tag-res4', 'tag-res5'], res)
+
+ # not-tags-any multi
+ filters = {'not-tags-any': 'red,blue'}
+ res = self._list_resource(filters)
+ self._assertEqualResources(['tag-res4', 'tag-res5'], res)
+
+
+class TagFilterNetworkTestJSON(TagFilterTestJSON):
+ resource = 'networks'
+
+ @classmethod
+ def _create_resource(cls, name):
+ res = cls.create_network(network_name=name)
+ return res['id']
+
+ def _list_resource(self, filters):
+ res = self.client.list_networks(**filters)
+ return res[self.resource]
+
+ @decorators.attr(type='smoke')
+ @decorators.idempotent_id('a66b5cca-7db2-40f5-a33d-8ac9f864e53e')
+ def test_filter_network_tags(self):
+ self._test_filter_tags()
+
+
+class TagFilterSubnetTestJSON(TagFilterTestJSON):
+ resource = 'subnets'
+
+ @classmethod
+ def _create_resource(cls, name):
+ network = cls.create_network()
+ res = cls.create_subnet(network, name=name)
+ return res['id']
+
+ def _list_resource(self, filters):
+ res = self.client.list_subnets(**filters)
+ return res[self.resource]
+
+ @decorators.attr(type='smoke')
+ @decorators.idempotent_id('dd8f9ba7-bcf6-496f-bead-714bd3daac10')
+ @test.requires_ext(extension="tag-ext", service="network")
+ def test_filter_subnet_tags(self):
+ self._test_filter_tags()
+
+
+class TagFilterPortTestJSON(TagFilterTestJSON):
+ resource = 'ports'
+
+ @classmethod
+ def _create_resource(cls, name):
+ network = cls.create_network()
+ res = cls.create_port(network, name=name)
+ return res['id']
+
+ def _list_resource(self, filters):
+ res = self.client.list_ports(**filters)
+ return res[self.resource]
+
+ @decorators.attr(type='smoke')
+ @decorators.idempotent_id('09c036b8-c8d0-4bee-b776-7f4601512898')
+ @test.requires_ext(extension="tag-ext", service="network")
+ def test_filter_port_tags(self):
+ self._test_filter_tags()
+
+
+class TagFilterSubnetpoolTestJSON(TagFilterTestJSON):
+ resource = 'subnetpools'
+
+ @classmethod
+ def _create_resource(cls, name):
+ res = cls.create_subnetpool(name, default_prefixlen=24,
+ prefixes=['10.0.0.0/8'])
+ return res['id']
+
+ def _list_resource(self, filters):
+ res = self.client.list_subnetpools(**filters)
+ return res[self.resource]
+
+ @decorators.attr(type='smoke')
+ @decorators.idempotent_id('16ae7ad2-55c2-4821-9195-bfd04ab245b7')
+ @test.requires_ext(extension="tag-ext", service="network")
+ def test_filter_subnetpool_tags(self):
+ self._test_filter_tags()
+
+
+class TagFilterRouterTestJSON(TagFilterTestJSON):
+ resource = 'routers'
+
+ @classmethod
+ def _create_resource(cls, name):
+ res = cls.create_router(router_name=name)
+ return res['id']
+
+ def _list_resource(self, filters):
+ res = self.client.list_routers(**filters)
+ return res[self.resource]
+
+ @decorators.attr(type='smoke')
+ @decorators.idempotent_id('cdd3f3ea-073d-4435-a6cb-826a4064193d')
+ @test.requires_ext(extension="tag-ext", service="network")
+ def test_filter_router_tags(self):
+ self._test_filter_tags()
+
+
+class UpdateTagsTest(base.BaseAdminNetworkTest):
+
+ required_extensions = ['tag']
+
+ def _get_and_compare_tags(self, tags, res_id):
+ # nothing specific about networks here, just a resource that is
+ # available in all setups
+ res_body = self.client.get_tags('networks', res_id)
+ self.assertItemsEqual(tags, res_body['tags'])
+
+ @decorators.attr(type='smoke')
+ @decorators.idempotent_id('74c56fb1-a3b1-4a62-a8d2-d04dca6bd4cd')
+ def test_update_tags_affects_only_updated_resource(self):
+ res1 = self.create_network()
+ res2 = self.create_network()
+
+ self.client.update_tags('networks', res1['id'], ['red', 'blue'])
+ self._get_and_compare_tags(['red', 'blue'], res1['id'])
+
+ self.client.update_tags('networks', res2['id'], ['red'])
+ self._get_and_compare_tags(['red'], res2['id'])
+
+ self.client.update_tags('networks', res2['id'], [])
+ self._get_and_compare_tags([], res2['id'])
+
+ # check that updates on res2 hasn't dropped tags from res1
+ self._get_and_compare_tags(['red', 'blue'], res1['id'])
diff --git a/neutron_tempest_plugin/api/test_timestamp.py b/neutron_tempest_plugin/api/test_timestamp.py
new file mode 100644
index 0000000..20d5703
--- /dev/null
+++ b/neutron_tempest_plugin/api/test_timestamp.py
@@ -0,0 +1,326 @@
+# 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 copy
+
+from tempest.lib.common.utils import data_utils
+from tempest.lib import decorators
+
+from neutron_tempest_plugin.api import base
+from neutron_tempest_plugin.api import base_routers
+from neutron_tempest_plugin.api import base_security_groups
+from neutron_tempest_plugin import config
+
+CONF = config.CONF
+
+
+class TestTimeStamp(base.BaseAdminNetworkTest):
+
+ required_extensions = ["standard-attr-timestamp"]
+
+ ## attributes for subnetpool
+ min_prefixlen = '28'
+ max_prefixlen = '31'
+ _ip_version = 4
+ subnet_cidr = '10.11.12.0/31'
+ new_prefix = '10.11.15.0/24'
+ larger_prefix = '10.11.0.0/16'
+
+ @classmethod
+ def resource_setup(cls):
+ super(TestTimeStamp, cls).resource_setup()
+ prefixes = ['10.11.12.0/24']
+ cls._subnetpool_data = {'min_prefixlen': '29', 'prefixes': prefixes}
+
+ def _create_subnetpool(self, is_admin=False, **kwargs):
+ name = data_utils.rand_name('subnetpool-')
+ subnetpool_data = copy.deepcopy(self._subnetpool_data)
+ for key in subnetpool_data.keys():
+ kwargs[key] = subnetpool_data[key]
+ return self.create_subnetpool(name=name, is_admin=is_admin, **kwargs)
+
+ @decorators.idempotent_id('462be770-b310-4df9-9c42-773217e4c8b1')
+ def test_create_network_with_timestamp(self):
+ network = self.create_network()
+ # Verifies body contains timestamp fields
+ self.assertIsNotNone(network['created_at'])
+ self.assertIsNotNone(network['updated_at'])
+
+ @decorators.idempotent_id('4db5417a-e11c-474d-a361-af00ebef57c5')
+ def test_update_network_with_timestamp(self):
+ network = self.create_network()
+ origin_updated_at = network['updated_at']
+ update_body = {'name': network['name'] + 'new'}
+ body = self.admin_client.update_network(network['id'],
+ **update_body)
+ updated_network = body['network']
+ new_updated_at = updated_network['updated_at']
+ self.assertEqual(network['created_at'], updated_network['created_at'])
+ # Verify that origin_updated_at is not same with new_updated_at
+ self.assertIsNot(origin_updated_at, new_updated_at)
+
+ @decorators.idempotent_id('2ac50ab2-7ebd-4e27-b3ce-a9e399faaea2')
+ def test_show_networks_attribute_with_timestamp(self):
+ network = self.create_network()
+ body = self.client.show_network(network['id'])
+ show_net = body['network']
+ # verify the timestamp from creation and showed is same
+ self.assertEqual(network['created_at'],
+ show_net['created_at'])
+ self.assertEqual(network['updated_at'],
+ show_net['updated_at'])
+
+ @decorators.idempotent_id('8ee55186-454f-4b97-9f9f-eb2772ee891c')
+ def test_create_subnet_with_timestamp(self):
+ network = self.create_network()
+ subnet = self.create_subnet(network)
+ # Verifies body contains timestamp fields
+ self.assertIsNotNone(subnet['created_at'])
+ self.assertIsNotNone(subnet['updated_at'])
+
+ @decorators.idempotent_id('a490215a-6f4c-4af9-9a4c-57c41f1c4fa1')
+ def test_update_subnet_with_timestamp(self):
+ network = self.create_network()
+ subnet = self.create_subnet(network)
+ origin_updated_at = subnet['updated_at']
+ update_body = {'name': subnet['name'] + 'new'}
+ body = self.admin_client.update_subnet(subnet['id'],
+ **update_body)
+ updated_subnet = body['subnet']
+ new_updated_at = updated_subnet['updated_at']
+ self.assertEqual(subnet['created_at'], updated_subnet['created_at'])
+ # Verify that origin_updated_at is not same with new_updated_at
+ self.assertIsNot(origin_updated_at, new_updated_at)
+
+ @decorators.idempotent_id('1836a086-e7cf-4141-bf57-0cfe79e8051e')
+ def test_show_subnet_attribute_with_timestamp(self):
+ network = self.create_network()
+ subnet = self.create_subnet(network)
+ body = self.client.show_subnet(subnet['id'])
+ show_subnet = body['subnet']
+ # verify the timestamp from creation and showed is same
+ self.assertEqual(subnet['created_at'],
+ show_subnet['created_at'])
+ self.assertEqual(subnet['updated_at'],
+ show_subnet['updated_at'])
+
+ @decorators.idempotent_id('e2450a7b-d84f-4600-a093-45e78597bbac')
+ def test_create_port_with_timestamp(self):
+ network = self.create_network()
+ port = self.create_port(network)
+ # Verifies body contains timestamp fields
+ self.assertIsNotNone(port['created_at'])
+ self.assertIsNotNone(port['updated_at'])
+
+ @decorators.idempotent_id('4241e0d3-54b4-46ce-a9a7-093fc764161b')
+ def test_update_port_with_timestamp(self):
+ network = self.create_network()
+ port = self.create_port(network)
+ origin_updated_at = port['updated_at']
+ update_body = {'name': port['name'] + 'new'}
+ body = self.admin_client.update_port(port['id'],
+ **update_body)
+ updated_port = body['port']
+ new_updated_at = updated_port['updated_at']
+ self.assertEqual(port['created_at'], updated_port['created_at'])
+ # Verify that origin_updated_at is not same with new_updated_at
+ self.assertIsNot(origin_updated_at, new_updated_at)
+
+ @decorators.idempotent_id('584c6723-40b6-4f26-81dd-f508f9d9fb51')
+ def test_show_port_attribute_with_timestamp(self):
+ network = self.create_network()
+ port = self.create_port(network)
+ body = self.client.show_port(port['id'])
+ show_port = body['port']
+ # verify the timestamp from creation and showed is same
+ self.assertEqual(port['created_at'],
+ show_port['created_at'])
+ self.assertEqual(port['updated_at'],
+ show_port['updated_at'])
+
+ @decorators.idempotent_id('87a8b196-4b90-44f0-b7f3-d2057d7d658e')
+ def test_create_subnetpool_with_timestamp(self):
+ sp = self._create_subnetpool()
+ # Verifies body contains timestamp fields
+ self.assertIsNotNone(sp['created_at'])
+ self.assertIsNotNone(sp['updated_at'])
+
+ @decorators.idempotent_id('d48c7578-c3d2-4f9b-a7a1-be2008c770a0')
+ def test_update_subnetpool_with_timestamp(self):
+ sp = self._create_subnetpool()
+ origin_updated_at = sp['updated_at']
+ update_body = {'name': sp['name'] + 'new',
+ 'min_prefixlen': self.min_prefixlen,
+ 'max_prefixlen': self.max_prefixlen}
+ body = self.client.update_subnetpool(sp['id'], **update_body)
+ updated_sp = body['subnetpool']
+ new_updated_at = updated_sp['updated_at']
+ self.assertEqual(sp['created_at'], updated_sp['created_at'])
+ # Verify that origin_updated_at is not same with new_updated_at
+ self.assertIsNot(origin_updated_at, new_updated_at)
+
+ @decorators.idempotent_id('1d3970e6-bcf7-46cd-b7d7-0807759c73b4')
+ def test_show_subnetpool_attribute_with_timestamp(self):
+ sp = self._create_subnetpool()
+ body = self.client.show_subnetpool(sp['id'])
+ show_sp = body['subnetpool']
+ # verify the timestamp from creation and showed is same
+ self.assertEqual(sp['created_at'], show_sp['created_at'])
+ self.assertEqual(sp['updated_at'], show_sp['updated_at'])
+
+
+class TestTimeStampWithL3(base_routers.BaseRouterTest):
+
+ required_extensions = ['standard-attr-timestamp']
+
+ @classmethod
+ def resource_setup(cls):
+ super(TestTimeStampWithL3, cls).resource_setup()
+ cls.ext_net_id = CONF.network.public_network_id
+
+ @decorators.idempotent_id('433ba770-b310-4da9-5d42-733217a1c7b1')
+ def test_create_router_with_timestamp(self):
+ router = self.create_router(router_name='test')
+ # Verifies body contains timestamp fields
+ self.assertIsNotNone(router['created_at'])
+ self.assertIsNotNone(router['updated_at'])
+
+ @decorators.idempotent_id('4a65417a-c11c-4b4d-a351-af01abcf57c6')
+ def test_update_router_with_timestamp(self):
+ router = self.create_router(router_name='test')
+ origin_updated_at = router['updated_at']
+ update_body = {'name': router['name'] + 'new'}
+ body = self.client.update_router(router['id'], **update_body)
+ updated_router = body['router']
+ new_updated_at = updated_router['updated_at']
+ self.assertEqual(router['created_at'], updated_router['created_at'])
+ # Verify that origin_updated_at is not same with new_updated_at
+ self.assertIsNot(origin_updated_at, new_updated_at)
+
+ @decorators.idempotent_id('1ab50ac2-7cbd-4a17-b23e-a9e36cfa4ec2')
+ def test_show_router_attribute_with_timestamp(self):
+ router = self.create_router(router_name='test')
+ body = self.client.show_router(router['id'])
+ show_router = body['router']
+ # verify the timestamp from creation and showed is same
+ self.assertEqual(router['created_at'],
+ show_router['created_at'])
+ # 'updated_at' timestamp can change immediately after creation
+ # if environment is HA or DVR, so just make sure it's >=
+ self.assertGreaterEqual(show_router['updated_at'],
+ router['updated_at'])
+
+ @decorators.idempotent_id('8ae55186-464f-4b87-1c9f-eb2765ee81ac')
+ def test_create_floatingip_with_timestamp(self):
+ fip = self.create_floatingip(self.ext_net_id)
+ # Verifies body contains timestamp fields
+ self.assertIsNotNone(fip['created_at'])
+ self.assertIsNotNone(fip['updated_at'])
+
+ @decorators.idempotent_id('a3ac215a-61ac-13f9-9d3c-57c51f11afa1')
+ def test_update_floatingip_with_timestamp(self):
+ fip = self.create_floatingip(self.ext_net_id)
+ origin_updated_at = fip['updated_at']
+ update_body = {'description': 'new'}
+ body = self.client.update_floatingip(fip['id'], **update_body)
+ updated_fip = body['floatingip']
+ new_updated_at = updated_fip['updated_at']
+ self.assertEqual(fip['created_at'], updated_fip['created_at'])
+ # Verify that origin_updated_at is not same with new_updated_at
+ self.assertIsNot(origin_updated_at, new_updated_at)
+
+ @decorators.idempotent_id('32a6a086-e1ef-413b-b13a-0cfe13ef051e')
+ def test_show_floatingip_attribute_with_timestamp(self):
+ fip = self.create_floatingip(self.ext_net_id)
+ body = self.client.show_floatingip(fip['id'])
+ show_fip = body['floatingip']
+ # verify the timestamp from creation and showed is same
+ self.assertEqual(fip['created_at'],
+ show_fip['created_at'])
+ self.assertEqual(fip['updated_at'],
+ show_fip['updated_at'])
+
+
+class TestTimeStampWithSecurityGroup(base_security_groups.BaseSecGroupTest):
+
+ required_extensions = ['standard-attr-timestamp']
+
+ @classmethod
+ def resource_setup(cls):
+ super(TestTimeStampWithSecurityGroup, cls).resource_setup()
+ cls.ext_net_id = CONF.network.public_network_id
+
+ @decorators.idempotent_id('a3150a7b-d31a-423a-abf3-45e71c97cbac')
+ def test_create_sg_with_timestamp(self):
+ sg, _ = self._create_security_group()
+ # Verifies body contains timestamp fields
+ self.assertIsNotNone(sg['security_group']['created_at'])
+ self.assertIsNotNone(sg['security_group']['updated_at'])
+
+ @decorators.idempotent_id('432ae0d3-32b4-413e-a9b3-091ac76da31b')
+ def test_update_sg_with_timestamp(self):
+ sgc, _ = self._create_security_group()
+ sg = sgc['security_group']
+ origin_updated_at = sg['updated_at']
+ update_body = {'name': sg['name'] + 'new'}
+ body = self.client.update_security_group(sg['id'], **update_body)
+ updated_sg = body['security_group']
+ new_updated_at = updated_sg['updated_at']
+ self.assertEqual(sg['created_at'], updated_sg['created_at'])
+ # Verify that origin_updated_at is not same with new_updated_at
+ self.assertIsNot(origin_updated_at, new_updated_at)
+
+ @decorators.idempotent_id('521e6723-43d6-12a6-8c3d-f5042ad9fc32')
+ def test_show_sg_attribute_with_timestamp(self):
+ sg, _ = self._create_security_group()
+ body = self.client.show_security_group(sg['security_group']['id'])
+ show_sg = body['security_group']
+ # verify the timestamp from creation and showed is same
+ self.assertEqual(sg['security_group']['created_at'],
+ show_sg['created_at'])
+ self.assertEqual(sg['security_group']['updated_at'],
+ show_sg['updated_at'])
+
+ def _prepare_sgrule_test(self):
+ sg, _ = self._create_security_group()
+ sg_id = sg['security_group']['id']
+ direction = 'ingress'
+ protocol = 'tcp'
+ port_range_min = 77
+ port_range_max = 77
+ rule_create_body = self.client.create_security_group_rule(
+ security_group_id=sg_id,
+ direction=direction,
+ ethertype=self.ethertype,
+ protocol=protocol,
+ port_range_min=port_range_min,
+ port_range_max=port_range_max,
+ remote_group_id=None,
+ remote_ip_prefix=None
+ )
+ return rule_create_body['security_group_rule']
+
+ @decorators.idempotent_id('83e8bd32-43e0-a3f0-1af3-12a5733c653e')
+ def test_create_sgrule_with_timestamp(self):
+ sgrule = self._prepare_sgrule_test()
+ # Verifies body contains timestamp fields
+ self.assertIsNotNone(sgrule['created_at'])
+ self.assertIsNotNone(sgrule['updated_at'])
+
+ @decorators.idempotent_id('143da0e6-ba17-43ad-b3d7-03aa759c3cb4')
+ def test_show_sgrule_attribute_with_timestamp(self):
+ sgrule = self._prepare_sgrule_test()
+ body = self.client.show_security_group_rule(sgrule['id'])
+ show_sgrule = body['security_group_rule']
+ # verify the timestamp from creation and showed is same
+ self.assertEqual(sgrule['created_at'], show_sgrule['created_at'])
+ self.assertEqual(sgrule['updated_at'], show_sgrule['updated_at'])
diff --git a/neutron_tempest_plugin/api/test_trunk.py b/neutron_tempest_plugin/api/test_trunk.py
new file mode 100644
index 0000000..965b248
--- /dev/null
+++ b/neutron_tempest_plugin/api/test_trunk.py
@@ -0,0 +1,390 @@
+# Copyright 2016 Hewlett Packard Enterprise Development Company LP
+#
+# 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.common.utils import data_utils
+from tempest.lib.common.utils import test_utils
+from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
+from tempest import test
+
+from neutron_tempest_plugin.api import base
+from neutron_tempest_plugin import config
+
+
+def trunks_cleanup(client, trunks):
+ for trunk in trunks:
+ # NOTE(armax): deleting a trunk with subports is permitted, however
+ # for testing purposes it is safer to be explicit and clean all the
+ # resources associated with the trunk beforehand.
+ subports = test_utils.call_and_ignore_notfound_exc(
+ client.get_subports, trunk['id'])
+ if subports:
+ client.remove_subports(
+ trunk['id'], subports['sub_ports'])
+ test_utils.call_and_ignore_notfound_exc(
+ client.delete_trunk, trunk['id'])
+
+
+class TrunkTestJSONBase(base.BaseAdminNetworkTest):
+
+ required_extensions = ['trunk']
+
+ def setUp(self):
+ self.addCleanup(self.resource_cleanup)
+ super(TrunkTestJSONBase, self).setUp()
+
+ @classmethod
+ def resource_setup(cls):
+ super(TrunkTestJSONBase, cls).resource_setup()
+ cls.trunks = []
+
+ @classmethod
+ def resource_cleanup(cls):
+ trunks_cleanup(cls.client, cls.trunks)
+ super(TrunkTestJSONBase, cls).resource_cleanup()
+
+ def _create_trunk_with_network_and_parent(self, subports, **kwargs):
+ network = self.create_network()
+ parent_port = self.create_port(network)
+ trunk = self.client.create_trunk(parent_port['id'], subports, **kwargs)
+ self.trunks.append(trunk['trunk'])
+ return trunk
+
+ def _show_trunk(self, trunk_id):
+ return self.client.show_trunk(trunk_id)
+
+ def _list_trunks(self):
+ return self.client.list_trunks()
+
+
+class TrunkTestJSON(TrunkTestJSONBase):
+
+ def _test_create_trunk(self, subports):
+ trunk = self._create_trunk_with_network_and_parent(subports)
+ observed_trunk = self._show_trunk(trunk['trunk']['id'])
+ self.assertEqual(trunk, observed_trunk)
+
+ @decorators.idempotent_id('e1a6355c-4768-41f3-9bf8-0f1d192bd501')
+ def test_create_trunk_empty_subports_list(self):
+ self._test_create_trunk([])
+
+ @decorators.idempotent_id('382dfa39-ca03-4bd3-9a1c-91e36d2e3796')
+ def test_create_trunk_subports_not_specified(self):
+ self._test_create_trunk(None)
+
+ @decorators.idempotent_id('7de46c22-e2b6-4959-ac5a-0e624632ab32')
+ def test_create_show_delete_trunk(self):
+ trunk = self._create_trunk_with_network_and_parent(None)
+ trunk_id = trunk['trunk']['id']
+ parent_port_id = trunk['trunk']['port_id']
+ res = self._show_trunk(trunk_id)
+ self.assertEqual(trunk_id, res['trunk']['id'])
+ self.assertEqual(parent_port_id, res['trunk']['port_id'])
+ self.client.delete_trunk(trunk_id)
+ self.assertRaises(lib_exc.NotFound, self._show_trunk, trunk_id)
+
+ @decorators.idempotent_id('8d83a6ca-662d-45b8-8062-d513077296aa')
+ @test.requires_ext(extension="project-id", service="network")
+ def test_show_trunk_has_project_id(self):
+ trunk = self._create_trunk_with_network_and_parent(None)
+ body = self._show_trunk(trunk['trunk']['id'])
+ show_trunk = body['trunk']
+ self.assertIn('project_id', show_trunk)
+ self.assertIn('tenant_id', show_trunk)
+ self.assertEqual(self.client.tenant_id, show_trunk['project_id'])
+ self.assertEqual(self.client.tenant_id, show_trunk['tenant_id'])
+
+ @decorators.idempotent_id('4ce46c22-a2b6-4659-bc5a-0ef2463cab32')
+ def test_create_update_trunk(self):
+ trunk = self._create_trunk_with_network_and_parent(None)
+ rev = trunk['trunk']['revision_number']
+ trunk_id = trunk['trunk']['id']
+ res = self._show_trunk(trunk_id)
+ self.assertTrue(res['trunk']['admin_state_up'])
+ self.assertEqual(rev, res['trunk']['revision_number'])
+ self.assertEqual("", res['trunk']['name'])
+ self.assertEqual("", res['trunk']['description'])
+ res = self.client.update_trunk(
+ trunk_id, name='foo', admin_state_up=False)
+ self.assertFalse(res['trunk']['admin_state_up'])
+ self.assertEqual("foo", res['trunk']['name'])
+ self.assertGreater(res['trunk']['revision_number'], rev)
+ # enable the trunk so that it can be managed
+ self.client.update_trunk(trunk_id, admin_state_up=True)
+
+ @decorators.idempotent_id('5ff46c22-a2b6-5559-bc5a-0ef2463cab32')
+ def test_create_update_trunk_with_description(self):
+ trunk = self._create_trunk_with_network_and_parent(
+ None, description="foo description")
+ trunk_id = trunk['trunk']['id']
+ self.assertEqual("foo description", trunk['trunk']['description'])
+ trunk = self.client.update_trunk(trunk_id, description='')
+ self.assertEqual('', trunk['trunk']['description'])
+
+ @decorators.idempotent_id('73365f73-bed6-42cd-960b-ec04e0c99d85')
+ def test_list_trunks(self):
+ trunk1 = self._create_trunk_with_network_and_parent(None)
+ trunk2 = self._create_trunk_with_network_and_parent(None)
+ expected_trunks = {trunk1['trunk']['id']: trunk1['trunk'],
+ trunk2['trunk']['id']: trunk2['trunk']}
+ trunk_list = self._list_trunks()['trunks']
+ matched_trunks = [x for x in trunk_list if x['id'] in expected_trunks]
+ self.assertEqual(2, len(matched_trunks))
+ for trunk in matched_trunks:
+ self.assertEqual(expected_trunks[trunk['id']], trunk)
+
+ @decorators.idempotent_id('bb5fcead-09b5-484a-bbe6-46d1e06d6cc0')
+ def test_add_subport(self):
+ trunk = self._create_trunk_with_network_and_parent([])
+ network = self.create_network()
+ port = self.create_port(network)
+ subports = [{'port_id': port['id'],
+ 'segmentation_type': 'vlan',
+ 'segmentation_id': 2}]
+ self.client.add_subports(trunk['trunk']['id'], subports)
+ trunk = self._show_trunk(trunk['trunk']['id'])
+ observed_subports = trunk['trunk']['sub_ports']
+ self.assertEqual(1, len(observed_subports))
+ created_subport = observed_subports[0]
+ self.assertEqual(subports[0], created_subport)
+
+ @decorators.idempotent_id('ee5fcead-1abf-483a-bce6-43d1e06d6aa0')
+ def test_delete_trunk_with_subport_is_allowed(self):
+ network = self.create_network()
+ port = self.create_port(network)
+ subports = [{'port_id': port['id'],
+ 'segmentation_type': 'vlan',
+ 'segmentation_id': 2}]
+ trunk = self._create_trunk_with_network_and_parent(subports)
+ self.client.delete_trunk(trunk['trunk']['id'])
+
+ @decorators.idempotent_id('96eea398-a03c-4c3e-a99e-864392c2ca53')
+ def test_remove_subport(self):
+ subport_parent1 = self.create_port(self.create_network())
+ subport_parent2 = self.create_port(self.create_network())
+ subports = [{'port_id': subport_parent1['id'],
+ 'segmentation_type': 'vlan',
+ 'segmentation_id': 2},
+ {'port_id': subport_parent2['id'],
+ 'segmentation_type': 'vlan',
+ 'segmentation_id': 4}]
+ trunk = self._create_trunk_with_network_and_parent(subports)
+ removed_subport = trunk['trunk']['sub_ports'][0]
+ expected_subport = None
+
+ for subport in subports:
+ if subport['port_id'] != removed_subport['port_id']:
+ expected_subport = subport
+ break
+
+ # Remove the subport and validate PUT response
+ res = self.client.remove_subports(trunk['trunk']['id'],
+ [removed_subport])
+ self.assertEqual(1, len(res['sub_ports']))
+ self.assertEqual(expected_subport, res['sub_ports'][0])
+
+ # Validate the results of a subport list
+ trunk = self._show_trunk(trunk['trunk']['id'])
+ observed_subports = trunk['trunk']['sub_ports']
+ self.assertEqual(1, len(observed_subports))
+ self.assertEqual(expected_subport, observed_subports[0])
+
+ @decorators.idempotent_id('bb5fcaad-09b5-484a-dde6-4cd1ea6d6ff0')
+ def test_get_subports(self):
+ network = self.create_network()
+ port = self.create_port(network)
+ subports = [{'port_id': port['id'],
+ 'segmentation_type': 'vlan',
+ 'segmentation_id': 2}]
+ trunk = self._create_trunk_with_network_and_parent(subports)
+ trunk = self.client.get_subports(trunk['trunk']['id'])
+ observed_subports = trunk['sub_ports']
+ self.assertEqual(1, len(observed_subports))
+
+
+class TrunkTestInheritJSONBase(TrunkTestJSONBase):
+
+ required_extensions = ['provider', 'trunk']
+
+ @classmethod
+ def skip_checks(cls):
+ super(TrunkTestInheritJSONBase, cls).skip_checks()
+ if ("vlan" not in
+ config.CONF.neutron_plugin_options.available_type_drivers):
+ raise cls.skipException("VLAN type_driver is not enabled")
+ if not config.CONF.neutron_plugin_options.provider_vlans:
+ raise cls.skipException("No provider VLAN networks available")
+
+ def create_provider_network(self):
+ foo_net = config.CONF.neutron_plugin_options.provider_vlans[0]
+ post_body = {'network_name': data_utils.rand_name('vlan-net-'),
+ 'provider:network_type': 'vlan',
+ 'provider:physical_network': foo_net}
+ return self.create_shared_network(**post_body)
+
+ @decorators.idempotent_id('0f05d98e-41f5-4629-dada-9aee269c9602')
+ def test_add_subport(self):
+ trunk_network = self.create_provider_network()
+ trunk_port = self.create_port(trunk_network)
+ subport_networks = [
+ self.create_provider_network(),
+ self.create_provider_network(),
+ ]
+ subport1 = self.create_port(subport_networks[0])
+ subport2 = self.create_port(subport_networks[1])
+ subports = [{'port_id': subport1['id'],
+ 'segmentation_type': 'inherit',
+ 'segmentation_id': subport1['id']},
+ {'port_id': subport2['id'],
+ 'segmentation_type': 'inherit',
+ 'segmentation_id': subport2['id']}]
+ trunk = self.client.create_trunk(trunk_port['id'], subports)['trunk']
+ self.trunks.append(trunk)
+ # Validate that subport got segmentation details from the network
+ for i in range(2):
+ self.assertEqual(subport_networks[i]['provider:network_type'],
+ trunk['sub_ports'][i]['segmentation_type'])
+ self.assertEqual(subport_networks[i]['provider:segmentation_id'],
+ trunk['sub_ports'][i]['segmentation_id'])
+
+
+class TrunkTestMtusJSONBase(TrunkTestJSONBase):
+
+ required_extensions = ['provider', 'trunk']
+
+ @classmethod
+ def skip_checks(cls):
+ super(TrunkTestMtusJSONBase, cls).skip_checks()
+ if any(t
+ not in config.CONF.neutron_plugin_options.available_type_drivers
+ for t in ['gre', 'vxlan']):
+ msg = "Either vxlan or gre type driver not enabled."
+ raise cls.skipException(msg)
+
+ def setUp(self):
+ super(TrunkTestMtusJSONBase, self).setUp()
+
+ # VXLAN autocomputed MTU (1450) is smaller than that of GRE (1458)
+ vxlan_kwargs = {'network_name': data_utils.rand_name('vxlan-net-'),
+ 'provider:network_type': 'vxlan'}
+ self.smaller_mtu_net = self.create_shared_network(**vxlan_kwargs)
+
+ gre_kwargs = {'network_name': data_utils.rand_name('gre-net-'),
+ 'provider:network_type': 'gre'}
+ self.larger_mtu_net = self.create_shared_network(**gre_kwargs)
+
+ self.smaller_mtu_port = self.create_port(self.smaller_mtu_net)
+ self.smaller_mtu_port_2 = self.create_port(self.smaller_mtu_net)
+ self.larger_mtu_port = self.create_port(self.larger_mtu_net)
+
+
+class TrunkTestMtusJSON(TrunkTestMtusJSONBase):
+
+ @decorators.idempotent_id('0f05d98e-41f5-4629-ac29-9aee269c9602')
+ def test_create_trunk_with_mtu_greater_than_subport(self):
+ subports = [{'port_id': self.smaller_mtu_port['id'],
+ 'segmentation_type': 'vlan',
+ 'segmentation_id': 2}]
+
+ trunk = self.client.create_trunk(self.larger_mtu_port['id'], subports)
+ self.trunks.append(trunk['trunk'])
+
+ @decorators.idempotent_id('2004c5c6-e557-4c43-8100-c820ad4953e8')
+ def test_add_subport_with_mtu_smaller_than_trunk(self):
+ subports = [{'port_id': self.smaller_mtu_port['id'],
+ 'segmentation_type': 'vlan',
+ 'segmentation_id': 2}]
+
+ trunk = self.client.create_trunk(self.larger_mtu_port['id'], None)
+ self.trunks.append(trunk['trunk'])
+
+ self.client.add_subports(trunk['trunk']['id'], subports)
+
+ @decorators.idempotent_id('22725101-f4bc-4e00-84ec-4e02cd7e0500')
+ def test_create_trunk_with_mtu_equal_to_subport(self):
+ subports = [{'port_id': self.smaller_mtu_port['id'],
+ 'segmentation_type': 'vlan',
+ 'segmentation_id': 2}]
+
+ trunk = self.client.create_trunk(self.smaller_mtu_port_2['id'],
+ subports)
+ self.trunks.append(trunk['trunk'])
+
+ @decorators.idempotent_id('175b05ae-66ad-44c7-857a-a12d16f1058f')
+ def test_add_subport_with_mtu_equal_to_trunk(self):
+ subports = [{'port_id': self.smaller_mtu_port['id'],
+ 'segmentation_type': 'vlan',
+ 'segmentation_id': 2}]
+
+ trunk = self.client.create_trunk(self.smaller_mtu_port_2['id'], None)
+ self.trunks.append(trunk['trunk'])
+
+ self.client.add_subports(trunk['trunk']['id'], subports)
+
+
+class TrunksSearchCriteriaTest(base.BaseSearchCriteriaTest):
+
+ required_extensions = ['trunk']
+ resource = 'trunk'
+
+ @classmethod
+ def resource_setup(cls):
+ super(TrunksSearchCriteriaTest, cls).resource_setup()
+ cls.trunks = []
+ net = cls.create_network(network_name='trunk-search-test-net')
+ for name in cls.resource_names:
+ parent_port = cls.create_port(net)
+ trunk = cls.client.create_trunk(parent_port['id'], [], name=name)
+ cls.trunks.append(trunk['trunk'])
+
+ @classmethod
+ def resource_cleanup(cls):
+ trunks_cleanup(cls.client, cls.trunks)
+ super(TrunksSearchCriteriaTest, cls).resource_cleanup()
+
+ @decorators.idempotent_id('fab73df4-960a-4ae3-87d3-60992b8d3e2d')
+ def test_list_sorts_asc(self):
+ self._test_list_sorts_asc()
+
+ @decorators.idempotent_id('a426671d-7270-430f-82ff-8f33eec93010')
+ def test_list_sorts_desc(self):
+ self._test_list_sorts_desc()
+
+ @decorators.idempotent_id('b202fdc8-6616-45df-b6a0-463932de6f94')
+ def test_list_pagination(self):
+ self._test_list_pagination()
+
+ @decorators.idempotent_id('c4723b8e-8186-4b9a-bf9e-57519967e048')
+ def test_list_pagination_with_marker(self):
+ self._test_list_pagination_with_marker()
+
+ @decorators.idempotent_id('dcd02a7a-f07e-4d5e-b0ca-b58e48927a9b')
+ def test_list_pagination_with_href_links(self):
+ self._test_list_pagination_with_href_links()
+
+ @decorators.idempotent_id('eafe7024-77ab-4cfe-824b-0b2bf4217727')
+ def test_list_no_pagination_limit_0(self):
+ self._test_list_no_pagination_limit_0()
+
+ @decorators.idempotent_id('f8857391-dc44-40cc-89b7-2800402e03ce')
+ def test_list_pagination_page_reverse_asc(self):
+ self._test_list_pagination_page_reverse_asc()
+
+ @decorators.idempotent_id('ae51e9c9-ceae-4ec0-afd4-147569247699')
+ def test_list_pagination_page_reverse_desc(self):
+ self._test_list_pagination_page_reverse_desc()
+
+ @decorators.idempotent_id('b4293e59-d794-4a93-be09-38667199ef68')
+ def test_list_pagination_page_reverse_with_href_links(self):
+ self._test_list_pagination_page_reverse_with_href_links()
diff --git a/neutron_tempest_plugin/api/test_trunk_details.py b/neutron_tempest_plugin/api/test_trunk_details.py
new file mode 100644
index 0000000..972f216
--- /dev/null
+++ b/neutron_tempest_plugin/api/test_trunk_details.py
@@ -0,0 +1,58 @@
+# Copyright 2016 Hewlett Packard Enterprise Development Company LP
+#
+# 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 decorators
+
+from neutron_tempest_plugin.api import test_trunk
+
+
+class TestTrunkDetailsJSON(test_trunk.TrunkTestJSONBase):
+
+ required_extensions = ['trunk-details']
+
+ @decorators.idempotent_id('f0bed24f-d36a-498b-b4e7-0d66e3fb7308')
+ def test_port_resource_trunk_details_no_subports(self):
+ trunk = self._create_trunk_with_network_and_parent([])
+ port = self.client.show_port(trunk['trunk']['port_id'])
+ expected_trunk_details = {'sub_ports': [],
+ 'trunk_id': trunk['trunk']['id']}
+ observed_trunk_details = port['port'].get('trunk_details')
+ self.assertIsNotNone(observed_trunk_details)
+ self.assertEqual(expected_trunk_details,
+ observed_trunk_details)
+
+ @decorators.idempotent_id('544bcaf2-86fb-4930-93ab-ece1c3cc33df')
+ def test_port_resource_trunk_details_with_subport(self):
+ subport_network = self.create_network()
+ subport = self.create_port(subport_network)
+ subport_data = {'port_id': subport['id'],
+ 'segmentation_type': 'vlan',
+ 'segmentation_id': 2}
+ trunk = self._create_trunk_with_network_and_parent([subport_data])
+ subport_data['mac_address'] = subport['mac_address']
+ parent_port = self.client.show_port(trunk['trunk']['port_id'])
+ expected_trunk_details = {'sub_ports': [subport_data],
+ 'trunk_id': trunk['trunk']['id']}
+ observed_trunk_details = parent_port['port'].get('trunk_details')
+ self.assertIsNotNone(observed_trunk_details)
+ self.assertEqual(expected_trunk_details,
+ observed_trunk_details)
+
+ @decorators.idempotent_id('fe6d865f-1d5c-432e-b65d-904157172f24')
+ def test_port_resource_empty_trunk_details(self):
+ network = self.create_network()
+ port = self.create_port(network)
+ port = self.client.show_port(port['id'])
+ observed_trunk_details = port['port'].get('trunk_details')
+ self.assertIsNone(observed_trunk_details)
diff --git a/neutron_tempest_plugin/api/test_trunk_negative.py b/neutron_tempest_plugin/api/test_trunk_negative.py
new file mode 100644
index 0000000..699b26f
--- /dev/null
+++ b/neutron_tempest_plugin/api/test_trunk_negative.py
@@ -0,0 +1,282 @@
+# Copyright 2016 Hewlett Packard Enterprise Development Company LP
+#
+# 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 oslo_utils import uuidutils
+from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
+import testtools
+
+from neutron_tempest_plugin.api import test_trunk
+
+
+class TrunkTestJSON(test_trunk.TrunkTestJSONBase):
+
+ @decorators.attr(type='negative')
+ @decorators.idempotent_id('1b5cf87a-1d3a-4a94-ba64-647153d54f32')
+ def test_create_trunk_nonexistent_port_id(self):
+ self.assertRaises(lib_exc.NotFound, self.client.create_trunk,
+ uuidutils.generate_uuid(), [])
+
+ @decorators.attr(type='negative')
+ @decorators.idempotent_id('980bca3b-b0be-45ac-8067-b401e445b796')
+ def test_create_trunk_nonexistent_subport_port_id(self):
+ network = self.create_network()
+ parent_port = self.create_port(network)
+ self.assertRaises(lib_exc.NotFound, self.client.create_trunk,
+ parent_port['id'],
+ [{'port_id': uuidutils.generate_uuid(),
+ 'segmentation_type': 'vlan',
+ 'segmentation_id': 2}])
+
+ @decorators.attr(type='negative')
+ @decorators.idempotent_id('a5c5200a-72a0-43c5-a11a-52f808490344')
+ def test_create_subport_nonexistent_port_id(self):
+ trunk = self._create_trunk_with_network_and_parent([])
+ self.assertRaises(lib_exc.NotFound, self.client.add_subports,
+ trunk['trunk']['id'],
+ [{'port_id': uuidutils.generate_uuid(),
+ 'segmentation_type': 'vlan',
+ 'segmentation_id': 2}])
+
+ @decorators.attr(type='negative')
+ @decorators.idempotent_id('80deb6a9-da2a-48db-b7fd-bcef5b14edc1')
+ def test_create_subport_nonexistent_trunk(self):
+ network = self.create_network()
+ parent_port = self.create_port(network)
+ self.assertRaises(lib_exc.NotFound, self.client.add_subports,
+ uuidutils.generate_uuid(),
+ [{'port_id': parent_port['id'],
+ 'segmentation_type': 'vlan',
+ 'segmentation_id': 2}])
+
+ @decorators.attr(type='negative')
+ @decorators.idempotent_id('7e0f99ab-fe37-408b-a889-9e44ef300084')
+ def test_create_subport_missing_segmentation_id(self):
+ trunk = self._create_trunk_with_network_and_parent([])
+ subport_network = self.create_network()
+ parent_port = self.create_port(subport_network)
+ self.assertRaises(lib_exc.BadRequest, self.client.add_subports,
+ trunk['trunk']['id'],
+ [{'port_id': parent_port['id'],
+ 'segmentation_type': 'vlan'}])
+
+ @decorators.attr(type='negative')
+ @decorators.idempotent_id('a315d78b-2f43-4efa-89ae-166044c568aa')
+ def test_create_trunk_with_subport_missing_segmentation_id(self):
+ subport_network = self.create_network()
+ parent_port = self.create_port(subport_network)
+ self.assertRaises(lib_exc.BadRequest, self.client.create_trunk,
+ parent_port['id'],
+ [{'port_id': uuidutils.generate_uuid(),
+ 'segmentation_type': 'vlan'}])
+
+ @decorators.attr(type='negative')
+ @decorators.idempotent_id('33498618-f75a-4796-8ae6-93d4fd203fa4')
+ def test_create_trunk_with_subport_missing_segmentation_type(self):
+ subport_network = self.create_network()
+ parent_port = self.create_port(subport_network)
+ self.assertRaises(lib_exc.BadRequest, self.client.create_trunk,
+ parent_port['id'],
+ [{'port_id': uuidutils.generate_uuid(),
+ 'segmentation_id': 3}])
+
+ @decorators.attr(type='negative')
+ @decorators.idempotent_id('a717691c-4e07-4d81-a98d-6f1c18c5d183')
+ def test_create_trunk_with_subport_missing_port_id(self):
+ subport_network = self.create_network()
+ parent_port = self.create_port(subport_network)
+ self.assertRaises(lib_exc.BadRequest, self.client.create_trunk,
+ parent_port['id'],
+ [{'segmentation_type': 'vlan',
+ 'segmentation_id': 3}])
+
+ @decorators.attr(type='negative')
+ @decorators.idempotent_id('40aed9be-e976-47d0-dada-bde2c7e74e57')
+ def test_create_subport_invalid_inherit_network_segmentation_type(self):
+ trunk = self._create_trunk_with_network_and_parent([])
+ subport_network = self.create_network()
+ parent_port = self.create_port(subport_network)
+ self.assertRaises(lib_exc.BadRequest, self.client.add_subports,
+ trunk['trunk']['id'],
+ [{'port_id': parent_port['id'],
+ 'segmentation_type': 'inherit',
+ 'segmentation_id': -1}])
+
+ @decorators.attr(type='negative')
+ @decorators.idempotent_id('40aed9be-e976-47d0-a555-bde2c7e74e57')
+ def test_create_trunk_duplicate_subport_segmentation_ids(self):
+ trunk = self._create_trunk_with_network_and_parent([])
+ subport_network1 = self.create_network()
+ subport_network2 = self.create_network()
+ parent_port1 = self.create_port(subport_network1)
+ parent_port2 = self.create_port(subport_network2)
+ self.assertRaises(lib_exc.BadRequest, self.client.create_trunk,
+ trunk['trunk']['id'],
+ [{'port_id': parent_port1['id'],
+ 'segmentation_id': 2,
+ 'segmentation_type': 'vlan'},
+ {'port_id': parent_port2['id'],
+ 'segmentation_id': 2,
+ 'segmentation_type': 'vlan'}])
+
+ @decorators.attr(type='negative')
+ @decorators.idempotent_id('6f132ccc-1380-42d8-9c44-50411612bd01')
+ def test_add_subport_port_id_uses_trunk_port_id(self):
+ trunk = self._create_trunk_with_network_and_parent(None)
+ self.assertRaises(lib_exc.Conflict, self.client.add_subports,
+ trunk['trunk']['id'],
+ [{'port_id': trunk['trunk']['port_id'],
+ 'segmentation_type': 'vlan',
+ 'segmentation_id': 2}])
+
+ @decorators.attr(type='negative')
+ @decorators.idempotent_id('7f132ccc-1380-42d8-9c44-50411612bd01')
+ def test_add_subport_port_id_disabled_trunk(self):
+ trunk = self._create_trunk_with_network_and_parent(
+ None, admin_state_up=False)
+ self.assertRaises(lib_exc.Conflict,
+ self.client.add_subports,
+ trunk['trunk']['id'],
+ [{'port_id': trunk['trunk']['port_id'],
+ 'segmentation_type': 'vlan',
+ 'segmentation_id': 2}])
+ self.client.update_trunk(
+ trunk['trunk']['id'], admin_state_up=True)
+
+ @decorators.attr(type='negative')
+ @decorators.idempotent_id('8f132ccc-1380-42d8-9c44-50411612bd01')
+ def test_remove_subport_port_id_disabled_trunk(self):
+ trunk = self._create_trunk_with_network_and_parent(
+ None, admin_state_up=False)
+ self.assertRaises(lib_exc.Conflict,
+ self.client.remove_subports,
+ trunk['trunk']['id'],
+ [{'port_id': trunk['trunk']['port_id'],
+ 'segmentation_type': 'vlan',
+ 'segmentation_id': 2}])
+ self.client.update_trunk(
+ trunk['trunk']['id'], admin_state_up=True)
+
+ @decorators.attr(type='negative')
+ @decorators.idempotent_id('9f132ccc-1380-42d8-9c44-50411612bd01')
+ def test_delete_trunk_disabled_trunk(self):
+ trunk = self._create_trunk_with_network_and_parent(
+ None, admin_state_up=False)
+ self.assertRaises(lib_exc.Conflict,
+ self.client.delete_trunk,
+ trunk['trunk']['id'])
+ self.client.update_trunk(
+ trunk['trunk']['id'], admin_state_up=True)
+
+ @decorators.attr(type='negative')
+ @decorators.idempotent_id('00cb40bb-1593-44c8-808c-72b47e64252f')
+ def test_add_subport_duplicate_segmentation_details(self):
+ trunk = self._create_trunk_with_network_and_parent(None)
+ network = self.create_network()
+ parent_port1 = self.create_port(network)
+ parent_port2 = self.create_port(network)
+ self.client.add_subports(trunk['trunk']['id'],
+ [{'port_id': parent_port1['id'],
+ 'segmentation_type': 'vlan',
+ 'segmentation_id': 2}])
+ self.assertRaises(lib_exc.Conflict, self.client.add_subports,
+ trunk['trunk']['id'],
+ [{'port_id': parent_port2['id'],
+ 'segmentation_type': 'vlan',
+ 'segmentation_id': 2}])
+
+ @decorators.attr(type='negative')
+ @decorators.idempotent_id('4eac8c25-83ee-4051-9620-34774f565730')
+ def test_add_subport_passing_dict(self):
+ trunk = self._create_trunk_with_network_and_parent(None)
+ self.assertRaises(lib_exc.BadRequest, self.client.add_subports,
+ trunk['trunk']['id'],
+ {'port_id': trunk['trunk']['port_id'],
+ 'segmentation_type': 'vlan',
+ 'segmentation_id': 2})
+
+ @decorators.attr(type='negative')
+ @decorators.idempotent_id('17ca7dd7-96a8-445a-941e-53c0c86c2fe2')
+ def test_remove_subport_passing_dict(self):
+ network = self.create_network()
+ parent_port = self.create_port(network)
+ subport_data = {'port_id': parent_port['id'],
+ 'segmentation_type': 'vlan',
+ 'segmentation_id': 2}
+ trunk = self._create_trunk_with_network_and_parent([subport_data])
+ self.assertRaises(lib_exc.BadRequest, self.client.remove_subports,
+ trunk['trunk']['id'], subport_data)
+
+ @decorators.attr(type='negative')
+ @decorators.idempotent_id('aaca7dd7-96b8-445a-931e-63f0d86d2fe2')
+ def test_remove_subport_not_found(self):
+ network = self.create_network()
+ parent_port = self.create_port(network)
+ subport_data = {'port_id': parent_port['id'],
+ 'segmentation_type': 'vlan',
+ 'segmentation_id': 2}
+ trunk = self._create_trunk_with_network_and_parent([])
+ self.assertRaises(lib_exc.NotFound, self.client.remove_subports,
+ trunk['trunk']['id'], [subport_data])
+
+ @decorators.attr(type='negative')
+ @decorators.idempotent_id('6c9c5126-4f61-11e6-8248-40a8f063c891')
+ def test_delete_port_in_use_by_trunk(self):
+ trunk = self._create_trunk_with_network_and_parent(None)
+ self.assertRaises(lib_exc.Conflict, self.client.delete_port,
+ trunk['trunk']['port_id'])
+
+ @decorators.attr(type='negative')
+ @decorators.idempotent_id('343a03d0-4f7c-11e6-97fa-40a8f063c891')
+ def test_delete_port_in_use_by_subport(self):
+ network = self.create_network()
+ port = self.create_port(network)
+ subports = [{'port_id': port['id'],
+ 'segmentation_type': 'vlan',
+ 'segmentation_id': 2}]
+ self._create_trunk_with_network_and_parent(subports)
+ self.assertRaises(lib_exc.Conflict, self.client.delete_port,
+ port['id'])
+
+
+class TrunkTestMtusJSON(test_trunk.TrunkTestMtusJSONBase):
+
+ required_extensions = (
+ ['net-mtu'] + test_trunk.TrunkTestMtusJSONBase.required_extensions)
+
+ @decorators.attr(type='negative')
+ @decorators.idempotent_id('228380ef-1b7a-495e-b759-5b1f08e3e858')
+ def test_create_trunk_with_mtu_smaller_than_subport(self):
+ subports = [{'port_id': self.larger_mtu_port['id'],
+ 'segmentation_type': 'vlan',
+ 'segmentation_id': 2}]
+
+ with testtools.ExpectedException(lib_exc.Conflict):
+ trunk = self.client.create_trunk(self.smaller_mtu_port['id'],
+ subports)
+ self.trunks.append(trunk['trunk'])
+
+ @decorators.attr(type='negative')
+ @decorators.idempotent_id('3b32bf77-8002-403e-ad01-6f4cf018daa5')
+ def test_add_subport_with_mtu_greater_than_trunk(self):
+ subports = [{'port_id': self.larger_mtu_port['id'],
+ 'segmentation_type': 'vlan',
+ 'segmentation_id': 2}]
+
+ trunk = self.client.create_trunk(self.smaller_mtu_port['id'], None)
+ self.trunks.append(trunk['trunk'])
+
+ self.assertRaises(lib_exc.Conflict,
+ self.client.add_subports,
+ trunk['trunk']['id'], subports)