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/README.rst b/neutron_tempest_plugin/README.rst
new file mode 100644
index 0000000..0d52b02
--- /dev/null
+++ b/neutron_tempest_plugin/README.rst
@@ -0,0 +1,9 @@
+WARNING
+=======
+
+Some files under this path were copied from tempest as part of the move of the
+api tests, and they will be removed as required over time to minimize the
+dependency on the tempest testing framework.  While it exists, only
+neutron_tempest_plugin.* should be importing files from this path.
+neutron_tempest_plugin.config uses the global cfg.CONF instance and importing it
+outside of the api tests has the potential to break Neutron's use of cfg.CONF.
diff --git a/neutron_tempest_plugin/__init__.py b/neutron_tempest_plugin/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/neutron_tempest_plugin/__init__.py
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)
diff --git a/neutron_tempest_plugin/common/__init__.py b/neutron_tempest_plugin/common/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/neutron_tempest_plugin/common/__init__.py
diff --git a/neutron_tempest_plugin/common/constants.py b/neutron_tempest_plugin/common/constants.py
new file mode 100644
index 0000000..4ad780d
--- /dev/null
+++ b/neutron_tempest_plugin/common/constants.py
@@ -0,0 +1,177 @@
+# Copyright (c) 2012 OpenStack Foundation.
+#
+# 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 as lib_constants
+
+
+ROUTER_PORT_OWNERS = lib_constants.ROUTER_INTERFACE_OWNERS_SNAT + \
+    (lib_constants.DEVICE_OWNER_ROUTER_GW,)
+
+ROUTER_STATUS_ACTIVE = 'ACTIVE'
+ROUTER_STATUS_ERROR = 'ERROR'
+
+DEVICE_ID_RESERVED_DHCP_PORT = "reserved_dhcp_port"
+
+HA_ROUTER_STATE_KEY = '_ha_state'
+METERING_LABEL_KEY = '_metering_labels'
+FLOATINGIP_AGENT_INTF_KEY = '_floatingip_agent_interfaces'
+SNAT_ROUTER_INTF_KEY = '_snat_router_interfaces'
+DVR_SNAT_BOUND = 'dvr_snat_bound'
+L3_AGENT_MODE_DVR_NO_EXTERNAL = 'dvr_no_external'
+
+HA_NETWORK_NAME = 'HA network tenant %s'
+HA_SUBNET_NAME = 'HA subnet tenant %s'
+HA_PORT_NAME = 'HA port tenant %s'
+HA_ROUTER_STATE_ACTIVE = 'active'
+HA_ROUTER_STATE_STANDBY = 'standby'
+
+PAGINATION_INFINITE = 'infinite'
+
+SORT_DIRECTION_ASC = 'asc'
+SORT_DIRECTION_DESC = 'desc'
+
+ETHERTYPE_NAME_ARP = 'arp'
+ETHERTYPE_ARP = 0x0806
+ETHERTYPE_IP = 0x0800
+ETHERTYPE_IPV6 = 0x86DD
+
+IP_PROTOCOL_NAME_ALIASES = {lib_constants.PROTO_NAME_IPV6_ICMP_LEGACY:
+                            lib_constants.PROTO_NAME_IPV6_ICMP}
+
+VALID_DSCP_MARKS = [0, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34,
+                    36, 38, 40, 46, 48, 56]
+
+IP_PROTOCOL_NUM_TO_NAME_MAP = {
+    str(v): k for k, v in lib_constants.IP_PROTOCOL_MAP.items()}
+
+# When using iptables-save we specify '-p {proto} -m {module}',
+# but sometimes those values are not identical.  This is a map
+# of known protocols that require a '-m {module}', along with
+# the module name that should be used.
+IPTABLES_PROTOCOL_MAP = {lib_constants.PROTO_NAME_DCCP: 'dccp',
+                         lib_constants.PROTO_NAME_ICMP: 'icmp',
+                         lib_constants.PROTO_NAME_IPV6_ICMP: 'icmp6',
+                         lib_constants.PROTO_NAME_SCTP: 'sctp',
+                         lib_constants.PROTO_NAME_TCP: 'tcp',
+                         lib_constants.PROTO_NAME_UDP: 'udp'}
+
+# Special provisional prefix for IPv6 Prefix Delegation
+PROVISIONAL_IPV6_PD_PREFIX = '::/64'
+
+# Timeout in seconds for getting an IPv6 LLA
+LLA_TASK_TIMEOUT = 40
+
+# length of all device prefixes (e.g. qvo, tap, qvb)
+LINUX_DEV_PREFIX_LEN = 3
+# must be shorter than linux IFNAMSIZ (which is 16)
+LINUX_DEV_LEN = 14
+
+# Possible prefixes to partial port IDs in interface names used by the OVS,
+# Linux Bridge, and IVS VIF drivers in Nova and the neutron agents. See the
+# 'get_ovs_interfaceid' method in Nova (nova/virt/libvirt/vif.py) for details.
+INTERFACE_PREFIXES = (lib_constants.TAP_DEVICE_PREFIX,
+                      lib_constants.VETH_DEVICE_PREFIX,
+                      lib_constants.SNAT_INT_DEV_PREFIX)
+
+ATTRIBUTES_TO_UPDATE = 'attributes_to_update'
+
+# Maximum value integer can take in MySQL and PostgreSQL
+# In SQLite integer can be stored in 1, 2, 3, 4, 6, or 8 bytes,
+# but here it will be limited by this value for consistency.
+DB_INTEGER_MAX_VALUE = 2 ** 31 - 1
+
+# TODO(amuller): Re-define the RPC namespaces once Oslo messaging supports
+# Targets with multiple namespaces. Neutron will then implement callbacks
+# for its RPC clients in order to support rolling upgrades.
+
+# RPC Interface for agents to call DHCP API implemented on the plugin side
+RPC_NAMESPACE_DHCP_PLUGIN = None
+# RPC interface for the metadata service to get info from the plugin side
+RPC_NAMESPACE_METADATA = None
+# RPC interface for agent to plugin security group API
+RPC_NAMESPACE_SECGROUP = None
+# RPC interface for agent to plugin DVR api
+RPC_NAMESPACE_DVR = None
+# RPC interface for reporting state back to the plugin
+RPC_NAMESPACE_STATE = None
+# RPC interface for agent to plugin resources API
+RPC_NAMESPACE_RESOURCES = None
+
+# Default network MTU value when not configured
+DEFAULT_NETWORK_MTU = 1500
+IPV6_MIN_MTU = 1280
+
+ROUTER_MARK_MASK = "0xffff"
+
+# Agent states as detected by server, used to reply on agent's state report
+# agent has just been registered
+AGENT_NEW = 'new'
+# agent is alive
+AGENT_ALIVE = 'alive'
+# agent has just returned to alive after being dead
+AGENT_REVIVED = 'revived'
+
+INGRESS_DIRECTION = 'ingress'
+EGRESS_DIRECTION = 'egress'
+
+VALID_DIRECTIONS = (INGRESS_DIRECTION, EGRESS_DIRECTION)
+VALID_ETHERTYPES = (lib_constants.IPv4, lib_constants.IPv6)
+
+IP_ALLOWED_VERSIONS = [lib_constants.IP_VERSION_4, lib_constants.IP_VERSION_6]
+
+PORT_RANGE_MIN = 1
+PORT_RANGE_MAX = 65535
+
+# Configuration values for accept_ra sysctl, copied from linux kernel
+# networking (netdev) tree, file Documentation/networking/ip-sysctl.txt
+#
+# Possible values are:
+#         0 Do not accept Router Advertisements.
+#         1 Accept Router Advertisements if forwarding is disabled.
+#         2 Overrule forwarding behaviour. Accept Router Advertisements
+#           even if forwarding is enabled.
+ACCEPT_RA_DISABLED = 0
+ACCEPT_RA_WITHOUT_FORWARDING = 1
+ACCEPT_RA_WITH_FORWARDING = 2
+
+# Some components communicate using private address ranges, define
+# them all here. These address ranges should not cause any issues
+# even if they overlap since they are used in disjoint namespaces,
+# but for now they are unique.
+# We define the metadata cidr since it falls in the range.
+PRIVATE_CIDR_RANGE = '169.254.0.0/16'
+DVR_FIP_LL_CIDR = '169.254.64.0/18'
+L3_HA_NET_CIDR = '169.254.192.0/18'
+METADATA_CIDR = '169.254.169.254/32'
+
+# The only defined IpamAllocation status at this stage is 'ALLOCATED'.
+# More states will be available in the future - e.g.: RECYCLABLE
+IPAM_ALLOCATION_STATUS_ALLOCATED = 'ALLOCATED'
+
+VALID_IPAM_ALLOCATION_STATUSES = (IPAM_ALLOCATION_STATUS_ALLOCATED,)
+
+# Port binding states for Live Migration
+PORT_BINDING_STATUS_ACTIVE = 'ACTIVE'
+PORT_BINDING_STATUS_INACTIVE = 'INACTIVE'
+PORT_BINDING_STATUSES = (PORT_BINDING_STATUS_ACTIVE,
+                         PORT_BINDING_STATUS_INACTIVE)
+
+VALID_FLOATINGIP_STATUS = (lib_constants.FLOATINGIP_STATUS_ACTIVE,
+                           lib_constants.FLOATINGIP_STATUS_DOWN,
+                           lib_constants.FLOATINGIP_STATUS_ERROR)
+
+# Possible types of values (e.g. in QoS rule types)
+VALUES_TYPE_CHOICES = "choices"
+VALUES_TYPE_RANGE = "range"
diff --git a/neutron_tempest_plugin/common/qos_consts.py b/neutron_tempest_plugin/common/qos_consts.py
new file mode 100644
index 0000000..e4a14cf
--- /dev/null
+++ b/neutron_tempest_plugin/common/qos_consts.py
@@ -0,0 +1,56 @@
+# Copyright (c) 2015 Red Hat 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.
+
+RULE_TYPE_BANDWIDTH_LIMIT = 'bandwidth_limit'
+RULE_TYPE_DSCP_MARKING = 'dscp_marking'
+RULE_TYPE_MINIMUM_BANDWIDTH = 'minimum_bandwidth'
+VALID_RULE_TYPES = [RULE_TYPE_BANDWIDTH_LIMIT,
+                    RULE_TYPE_DSCP_MARKING,
+                    RULE_TYPE_MINIMUM_BANDWIDTH,
+                    ]
+
+# Names of rules' attributes
+MAX_KBPS = "max_kbps"
+MAX_BURST = "max_burst_kbps"
+MIN_KBPS = "min_kbps"
+DIRECTION = "direction"
+DSCP_MARK = "dscp_mark"
+
+QOS_POLICY_ID = 'qos_policy_id'
+
+QOS_PLUGIN = 'qos_plugin'
+
+# NOTE(slaweq): Value used to calculate burst value for egress bandwidth limit
+# if burst is not given by user. In such case burst value will be calculated
+# as 80% of bw_limit to ensure that at least limits for TCP traffic will work
+# fine.
+DEFAULT_BURST_RATE = 0.8
+
+# Method names for QoSDriver
+PRECOMMIT_POSTFIX = '_precommit'
+CREATE_POLICY = 'create_policy'
+CREATE_POLICY_PRECOMMIT = CREATE_POLICY + PRECOMMIT_POSTFIX
+UPDATE_POLICY = 'update_policy'
+UPDATE_POLICY_PRECOMMIT = UPDATE_POLICY + PRECOMMIT_POSTFIX
+DELETE_POLICY = 'delete_policy'
+DELETE_POLICY_PRECOMMIT = DELETE_POLICY + PRECOMMIT_POSTFIX
+
+QOS_CALL_METHODS = (
+    CREATE_POLICY,
+    CREATE_POLICY_PRECOMMIT,
+    UPDATE_POLICY,
+    UPDATE_POLICY_PRECOMMIT,
+    DELETE_POLICY,
+    DELETE_POLICY_PRECOMMIT, )
diff --git a/neutron_tempest_plugin/common/ssh.py b/neutron_tempest_plugin/common/ssh.py
new file mode 100644
index 0000000..b919b65
--- /dev/null
+++ b/neutron_tempest_plugin/common/ssh.py
@@ -0,0 +1,24 @@
+# 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 import ssh
+
+from neutron_tempest_plugin import config
+
+
+class Client(ssh.Client):
+    def __init__(self, *args, **kwargs):
+        if 'timeout' not in kwargs:
+            kwargs['timeout'] = config.CONF.validation.ssh_timeout
+        super(Client, self).__init__(*args, **kwargs)
diff --git a/neutron_tempest_plugin/common/tempest_fixtures.py b/neutron_tempest_plugin/common/tempest_fixtures.py
new file mode 100644
index 0000000..d416857
--- /dev/null
+++ b/neutron_tempest_plugin/common/tempest_fixtures.py
@@ -0,0 +1,21 @@
+# Copyright 2013 IBM Corp.
+# 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_concurrency.fixture import lockutils
+
+
+class LockFixture(lockutils.LockFixture):
+    def __init__(self, name):
+        super(LockFixture, self).__init__(name, 'tempest-')
diff --git a/neutron_tempest_plugin/common/utils.py b/neutron_tempest_plugin/common/utils.py
new file mode 100644
index 0000000..ecccd18
--- /dev/null
+++ b/neutron_tempest_plugin/common/utils.py
@@ -0,0 +1,72 @@
+# Copyright 2011, VMware, 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.
+#
+# Borrowed from nova code base, more utilities will be added/borrowed as and
+# when needed.
+
+"""Utilities and helper functions."""
+
+import eventlet
+import threading
+import time
+
+
+class classproperty(object):
+    def __init__(self, f):
+        self.func = f
+
+    def __get__(self, obj, owner):
+        return self.func(owner)
+
+
+class WaitTimeout(Exception):
+    """Default exception coming from wait_until_true() function."""
+
+
+class LockWithTimer(object):
+    def __init__(self, threshold):
+        self._threshold = threshold
+        self.timestamp = 0
+        self._lock = threading.Lock()
+
+    def acquire(self):
+        return self._lock.acquire(False)
+
+    def release(self):
+        return self._lock.release()
+
+    def time_to_wait(self):
+        return self.timestamp - time.time() + self._threshold
+
+
+def wait_until_true(predicate, timeout=60, sleep=1, exception=None):
+    """
+    Wait until callable predicate is evaluated as True
+    :param predicate: Callable deciding whether waiting should continue.
+    Best practice is to instantiate predicate with functools.partial()
+    :param timeout: Timeout in seconds how long should function wait.
+    :param sleep: Polling interval for results in seconds.
+    :param exception: Exception instance to raise on timeout. If None is passed
+                      (default) then WaitTimeout exception is raised.
+    """
+    try:
+        with eventlet.Timeout(timeout):
+            while not predicate():
+                eventlet.sleep(sleep)
+    except eventlet.Timeout:
+        if exception is not None:
+            #pylint: disable=raising-bad-type
+            raise exception
+        raise WaitTimeout("Timed out after %d seconds" % timeout)
diff --git a/neutron_tempest_plugin/config.py b/neutron_tempest_plugin/config.py
new file mode 100644
index 0000000..6830b18
--- /dev/null
+++ b/neutron_tempest_plugin/config.py
@@ -0,0 +1,59 @@
+#    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 import config
+
+
+CONF = config.CONF
+
+
+NeutronPluginOptions = [
+    cfg.ListOpt('provider_vlans',
+                default=[],
+                help='List of provider networks available in the deployment.'),
+    cfg.BoolOpt('specify_floating_ip_address_available',
+                default=True,
+                help='Allow passing an IP Address of the floating ip when '
+                     'creating the floating ip'),
+    cfg.ListOpt('available_type_drivers',
+                default=[],
+                help='List of network types available to neutron, '
+                     'e.g. vxlan,vlan,gre.'),
+    cfg.BoolOpt('image_is_advanced',
+                default=False,
+                help='Image that supports features that cirros does not, like'
+                     ' Ubuntu or CentOS supporting advanced features'),
+]
+
+# TODO(amuller): Redo configuration options registration as part of the planned
+# transition to the Tempest plugin architecture
+for opt in NeutronPluginOptions:
+    CONF.register_opt(opt, 'neutron_plugin_options')
+
+
+config_opts_translator = {
+    'project_network_cidr': 'tenant_network_cidr',
+    'project_network_v6_cidr': 'tenant_network_v6_cidr',
+    'project_network_mask_bits': 'tenant_network_mask_bits',
+    'project_network_v6_mask_bits': 'tenant_network_v6_mask_bits'}
+
+
+def safe_get_config_value(group, name):
+    """Safely get Oslo config opts from Tempest, using old and new names."""
+    conf_group = getattr(CONF, group)
+
+    try:
+        return getattr(conf_group, name)
+    except cfg.NoSuchOptError:
+        return getattr(conf_group, config_opts_translator[name])
diff --git a/neutron_tempest_plugin/exceptions.py b/neutron_tempest_plugin/exceptions.py
new file mode 100644
index 0000000..c9264ca
--- /dev/null
+++ b/neutron_tempest_plugin/exceptions.py
@@ -0,0 +1,30 @@
+# 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 exceptions
+
+TempestException = exceptions.TempestException
+
+
+class InvalidConfiguration(TempestException):
+    message = "Invalid Configuration"
+
+
+class InvalidCredentials(TempestException):
+    message = "Invalid Credentials"
+
+
+class InvalidServiceTag(TempestException):
+    message = "Invalid service tag"
diff --git a/neutron_tempest_plugin/plugin.py b/neutron_tempest_plugin/plugin.py
new file mode 100644
index 0000000..fc41bdd
--- /dev/null
+++ b/neutron_tempest_plugin/plugin.py
@@ -0,0 +1,34 @@
+# Copyright 2015
+# 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 os
+
+from tempest.test_discover import plugins
+
+
+class NeutronTempestPlugin(plugins.TempestPlugin):
+    def load_tests(self):
+        base_path = os.path.split(os.path.dirname(
+            os.path.abspath(__file__)))[0]
+        test_dir = "neutron_tempest_plugin"
+        full_test_dir = os.path.join(base_path, test_dir)
+        return full_test_dir, base_path
+
+    def register_opts(self, conf):
+        pass
+
+    def get_opt_lists(self):
+        pass
diff --git a/neutron_tempest_plugin/scenario/__init__.py b/neutron_tempest_plugin/scenario/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/neutron_tempest_plugin/scenario/__init__.py
diff --git a/neutron_tempest_plugin/scenario/base.py b/neutron_tempest_plugin/scenario/base.py
new file mode 100644
index 0000000..e810490
--- /dev/null
+++ b/neutron_tempest_plugin/scenario/base.py
@@ -0,0 +1,278 @@
+# Copyright 2016 Red Hat, 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.
+
+import netaddr
+from oslo_log import log
+from tempest.common.utils import net_utils
+from tempest.common import waiters
+from tempest.lib.common.utils import data_utils
+from tempest.lib.common.utils import test_utils
+from tempest.lib import exceptions as lib_exc
+
+from neutron_tempest_plugin.api import base as base_api
+from neutron_tempest_plugin.common import ssh
+from neutron_tempest_plugin import config
+from neutron_tempest_plugin.scenario import constants
+
+CONF = config.CONF
+
+LOG = log.getLogger(__name__)
+
+
+class BaseTempestTestCase(base_api.BaseNetworkTest):
+    @classmethod
+    def resource_setup(cls):
+        super(BaseTempestTestCase, cls).resource_setup()
+
+        cls.keypairs = []
+
+    @classmethod
+    def resource_cleanup(cls):
+        for keypair in cls.keypairs:
+            cls.os_primary.keypairs_client.delete_keypair(
+                keypair_name=keypair['name'])
+
+        super(BaseTempestTestCase, cls).resource_cleanup()
+
+    def create_server(self, flavor_ref, image_ref, key_name, networks,
+                      name=None, security_groups=None):
+        """Create a server using tempest lib
+        All the parameters are the ones used in Compute API
+
+        Args:
+           flavor_ref(str): The flavor of the server to be provisioned.
+           image_ref(str):  The image of the server to be provisioned.
+           key_name(str): SSH key to to be used to connect to the
+                            provisioned server.
+           networks(list): List of dictionaries where each represent
+               an interface to be attached to the server. For network
+               it should be {'uuid': network_uuid} and for port it should
+               be {'port': port_uuid}
+           name(str): Name of the server to be provisioned.
+           security_groups(list): List of dictionaries where
+                the keys is 'name' and the value is the name of
+                the security group. If it's not passed the default
+                security group will be used.
+        """
+
+        name = name or data_utils.rand_name('server-test')
+        if not security_groups:
+            security_groups = [{'name': 'default'}]
+
+        server = self.os_primary.servers_client.create_server(
+            name=name,
+            flavorRef=flavor_ref,
+            imageRef=image_ref,
+            key_name=key_name,
+            networks=networks,
+            security_groups=security_groups)
+
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+            waiters.wait_for_server_termination,
+            self.os_primary.servers_client, server['server']['id'])
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        self.os_primary.servers_client.delete_server,
+                        server['server']['id'])
+        return server
+
+    @classmethod
+    def create_keypair(cls, client=None):
+        client = client or cls.os_primary.keypairs_client
+        name = data_utils.rand_name('keypair-test')
+        body = client.create_keypair(name=name)
+        cls.keypairs.append(body['keypair'])
+        return body['keypair']
+
+    @classmethod
+    def create_secgroup_rules(cls, rule_list, secgroup_id=None):
+        client = cls.os_primary.network_client
+        if not secgroup_id:
+            sgs = client.list_security_groups()['security_groups']
+            for sg in sgs:
+                if sg['name'] == constants.DEFAULT_SECURITY_GROUP:
+                    secgroup_id = sg['id']
+                    break
+
+        for rule in rule_list:
+            direction = rule.pop('direction')
+            client.create_security_group_rule(
+                direction=direction,
+                security_group_id=secgroup_id,
+                **rule)
+
+    @classmethod
+    def create_loginable_secgroup_rule(cls, secgroup_id=None):
+        """This rule is intended to permit inbound ssh
+
+        Allowing ssh traffic traffic from all sources, so no group_id is
+        provided.
+        Setting a group_id would only permit traffic from ports
+        belonging to the same security group.
+        """
+
+        rule_list = [{'protocol': 'tcp',
+                      'direction': 'ingress',
+                      'port_range_min': 22,
+                      'port_range_max': 22,
+                      'remote_ip_prefix': '0.0.0.0/0'}]
+        cls.create_secgroup_rules(rule_list, secgroup_id=secgroup_id)
+
+    @classmethod
+    def create_pingable_secgroup_rule(cls, secgroup_id=None):
+        """This rule is intended to permit inbound ping
+        """
+
+        rule_list = [{'protocol': 'icmp',
+                      'direction': 'ingress',
+                      'port_range_min': 8,  # type
+                      'port_range_max': 0,  # code
+                      'remote_ip_prefix': '0.0.0.0/0'}]
+        cls.create_secgroup_rules(rule_list, secgroup_id=secgroup_id)
+
+    @classmethod
+    def create_router_by_client(cls, is_admin=False, **kwargs):
+        kwargs.update({'router_name': data_utils.rand_name('router'),
+                       'admin_state_up': True,
+                       'external_network_id': CONF.network.public_network_id})
+        if not is_admin:
+            router = cls.create_router(**kwargs)
+        else:
+            router = cls.create_admin_router(**kwargs)
+        LOG.debug("Created router %s", router['name'])
+        cls.routers.append(router)
+        return router
+
+    def create_and_associate_floatingip(self, port_id):
+        fip = self.os_primary.network_client.create_floatingip(
+            CONF.network.public_network_id,
+            port_id=port_id)['floatingip']
+        self.floating_ips.append(fip)
+        return fip
+
+    def setup_network_and_server(self, router=None, **kwargs):
+        """Create network resources and a server.
+
+        Creating a network, subnet, router, keypair, security group
+        and a server.
+        """
+        self.network = self.create_network()
+        LOG.debug("Created network %s", self.network['name'])
+        self.subnet = self.create_subnet(self.network)
+        LOG.debug("Created subnet %s", self.subnet['id'])
+
+        secgroup = self.os_primary.network_client.create_security_group(
+            name=data_utils.rand_name('secgroup-'))
+        LOG.debug("Created security group %s",
+                  secgroup['security_group']['name'])
+        self.security_groups.append(secgroup['security_group'])
+        if not router:
+            router = self.create_router_by_client(**kwargs)
+        self.create_router_interface(router['id'], self.subnet['id'])
+        self.keypair = self.create_keypair()
+        self.create_loginable_secgroup_rule(
+            secgroup_id=secgroup['security_group']['id'])
+        self.server = self.create_server(
+            flavor_ref=CONF.compute.flavor_ref,
+            image_ref=CONF.compute.image_ref,
+            key_name=self.keypair['name'],
+            networks=[{'uuid': self.network['id']}],
+            security_groups=[{'name': secgroup['security_group']['name']}])
+        waiters.wait_for_server_status(self.os_primary.servers_client,
+                                       self.server['server']['id'],
+                                       constants.SERVER_STATUS_ACTIVE)
+        self.port = self.client.list_ports(network_id=self.network['id'],
+                                           device_id=self.server[
+                                               'server']['id'])['ports'][0]
+        self.fip = self.create_and_associate_floatingip(self.port['id'])
+
+    def check_connectivity(self, host, ssh_user, ssh_key, servers=None):
+        ssh_client = ssh.Client(host, ssh_user, pkey=ssh_key)
+        try:
+            ssh_client.test_connection_auth()
+        except lib_exc.SSHTimeout as ssh_e:
+            LOG.debug(ssh_e)
+            self._log_console_output(servers)
+            raise
+
+    def _log_console_output(self, servers=None):
+        if not CONF.compute_feature_enabled.console_output:
+            LOG.debug('Console output not supported, cannot log')
+            return
+        if not servers:
+            servers = self.os_primary.servers_client.list_servers()
+            servers = servers['servers']
+        for server in servers:
+            try:
+                console_output = (
+                    self.os_primary.servers_client.get_console_output(
+                        server['id'])['output'])
+                LOG.debug('Console output for %s\nbody=\n%s',
+                          server['id'], console_output)
+            except lib_exc.NotFound:
+                LOG.debug("Server %s disappeared(deleted) while looking "
+                          "for the console log", server['id'])
+
+    def _check_remote_connectivity(self, source, dest, should_succeed=True,
+                                   nic=None, mtu=None, fragmentation=True):
+        """check ping server via source ssh connection
+
+        :param source: RemoteClient: an ssh connection from which to ping
+        :param dest: and IP to ping against
+        :param should_succeed: boolean should ping succeed or not
+        :param nic: specific network interface to ping from
+        :param mtu: mtu size for the packet to be sent
+        :param fragmentation: Flag for packet fragmentation
+        :returns: boolean -- should_succeed == ping
+        :returns: ping is false if ping failed
+        """
+        def ping_host(source, host, count=CONF.validation.ping_count,
+                      size=CONF.validation.ping_size, nic=None, mtu=None,
+                      fragmentation=True):
+            addr = netaddr.IPAddress(host)
+            cmd = 'ping6' if addr.version == 6 else 'ping'
+            if nic:
+                cmd = 'sudo {cmd} -I {nic}'.format(cmd=cmd, nic=nic)
+            if mtu:
+                if not fragmentation:
+                    cmd += ' -M do'
+                size = str(net_utils.get_ping_payload_size(
+                    mtu=mtu, ip_version=addr.version))
+            cmd += ' -c{0} -w{0} -s{1} {2}'.format(count, size, host)
+            return source.exec_command(cmd)
+
+        def ping_remote():
+            try:
+                result = ping_host(source, dest, nic=nic, mtu=mtu,
+                                   fragmentation=fragmentation)
+
+            except lib_exc.SSHExecCommandFailed:
+                LOG.warning('Failed to ping IP: %s via a ssh connection '
+                            'from: %s.', dest, source.host)
+                return not should_succeed
+            LOG.debug('ping result: %s', result)
+            # Assert that the return traffic was from the correct
+            # source address.
+            from_source = 'from %s' % dest
+            self.assertIn(from_source, result)
+            return should_succeed
+
+        return test_utils.call_until_true(ping_remote,
+                                          CONF.validation.ping_timeout,
+                                          1)
+
+    def check_remote_connectivity(self, source, dest, should_succeed=True,
+                                  nic=None, mtu=None, fragmentation=True):
+        self.assertTrue(self._check_remote_connectivity(
+            source, dest, should_succeed, nic, mtu, fragmentation))
diff --git a/neutron_tempest_plugin/scenario/constants.py b/neutron_tempest_plugin/scenario/constants.py
new file mode 100644
index 0000000..258c587
--- /dev/null
+++ b/neutron_tempest_plugin/scenario/constants.py
@@ -0,0 +1,18 @@
+# Copyright 2016 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.
+
+SERVER_STATUS_ACTIVE = 'ACTIVE'
+DEFAULT_SECURITY_GROUP = 'default'
+LIMIT_KILO_BITS_PER_SECOND = 1000
+SOCKET_CONNECT_TIMEOUT = 60
diff --git a/neutron_tempest_plugin/scenario/exceptions.py b/neutron_tempest_plugin/scenario/exceptions.py
new file mode 100644
index 0000000..369a85b
--- /dev/null
+++ b/neutron_tempest_plugin/scenario/exceptions.py
@@ -0,0 +1,33 @@
+# Copyright 2016 Red Hat, 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 exceptions
+
+TempestException = exceptions.TempestException
+
+
+class QoSLimitReached(TempestException):
+    message = "Limit reached, limit = %(limit)d"
+
+
+class SocketConnectionRefused(TempestException):
+    message = "Unable to connect to %(host)s port %(port)d:Connection Refused"
+
+
+class ConnectionTimeoutException(TempestException):
+    message = "Timeout connecting to %(host)s port %(port)d"
+
+
+class FileCreationFailedException(TempestException):
+    message = "File %(file)s has not been created or has the wrong size"
diff --git a/neutron_tempest_plugin/scenario/test_basic.py b/neutron_tempest_plugin/scenario/test_basic.py
new file mode 100644
index 0000000..d825e15
--- /dev/null
+++ b/neutron_tempest_plugin/scenario/test_basic.py
@@ -0,0 +1,35 @@
+# Copyright 2016 Red Hat, 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 neutron_tempest_plugin import config
+from neutron_tempest_plugin.scenario import base
+
+CONF = config.CONF
+
+
+class NetworkBasicTest(base.BaseTempestTestCase):
+    credentials = ['primary']
+    force_tenant_isolation = False
+
+    # Default to ipv4.
+    _ip_version = 4
+
+    @decorators.idempotent_id('de07fe0a-e955-449e-b48b-8641c14cd52e')
+    def test_basic_instance(self):
+        self.setup_network_and_server()
+        self.check_connectivity(self.fip['floating_ip_address'],
+                                CONF.validation.image_ssh_user,
+                                self.keypair['private_key'])
diff --git a/neutron_tempest_plugin/scenario/test_dvr.py b/neutron_tempest_plugin/scenario/test_dvr.py
new file mode 100644
index 0000000..3da0694
--- /dev/null
+++ b/neutron_tempest_plugin/scenario/test_dvr.py
@@ -0,0 +1,66 @@
+# Copyright 2016 Red Hat, 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 import test
+
+from neutron_lib import constants
+from neutron_tempest_plugin import config
+from neutron_tempest_plugin.scenario import base
+
+CONF = config.CONF
+
+
+class NetworkTestMixin(object):
+    def _check_connectivity(self):
+        self.check_connectivity(self.fip['floating_ip_address'],
+                                CONF.validation.image_ssh_user,
+                                self.keypair['private_key'])
+
+    def _check_snat_port_connectivity(self):
+        self._check_connectivity()
+
+        # Put the Router_SNAT port down to make sure the traffic flows through
+        # Compute node.
+        self._put_snat_port_down(self.network['id'])
+        self._check_connectivity()
+
+    def _put_snat_port_down(self, network_id):
+        port_id = self.client.list_ports(
+            network_id=network_id,
+            device_owner=constants.DEVICE_OWNER_ROUTER_SNAT)['ports'][0]['id']
+        self.os_admin.network_client.update_port(
+            port_id, admin_state_up=False)
+
+
+class NetworkDvrTest(base.BaseTempestTestCase, NetworkTestMixin):
+    credentials = ['primary', 'admin']
+    force_tenant_isolation = False
+
+    @classmethod
+    @test.requires_ext(extension="dvr", service="network")
+    def skip_checks(cls):
+        super(NetworkDvrTest, cls).skip_checks()
+
+    @decorators.idempotent_id('3d73ec1a-2ec6-45a9-b0f8-04a283d9d344')
+    def test_vm_reachable_through_compute(self):
+        """Check that the VM is reachable through compute node.
+
+        The test is done by putting the SNAT port down on controller node.
+        """
+        router = self.create_router_by_client(
+            distributed=True, tenant_id=self.client.tenant_id, is_admin=True,
+            ha=False)
+        self.setup_network_and_server(router=router)
+        self._check_snat_port_connectivity()
diff --git a/neutron_tempest_plugin/scenario/test_floatingip.py b/neutron_tempest_plugin/scenario/test_floatingip.py
new file mode 100644
index 0000000..97bfcc5
--- /dev/null
+++ b/neutron_tempest_plugin/scenario/test_floatingip.py
@@ -0,0 +1,156 @@
+# Copyright (c) 2017 Midokura SARL
+# 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.common import waiters
+from tempest.lib.common.utils import data_utils
+from tempest.lib import decorators
+from tempest import test
+import testscenarios
+from testscenarios.scenarios import multiply_scenarios
+
+from neutron_tempest_plugin.common import ssh
+from neutron_tempest_plugin import config
+from neutron_tempest_plugin.scenario import base
+from neutron_tempest_plugin.scenario import constants
+
+
+CONF = config.CONF
+
+
+load_tests = testscenarios.load_tests_apply_scenarios
+
+
+class FloatingIpTestCasesMixin(object):
+    credentials = ['primary', 'admin']
+
+    @classmethod
+    @test.requires_ext(extension="router", service="network")
+    def resource_setup(cls):
+        super(FloatingIpTestCasesMixin, cls).resource_setup()
+        cls.network = cls.create_network()
+        cls.subnet = cls.create_subnet(cls.network)
+        cls.router = cls.create_router_by_client()
+        cls.create_router_interface(cls.router['id'], cls.subnet['id'])
+        cls.keypair = cls.create_keypair()
+
+        cls.secgroup = cls.os_primary.network_client.create_security_group(
+            name=data_utils.rand_name('secgroup-'))['security_group']
+        cls.security_groups.append(cls.secgroup)
+        cls.create_loginable_secgroup_rule(secgroup_id=cls.secgroup['id'])
+        cls.create_pingable_secgroup_rule(secgroup_id=cls.secgroup['id'])
+
+        if cls.same_network:
+            cls._dest_network = cls.network
+        else:
+            cls._dest_network = cls._create_dest_network()
+
+    @classmethod
+    def _create_dest_network(cls):
+        network = cls.create_network()
+        subnet = cls.create_subnet(network,
+            cidr=netaddr.IPNetwork('10.10.0.0/24'))
+        cls.create_router_interface(cls.router['id'], subnet['id'])
+        return network
+
+    def _create_server(self, create_floating_ip=True, network=None):
+        if network is None:
+            network = self.network
+        port = self.create_port(network, security_groups=[self.secgroup['id']])
+        if create_floating_ip:
+            fip = self.create_and_associate_floatingip(port['id'])
+        else:
+            fip = None
+        server = self.create_server(
+            flavor_ref=CONF.compute.flavor_ref,
+            image_ref=CONF.compute.image_ref,
+            key_name=self.keypair['name'],
+            networks=[{'port': port['id']}])['server']
+        waiters.wait_for_server_status(self.os_primary.servers_client,
+                                       server['id'],
+                                       constants.SERVER_STATUS_ACTIVE)
+        return {'port': port, 'fip': fip, 'server': server}
+
+    def _test_east_west(self):
+        # The proxy VM is used to control the source VM when it doesn't
+        # have a floating-ip.
+        if self.src_has_fip:
+            proxy = None
+            proxy_client = None
+        else:
+            proxy = self._create_server()
+            proxy_client = ssh.Client(proxy['fip']['floating_ip_address'],
+                                      CONF.validation.image_ssh_user,
+                                      pkey=self.keypair['private_key'])
+
+        # Source VM
+        if self.src_has_fip:
+            src_server = self._create_server()
+            src_server_ip = src_server['fip']['floating_ip_address']
+        else:
+            src_server = self._create_server(create_floating_ip=False)
+            src_server_ip = src_server['port']['fixed_ips'][0]['ip_address']
+        ssh_client = ssh.Client(src_server_ip,
+                                CONF.validation.image_ssh_user,
+                                pkey=self.keypair['private_key'],
+                                proxy_client=proxy_client)
+
+        # Destination VM
+        if self.dest_has_fip:
+            dest_server = self._create_server(network=self._dest_network)
+        else:
+            dest_server = self._create_server(create_floating_ip=False,
+                                              network=self._dest_network)
+
+        # Check connectivity
+        self.check_remote_connectivity(ssh_client,
+            dest_server['port']['fixed_ips'][0]['ip_address'])
+        if self.dest_has_fip:
+            self.check_remote_connectivity(ssh_client,
+                dest_server['fip']['floating_ip_address'])
+
+
+class FloatingIpSameNetwork(FloatingIpTestCasesMixin,
+                            base.BaseTempestTestCase):
+    scenarios = multiply_scenarios([
+        ('SRC with FIP', dict(src_has_fip=True)),
+        ('SRC without FIP', dict(src_has_fip=False)),
+    ], [
+        ('DEST with FIP', dict(dest_has_fip=True)),
+        ('DEST without FIP', dict(dest_has_fip=False)),
+    ])
+
+    same_network = True
+
+    @decorators.idempotent_id('05c4e3b3-7319-4052-90ad-e8916436c23b')
+    def test_east_west(self):
+        self._test_east_west()
+
+
+class FloatingIpSeparateNetwork(FloatingIpTestCasesMixin,
+                                base.BaseTempestTestCase):
+    scenarios = multiply_scenarios([
+        ('SRC with FIP', dict(src_has_fip=True)),
+        ('SRC without FIP', dict(src_has_fip=False)),
+    ], [
+        ('DEST with FIP', dict(dest_has_fip=True)),
+        ('DEST without FIP', dict(dest_has_fip=False)),
+    ])
+
+    same_network = False
+
+    @decorators.idempotent_id('f18f0090-3289-4783-b956-a0f8ac511e8b')
+    def test_east_west(self):
+        self._test_east_west()
diff --git a/neutron_tempest_plugin/scenario/test_migration.py b/neutron_tempest_plugin/scenario/test_migration.py
new file mode 100644
index 0000000..291611c
--- /dev/null
+++ b/neutron_tempest_plugin/scenario/test_migration.py
@@ -0,0 +1,127 @@
+# Copyright 2017 Red Hat, 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 import test
+
+from neutron_tempest_plugin.scenario import base
+from neutron_tempest_plugin.scenario import test_dvr
+
+
+class NetworkMigrationTestBase(base.BaseTempestTestCase,
+                               test_dvr.NetworkTestMixin):
+    credentials = ['primary', 'admin']
+    force_tenant_isolation = False
+
+    @classmethod
+    @test.requires_ext(extension="dvr", service="network")
+    @test.requires_ext(extension="l3-ha", service="network")
+    def skip_checks(cls):
+        super(NetworkMigrationTestBase, cls).skip_checks()
+
+    def _check_update(self, router, is_dvr, is_ha):
+        router = self.os_admin.network_client.show_router(router['id'])
+        self.assertEqual(is_dvr, router['router']['distributed'])
+        self.assertEqual(is_ha, router['router']['ha'])
+
+    def _test_migration(self, before_dvr, before_ha, after_dvr, after_ha):
+        router = self.create_router_by_client(
+            distributed=before_dvr, ha=before_ha,
+            tenant_id=self.client.tenant_id, is_admin=True)
+
+        self.setup_network_and_server(router=router)
+        self._check_connectivity()
+
+        self.os_admin.network_client.update_router(
+            router_id=router['id'], admin_state_up=False)
+        self.os_admin.network_client.update_router(
+            router_id=router['id'], distributed=after_dvr, ha=after_ha)
+        self._check_update(router, after_dvr, after_ha)
+
+        self.os_admin.network_client.update_router(
+            router_id=router['id'], admin_state_up=True)
+        self._check_connectivity()
+
+
+class NetworkMigrationFromLegacy(NetworkMigrationTestBase):
+
+    @decorators.idempotent_id('23724222-483a-4129-bc15-7a9278f3828b')
+    def test_from_legacy_to_dvr(self):
+        self._test_migration(before_dvr=False, before_ha=False,
+                             after_dvr=True, after_ha=False)
+
+    @decorators.idempotent_id('09d85102-994f-4ff9-bf3e-17051145ca12')
+    def test_from_legacy_to_ha(self):
+        self._test_migration(before_dvr=False, before_ha=False,
+                             after_dvr=False, after_ha=True)
+
+    @decorators.idempotent_id('fe169f2c-6ed3-4eb0-8afe-2d540c4b49e2')
+    def test_from_legacy_to_dvr_ha(self):
+        self._test_migration(before_dvr=False, before_ha=False,
+                             after_dvr=True, after_ha=True)
+
+
+class NetworkMigrationFromHA(NetworkMigrationTestBase):
+
+    @decorators.idempotent_id('b4e68ac0-3b76-4306-ae8a-51cf4d363b22')
+    def test_from_ha_to_legacy(self):
+        self._test_migration(before_dvr=False, before_ha=True,
+                             after_dvr=False, after_ha=False)
+
+    @decorators.idempotent_id('42260eea-5d56-4d30-b62a-a62694dfe4d5')
+    def test_from_ha_to_dvr(self):
+        self._test_migration(before_dvr=False, before_ha=True,
+                             after_dvr=True, after_ha=False)
+
+    @decorators.idempotent_id('e4149576-248b-43fa-9d0b-a5c2f51967ce')
+    def test_from_ha_to_dvr_ha(self):
+        self._test_migration(before_dvr=False, before_ha=True,
+                             after_dvr=True, after_ha=True)
+
+
+class NetworkMigrationFromDVR(NetworkMigrationTestBase):
+
+    @decorators.idempotent_id('e5cac02c-248d-4aac-bd5e-9d47c5197307')
+    def test_from_dvr_to_legacy(self):
+        self._test_migration(before_dvr=True, before_ha=False,
+                             after_dvr=False, after_ha=False)
+
+    @decorators.idempotent_id('a00d5ad7-8509-4bb0-bdd2-7f1ee052d1cd')
+    def test_from_dvr_to_ha(self):
+        self._test_migration(before_dvr=True, before_ha=False,
+                             after_dvr=False, after_ha=True)
+
+    @decorators.idempotent_id('25304a51-93a8-4cf3-9523-bce8b4eaecf8')
+    def test_from_dvr_to_dvr_ha(self):
+        self._test_migration(before_dvr=True, before_ha=False,
+                             after_dvr=True, after_ha=True)
+
+
+class NetworkMigrationFromDVRHA(NetworkMigrationTestBase):
+
+    @decorators.idempotent_id('1be9b2e2-379c-40a4-a269-6687b81df691')
+    def test_from_dvr_ha_to_legacy(self):
+        self._test_migration(before_dvr=True, before_ha=True,
+                             after_dvr=False, after_ha=False)
+
+    @decorators.idempotent_id('55957267-4e84-4314-a2f7-7cd36a2df04b')
+    def test_from_dvr_ha_to_ha(self):
+        self._test_migration(before_dvr=True, before_ha=True,
+                             after_dvr=False, after_ha=True)
+
+    @decorators.idempotent_id('d6bedff1-72be-4a9a-8ea2-dc037cd838e0')
+    def test_from_dvr_ha_to_dvr(self):
+        self._test_migration(before_dvr=True, before_ha=True,
+                             after_dvr=True, after_ha=False)
diff --git a/neutron_tempest_plugin/scenario/test_portsecurity.py b/neutron_tempest_plugin/scenario/test_portsecurity.py
new file mode 100644
index 0000000..257627c
--- /dev/null
+++ b/neutron_tempest_plugin/scenario/test_portsecurity.py
@@ -0,0 +1,53 @@
+# 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 neutron_tempest_plugin import config
+from neutron_tempest_plugin.scenario import base
+
+CONF = config.CONF
+
+
+class PortSecurityTest(base.BaseTempestTestCase):
+    credentials = ['primary']
+    required_extensions = ['port-security']
+
+    @decorators.idempotent_id('61ab176e-d48b-42b7-b38a-1ba571ecc033')
+    def test_port_security_removed_added(self):
+        """Test connection works after port security has been removed
+
+        Initial test that vm is accessible. Then port security is removed,
+        checked connectivity. Port security is added back and checked
+        connectivity again.
+        """
+        self.setup_network_and_server()
+        self.check_connectivity(self.fip['floating_ip_address'],
+                                CONF.validation.image_ssh_user,
+                                self.keypair['private_key'])
+        sec_group_id = self.security_groups[0]['id']
+
+        self.port = self.update_port(port=self.port,
+                                     port_security_enabled=False,
+                                     security_groups=[])
+        self.check_connectivity(self.fip['floating_ip_address'],
+                                CONF.validation.image_ssh_user,
+                                self.keypair['private_key'])
+
+        self.port = self.update_port(port=self.port,
+                                     port_security_enabled=True,
+                                     security_groups=[sec_group_id])
+        self.check_connectivity(self.fip['floating_ip_address'],
+                                CONF.validation.image_ssh_user,
+                                self.keypair['private_key'])
diff --git a/neutron_tempest_plugin/scenario/test_qos.py b/neutron_tempest_plugin/scenario/test_qos.py
new file mode 100644
index 0000000..d93f57f
--- /dev/null
+++ b/neutron_tempest_plugin/scenario/test_qos.py
@@ -0,0 +1,175 @@
+# Copyright 2016 Red Hat, 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.
+import errno
+import socket
+import time
+
+from oslo_log import log as logging
+from tempest.lib import decorators
+from tempest.lib import exceptions
+from tempest import test
+
+from neutron_tempest_plugin.api import base as base_api
+from neutron_tempest_plugin.common import qos_consts
+from neutron_tempest_plugin.common import ssh
+from neutron_tempest_plugin.common import utils
+from neutron_tempest_plugin import config
+from neutron_tempest_plugin.scenario import base
+from neutron_tempest_plugin.scenario import constants
+from neutron_tempest_plugin.scenario import exceptions as sc_exceptions
+
+CONF = config.CONF
+LOG = logging.getLogger(__name__)
+
+
+def _try_connect(host_ip, port):
+    try:
+        client_socket = socket.socket(socket.AF_INET,
+                                      socket.SOCK_STREAM)
+        client_socket.connect((host_ip, port))
+        return client_socket
+    except socket.error as serr:
+        if serr.errno == errno.ECONNREFUSED:
+            raise sc_exceptions.SocketConnectionRefused(host=host_ip,
+                                                        port=port)
+        else:
+            raise
+
+
+def _connect_socket(host, port):
+    """Try to initiate a connection to a host using an ip address
+    and a port.
+
+    Trying couple of times until a timeout is reached in case the listening
+    host is not ready yet.
+    """
+
+    start = time.time()
+    while True:
+        try:
+            return _try_connect(host, port)
+        except sc_exceptions.SocketConnectionRefused:
+            if time.time() - start > constants.SOCKET_CONNECT_TIMEOUT:
+                raise sc_exceptions.ConnectionTimeoutException(host=host,
+                                                               port=port)
+
+
+class QoSTest(base.BaseTempestTestCase):
+    credentials = ['primary', 'admin']
+    force_tenant_isolation = False
+
+    BUFFER_SIZE = 1024 * 1024
+    TOLERANCE_FACTOR = 1.5
+    BS = 512
+    COUNT = BUFFER_SIZE / BS
+    FILE_SIZE = BS * COUNT
+    LIMIT_BYTES_SEC = (constants.LIMIT_KILO_BITS_PER_SECOND * 1024
+                       * TOLERANCE_FACTOR / 8.0)
+    FILE_PATH = "/tmp/img"
+
+    @classmethod
+    @test.requires_ext(extension="qos", service="network")
+    @base_api.require_qos_rule_type(qos_consts.RULE_TYPE_BANDWIDTH_LIMIT)
+    def resource_setup(cls):
+        super(QoSTest, cls).resource_setup()
+
+    def _create_file_for_bw_tests(self, ssh_client):
+        cmd = ("(dd if=/dev/zero bs=%(bs)d count=%(count)d of=%(file_path)s) "
+               % {'bs': QoSTest.BS, 'count': QoSTest.COUNT,
+               'file_path': QoSTest.FILE_PATH})
+        ssh_client.exec_command(cmd)
+        cmd = "stat -c %%s %s" % QoSTest.FILE_PATH
+        filesize = ssh_client.exec_command(cmd)
+        if int(filesize.strip()) != QoSTest.FILE_SIZE:
+            raise sc_exceptions.FileCreationFailedException(
+                file=QoSTest.FILE_PATH)
+
+    def _check_bw(self, ssh_client, host, port):
+        cmd = "killall -q nc"
+        try:
+            ssh_client.exec_command(cmd)
+        except exceptions.SSHExecCommandFailed:
+            pass
+        cmd = ("(nc -ll -p %(port)d < %(file_path)s > /dev/null &)" % {
+                'port': port, 'file_path': QoSTest.FILE_PATH})
+        ssh_client.exec_command(cmd)
+
+        start_time = time.time()
+        client_socket = _connect_socket(host, port)
+        total_bytes_read = 0
+
+        while total_bytes_read < QoSTest.FILE_SIZE:
+            data = client_socket.recv(QoSTest.BUFFER_SIZE)
+            total_bytes_read += len(data)
+
+        time_elapsed = time.time() - start_time
+        bytes_per_second = total_bytes_read / time_elapsed
+
+        LOG.debug("time_elapsed = %(time_elapsed)d, "
+                  "total_bytes_read = %(total_bytes_read)d, "
+                  "bytes_per_second = %(bytes_per_second)d",
+                  {'time_elapsed': time_elapsed,
+                   'total_bytes_read': total_bytes_read,
+                   'bytes_per_second': bytes_per_second})
+
+        return bytes_per_second <= QoSTest.LIMIT_BYTES_SEC
+
+    @decorators.idempotent_id('1f7ed39b-428f-410a-bd2b-db9f465680df')
+    def test_qos(self):
+        """This is a basic test that check that a QoS policy with
+
+           a bandwidth limit rule is applied correctly by sending
+           a file from the instance to the test node.
+           Then calculating the bandwidth every ~1 sec by the number of bits
+           received / elapsed time.
+        """
+
+        NC_PORT = 1234
+
+        self.setup_network_and_server()
+        self.check_connectivity(self.fip['floating_ip_address'],
+                                CONF.validation.image_ssh_user,
+                                self.keypair['private_key'])
+        rulesets = [{'protocol': 'tcp',
+                     'direction': 'ingress',
+                     'port_range_min': NC_PORT,
+                     'port_range_max': NC_PORT,
+                     'remote_ip_prefix': '0.0.0.0/0'}]
+        self.create_secgroup_rules(rulesets,
+                                   self.security_groups[-1]['id'])
+
+        ssh_client = ssh.Client(self.fip['floating_ip_address'],
+                                CONF.validation.image_ssh_user,
+                                pkey=self.keypair['private_key'])
+        policy = self.os_admin.network_client.create_qos_policy(
+                                       name='test-policy',
+                                       description='test-qos-policy',
+                                       shared=True)
+        policy_id = policy['policy']['id']
+        self.os_admin.network_client.create_bandwidth_limit_rule(
+            policy_id, max_kbps=constants.LIMIT_KILO_BITS_PER_SECOND,
+            max_burst_kbps=constants.LIMIT_KILO_BITS_PER_SECOND)
+        port = self.client.list_ports(network_id=self.network['id'],
+                                      device_id=self.server[
+                                      'server']['id'])['ports'][0]
+        self.os_admin.network_client.update_port(port['id'],
+                                                 qos_policy_id=policy_id)
+        self._create_file_for_bw_tests(ssh_client)
+        utils.wait_until_true(lambda: self._check_bw(
+            ssh_client,
+            self.fip['floating_ip_address'],
+            port=NC_PORT),
+            timeout=120,
+            sleep=1)
diff --git a/neutron_tempest_plugin/scenario/test_trunk.py b/neutron_tempest_plugin/scenario/test_trunk.py
new file mode 100644
index 0000000..95906a0
--- /dev/null
+++ b/neutron_tempest_plugin/scenario/test_trunk.py
@@ -0,0 +1,266 @@
+# 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_log import log as logging
+from tempest.common import waiters
+from tempest.lib.common.utils import data_utils
+from tempest.lib import decorators
+from tempest import test
+import testtools
+
+from neutron_tempest_plugin.common import ssh
+from neutron_tempest_plugin.common import utils
+from neutron_tempest_plugin import config
+from neutron_tempest_plugin.scenario import base
+from neutron_tempest_plugin.scenario import constants
+
+LOG = logging.getLogger(__name__)
+CONF = config.CONF
+
+CONFIGURE_VLAN_INTERFACE_COMMANDS = (
+    'IFACE=$(PATH=$PATH:/usr/sbin ip l | grep "^[0-9]*: e" |'
+    'cut -d \: -f 2) && '
+    'sudo su -c '
+    '"ip l a link $IFACE name $IFACE.%(tag)d type vlan id %(tag)d &&'
+    'ip l s up dev $IFACE.%(tag)d && '
+    'dhclient $IFACE.%(tag)d"')
+
+
+def get_next_subnet(cidr):
+    return netaddr.IPNetwork(cidr).next()
+
+
+class TrunkTest(base.BaseTempestTestCase):
+    credentials = ['primary']
+    force_tenant_isolation = False
+
+    @classmethod
+    @test.requires_ext(extension="trunk", service="network")
+    def resource_setup(cls):
+        super(TrunkTest, cls).resource_setup()
+        # setup basic topology for servers we can log into
+        cls.network = cls.create_network()
+        cls.subnet = cls.create_subnet(cls.network)
+        router = cls.create_router_by_client()
+        cls.create_router_interface(router['id'], cls.subnet['id'])
+        cls.keypair = cls.create_keypair()
+        cls.secgroup = cls.os_primary.network_client.create_security_group(
+            name=data_utils.rand_name('secgroup-'))
+        cls.security_groups.append(cls.secgroup['security_group'])
+        cls.create_loginable_secgroup_rule(
+            secgroup_id=cls.secgroup['security_group']['id'])
+
+    def _create_server_with_trunk_port(self):
+        port = self.create_port(self.network, security_groups=[
+            self.secgroup['security_group']['id']])
+        trunk = self.client.create_trunk(port['id'], subports=[])['trunk']
+        server, fip = self._create_server_with_fip(port['id'])
+        self.addCleanup(self._detach_and_delete_trunk, server, trunk)
+        return {'port': port, 'trunk': trunk, 'fip': fip,
+                'server': server}
+
+    def _create_server_with_fip(self, port_id, **server_kwargs):
+        fip = self.create_and_associate_floatingip(port_id)
+        return (
+            self.create_server(
+                flavor_ref=CONF.compute.flavor_ref,
+                image_ref=CONF.compute.image_ref,
+                key_name=self.keypair['name'],
+                networks=[{'port': port_id}],
+                security_groups=[{'name': self.secgroup[
+                    'security_group']['name']}],
+                **server_kwargs)['server'],
+            fip)
+
+    def _detach_and_delete_trunk(self, server, trunk):
+        # we have to detach the interface from the server before
+        # the trunk can be deleted.
+        self.os_primary.compute.InterfacesClient().delete_interface(
+            server['id'], trunk['port_id'])
+
+        def is_port_detached():
+            p = self.client.show_port(trunk['port_id'])['port']
+            return p['device_id'] == ''
+        utils.wait_until_true(is_port_detached)
+        self.client.delete_trunk(trunk['id'])
+
+    def _is_port_down(self, port_id):
+        p = self.client.show_port(port_id)['port']
+        return p['status'] == 'DOWN'
+
+    def _is_port_active(self, port_id):
+        p = self.client.show_port(port_id)['port']
+        return p['status'] == 'ACTIVE'
+
+    def _is_trunk_active(self, trunk_id):
+        t = self.client.show_trunk(trunk_id)['trunk']
+        return t['status'] == 'ACTIVE'
+
+    def _create_server_with_port_and_subport(self, vlan_network, vlan_tag):
+        parent_port = self.create_port(self.network, security_groups=[
+            self.secgroup['security_group']['id']])
+        port_for_subport = self.create_port(
+            vlan_network,
+            security_groups=[self.secgroup['security_group']['id']],
+            mac_address=parent_port['mac_address'])
+        subport = {
+            'port_id': port_for_subport['id'],
+            'segmentation_type': 'vlan',
+            'segmentation_id': vlan_tag}
+        trunk = self.client.create_trunk(
+            parent_port['id'], subports=[subport])['trunk']
+
+        server, fip = self._create_server_with_fip(parent_port['id'])
+        self.addCleanup(self._detach_and_delete_trunk, server, trunk)
+
+        server_ssh_client = ssh.Client(
+            fip['floating_ip_address'],
+            CONF.validation.image_ssh_user,
+            pkey=self.keypair['private_key'])
+
+        return {
+            'server': server,
+            'fip': fip,
+            'ssh_client': server_ssh_client,
+            'subport': port_for_subport,
+        }
+
+    def _wait_for_server(self, server):
+        waiters.wait_for_server_status(self.os_primary.servers_client,
+                                       server['server']['id'],
+                                       constants.SERVER_STATUS_ACTIVE)
+        self.check_connectivity(server['fip']['floating_ip_address'],
+                                CONF.validation.image_ssh_user,
+                                self.keypair['private_key'])
+
+    @decorators.idempotent_id('bb13fe28-f152-4000-8131-37890a40c79e')
+    def test_trunk_subport_lifecycle(self):
+        """Test trunk creation and subport transition to ACTIVE status.
+
+        This is a basic test for the trunk extension to ensure that we
+        can create a trunk, attach it to a server, add/remove subports,
+        while ensuring the status transitions as appropriate.
+
+        This test does not assert any dataplane behavior for the subports.
+        It's just a high-level check to ensure the agents claim to have
+        wired the port correctly and that the trunk port itself maintains
+        connectivity.
+        """
+        server1 = self._create_server_with_trunk_port()
+        server2 = self._create_server_with_trunk_port()
+        for server in (server1, server2):
+            self._wait_for_server(server)
+        trunk1_id, trunk2_id = server1['trunk']['id'], server2['trunk']['id']
+        # trunks should transition to ACTIVE without any subports
+        utils.wait_until_true(
+            lambda: self._is_trunk_active(trunk1_id),
+            exception=RuntimeError("Timed out waiting for trunk %s to "
+                                   "transition to ACTIVE." % trunk1_id))
+        utils.wait_until_true(
+            lambda: self._is_trunk_active(trunk2_id),
+            exception=RuntimeError("Timed out waiting for trunk %s to "
+                                   "transition to ACTIVE." % trunk2_id))
+        # create a few more networks and ports for subports
+        subports = [{'port_id': self.create_port(self.create_network())['id'],
+                     'segmentation_type': 'vlan', 'segmentation_id': seg_id}
+                    for seg_id in range(3, 7)]
+        # add all subports to server1
+        self.client.add_subports(trunk1_id, subports)
+        # ensure trunk transitions to ACTIVE
+        utils.wait_until_true(
+            lambda: self._is_trunk_active(trunk1_id),
+            exception=RuntimeError("Timed out waiting for trunk %s to "
+                                   "transition to ACTIVE." % trunk1_id))
+        # ensure all underlying subports transitioned to ACTIVE
+        for s in subports:
+            utils.wait_until_true(lambda: self._is_port_active(s['port_id']))
+        # ensure main dataplane wasn't interrupted
+        self.check_connectivity(server1['fip']['floating_ip_address'],
+                                CONF.validation.image_ssh_user,
+                                self.keypair['private_key'])
+        # move subports over to other server
+        self.client.remove_subports(trunk1_id, subports)
+        # ensure all subports go down
+        for s in subports:
+            utils.wait_until_true(
+                lambda: self._is_port_down(s['port_id']),
+                exception=RuntimeError("Timed out waiting for subport %s to "
+                                       "transition to DOWN." % s['port_id']))
+        self.client.add_subports(trunk2_id, subports)
+        # wait for both trunks to go back to ACTIVE
+        utils.wait_until_true(
+            lambda: self._is_trunk_active(trunk1_id),
+            exception=RuntimeError("Timed out waiting for trunk %s to "
+                                   "transition to ACTIVE." % trunk1_id))
+        utils.wait_until_true(
+            lambda: self._is_trunk_active(trunk2_id),
+            exception=RuntimeError("Timed out waiting for trunk %s to "
+                                   "transition to ACTIVE." % trunk2_id))
+        # ensure subports come up on other trunk
+        for s in subports:
+            utils.wait_until_true(
+                lambda: self._is_port_active(s['port_id']),
+                exception=RuntimeError("Timed out waiting for subport %s to "
+                                       "transition to ACTIVE." % s['port_id']))
+        # final connectivity check
+        self.check_connectivity(server1['fip']['floating_ip_address'],
+                                CONF.validation.image_ssh_user,
+                                self.keypair['private_key'])
+        self.check_connectivity(server2['fip']['floating_ip_address'],
+                                CONF.validation.image_ssh_user,
+                                self.keypair['private_key'])
+
+    @testtools.skipUnless(
+          CONF.neutron_plugin_options.image_is_advanced,
+          "Advanced image is required to run this test.")
+    @decorators.idempotent_id('a8a02c9b-b453-49b5-89a2-cce7da66aafb')
+    def test_subport_connectivity(self):
+        vlan_tag = 10
+
+        vlan_network = self.create_network()
+        new_subnet_cidr = get_next_subnet(
+            config.safe_get_config_value('network', 'project_network_cidr'))
+        self.create_subnet(vlan_network, gateway=None, cidr=new_subnet_cidr)
+
+        servers = [
+            self._create_server_with_port_and_subport(vlan_network, vlan_tag)
+            for i in range(2)]
+
+        for server in servers:
+            self._wait_for_server(server)
+            # Configure VLAN interfaces on server
+            command = CONFIGURE_VLAN_INTERFACE_COMMANDS % {'tag': vlan_tag}
+            server['ssh_client'].exec_command(command)
+            out = server['ssh_client'].exec_command(
+                'PATH=$PATH:/usr/sbin;ip addr list')
+            LOG.debug("Interfaces on server %s: %s", server, out)
+
+        # Ping from server1 to server2 via VLAN interface should fail because
+        # we haven't allowed ICMP
+        self.check_remote_connectivity(
+            servers[0]['ssh_client'],
+            servers[1]['subport']['fixed_ips'][0]['ip_address'],
+            should_succeed=False
+        )
+        # allow intra-securitygroup traffic
+        self.client.create_security_group_rule(
+            security_group_id=self.secgroup['security_group']['id'],
+            direction='ingress', ethertype='IPv4', protocol='icmp',
+            remote_group_id=self.secgroup['security_group']['id'])
+        self.check_remote_connectivity(
+            servers[0]['ssh_client'],
+            servers[1]['subport']['fixed_ips'][0]['ip_address'],
+            should_succeed=True
+        )
diff --git a/neutron_tempest_plugin/services/__init__.py b/neutron_tempest_plugin/services/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/neutron_tempest_plugin/services/__init__.py
diff --git a/neutron_tempest_plugin/services/network/__init__.py b/neutron_tempest_plugin/services/network/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/neutron_tempest_plugin/services/network/__init__.py
diff --git a/neutron_tempest_plugin/services/network/json/__init__.py b/neutron_tempest_plugin/services/network/json/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/neutron_tempest_plugin/services/network/json/__init__.py
diff --git a/neutron_tempest_plugin/services/network/json/network_client.py b/neutron_tempest_plugin/services/network/json/network_client.py
new file mode 100644
index 0000000..48b537d
--- /dev/null
+++ b/neutron_tempest_plugin/services/network/json/network_client.py
@@ -0,0 +1,974 @@
+#    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 time
+
+from oslo_serialization import jsonutils
+from six.moves.urllib import parse as urlparse
+from tempest.lib.common import rest_client as service_client
+from tempest.lib import exceptions as lib_exc
+
+from neutron_tempest_plugin import exceptions
+
+
+class NetworkClientJSON(service_client.RestClient):
+
+    """
+    Tempest REST client for Neutron. Uses v2 of the Neutron API, since the
+    V1 API has been removed from the code base.
+
+    Implements create, delete, update, list and show for the basic Neutron
+    abstractions (networks, sub-networks, routers, ports and floating IP):
+
+    Implements add/remove interface to router using subnet ID / port ID
+
+    It also implements list, show, update and reset for OpenStack Networking
+    quotas
+    """
+
+    version = '2.0'
+    uri_prefix = "v2.0"
+
+    def get_uri(self, plural_name):
+        # get service prefix from resource name
+
+        # The following list represents resource names that do not require
+        # changing underscore to a hyphen
+        hyphen_exceptions = ["service_profiles"]
+        # the following map is used to construct proper URI
+        # for the given neutron resource
+        service_resource_prefix_map = {
+            'networks': '',
+            'subnets': '',
+            'subnetpools': '',
+            'ports': '',
+            'metering_labels': 'metering',
+            'metering_label_rules': 'metering',
+            'policies': 'qos',
+            'bandwidth_limit_rules': 'qos',
+            'minimum_bandwidth_rules': 'qos',
+            'rule_types': 'qos',
+            'rbac-policies': '',
+        }
+        service_prefix = service_resource_prefix_map.get(
+            plural_name)
+        if plural_name not in hyphen_exceptions:
+            plural_name = plural_name.replace("_", "-")
+        if service_prefix:
+            uri = '%s/%s/%s' % (self.uri_prefix, service_prefix,
+                                plural_name)
+        else:
+            uri = '%s/%s' % (self.uri_prefix, plural_name)
+        return uri
+
+    def build_uri(self, plural_name, **kwargs):
+        uri = self.get_uri(plural_name)
+        if kwargs:
+            uri += '?' + urlparse.urlencode(kwargs, doseq=1)
+        return uri
+
+    def pluralize(self, resource_name):
+        # get plural from map or just add 's'
+
+        # map from resource name to a plural name
+        # needed only for those which can't be constructed as name + 's'
+        resource_plural_map = {
+            'security_groups': 'security_groups',
+            'security_group_rules': 'security_group_rules',
+            'quotas': 'quotas',
+            'qos_policy': 'policies',
+            'rbac_policy': 'rbac_policies',
+            'network_ip_availability': 'network_ip_availabilities',
+        }
+        return resource_plural_map.get(resource_name, resource_name + 's')
+
+    def get_uri_with_links(self, plural_name, uri):
+        resp, body = self.get(uri)
+        result = {plural_name: self.deserialize_list(body)}
+        links = self.deserialize_links(body)
+        self.expected_success(200, resp.status)
+        return links, service_client.ResponseBody(resp, result)
+
+    def _lister(self, plural_name):
+        def _list(**filters):
+            uri = self.build_uri(plural_name, **filters)
+            resp, body = self.get(uri)
+            result = {plural_name: self.deserialize_list(body)}
+            self.expected_success(200, resp.status)
+            return service_client.ResponseBody(resp, result)
+
+        return _list
+
+    def _deleter(self, resource_name):
+        def _delete(resource_id):
+            plural = self.pluralize(resource_name)
+            uri = '%s/%s' % (self.get_uri(plural), resource_id)
+            resp, body = self.delete(uri)
+            self.expected_success(204, resp.status)
+            return service_client.ResponseBody(resp, body)
+
+        return _delete
+
+    def _shower(self, resource_name):
+        def _show(resource_id, **fields):
+            # fields is a dict which key is 'fields' and value is a
+            # list of field's name. An example:
+            # {'fields': ['id', 'name']}
+            plural = self.pluralize(resource_name)
+            if 'details_quotas' in plural:
+                details, plural = plural.split('_')
+                uri = '%s/%s/%s' % (self.get_uri(plural),
+                                    resource_id, details)
+            else:
+                uri = '%s/%s' % (self.get_uri(plural), resource_id)
+
+            if fields:
+                uri += '?' + urlparse.urlencode(fields, doseq=1)
+            resp, body = self.get(uri)
+            body = self.deserialize_single(body)
+            self.expected_success(200, resp.status)
+            return service_client.ResponseBody(resp, body)
+
+        return _show
+
+    def _creater(self, resource_name):
+        def _create(**kwargs):
+            plural = self.pluralize(resource_name)
+            uri = self.get_uri(plural)
+            post_data = self.serialize({resource_name: kwargs})
+            resp, body = self.post(uri, post_data)
+            body = self.deserialize_single(body)
+            self.expected_success(201, resp.status)
+            return service_client.ResponseBody(resp, body)
+
+        return _create
+
+    def _updater(self, resource_name):
+        def _update(res_id, **kwargs):
+            headers = kwargs.pop('headers', {})
+            plural = self.pluralize(resource_name)
+            uri = '%s/%s' % (self.get_uri(plural), res_id)
+            post_data = self.serialize({resource_name: kwargs})
+            resp, body = self.put(uri, post_data, headers=headers)
+            body = self.deserialize_single(body)
+            self.expected_success(200, resp.status)
+            return service_client.ResponseBody(resp, body)
+
+        return _update
+
+    def __getattr__(self, name):
+        method_prefixes = ["list_", "delete_", "show_", "create_", "update_"]
+        method_functors = [self._lister,
+                           self._deleter,
+                           self._shower,
+                           self._creater,
+                           self._updater]
+        for index, prefix in enumerate(method_prefixes):
+            prefix_len = len(prefix)
+            if name[:prefix_len] == prefix:
+                return method_functors[index](name[prefix_len:])
+        raise AttributeError(name)
+
+    # Subnetpool methods
+    def create_subnetpool(self, name, **kwargs):
+        subnetpool_data = {'name': name}
+        for arg in kwargs:
+            subnetpool_data[arg] = kwargs[arg]
+
+        post_data = {'subnetpool': subnetpool_data}
+        body = self.serialize_list(post_data, "subnetpools", "subnetpool")
+        uri = self.get_uri("subnetpools")
+        resp, body = self.post(uri, body)
+        body = {'subnetpool': self.deserialize_list(body)}
+        self.expected_success(201, resp.status)
+        return service_client.ResponseBody(resp, body)
+
+    def get_subnetpool(self, id):
+        uri = self.get_uri("subnetpools")
+        subnetpool_uri = '%s/%s' % (uri, id)
+        resp, body = self.get(subnetpool_uri)
+        body = {'subnetpool': self.deserialize_list(body)}
+        self.expected_success(200, resp.status)
+        return service_client.ResponseBody(resp, body)
+
+    def delete_subnetpool(self, id):
+        uri = self.get_uri("subnetpools")
+        subnetpool_uri = '%s/%s' % (uri, id)
+        resp, body = self.delete(subnetpool_uri)
+        self.expected_success(204, resp.status)
+        return service_client.ResponseBody(resp, body)
+
+    def list_subnetpools(self, **filters):
+        uri = self.get_uri("subnetpools")
+        if filters:
+            uri = '?'.join([uri, urlparse.urlencode(filters)])
+        resp, body = self.get(uri)
+        body = {'subnetpools': self.deserialize_list(body)}
+        self.expected_success(200, resp.status)
+        return service_client.ResponseBody(resp, body)
+
+    def update_subnetpool(self, id, **kwargs):
+        subnetpool_data = {}
+        for arg in kwargs:
+            subnetpool_data[arg] = kwargs[arg]
+
+        post_data = {'subnetpool': subnetpool_data}
+        body = self.serialize_list(post_data, "subnetpools", "subnetpool")
+        uri = self.get_uri("subnetpools")
+        subnetpool_uri = '%s/%s' % (uri, id)
+        resp, body = self.put(subnetpool_uri, body)
+        body = {'subnetpool': self.deserialize_list(body)}
+        self.expected_success(200, resp.status)
+        return service_client.ResponseBody(resp, body)
+
+    # Common methods that are hard to automate
+    def create_bulk_network(self, names, shared=False):
+        network_list = [{'name': name, 'shared': shared} for name in names]
+        post_data = {'networks': network_list}
+        body = self.serialize_list(post_data, "networks", "network")
+        uri = self.get_uri("networks")
+        resp, body = self.post(uri, body)
+        body = {'networks': self.deserialize_list(body)}
+        self.expected_success(201, resp.status)
+        return service_client.ResponseBody(resp, body)
+
+    def create_bulk_subnet(self, subnet_list):
+        post_data = {'subnets': subnet_list}
+        body = self.serialize_list(post_data, 'subnets', 'subnet')
+        uri = self.get_uri('subnets')
+        resp, body = self.post(uri, body)
+        body = {'subnets': self.deserialize_list(body)}
+        self.expected_success(201, resp.status)
+        return service_client.ResponseBody(resp, body)
+
+    def create_bulk_port(self, port_list):
+        post_data = {'ports': port_list}
+        body = self.serialize_list(post_data, 'ports', 'port')
+        uri = self.get_uri('ports')
+        resp, body = self.post(uri, body)
+        body = {'ports': self.deserialize_list(body)}
+        self.expected_success(201, resp.status)
+        return service_client.ResponseBody(resp, body)
+
+    def create_bulk_security_groups(self, security_group_list):
+        group_list = [{'security_group': {'name': name}}
+                      for name in security_group_list]
+        post_data = {'security_groups': group_list}
+        body = self.serialize_list(post_data, 'security_groups',
+                                   'security_group')
+        uri = self.get_uri("security-groups")
+        resp, body = self.post(uri, body)
+        body = {'security_groups': self.deserialize_list(body)}
+        self.expected_success(201, resp.status)
+        return service_client.ResponseBody(resp, body)
+
+    def wait_for_resource_deletion(self, resource_type, id):
+        """Waits for a resource to be deleted."""
+        start_time = int(time.time())
+        while True:
+            if self.is_resource_deleted(resource_type, id):
+                return
+            if int(time.time()) - start_time >= self.build_timeout:
+                raise exceptions.TimeoutException
+            time.sleep(self.build_interval)
+
+    def is_resource_deleted(self, resource_type, id):
+        method = 'show_' + resource_type
+        try:
+            getattr(self, method)(id)
+        except AttributeError:
+            raise Exception("Unknown resource type %s " % resource_type)
+        except lib_exc.NotFound:
+            return True
+        return False
+
+    def deserialize_single(self, body):
+        return jsonutils.loads(body)
+
+    def deserialize_list(self, body):
+        res = jsonutils.loads(body)
+        # expecting response in form
+        # {'resources': [ res1, res2] } => when pagination disabled
+        # {'resources': [..], 'resources_links': {}} => if pagination enabled
+        for k in res.keys():
+            if k.endswith("_links"):
+                continue
+            return res[k]
+
+    def deserialize_links(self, body):
+        res = jsonutils.loads(body)
+        # expecting response in form
+        # {'resources': [ res1, res2] } => when pagination disabled
+        # {'resources': [..], 'resources_links': {}} => if pagination enabled
+        for k in res.keys():
+            if k.endswith("_links"):
+                return {
+                    link['rel']: link['href']
+                    for link in res[k]
+                }
+        return {}
+
+    def serialize(self, data):
+        return jsonutils.dumps(data)
+
+    def serialize_list(self, data, root=None, item=None):
+        return self.serialize(data)
+
+    def update_quotas(self, tenant_id, **kwargs):
+        put_body = {'quota': kwargs}
+        body = jsonutils.dumps(put_body)
+        uri = '%s/quotas/%s' % (self.uri_prefix, tenant_id)
+        resp, body = self.put(uri, body)
+        self.expected_success(200, resp.status)
+        body = jsonutils.loads(body)
+        return service_client.ResponseBody(resp, body['quota'])
+
+    def reset_quotas(self, tenant_id):
+        uri = '%s/quotas/%s' % (self.uri_prefix, tenant_id)
+        resp, body = self.delete(uri)
+        self.expected_success(204, resp.status)
+        return service_client.ResponseBody(resp, body)
+
+    def create_router(self, name, admin_state_up=True, **kwargs):
+        post_body = {'router': kwargs}
+        post_body['router']['name'] = name
+        post_body['router']['admin_state_up'] = admin_state_up
+        body = jsonutils.dumps(post_body)
+        uri = '%s/routers' % (self.uri_prefix)
+        resp, body = self.post(uri, body)
+        self.expected_success(201, resp.status)
+        body = jsonutils.loads(body)
+        return service_client.ResponseBody(resp, body)
+
+    def _update_router(self, router_id, set_enable_snat, **kwargs):
+        uri = '%s/routers/%s' % (self.uri_prefix, router_id)
+        resp, body = self.get(uri)
+        self.expected_success(200, resp.status)
+        body = jsonutils.loads(body)
+        update_body = {}
+        update_body['name'] = kwargs.get('name', body['router']['name'])
+        update_body['admin_state_up'] = kwargs.get(
+            'admin_state_up', body['router']['admin_state_up'])
+        if 'description' in kwargs:
+            update_body['description'] = kwargs['description']
+        cur_gw_info = body['router']['external_gateway_info']
+        if cur_gw_info:
+            # TODO(kevinbenton): setting the external gateway info is not
+            # allowed for a regular tenant. If the ability to update is also
+            # merged, a test case for this will need to be added similar to
+            # the SNAT case.
+            cur_gw_info.pop('external_fixed_ips', None)
+            if not set_enable_snat:
+                cur_gw_info.pop('enable_snat', None)
+        update_body['external_gateway_info'] = kwargs.get(
+            'external_gateway_info', body['router']['external_gateway_info'])
+        if 'distributed' in kwargs:
+            update_body['distributed'] = kwargs['distributed']
+        if 'ha' in kwargs:
+            update_body['ha'] = kwargs['ha']
+        update_body = dict(router=update_body)
+        update_body = jsonutils.dumps(update_body)
+        resp, body = self.put(uri, update_body)
+        self.expected_success(200, resp.status)
+        body = jsonutils.loads(body)
+        return service_client.ResponseBody(resp, body)
+
+    def update_router(self, router_id, **kwargs):
+        """Update a router leaving enable_snat to its default value."""
+        # If external_gateway_info contains enable_snat the request will fail
+        # with 404 unless executed with admin client, and therefore we instruct
+        # _update_router to not set this attribute
+        # NOTE(salv-orlando): The above applies as long as Neutron's default
+        # policy is to restrict enable_snat usage to admins only.
+        return self._update_router(router_id, set_enable_snat=False, **kwargs)
+
+    def update_router_with_snat_gw_info(self, router_id, **kwargs):
+        """Update a router passing also the enable_snat attribute.
+
+        This method must be execute with admin credentials, otherwise the API
+        call will return a 404 error.
+        """
+        return self._update_router(router_id, set_enable_snat=True, **kwargs)
+
+    def add_router_interface_with_subnet_id(self, router_id, subnet_id):
+        uri = '%s/routers/%s/add_router_interface' % (self.uri_prefix,
+                                                      router_id)
+        update_body = {"subnet_id": subnet_id}
+        update_body = jsonutils.dumps(update_body)
+        resp, body = self.put(uri, update_body)
+        self.expected_success(200, resp.status)
+        body = jsonutils.loads(body)
+        return service_client.ResponseBody(resp, body)
+
+    def add_router_interface_with_port_id(self, router_id, port_id):
+        uri = '%s/routers/%s/add_router_interface' % (self.uri_prefix,
+                                                      router_id)
+        update_body = {"port_id": port_id}
+        update_body = jsonutils.dumps(update_body)
+        resp, body = self.put(uri, update_body)
+        self.expected_success(200, resp.status)
+        body = jsonutils.loads(body)
+        return service_client.ResponseBody(resp, body)
+
+    def remove_router_interface_with_subnet_id(self, router_id, subnet_id):
+        uri = '%s/routers/%s/remove_router_interface' % (self.uri_prefix,
+                                                         router_id)
+        update_body = {"subnet_id": subnet_id}
+        update_body = jsonutils.dumps(update_body)
+        resp, body = self.put(uri, update_body)
+        self.expected_success(200, resp.status)
+        body = jsonutils.loads(body)
+        return service_client.ResponseBody(resp, body)
+
+    def remove_router_interface_with_port_id(self, router_id, port_id):
+        uri = '%s/routers/%s/remove_router_interface' % (self.uri_prefix,
+                                                         router_id)
+        update_body = {"port_id": port_id}
+        update_body = jsonutils.dumps(update_body)
+        resp, body = self.put(uri, update_body)
+        self.expected_success(200, resp.status)
+        body = jsonutils.loads(body)
+        return service_client.ResponseBody(resp, body)
+
+    def list_router_interfaces(self, uuid):
+        uri = '%s/ports?device_id=%s' % (self.uri_prefix, uuid)
+        resp, body = self.get(uri)
+        self.expected_success(200, resp.status)
+        body = jsonutils.loads(body)
+        return service_client.ResponseBody(resp, body)
+
+    def update_agent(self, agent_id, agent_info):
+        """
+        :param agent_info: Agent update information.
+        E.g {"admin_state_up": True}
+        """
+        uri = '%s/agents/%s' % (self.uri_prefix, agent_id)
+        agent = {"agent": agent_info}
+        body = jsonutils.dumps(agent)
+        resp, body = self.put(uri, body)
+        self.expected_success(200, resp.status)
+        body = jsonutils.loads(body)
+        return service_client.ResponseBody(resp, body)
+
+    def list_routers_on_l3_agent(self, agent_id):
+        uri = '%s/agents/%s/l3-routers' % (self.uri_prefix, agent_id)
+        resp, body = self.get(uri)
+        self.expected_success(200, resp.status)
+        body = jsonutils.loads(body)
+        return service_client.ResponseBody(resp, body)
+
+    def list_l3_agents_hosting_router(self, router_id):
+        uri = '%s/routers/%s/l3-agents' % (self.uri_prefix, router_id)
+        resp, body = self.get(uri)
+        self.expected_success(200, resp.status)
+        body = jsonutils.loads(body)
+        return service_client.ResponseBody(resp, body)
+
+    def add_router_to_l3_agent(self, agent_id, router_id):
+        uri = '%s/agents/%s/l3-routers' % (self.uri_prefix, agent_id)
+        post_body = {"router_id": router_id}
+        body = jsonutils.dumps(post_body)
+        resp, body = self.post(uri, body)
+        self.expected_success(201, resp.status)
+        body = jsonutils.loads(body)
+        return service_client.ResponseBody(resp, body)
+
+    def remove_router_from_l3_agent(self, agent_id, router_id):
+        uri = '%s/agents/%s/l3-routers/%s' % (
+            self.uri_prefix, agent_id, router_id)
+        resp, body = self.delete(uri)
+        self.expected_success(204, resp.status)
+        return service_client.ResponseBody(resp, body)
+
+    def list_dhcp_agent_hosting_network(self, network_id):
+        uri = '%s/networks/%s/dhcp-agents' % (self.uri_prefix, network_id)
+        resp, body = self.get(uri)
+        self.expected_success(200, resp.status)
+        body = jsonutils.loads(body)
+        return service_client.ResponseBody(resp, body)
+
+    def list_networks_hosted_by_one_dhcp_agent(self, agent_id):
+        uri = '%s/agents/%s/dhcp-networks' % (self.uri_prefix, agent_id)
+        resp, body = self.get(uri)
+        self.expected_success(200, resp.status)
+        body = jsonutils.loads(body)
+        return service_client.ResponseBody(resp, body)
+
+    def remove_network_from_dhcp_agent(self, agent_id, network_id):
+        uri = '%s/agents/%s/dhcp-networks/%s' % (self.uri_prefix, agent_id,
+                                                 network_id)
+        resp, body = self.delete(uri)
+        self.expected_success(204, resp.status)
+        return service_client.ResponseBody(resp, body)
+
+    def update_extra_routes(self, router_id, nexthop, destination):
+        uri = '%s/routers/%s' % (self.uri_prefix, router_id)
+        put_body = {
+            'router': {
+                'routes': [{'nexthop': nexthop,
+                            "destination": destination}]
+            }
+        }
+        body = jsonutils.dumps(put_body)
+        resp, body = self.put(uri, body)
+        self.expected_success(200, resp.status)
+        body = jsonutils.loads(body)
+        return service_client.ResponseBody(resp, body)
+
+    def delete_extra_routes(self, router_id):
+        uri = '%s/routers/%s' % (self.uri_prefix, router_id)
+        null_routes = None
+        put_body = {
+            'router': {
+                'routes': null_routes
+            }
+        }
+        body = jsonutils.dumps(put_body)
+        resp, body = self.put(uri, body)
+        self.expected_success(200, resp.status)
+        body = jsonutils.loads(body)
+        return service_client.ResponseBody(resp, body)
+
+    def add_dhcp_agent_to_network(self, agent_id, network_id):
+        post_body = {'network_id': network_id}
+        body = jsonutils.dumps(post_body)
+        uri = '%s/agents/%s/dhcp-networks' % (self.uri_prefix, agent_id)
+        resp, body = self.post(uri, body)
+        self.expected_success(201, resp.status)
+        body = jsonutils.loads(body)
+        return service_client.ResponseBody(resp, body)
+
+    def list_qos_policies(self, **filters):
+        if filters:
+            uri = '%s/qos/policies?%s' % (self.uri_prefix,
+                                          urlparse.urlencode(filters))
+        else:
+            uri = '%s/qos/policies' % self.uri_prefix
+        resp, body = self.get(uri)
+        self.expected_success(200, resp.status)
+        body = jsonutils.loads(body)
+        return service_client.ResponseBody(resp, body)
+
+    def create_qos_policy(self, name, description=None, shared=False,
+                          tenant_id=None, is_default=False):
+        uri = '%s/qos/policies' % self.uri_prefix
+        post_data = {
+            'policy': {
+                'name': name,
+                'shared': shared,
+                'is_default': is_default
+            }
+        }
+        if description is not None:
+            post_data['policy']['description'] = description
+        if tenant_id is not None:
+            post_data['policy']['tenant_id'] = tenant_id
+        resp, body = self.post(uri, self.serialize(post_data))
+        body = self.deserialize_single(body)
+        self.expected_success(201, resp.status)
+        return service_client.ResponseBody(resp, body)
+
+    def update_qos_policy(self, policy_id, **kwargs):
+        uri = '%s/qos/policies/%s' % (self.uri_prefix, policy_id)
+        post_data = self.serialize({'policy': kwargs})
+        resp, body = self.put(uri, post_data)
+        body = self.deserialize_single(body)
+        self.expected_success(200, resp.status)
+        return service_client.ResponseBody(resp, body)
+
+    def create_bandwidth_limit_rule(self, policy_id, max_kbps,
+                                    max_burst_kbps, direction=None):
+        uri = '%s/qos/policies/%s/bandwidth_limit_rules' % (
+            self.uri_prefix, policy_id)
+        post_data = {
+            'bandwidth_limit_rule': {
+                'max_kbps': max_kbps,
+                'max_burst_kbps': max_burst_kbps
+            }
+        }
+        if direction:
+            post_data['bandwidth_limit_rule']['direction'] = direction
+        resp, body = self.post(uri, self.serialize(post_data))
+        self.expected_success(201, resp.status)
+        body = jsonutils.loads(body)
+        return service_client.ResponseBody(resp, body)
+
+    def list_bandwidth_limit_rules(self, policy_id):
+        uri = '%s/qos/policies/%s/bandwidth_limit_rules' % (
+            self.uri_prefix, policy_id)
+        resp, body = self.get(uri)
+        body = self.deserialize_single(body)
+        self.expected_success(200, resp.status)
+        return service_client.ResponseBody(resp, body)
+
+    def show_bandwidth_limit_rule(self, policy_id, rule_id):
+        uri = '%s/qos/policies/%s/bandwidth_limit_rules/%s' % (
+            self.uri_prefix, policy_id, rule_id)
+        resp, body = self.get(uri)
+        body = self.deserialize_single(body)
+        self.expected_success(200, resp.status)
+        return service_client.ResponseBody(resp, body)
+
+    def update_bandwidth_limit_rule(self, policy_id, rule_id, **kwargs):
+        uri = '%s/qos/policies/%s/bandwidth_limit_rules/%s' % (
+            self.uri_prefix, policy_id, rule_id)
+        if "direction" in kwargs and kwargs['direction'] is None:
+            kwargs.pop('direction')
+        post_data = {'bandwidth_limit_rule': kwargs}
+        resp, body = self.put(uri, jsonutils.dumps(post_data))
+        body = self.deserialize_single(body)
+        self.expected_success(200, resp.status)
+        return service_client.ResponseBody(resp, body)
+
+    def delete_bandwidth_limit_rule(self, policy_id, rule_id):
+        uri = '%s/qos/policies/%s/bandwidth_limit_rules/%s' % (
+            self.uri_prefix, policy_id, rule_id)
+        resp, body = self.delete(uri)
+        self.expected_success(204, resp.status)
+        return service_client.ResponseBody(resp, body)
+
+    def create_dscp_marking_rule(self, policy_id, dscp_mark):
+        uri = '%s/qos/policies/%s/dscp_marking_rules' % (
+            self.uri_prefix, policy_id)
+        post_data = self.serialize({
+            'dscp_marking_rule': {
+                'dscp_mark': dscp_mark
+            }
+        })
+        resp, body = self.post(uri, post_data)
+        self.expected_success(201, resp.status)
+        body = jsonutils.loads(body)
+        return service_client.ResponseBody(resp, body)
+
+    def list_dscp_marking_rules(self, policy_id):
+        uri = '%s/qos/policies/%s/dscp_marking_rules' % (
+            self.uri_prefix, policy_id)
+        resp, body = self.get(uri)
+        body = self.deserialize_single(body)
+        self.expected_success(200, resp.status)
+        return service_client.ResponseBody(resp, body)
+
+    def show_dscp_marking_rule(self, policy_id, rule_id):
+        uri = '%s/qos/policies/%s/dscp_marking_rules/%s' % (
+            self.uri_prefix, policy_id, rule_id)
+        resp, body = self.get(uri)
+        body = self.deserialize_single(body)
+        self.expected_success(200, resp.status)
+        return service_client.ResponseBody(resp, body)
+
+    def update_dscp_marking_rule(self, policy_id, rule_id, **kwargs):
+        uri = '%s/qos/policies/%s/dscp_marking_rules/%s' % (
+            self.uri_prefix, policy_id, rule_id)
+        post_data = {'dscp_marking_rule': kwargs}
+        resp, body = self.put(uri, jsonutils.dumps(post_data))
+        body = self.deserialize_single(body)
+        self.expected_success(200, resp.status)
+        return service_client.ResponseBody(resp, body)
+
+    def delete_dscp_marking_rule(self, policy_id, rule_id):
+        uri = '%s/qos/policies/%s/dscp_marking_rules/%s' % (
+            self.uri_prefix, policy_id, rule_id)
+        resp, body = self.delete(uri)
+        self.expected_success(204, resp.status)
+        return service_client.ResponseBody(resp, body)
+
+    def create_minimum_bandwidth_rule(self, policy_id, direction,
+                                      min_kbps=None):
+        uri = '%s/qos/policies/%s/minimum_bandwidth_rules' % (
+            self.uri_prefix, policy_id)
+        data = {
+            'direction': direction,
+        }
+        if min_kbps is not None:
+            data['min_kbps'] = min_kbps
+        post_data = self.serialize({'minimum_bandwidth_rule': data})
+        resp, body = self.post(uri, post_data)
+        self.expected_success(201, resp.status)
+        body = jsonutils.loads(body)
+        return service_client.ResponseBody(resp, body)
+
+    def list_minimum_bandwidth_rules(self, policy_id):
+        uri = '%s/qos/policies/%s/minimum_bandwidth_rules' % (
+            self.uri_prefix, policy_id)
+        resp, body = self.get(uri)
+        body = self.deserialize_single(body)
+        self.expected_success(200, resp.status)
+        return service_client.ResponseBody(resp, body)
+
+    def show_minimum_bandwidth_rule(self, policy_id, rule_id):
+        uri = '%s/qos/policies/%s/minimum_bandwidth_rules/%s' % (
+            self.uri_prefix, policy_id, rule_id)
+        resp, body = self.get(uri)
+        body = self.deserialize_single(body)
+        self.expected_success(200, resp.status)
+        return service_client.ResponseBody(resp, body)
+
+    def update_minimum_bandwidth_rule(self, policy_id, rule_id, **kwargs):
+        uri = '%s/qos/policies/%s/minimum_bandwidth_rules/%s' % (
+            self.uri_prefix, policy_id, rule_id)
+        post_data = {'minimum_bandwidth_rule': kwargs}
+        resp, body = self.put(uri, jsonutils.dumps(post_data))
+        body = self.deserialize_single(body)
+        self.expected_success(200, resp.status)
+        return service_client.ResponseBody(resp, body)
+
+    def delete_minimum_bandwidth_rule(self, policy_id, rule_id):
+        uri = '%s/qos/policies/%s/minimum_bandwidth_rules/%s' % (
+            self.uri_prefix, policy_id, rule_id)
+        resp, body = self.delete(uri)
+        self.expected_success(204, resp.status)
+        return service_client.ResponseBody(resp, body)
+
+    def list_qos_rule_types(self):
+        uri = '%s/qos/rule-types' % self.uri_prefix
+        resp, body = self.get(uri)
+        self.expected_success(200, resp.status)
+        body = jsonutils.loads(body)
+        return service_client.ResponseBody(resp, body)
+
+    def show_qos_rule_type(self, rule_type_name):
+        uri = '%s/qos/rule-types/%s' % (
+            self.uri_prefix, rule_type_name)
+        resp, body = self.get(uri)
+        self.expected_success(200, resp.status)
+        body = jsonutils.loads(body)
+        return service_client.ResponseBody(resp, body)
+
+    def create_trunk(self, parent_port_id, subports,
+                     tenant_id=None, name=None, admin_state_up=None,
+                     description=None):
+        uri = '%s/trunks' % self.uri_prefix
+        post_data = {
+            'trunk': {
+                'port_id': parent_port_id,
+            }
+        }
+        if subports is not None:
+            post_data['trunk']['sub_ports'] = subports
+        if tenant_id is not None:
+            post_data['trunk']['tenant_id'] = tenant_id
+        if name is not None:
+            post_data['trunk']['name'] = name
+        if description is not None:
+            post_data['trunk']['description'] = description
+        if admin_state_up is not None:
+            post_data['trunk']['admin_state_up'] = admin_state_up
+        resp, body = self.post(uri, self.serialize(post_data))
+        body = self.deserialize_single(body)
+        self.expected_success(201, resp.status)
+        return service_client.ResponseBody(resp, body)
+
+    def update_trunk(self, trunk_id, **kwargs):
+        put_body = {'trunk': kwargs}
+        body = jsonutils.dumps(put_body)
+        uri = '%s/trunks/%s' % (self.uri_prefix, trunk_id)
+        resp, body = self.put(uri, body)
+        self.expected_success(200, resp.status)
+        body = jsonutils.loads(body)
+        return service_client.ResponseBody(resp, body)
+
+    def show_trunk(self, trunk_id):
+        uri = '%s/trunks/%s' % (self.uri_prefix, trunk_id)
+        resp, body = self.get(uri)
+        body = self.deserialize_single(body)
+        self.expected_success(200, resp.status)
+        return service_client.ResponseBody(resp, body)
+
+    def list_trunks(self, **kwargs):
+        uri = '%s/trunks' % self.uri_prefix
+        if kwargs:
+            uri += '?' + urlparse.urlencode(kwargs, doseq=1)
+        resp, body = self.get(uri)
+        self.expected_success(200, resp.status)
+        body = self.deserialize_single(body)
+        return service_client.ResponseBody(resp, body)
+
+    def delete_trunk(self, trunk_id):
+        uri = '%s/trunks/%s' % (self.uri_prefix, trunk_id)
+        resp, body = self.delete(uri)
+        self.expected_success(204, resp.status)
+        return service_client.ResponseBody(resp, body)
+
+    def _subports_action(self, action, trunk_id, subports):
+        uri = '%s/trunks/%s/%s' % (self.uri_prefix, trunk_id, action)
+        resp, body = self.put(uri, jsonutils.dumps({'sub_ports': subports}))
+        body = self.deserialize_single(body)
+        self.expected_success(200, resp.status)
+        return service_client.ResponseBody(resp, body)
+
+    def add_subports(self, trunk_id, subports):
+        return self._subports_action('add_subports', trunk_id, subports)
+
+    def remove_subports(self, trunk_id, subports):
+        return self._subports_action('remove_subports', trunk_id, subports)
+
+    def get_subports(self, trunk_id):
+        uri = '%s/trunks/%s/%s' % (self.uri_prefix, trunk_id, 'get_subports')
+        resp, body = self.get(uri)
+        self.expected_success(200, resp.status)
+        body = jsonutils.loads(body)
+        return service_client.ResponseBody(resp, body)
+
+    def get_auto_allocated_topology(self, tenant_id=None):
+        uri = '%s/auto-allocated-topology/%s' % (self.uri_prefix, tenant_id)
+        resp, body = self.get(uri)
+        self.expected_success(200, resp.status)
+        body = jsonutils.loads(body)
+        return service_client.ResponseBody(resp, body)
+
+    def delete_auto_allocated_topology(self, tenant_id=None):
+        uri = '%s/auto-allocated-topology/%s' % (self.uri_prefix, tenant_id)
+        resp, body = self.delete(uri)
+        self.expected_success(204, resp.status)
+        return service_client.ResponseBody(resp, body)
+
+    def create_flavor_service_profile(self, flavor_id, service_profile_id):
+        body = jsonutils.dumps({'service_profile': {'id': service_profile_id}})
+        uri = '%s/flavors/%s/service_profiles' % (self.uri_prefix, flavor_id)
+        resp, body = self.post(uri, body)
+        self.expected_success(201, resp.status)
+        body = jsonutils.loads(body)
+        return service_client.ResponseBody(resp, body)
+
+    def list_flavor_service_profiles(self, flavor_id):
+        uri = '%s/flavors/%s/service_profiles' % (self.uri_prefix, flavor_id)
+        resp, body = self.get(uri)
+        self.expected_success(200, resp.status)
+        body = jsonutils.loads(body)
+        return service_client.ResponseBody(resp, body)
+
+    def delete_flavor_service_profile(self, flavor_id, service_profile_id):
+        uri = '%s/flavors/%s/service_profiles/%s' % (self.uri_prefix,
+                                                     flavor_id,
+                                                     service_profile_id)
+        resp, body = self.delete(uri)
+        self.expected_success(204, resp.status)
+        return service_client.ResponseBody(resp, body)
+
+    def create_security_group_rule(self, direction, security_group_id,
+                                   **kwargs):
+        post_body = {'security_group_rule': kwargs}
+        post_body['security_group_rule']['direction'] = direction
+        post_body['security_group_rule'][
+            'security_group_id'] = security_group_id
+        body = jsonutils.dumps(post_body)
+        uri = '%s/security-group-rules' % self.uri_prefix
+        resp, body = self.post(uri, body)
+        self.expected_success(201, resp.status)
+        body = jsonutils.loads(body)
+        return service_client.ResponseBody(resp, body)
+
+    def list_security_groups(self, **kwargs):
+        post_body = {'security_groups': kwargs}
+        body = jsonutils.dumps(post_body)
+        uri = '%s/security-groups' % self.uri_prefix
+        if kwargs:
+            uri += '?' + urlparse.urlencode(kwargs, doseq=1)
+        resp, body = self.get(uri)
+        self.expected_success(200, resp.status)
+        body = jsonutils.loads(body)
+        return service_client.ResponseBody(resp, body)
+
+    def delete_security_group(self, security_group_id):
+        uri = '%s/security-groups/%s' % (
+            self.uri_prefix, security_group_id)
+        resp, body = self.delete(uri)
+        self.expected_success(204, resp.status)
+        return service_client.ResponseBody(resp, body)
+
+    def list_ports(self, **kwargs):
+        post_body = {'ports': kwargs}
+        body = jsonutils.dumps(post_body)
+        uri = '%s/ports' % self.uri_prefix
+        if kwargs:
+            uri += '?' + urlparse.urlencode(kwargs, doseq=1)
+        resp, body = self.get(uri)
+        self.expected_success(200, resp.status)
+        body = jsonutils.loads(body)
+        return service_client.ResponseBody(resp, body)
+
+    def create_floatingip(self, floating_network_id, **kwargs):
+        post_body = {'floatingip': {
+            'floating_network_id': floating_network_id}}
+        if kwargs:
+            post_body['floatingip'].update(kwargs)
+        body = jsonutils.dumps(post_body)
+        uri = '%s/floatingips' % self.uri_prefix
+        resp, body = self.post(uri, body)
+        self.expected_success(201, resp.status)
+        body = jsonutils.loads(body)
+        return service_client.ResponseBody(resp, body)
+
+    def create_network_keystone_v3(self, name, project_id, tenant_id=None):
+        uri = '%s/networks' % self.uri_prefix
+        post_data = {
+            'network': {
+                'name': name,
+                'project_id': project_id
+            }
+        }
+        if tenant_id is not None:
+            post_data['network']['tenant_id'] = tenant_id
+        resp, body = self.post(uri, self.serialize(post_data))
+        body = self.deserialize_single(body)
+        self.expected_success(201, resp.status)
+        return service_client.ResponseBody(resp, body)
+
+    def list_extensions(self, **filters):
+        uri = self.get_uri("extensions")
+        if filters:
+            uri = '?'.join([uri, urlparse.urlencode(filters)])
+        resp, body = self.get(uri)
+        body = {'extensions': self.deserialize_list(body)}
+        self.expected_success(200, resp.status)
+        return service_client.ResponseBody(resp, body)
+
+    def get_tags(self, resource_type, resource_id):
+        uri = '%s/%s/%s/tags' % (
+            self.uri_prefix, resource_type, resource_id)
+        resp, body = self.get(uri)
+        self.expected_success(200, resp.status)
+        body = jsonutils.loads(body)
+        return service_client.ResponseBody(resp, body)
+
+    def get_tag(self, resource_type, resource_id, tag):
+        uri = '%s/%s/%s/tags/%s' % (
+            self.uri_prefix, resource_type, resource_id, tag)
+        resp, body = self.get(uri)
+        self.expected_success(204, resp.status)
+
+    def update_tag(self, resource_type, resource_id, tag):
+        uri = '%s/%s/%s/tags/%s' % (
+            self.uri_prefix, resource_type, resource_id, tag)
+        resp, body = self.put(uri, None)
+        self.expected_success(201, resp.status)
+
+    def update_tags(self, resource_type, resource_id, tags):
+        uri = '%s/%s/%s/tags' % (
+            self.uri_prefix, resource_type, resource_id)
+        req_body = jsonutils.dumps({'tags': tags})
+        resp, body = self.put(uri, req_body)
+        self.expected_success(200, resp.status)
+        body = jsonutils.loads(body)
+        return service_client.ResponseBody(resp, body)
+
+    def delete_tags(self, resource_type, resource_id):
+        uri = '%s/%s/%s/tags' % (
+            self.uri_prefix, resource_type, resource_id)
+        resp, body = self.delete(uri)
+        self.expected_success(204, resp.status)
+
+    def delete_tag(self, resource_type, resource_id, tag):
+        uri = '%s/%s/%s/tags/%s' % (
+            self.uri_prefix, resource_type, resource_id, tag)
+        resp, body = self.delete(uri)
+        self.expected_success(204, resp.status)