Merge "qos: Add API test for shared policy"
diff --git a/neutron/tests/tempest/api/admin/test_external_network_extension.py b/neutron/tests/tempest/api/admin/test_external_network_extension.py
index ed56144..99e10ee 100644
--- a/neutron/tests/tempest/api/admin/test_external_network_extension.py
+++ b/neutron/tests/tempest/api/admin/test_external_network_extension.py
@@ -53,7 +53,7 @@
             external_gateway_info={'network_id': net['id']})['router']
         self.addCleanup(self.admin_client.delete_router, r['id'])
 
-    @test.idempotent_id('afd8f1b7-a81e-4629-bca8-a367b3a144bb')
+    @test.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):
diff --git a/neutron/tests/tempest/api/base.py b/neutron/tests/tempest/api/base.py
index 31d6c3a..45e12f0 100644
--- a/neutron/tests/tempest/api/base.py
+++ b/neutron/tests/tempest/api/base.py
@@ -14,6 +14,7 @@
 #    under the License.
 
 import functools
+import math
 
 import netaddr
 from tempest.lib.common.utils import data_utils
@@ -21,6 +22,7 @@
 from tempest import test
 
 from neutron.common import constants
+from neutron.common import utils
 from neutron.tests.tempest.api import clients
 from neutron.tests.tempest import config
 from neutron.tests.tempest import exceptions
@@ -499,14 +501,31 @@
 
     list_kwargs = {}
 
+    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['name'], res['name'])
+
+    @utils.classproperty
+    def plural_name(self):
+        return '%ss' % self.resource
+
     def list_method(self, *args, **kwargs):
-        method = getattr(self.client, 'list_%ss' % self.resource)
+        method = getattr(self.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['%ss' % cls.resource]
+        return body[cls.plural_name]
 
     def _test_list_sorts(self, direction):
         sort_args = {
@@ -550,9 +569,7 @@
         resources = self._extract_resources(body)
         self.assertTrue(len(resources) >= len(self.resource_names))
 
-    @_require_pagination
-    @_require_sorting
-    def _test_list_pagination_with_marker(self):
+    def _test_list_pagination_iteratively(self, lister):
         # first, collect all resources for later comparison
         sort_args = {
             'sort_dir': constants.SORT_DIRECTION_ASC,
@@ -562,10 +579,19 @@
         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(len(expected_resources)):
+        for i in range(niterations):
             pagination_args = sort_args.copy()
             pagination_args['limit'] = 1
             if resources:
@@ -574,8 +600,121 @@
             resources_ = self._extract_resources(body)
             self.assertEqual(1, len(resources_))
             resources.extend(resources_)
+        return resources
 
-        # finally, compare that the list retrieved in one go is identical to
-        # the one containing pagination results
-        for expected, res in zip(expected_resources, resources):
-            self.assertEqual(expected['name'], res['name'])
+    @_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.client.build_uri(
+                    self.plural_name, limit=1, **sort_args)
+            prev_links, body = self.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.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': 'name',
+        }
+        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.client.build_uri(
+                    self.plural_name, page_reverse=True, **pagination_args)
+            prev_links, body = self.client.get_uri_with_links(
+                self.plural_name, uri
+            )
+            resources_ = self._extract_resources(body)
+            self.assertTrue(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': 'name',
+            '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))
diff --git a/neutron/tests/tempest/api/clients.py b/neutron/tests/tempest/api/clients.py
index d9879b0..8b51c6e 100644
--- a/neutron/tests/tempest/api/clients.py
+++ b/neutron/tests/tempest/api/clients.py
@@ -13,24 +13,21 @@
 #    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 import manager
-from tempest.services.identity.v2.json.tenants_client import \
-    TenantsClient
+from tempest.services.identity.v2.json import tenants_client
 
 from neutron.tests.tempest import config
-from neutron.tests.tempest.services.network.json.network_client import \
-     NetworkClientJSON
-
+from neutron.tests.tempest.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,
@@ -51,7 +48,7 @@
 
         self._set_identity_clients()
 
-        self.network_client = NetworkClientJSON(
+        self.network_client = network_client.NetworkClientJSON(
             self.auth_provider,
             CONF.network.catalog_type,
             CONF.network.region or CONF.identity.region,
@@ -60,6 +57,23 @@
             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,
@@ -69,5 +83,5 @@
         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 = TenantsClient(self.auth_provider,
-                                            **params_v2_admin)
+        self.tenants_client = tenants_client.TenantsClient(self.auth_provider,
+                                                           **params_v2_admin)
diff --git a/neutron/tests/tempest/api/test_extension_driver_port_security.py b/neutron/tests/tempest/api/test_extension_driver_port_security.py
index de25d9f..71cc6c0 100644
--- a/neutron/tests/tempest/api/test_extension_driver_port_security.py
+++ b/neutron/tests/tempest/api/test_extension_driver_port_security.py
@@ -53,7 +53,7 @@
         self.assertEqual(network['port_security_enabled'], port_sec_net)
         self.assertEqual(port['port_security_enabled'], expected)
 
-    @test.idempotent_id('05642059-1bfc-4581-9bc9-aaa5db08dd60')
+    @test.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)
@@ -69,7 +69,7 @@
         self.assertEmpty(port['security_groups'])
 
     @test.attr(type='negative')
-    @test.idempotent_id('05642059-1bfc-4581-9bc9-aaa5db08dd60')
+    @test.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()
diff --git a/neutron/tests/tempest/api/test_flavors_extensions.py b/neutron/tests/tempest/api/test_flavors_extensions.py
index 57ce27d..e0dda0b 100644
--- a/neutron/tests/tempest/api/test_flavors_extensions.py
+++ b/neutron/tests/tempest/api/test_flavors_extensions.py
@@ -63,7 +63,7 @@
         labels = self.admin_client.list_service_profiles(id=service_profile_id)
         self.assertEqual(len(labels['service_profiles']), 0)
 
-    @test.idempotent_id('ec8e15ff-95d0-433b-b8a6-b466bddb1e50')
+    @test.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"
@@ -86,7 +86,7 @@
                   id=service_profile['id']))
         self.assertEqual(len(labels['service_profiles']), 1)
 
-    @test.idempotent_id('ec8e15ff-95d0-433b-b8a6-b466bddb1e50')
+    @test.idempotent_id('136bcf09-00af-4da7-9b7f-174735d4aebd')
     def test_create_update_delete_flavor(self):
         # Creates a flavor
         description = "flavor created by tempest"
@@ -118,7 +118,7 @@
                          service_profile['metainfo'])
         self.assertTrue(service_profile['enabled'])
 
-    @test.idempotent_id('30abb445-0eea-472e-bd02-8649f54a5968')
+    @test.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'])
@@ -128,7 +128,7 @@
         self.assertEqual(self.flavor['name'], flavor['name'])
         self.assertTrue(flavor['enabled'])
 
-    @test.idempotent_id('e2fb2f8c-45bf-429a-9f17-171c70444612')
+    @test.idempotent_id('eb3dd12e-6dfd-45f4-8393-46e0fa19860e')
     def test_list_flavors(self):
         # Verify flavor lists
         body = self.admin_client.list_flavors(id=33)
diff --git a/neutron/tests/tempest/api/test_metering_extensions.py b/neutron/tests/tempest/api/test_metering_extensions.py
index 42b8e48..756cd5a 100644
--- a/neutron/tests/tempest/api/test_metering_extensions.py
+++ b/neutron/tests/tempest/api/test_metering_extensions.py
@@ -58,7 +58,7 @@
                  id=metering_label_rule_id))
         self.assertEqual(len(rules['metering_label_rules']), 0)
 
-    @test.idempotent_id('e2fb2f8c-45bf-429a-9f17-171c70444612')
+    @test.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)
@@ -81,7 +81,7 @@
                   id=metering_label['id']))
         self.assertEqual(len(labels['metering_labels']), 1)
 
-    @test.idempotent_id('30abb445-0eea-472e-bd02-8649f54a5968')
+    @test.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'])
diff --git a/neutron/tests/tempest/api/test_networks.py b/neutron/tests/tempest/api/test_networks.py
index 7b133b1..951e8c9 100644
--- a/neutron/tests/tempest/api/test_networks.py
+++ b/neutron/tests/tempest/api/test_networks.py
@@ -119,11 +119,31 @@
         self._test_list_pagination()
 
     @test.attr(type='smoke')
-    @test.idempotent_id('71389852-f57b-49f2-b109-77b705e9e8af')
+    @test.idempotent_id('b7e153d2-37c3-48d4-8390-ec13498fee3d')
     def test_list_pagination_with_marker(self):
         self._test_list_pagination_with_marker()
 
     @test.attr(type='smoke')
+    @test.idempotent_id('8a9c89df-0ee7-4c0d-8f1d-ec8f27cf362f')
+    def test_list_pagination_with_href_links(self):
+        self._test_list_pagination_with_href_links()
+
+    @test.attr(type='smoke')
+    @test.idempotent_id('79a52810-2156-4ab6-b577-9e46e58d4b58')
+    def test_list_pagination_page_reverse_asc(self):
+        self._test_list_pagination_page_reverse_asc()
+
+    @test.attr(type='smoke')
+    @test.idempotent_id('36a4671f-a542-442f-bc44-a8873ee778d1')
+    def test_list_pagination_page_reverse_desc(self):
+        self._test_list_pagination_page_reverse_desc()
+
+    @test.attr(type='smoke')
+    @test.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()
+
+    @test.attr(type='smoke')
     @test.idempotent_id('f1867fc5-e1d6-431f-bc9f-8b882e43a7f9')
     def test_list_no_pagination_limit_0(self):
         self._test_list_no_pagination_limit_0()
diff --git a/neutron/tests/tempest/api/test_ports.py b/neutron/tests/tempest/api/test_ports.py
index a8e8c4d..eec97ba 100644
--- a/neutron/tests/tempest/api/test_ports.py
+++ b/neutron/tests/tempest/api/test_ports.py
@@ -80,6 +80,26 @@
         self._test_list_pagination_with_marker()
 
     @test.attr(type='smoke')
+    @test.idempotent_id('fcd02a7a-f07e-4d5e-b0ca-b58e48927a9b')
+    def test_list_pagination_with_href_links(self):
+        self._test_list_pagination_with_href_links()
+
+    @test.attr(type='smoke')
     @test.idempotent_id('3afe7024-77ab-4cfe-824b-0b2bf4217727')
     def test_list_no_pagination_limit_0(self):
         self._test_list_no_pagination_limit_0()
+
+    @test.attr(type='smoke')
+    @test.idempotent_id('b8857391-dc44-40cc-89b7-2800402e03ce')
+    def test_list_pagination_page_reverse_asc(self):
+        self._test_list_pagination_page_reverse_asc()
+
+    @test.attr(type='smoke')
+    @test.idempotent_id('4e51e9c9-ceae-4ec0-afd4-147569247699')
+    def test_list_pagination_page_reverse_desc(self):
+        self._test_list_pagination_page_reverse_desc()
+
+    @test.attr(type='smoke')
+    @test.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/tests/tempest/api/test_qos.py b/neutron/tests/tempest/api/test_qos.py
index 7eef283..2d688df 100644
--- a/neutron/tests/tempest/api/test_qos.py
+++ b/neutron/tests/tempest/api/test_qos.py
@@ -75,6 +75,24 @@
         self.assertTrue(retrieved_policy['shared'])
         self.assertEqual([], retrieved_policy['rules'])
 
+    @test.idempotent_id('ee263db4-009a-4641-83e5-d0e83506ba4c')
+    def test_shared_policy_update(self):
+        policy = self.create_qos_policy(name='test-policy',
+                                        description='',
+                                        shared=True)
+
+        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'])
+
     @test.idempotent_id('1cb42653-54bd-4a9a-b888-c55e18199201')
     def test_delete_policy(self):
         policy = self.admin_client.create_qos_policy(
@@ -531,7 +549,7 @@
         self.admin_client.delete_rbac_policy(wildcard_rbac['id'])
         self.client.list_rbac_policies(id=client_rbac['id'])
 
-    @test.idempotent_id('328b1f70-d424-11e5-a57f-54ee756c66df')
+    @test.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
@@ -693,7 +711,7 @@
     def resource_setup(cls):
         super(QosDscpMarkingRuleTestJSON, cls).resource_setup()
 
-    @test.idempotent_id('8a59b00b-3e9c-4787-92f8-93a5cdf5e378')
+    @test.idempotent_id('f5cbaceb-5829-497c-9c60-ad70969e9a08')
     def test_rule_create(self):
         policy = self.create_qos_policy(name='test-policy',
                                         description='test policy',
@@ -722,7 +740,7 @@
         self.assertEqual(qos_consts.RULE_TYPE_DSCP_MARK,
                          policy_rules[0]['type'])
 
-    @test.idempotent_id('8a59b00b-ab01-4787-92f8-93a5cdf5e378')
+    @test.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',
@@ -735,7 +753,7 @@
                           policy_id=policy['id'],
                           dscp_mark=self.VALID_DSCP_MARK2)
 
-    @test.idempotent_id('149a6988-2568-47d2-931e-2dbc858943b3')
+    @test.idempotent_id('76f632e5-3175-4408-9a32-3625e599c8a2')
     def test_rule_update(self):
         policy = self.create_qos_policy(name='test-policy',
                                         description='test policy',
@@ -751,7 +769,7 @@
         retrieved_policy = retrieved_policy['dscp_marking_rule']
         self.assertEqual(self.VALID_DSCP_MARK2, retrieved_policy['dscp_mark'])
 
-    @test.idempotent_id('67ee6efd-7b33-4a68-927d-275b4f8ba958')
+    @test.idempotent_id('74f81904-c35f-48a3-adae-1f5424cb3c18')
     def test_rule_delete(self):
         policy = self.create_qos_policy(name='test-policy',
                                         description='test policy',
@@ -769,14 +787,14 @@
                           self.admin_client.show_dscp_marking_rule,
                           policy['id'], rule['id'])
 
-    @test.idempotent_id('f211222c-5808-46cb-a961-983bbab6b852')
+    @test.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)
 
-    @test.idempotent_id('a4a2e7ad-786f-4927-a85a-e545a93bd274')
+    @test.idempotent_id('bf6002ea-29de-486f-b65d-08aea6d4c4e2')
     def test_rule_create_forbidden_for_regular_tenants(self):
         self.assertRaises(
             exceptions.Forbidden,
@@ -793,7 +811,7 @@
             self.admin_client.create_dscp_marking_rule,
             policy['id'], 58)
 
-    @test.idempotent_id('ce0bd0c2-54d9-4e29-85f1-cfb36ac3ebe2')
+    @test.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',
diff --git a/neutron/tests/tempest/api/test_subnetpools.py b/neutron/tests/tempest/api/test_subnetpools.py
index d70b785..87d5c05 100644
--- a/neutron/tests/tempest/api/test_subnetpools.py
+++ b/neutron/tests/tempest/api/test_subnetpools.py
@@ -32,19 +32,20 @@
         cls._subnetpool_data = {'prefixes': prefixes,
                                 'min_prefixlen': min_prefixlen}
 
-    def _create_subnetpool(self, is_admin=False, **kwargs):
+    @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'] = self._subnetpool_data['prefixes']
+            kwargs['prefixes'] = cls._subnetpool_data['prefixes']
 
         if 'min_prefixlen' not in kwargs:
-            kwargs['min_prefixlen'] = self._subnetpool_data['min_prefixlen']
+            kwargs['min_prefixlen'] = cls._subnetpool_data['min_prefixlen']
 
-        return self.create_subnetpool(name=name, is_admin=is_admin, **kwargs)
+        return cls.create_subnetpool(name=name, is_admin=is_admin, **kwargs)
 
 
 class SubnetPoolsTest(SubnetPoolsTestBase):
@@ -339,3 +340,60 @@
             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'
+
+    @classmethod
+    def resource_setup(cls):
+        super(SubnetPoolsSearchCriteriaTest, cls).resource_setup()
+        for name in cls.resource_names:
+            cls._create_subnetpool(name=name)
+
+    @test.attr(type='smoke')
+    @test.idempotent_id('6e3f842e-6bfb-49cb-82d3-0026be4e8e04')
+    def test_list_sorts_asc(self):
+        self._test_list_sorts_asc()
+
+    @test.attr(type='smoke')
+    @test.idempotent_id('f336859b-b868-438c-a6fc-2c06374115f2')
+    def test_list_sorts_desc(self):
+        self._test_list_sorts_desc()
+
+    @test.attr(type='smoke')
+    @test.idempotent_id('1291fae7-c196-4372-ad59-ce7988518f7b')
+    def test_list_pagination(self):
+        self._test_list_pagination()
+
+    @test.attr(type='smoke')
+    @test.idempotent_id('ddb20d14-1952-49b4-a17e-231cc2239a52')
+    def test_list_pagination_with_marker(self):
+        self._test_list_pagination_with_marker()
+
+    @test.attr(type='smoke')
+    @test.idempotent_id('b3bd9665-2769-4a43-b50c-31b1add12891')
+    def test_list_pagination_with_href_links(self):
+        self._test_list_pagination_with_href_links()
+
+    @test.attr(type='smoke')
+    @test.idempotent_id('1ec1f325-43b0-406e-96ce-20539e38a61d')
+    def test_list_pagination_page_reverse_asc(self):
+        self._test_list_pagination_page_reverse_asc()
+
+    @test.attr(type='smoke')
+    @test.idempotent_id('f43a293e-4aaa-48f4-aeaf-de63a676357c')
+    def test_list_pagination_page_reverse_desc(self):
+        self._test_list_pagination_page_reverse_desc()
+
+    @test.attr(type='smoke')
+    @test.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()
+
+    @test.attr(type='smoke')
+    @test.idempotent_id('82a13efc-c18f-4249-b8ec-cec7cf26fbd6')
+    def test_list_no_pagination_limit_0(self):
+        self._test_list_no_pagination_limit_0()
diff --git a/neutron/tests/tempest/scenario/base.py b/neutron/tests/tempest/scenario/base.py
new file mode 100644
index 0000000..04b591a
--- /dev/null
+++ b/neutron/tests/tempest/scenario/base.py
@@ -0,0 +1,113 @@
+# 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 oslo_log import log as logging
+
+from tempest.common import waiters
+from tempest.lib.common import ssh
+from tempest.lib.common.utils import data_utils
+
+from neutron.tests.tempest.api import base as base_api
+from neutron.tests.tempest import config
+from neutron.tests.tempest.scenario import constants
+
+CONF = config.CONF
+LOG = logging.getLogger(__name__)
+
+
+class BaseTempestTestCase(base_api.BaseNetworkTest):
+    @classmethod
+    def resource_setup(cls):
+        super(BaseTempestTestCase, cls).resource_setup()
+
+        cls.servers = []
+        cls.keypairs = []
+
+    @classmethod
+    def resource_cleanup(cls):
+        for server in cls.servers:
+            cls.manager.servers_client.delete_server(server)
+            waiters.wait_for_server_termination(cls.manager.servers_client,
+                                                server)
+
+        for keypair in cls.keypairs:
+            cls.manager.keypairs_client.delete_keypair(
+                keypair_name=keypair['name'])
+
+        super(BaseTempestTestCase, cls).resource_cleanup()
+
+    @classmethod
+    def create_server(cls, flavor_ref, image_ref, key_name, networks,
+                      name=None):
+        name = name or data_utils.rand_name('server-test')
+        server = cls.manager.servers_client.create_server(
+            name=name, flavorRef=flavor_ref,
+            imageRef=image_ref,
+            key_name=key_name,
+            networks=networks)
+        cls.servers.append(server['server']['id'])
+        return server
+
+    @classmethod
+    def create_keypair(cls, client=None):
+        client = client or cls.manager.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_loginable_secgroup_rule(cls, secgroup_id=None):
+        client = cls.manager.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
+
+        # This rule is intended to permit inbound ssh
+        # 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.
+        ruleset = {'protocol': 'tcp',
+                   'port_range_min': 22,
+                   'port_range_max': 22,
+                   'remote_ip_prefix': '0.0.0.0/0'}
+        rules = [client.create_security_group_rule(
+                     direction='ingress', security_group_id=secgroup_id,
+                     **ruleset)['security_group_rule']]
+        return rules
+
+    @classmethod
+    def create_router_and_interface(cls, subnet_id):
+        router = cls.create_router(
+            data_utils.rand_name('router'), admin_state_up=True,
+            external_network_id=CONF.network.public_network_id)
+        cls.create_router_interface(router['id'], subnet_id)
+        cls.routers.append(router)
+        return router
+
+    @classmethod
+    def create_and_associate_floatingip(cls, port_id):
+        fip = cls.manager.network_client.create_floatingip(
+            CONF.network.public_network_id,
+            port_id=port_id)['floatingip']
+        cls.floating_ips.append(fip)
+        return fip
+
+    @classmethod
+    def check_connectivity(cls, host, ssh_user, ssh_key=None):
+        ssh_client = ssh.Client(host, ssh_user, pkey=ssh_key)
+        ssh_client.test_connection_auth()
diff --git a/neutron/tests/tempest/scenario/constants.py b/neutron/tests/tempest/scenario/constants.py
new file mode 100644
index 0000000..bb1cab3
--- /dev/null
+++ b/neutron/tests/tempest/scenario/constants.py
@@ -0,0 +1,16 @@
+# 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'
diff --git a/neutron/tests/tempest/scenario/test_basic.py b/neutron/tests/tempest/scenario/test_basic.py
new file mode 100644
index 0000000..c05e1c2
--- /dev/null
+++ b/neutron/tests/tempest/scenario/test_basic.py
@@ -0,0 +1,57 @@
+# 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 oslo_log import log as logging
+from tempest.common import waiters
+from tempest import test
+
+from neutron.tests.tempest import config
+from neutron.tests.tempest.scenario import base
+from neutron.tests.tempest.scenario import constants
+
+CONF = config.CONF
+LOG = logging.getLogger(__name__)
+
+
+class NetworkBasicTest(base.BaseTempestTestCase):
+    credentials = ['primary']
+    force_tenant_isolation = False
+
+    # Default to ipv4.
+    _ip_version = 4
+
+
+    @test.idempotent_id('de07fe0a-e955-449e-b48b-8641c14cd52e')
+    def test_basic_instance(self):
+        network = self.create_network()
+        subnet = self.create_subnet(network)
+
+        self.create_router_and_interface(subnet['id'])
+        keypair = self.create_keypair()
+        self.create_loginable_secgroup_rule()
+        server = self.create_server(
+            flavor_ref=CONF.compute.flavor_ref,
+            image_ref=CONF.compute.image_ref,
+            key_name=keypair['name'],
+            networks=[{'uuid': network['id']}])
+        waiters.wait_for_server_status(self.manager.servers_client,
+                                       server['server']['id'],
+                                       constants.SERVER_STATUS_ACTIVE)
+        port = self.client.list_ports(network_id=network['id'],
+                                      device_id=server[
+                                          'server']['id'])['ports'][0]
+        fip = self.create_and_associate_floatingip(port['id'])
+        self.check_connectivity(fip['floating_ip_address'],
+                                CONF.validation.image_ssh_user,
+                                keypair['private_key'])
diff --git a/neutron/tests/tempest/services/network/json/network_client.py b/neutron/tests/tempest/services/network/json/network_client.py
index 4a89ad5..87a77e5 100644
--- a/neutron/tests/tempest/services/network/json/network_client.py
+++ b/neutron/tests/tempest/services/network/json/network_client.py
@@ -69,6 +69,12 @@
             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'
 
@@ -83,11 +89,16 @@
         }
         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.get_uri(plural_name)
-            if filters:
-                uri += '?' + urlparse.urlencode(filters, doseq=1)
+            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)
@@ -272,6 +283,19 @@
                 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)
 
@@ -639,3 +663,57 @@
         self.expected_success(200, resp.status)
         body = jsonutils.loads(body)
         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)