Merge "Merge wait_for_volume_status to common method"
diff --git a/requirements.txt b/requirements.txt
index d15a5a1..66e5b16 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -24,3 +24,4 @@
 testscenarios>=0.4
 tempest-lib>=0.6.1
 PyYAML>=3.1.0
+stevedore>=1.5.0 # Apache-2.0
diff --git a/tempest/api/compute/admin/test_servers.py b/tempest/api/compute/admin/test_servers.py
index 0062c1e..51a03f5 100644
--- a/tempest/api/compute/admin/test_servers.py
+++ b/tempest/api/compute/admin/test_servers.py
@@ -52,7 +52,7 @@
     @test.idempotent_id('51717b38-bdc1-458b-b636-1cf82d99f62f')
     def test_list_servers_by_admin(self):
         # Listing servers by admin user returns empty list by default
-        body = self.client.list_servers_with_detail()
+        body = self.client.list_servers(detail=True)
         servers = body['servers']
         self.assertEqual([], servers)
 
@@ -61,7 +61,7 @@
         # Filter the list of servers by server error status
         params = {'status': 'error'}
         self.client.reset_state(self.s1_id, state='error')
-        body = self.non_admin_client.list_servers(params)
+        body = self.non_admin_client.list_servers(**params)
         # Reset server's state to 'active'
         self.client.reset_state(self.s1_id, state='active')
         # Verify server's state
@@ -77,7 +77,7 @@
         # Listing servers by admin user with all tenants parameter
         # Here should be listed all servers
         params = {'all_tenants': ''}
-        body = self.client.list_servers_with_detail(params)
+        body = self.client.list_servers(detail=True, **params)
         servers = body['servers']
         servers_name = map(lambda x: x['name'], servers)
 
@@ -91,14 +91,14 @@
         # List the primary tenant but get nothing due to odd specified behavior
         tenant_id = self.non_admin_client.tenant_id
         params = {'tenant_id': tenant_id}
-        body = self.client.list_servers_with_detail(params)
+        body = self.client.list_servers(detail=True, **params)
         servers = body['servers']
         self.assertEqual([], servers)
 
         # List the admin tenant which has no servers
         admin_tenant_id = self.client.tenant_id
         params = {'all_tenants': '', 'tenant_id': admin_tenant_id}
-        body = self.client.list_servers_with_detail(params)
+        body = self.client.list_servers(detail=True, **params)
         servers = body['servers']
         self.assertEqual([], servers)
 
@@ -118,10 +118,10 @@
         self.assertEqual(server['status'], 'ACTIVE')
         hostname = server[self._host_key]
         params = {'host': hostname}
-        body = self.client.list_servers(params)
+        body = self.client.list_servers(**params)
         servers = body['servers']
         nonexistent_params = {'host': 'nonexistent_host'}
-        nonexistent_body = self.client.list_servers(nonexistent_params)
+        nonexistent_body = self.client.list_servers(**nonexistent_params)
         nonexistent_servers = nonexistent_body['servers']
         self.assertIn(test_server['id'], map(lambda x: x['id'], servers))
         self.assertNotIn(test_server['id'],
diff --git a/tempest/api/compute/servers/test_create_server.py b/tempest/api/compute/servers/test_create_server.py
index 8b0e1a4..ea5f142 100644
--- a/tempest/api/compute/servers/test_create_server.py
+++ b/tempest/api/compute/servers/test_create_server.py
@@ -91,7 +91,7 @@
     @test.idempotent_id('585e934c-448e-43c4-acbf-d06a9b899997')
     def test_list_servers_with_detail(self):
         # The created server should be in the detailed list of all servers
-        body = self.client.list_servers_with_detail()
+        body = self.client.list_servers(detail=True)
         servers = body['servers']
         found = any([i for i in servers if i['id'] == self.server['id']])
         self.assertTrue(found)
diff --git a/tempest/api/compute/servers/test_list_server_filters.py b/tempest/api/compute/servers/test_list_server_filters.py
index 326334c..6d546d8 100644
--- a/tempest/api/compute/servers/test_list_server_filters.py
+++ b/tempest/api/compute/servers/test_list_server_filters.py
@@ -92,7 +92,7 @@
     def test_list_servers_filter_by_image(self):
         # Filter the list of servers by image
         params = {'image': self.image_ref}
-        body = self.client.list_servers(params)
+        body = self.client.list_servers(**params)
         servers = body['servers']
 
         self.assertIn(self.s1['id'], map(lambda x: x['id'], servers))
@@ -103,7 +103,7 @@
     def test_list_servers_filter_by_flavor(self):
         # Filter the list of servers by flavor
         params = {'flavor': self.flavor_ref_alt}
-        body = self.client.list_servers(params)
+        body = self.client.list_servers(**params)
         servers = body['servers']
 
         self.assertNotIn(self.s1['id'], map(lambda x: x['id'], servers))
@@ -114,7 +114,7 @@
     def test_list_servers_filter_by_server_name(self):
         # Filter the list of servers by server name
         params = {'name': self.s1_name}
-        body = self.client.list_servers(params)
+        body = self.client.list_servers(**params)
         servers = body['servers']
 
         self.assertIn(self.s1_name, map(lambda x: x['name'], servers))
@@ -125,7 +125,7 @@
     def test_list_servers_filter_by_server_status(self):
         # Filter the list of servers by server status
         params = {'status': 'active'}
-        body = self.client.list_servers(params)
+        body = self.client.list_servers(**params)
         servers = body['servers']
 
         self.assertIn(self.s1['id'], map(lambda x: x['id'], servers))
@@ -139,7 +139,7 @@
         self.client.stop(self.s1['id'])
         self.client.wait_for_server_status(self.s1['id'],
                                            'SHUTOFF')
-        body = self.client.list_servers(params)
+        body = self.client.list_servers(**params)
         self.client.start(self.s1['id'])
         self.client.wait_for_server_status(self.s1['id'],
                                            'ACTIVE')
@@ -153,21 +153,21 @@
     def test_list_servers_filter_by_limit(self):
         # Verify only the expected number of servers are returned
         params = {'limit': 1}
-        servers = self.client.list_servers(params)
+        servers = self.client.list_servers(**params)
         self.assertEqual(1, len([x for x in servers['servers'] if 'id' in x]))
 
     @test.idempotent_id('b1495414-2d93-414c-8019-849afe8d319e')
     def test_list_servers_filter_by_zero_limit(self):
         # Verify only the expected number of servers are returned
         params = {'limit': 0}
-        servers = self.client.list_servers(params)
+        servers = self.client.list_servers(**params)
         self.assertEqual(0, len(servers['servers']))
 
     @test.idempotent_id('37791bbd-90c0-4de0-831e-5f38cba9c6b3')
     def test_list_servers_filter_by_exceed_limit(self):
         # Verify only the expected number of servers are returned
         params = {'limit': 100000}
-        servers = self.client.list_servers(params)
+        servers = self.client.list_servers(**params)
         all_servers = self.client.list_servers()
         self.assertEqual(len([x for x in all_servers['servers'] if 'id' in x]),
                          len([x for x in servers['servers'] if 'id' in x]))
@@ -177,7 +177,7 @@
     def test_list_servers_detailed_filter_by_image(self):
         # Filter the detailed list of servers by image
         params = {'image': self.image_ref}
-        body = self.client.list_servers_with_detail(params)
+        body = self.client.list_servers(detail=True, **params)
         servers = body['servers']
 
         self.assertIn(self.s1['id'], map(lambda x: x['id'], servers))
@@ -188,7 +188,7 @@
     def test_list_servers_detailed_filter_by_flavor(self):
         # Filter the detailed list of servers by flavor
         params = {'flavor': self.flavor_ref_alt}
-        body = self.client.list_servers_with_detail(params)
+        body = self.client.list_servers(detail=True, **params)
         servers = body['servers']
 
         self.assertNotIn(self.s1['id'], map(lambda x: x['id'], servers))
@@ -199,7 +199,7 @@
     def test_list_servers_detailed_filter_by_server_name(self):
         # Filter the detailed list of servers by server name
         params = {'name': self.s1_name}
-        body = self.client.list_servers_with_detail(params)
+        body = self.client.list_servers(detail=True, **params)
         servers = body['servers']
 
         self.assertIn(self.s1_name, map(lambda x: x['name'], servers))
@@ -210,7 +210,7 @@
     def test_list_servers_detailed_filter_by_server_status(self):
         # Filter the detailed list of servers by server status
         params = {'status': 'active'}
-        body = self.client.list_servers_with_detail(params)
+        body = self.client.list_servers(detail=True, **params)
         servers = body['servers']
         test_ids = [s['id'] for s in (self.s1, self.s2, self.s3)]
 
@@ -224,7 +224,7 @@
     def test_list_servers_filtered_by_name_wildcard(self):
         # List all servers that contains '-instance' in name
         params = {'name': '-instance'}
-        body = self.client.list_servers(params)
+        body = self.client.list_servers(**params)
         servers = body['servers']
 
         self.assertIn(self.s1_name, map(lambda x: x['name'], servers))
@@ -235,7 +235,7 @@
         part_name = self.s1_name[6:-1]
 
         params = {'name': part_name}
-        body = self.client.list_servers(params)
+        body = self.client.list_servers(**params)
         servers = body['servers']
 
         self.assertIn(self.s1_name, map(lambda x: x['name'], servers))
@@ -248,7 +248,7 @@
         regexes = ['^.*\-instance\-[0-9]+$', '^.*\-instance\-.*$']
         for regex in regexes:
             params = {'name': regex}
-            body = self.client.list_servers(params)
+            body = self.client.list_servers(**params)
             servers = body['servers']
 
             self.assertIn(self.s1_name, map(lambda x: x['name'], servers))
@@ -259,7 +259,7 @@
         part_name = self.s1_name[-10:]
 
         params = {'name': part_name}
-        body = self.client.list_servers(params)
+        body = self.client.list_servers(**params)
         servers = body['servers']
 
         self.assertIn(self.s1_name, map(lambda x: x['name'], servers))
@@ -282,7 +282,7 @@
         else:
             msg = "Skipped until bug 1450859 is resolved"
             raise self.skipException(msg)
-        body = self.client.list_servers(params)
+        body = self.client.list_servers(**params)
         servers = body['servers']
 
         self.assertIn(self.s1_name, map(lambda x: x['name'], servers))
@@ -304,7 +304,7 @@
             params = {'ip': ip}
         else:
             params = {'ip6': ip}
-        body = self.client.list_servers(params)
+        body = self.client.list_servers(**params)
         servers = body['servers']
 
         self.assertIn(self.s1_name, map(lambda x: x['name'], servers))
@@ -315,5 +315,5 @@
     def test_list_servers_detailed_limit_results(self):
         # Verify only the expected number of detailed results are returned
         params = {'limit': 1}
-        servers = self.client.list_servers_with_detail(params)
+        servers = self.client.list_servers(detail=True, **params)
         self.assertEqual(1, len(servers['servers']))
diff --git a/tempest/api/compute/servers/test_list_servers_negative.py b/tempest/api/compute/servers/test_list_servers_negative.py
index fd4d902..def6cf5 100644
--- a/tempest/api/compute/servers/test_list_servers_negative.py
+++ b/tempest/api/compute/servers/test_list_servers_negative.py
@@ -68,7 +68,7 @@
     def test_list_servers_by_non_existing_image(self):
         # Listing servers for a non existing image returns empty list
         non_existing_image = '1234abcd-zzz0-aaa9-ppp3-0987654abcde'
-        body = self.client.list_servers(dict(image=non_existing_image))
+        body = self.client.list_servers(image=non_existing_image)
         servers = body['servers']
         self.assertEqual([], servers)
 
@@ -77,7 +77,7 @@
     def test_list_servers_by_non_existing_flavor(self):
         # Listing servers by non existing flavor returns empty list
         non_existing_flavor = 1234
-        body = self.client.list_servers(dict(flavor=non_existing_flavor))
+        body = self.client.list_servers(flavor=non_existing_flavor)
         servers = body['servers']
         self.assertEqual([], servers)
 
@@ -86,7 +86,7 @@
     def test_list_servers_by_non_existing_server_name(self):
         # Listing servers for a non existent server name returns empty list
         non_existing_name = 'junk_server_1234'
-        body = self.client.list_servers(dict(name=non_existing_name))
+        body = self.client.list_servers(name=non_existing_name)
         servers = body['servers']
         self.assertEqual([], servers)
 
@@ -95,21 +95,21 @@
     def test_list_servers_status_non_existing(self):
         # Return an empty list when invalid status is specified
         non_existing_status = 'BALONEY'
-        body = self.client.list_servers(dict(status=non_existing_status))
+        body = self.client.list_servers(status=non_existing_status)
         servers = body['servers']
         self.assertEqual([], servers)
 
     @test.idempotent_id('12c80a9f-2dec-480e-882b-98ba15757659')
     def test_list_servers_by_limits(self):
         # List servers by specifying limits
-        body = self.client.list_servers({'limit': 1})
+        body = self.client.list_servers(limit=1)
         self.assertEqual(1, len([x for x in body['servers'] if 'id' in x]))
 
     @test.attr(type=['negative'])
     @test.idempotent_id('d47c17fb-eebd-4287-8e95-f20a7e627b18')
     def test_list_servers_by_limits_greater_than_actual_count(self):
         # List servers by specifying a greater value for limit
-        body = self.client.list_servers({'limit': 100})
+        body = self.client.list_servers(limit=100)
         self.assertEqual(len(self.existing_fixtures), len(body['servers']))
 
     @test.attr(type=['negative'])
@@ -117,28 +117,29 @@
     def test_list_servers_by_limits_pass_string(self):
         # Return an error if a string value is passed for limit
         self.assertRaises(lib_exc.BadRequest, self.client.list_servers,
-                          {'limit': 'testing'})
+                          limit='testing')
 
     @test.attr(type=['negative'])
     @test.idempotent_id('62610dd9-4713-4ee0-8beb-fd2c1aa7f950')
     def test_list_servers_by_limits_pass_negative_value(self):
         # Return an error if a negative value for limit is passed
         self.assertRaises(lib_exc.BadRequest, self.client.list_servers,
-                          {'limit': -1})
+                          limit=-1)
 
     @test.attr(type=['negative'])
     @test.idempotent_id('87d12517-e20a-4c9c-97b6-dd1628d6d6c9')
     def test_list_servers_by_changes_since_invalid_date(self):
         # Return an error when invalid date format is passed
+        params = {'changes-since': '2011/01/01'}
         self.assertRaises(lib_exc.BadRequest, self.client.list_servers,
-                          {'changes-since': '2011/01/01'})
+                          **params)
 
     @test.attr(type=['negative'])
     @test.idempotent_id('74745ad8-b346-45b5-b9b8-509d7447fc1f')
     def test_list_servers_by_changes_since_future_date(self):
         # Return an empty list when a date in the future is passed
         changes_since = {'changes-since': '2051-01-01T12:34:00Z'}
-        body = self.client.list_servers(changes_since)
+        body = self.client.list_servers(**changes_since)
         self.assertEqual(0, len(body['servers']))
 
     @test.attr(type=['negative'])
@@ -146,7 +147,7 @@
     def test_list_servers_detail_server_is_deleted(self):
         # Server details are not listed for a deleted server
         deleted_ids = [s['id'] for s in self.deleted_fixtures]
-        body = self.client.list_servers_with_detail()
+        body = self.client.list_servers(detail=True)
         servers = body['servers']
         actual = [srv for srv in servers
                   if srv['id'] in deleted_ids]
diff --git a/tempest/api/identity/v2/test_tokens.py b/tempest/api/identity/v2/test_tokens.py
index 5a8afa0..3b508f4 100644
--- a/tempest/api/identity/v2/test_tokens.py
+++ b/tempest/api/identity/v2/test_tokens.py
@@ -13,6 +13,8 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+from oslo_utils import timeutils
+import six
 from tempest.api.identity import base
 from tempest import test
 
@@ -30,9 +32,19 @@
         password = creds.password
         tenant_name = creds.tenant_name
 
-        body = token_client.auth(username,
-                                 password,
-                                 tenant_name)
+        body = token_client.auth(username, password, tenant_name)
 
+        self.assertNotEmpty(body['token']['id'])
+        self.assertIsInstance(body['token']['id'], six.string_types)
+
+        now = timeutils.utcnow()
+        expires_at = timeutils.normalize_time(
+            timeutils.parse_isotime(body['token']['expires']))
+        self.assertGreater(expires_at, now)
+
+        self.assertEqual(body['token']['tenant']['id'],
+                         creds.credentials.tenant_id)
         self.assertEqual(body['token']['tenant']['name'],
                          tenant_name)
+
+        self.assertEqual(body['user']['id'], creds.credentials.user_id)
diff --git a/tempest/api/identity/v3/test_tokens.py b/tempest/api/identity/v3/test_tokens.py
index ab4a09f..3151763 100644
--- a/tempest/api/identity/v3/test_tokens.py
+++ b/tempest/api/identity/v3/test_tokens.py
@@ -13,6 +13,8 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+from oslo_utils import timeutils
+import six
 from tempest.api.identity import base
 from tempest import test
 
@@ -26,8 +28,25 @@
         user_id = creds.user_id
         username = creds.username
         password = creds.password
-        resp = self.non_admin_token.auth(user_id=user_id,
-                                         password=password)
 
-        subject_name = resp['token']['user']['name']
+        token_id, resp = self.non_admin_token.get_token(user_id=user_id,
+                                                        password=password,
+                                                        auth_data=True)
+
+        self.assertNotEmpty(token_id)
+        self.assertIsInstance(token_id, six.string_types)
+
+        now = timeutils.utcnow()
+        expires_at = timeutils.normalize_time(
+            timeutils.parse_isotime(resp['expires_at']))
+        self.assertGreater(resp['expires_at'],
+                           resp['issued_at'])
+        self.assertGreater(expires_at, now)
+
+        subject_id = resp['user']['id']
+        self.assertEqual(subject_id, user_id)
+
+        subject_name = resp['user']['name']
         self.assertEqual(subject_name, username)
+
+        self.assertEqual(resp['methods'][0], 'password')
diff --git a/tempest/api/network/test_networks_negative.py b/tempest/api/network/test_networks_negative.py
index 520342e..5dc1c21 100644
--- a/tempest/api/network/test_networks_negative.py
+++ b/tempest/api/network/test_networks_negative.py
@@ -57,3 +57,38 @@
         non_exist_id = data_utils.rand_name('network')
         self.assertRaises(lib_exc.NotFound, self.client.delete_network,
                           non_exist_id)
+
+    @test.attr(type=['negative'])
+    @test.idempotent_id('1cc47884-ac52-4415-a31c-e7ce5474a868')
+    def test_update_non_existent_subnet(self):
+        non_exist_id = data_utils.rand_uuid()
+        self.assertRaises(lib_exc.NotFound, self.client.update_subnet,
+                          non_exist_id, name='new_name')
+
+    @test.attr(type=['negative'])
+    @test.idempotent_id('a176c859-99fb-42ec-a208-8a85b552a239')
+    def test_delete_non_existent_subnet(self):
+        non_exist_id = data_utils.rand_uuid()
+        self.assertRaises(lib_exc.NotFound,
+                          self.client.delete_subnet, non_exist_id)
+
+    @test.attr(type=['negative'])
+    @test.idempotent_id('13d3b106-47e6-4b9b-8d53-dae947f092fe')
+    def test_create_port_on_non_existent_network(self):
+        non_exist_net_id = data_utils.rand_uuid()
+        self.assertRaises(lib_exc.NotFound,
+                          self.client.create_port, network_id=non_exist_net_id)
+
+    @test.attr(type=['negative'])
+    @test.idempotent_id('cf8eef21-4351-4f53-adcd-cc5cb1e76b92')
+    def test_update_non_existent_port(self):
+        non_exist_port_id = data_utils.rand_uuid()
+        self.assertRaises(lib_exc.NotFound, self.client.update_port,
+                          non_exist_port_id, name='new_name')
+
+    @test.attr(type=['negative'])
+    @test.idempotent_id('49ec2bbd-ac2e-46fd-8054-798e679ff894')
+    def test_delete_non_existent_port(self):
+        non_exist_port_id = data_utils.rand_uuid()
+        self.assertRaises(lib_exc.NotFound,
+                          self.client.delete_port, non_exist_port_id)
diff --git a/tempest/api_schema/response/compute/v2_1/fixed_ips.py b/tempest/api_schema/response/compute/v2_1/fixed_ips.py
index 6d5ba67..3586b70 100644
--- a/tempest/api_schema/response/compute/v2_1/fixed_ips.py
+++ b/tempest/api_schema/response/compute/v2_1/fixed_ips.py
@@ -12,6 +12,8 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+from tempest.api_schema.response.compute.v2_1 import parameter_types
+
 get_fixed_ip = {
     'status_code': [200],
     'response_body': {
@@ -20,10 +22,7 @@
             'fixed_ip': {
                 'type': 'object',
                 'properties': {
-                    'address': {
-                        'type': 'string',
-                        'format': 'ip-address'
-                    },
+                    'address': parameter_types.ip_address,
                     'cidr': {'type': 'string'},
                     'host': {'type': 'string'},
                     'hostname': {'type': 'string'}
diff --git a/tempest/api_schema/response/compute/v2_1/floating_ips.py b/tempest/api_schema/response/compute/v2_1/floating_ips.py
index 28dd40a..3551681 100644
--- a/tempest/api_schema/response/compute/v2_1/floating_ips.py
+++ b/tempest/api_schema/response/compute/v2_1/floating_ips.py
@@ -12,6 +12,8 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+from tempest.api_schema.response.compute.v2_1 import parameter_types
+
 common_floating_ip_info = {
     'type': 'object',
     'properties': {
@@ -21,14 +23,8 @@
         'id': {'type': ['integer', 'string']},
         'pool': {'type': ['string', 'null']},
         'instance_id': {'type': ['string', 'null']},
-        'ip': {
-            'type': 'string',
-            'format': 'ip-address'
-        },
-        'fixed_ip': {
-            'type': ['string', 'null'],
-            'format': 'ip-address'
-        }
+        'ip': parameter_types.ip_address,
+        'fixed_ip': parameter_types.ip_address
     },
     'additionalProperties': False,
     'required': ['id', 'pool', 'instance_id',
@@ -131,18 +127,12 @@
                 'items': {
                     'type': 'object',
                     'properties': {
-                        'address': {
-                            'type': 'string',
-                            'format': 'ip-address'
-                        },
+                        'address': parameter_types.ip_address,
                         'instance_uuid': {'type': ['string', 'null']},
                         'interface': {'type': ['string', 'null']},
                         'pool': {'type': ['string', 'null']},
                         'project_id': {'type': ['string', 'null']},
-                        'fixed_ip': {
-                            'type': ['string', 'null'],
-                            'format': 'ip-address'
-                        }
+                        'fixed_ip': parameter_types.ip_address
                     },
                     'additionalProperties': False,
                     # NOTE: fixed_ip is introduced after JUNO release,
diff --git a/tempest/api_schema/response/compute/v2_1/hypervisors.py b/tempest/api_schema/response/compute/v2_1/hypervisors.py
index e24389d..05901b6 100644
--- a/tempest/api_schema/response/compute/v2_1/hypervisors.py
+++ b/tempest/api_schema/response/compute/v2_1/hypervisors.py
@@ -14,6 +14,8 @@
 
 import copy
 
+from tempest.api_schema.response.compute.v2_1 import parameter_types
+
 get_hypervisor_statistics = {
     'status_code': [200],
     'response_body': {
@@ -57,10 +59,7 @@
         'cpu_info': {'type': 'string'},
         'current_workload': {'type': 'integer'},
         'disk_available_least': {'type': ['integer', 'null']},
-        'host_ip': {
-            'type': 'string',
-            'format': 'ip-address'
-        },
+        'host_ip': parameter_types.ip_address,
         'free_disk_gb': {'type': 'integer'},
         'free_ram_mb': {'type': 'integer'},
         'hypervisor_hostname': {'type': 'string'},
diff --git a/tempest/api_schema/response/compute/v2_1/interfaces.py b/tempest/api_schema/response/compute/v2_1/interfaces.py
index b18fba6..130775b 100644
--- a/tempest/api_schema/response/compute/v2_1/interfaces.py
+++ b/tempest/api_schema/response/compute/v2_1/interfaces.py
@@ -27,10 +27,7 @@
                         'type': 'string',
                         'format': 'uuid'
                     },
-                    'ip_address': {
-                        'type': 'string',
-                        'format': 'ipv4'
-                    }
+                    'ip_address': parameter_types.ip_address
                 },
                 'additionalProperties': False,
                 'required': ['subnet_id', 'ip_address']
diff --git a/tempest/api_schema/response/compute/v2_1/parameter_types.py b/tempest/api_schema/response/compute/v2_1/parameter_types.py
index 7b4264c..07cc890 100644
--- a/tempest/api_schema/response/compute/v2_1/parameter_types.py
+++ b/tempest/api_schema/response/compute/v2_1/parameter_types.py
@@ -33,14 +33,27 @@
     'pattern': '(?:[a-f0-9]{2}:){5}[a-f0-9]{2}'
 }
 
+ip_address = {
+    'oneOf': [
+        {
+            'type': 'string',
+            'oneOf': [
+                {'format': 'ipv4'},
+                {'format': 'ipv6'}
+            ]
+        },
+        {'type': 'null'}
+    ]
+}
+
 access_ip_v4 = {
     'type': 'string',
-    'anyOf': [{'format': 'ipv4'}, {'enum': ['']}]
+    'oneOf': [{'format': 'ipv4'}, {'enum': ['']}]
 }
 
 access_ip_v6 = {
     'type': 'string',
-    'anyOf': [{'format': 'ipv6'}, {'enum': ['']}]
+    'oneOf': [{'format': 'ipv6'}, {'enum': ['']}]
 }
 
 addresses = {
@@ -55,7 +68,7 @@
                     'version': {'type': 'integer'},
                     'addr': {
                         'type': 'string',
-                        'anyOf': [
+                        'oneOf': [
                             {'format': 'ipv4'},
                             {'format': 'ipv6'}
                         ]
diff --git a/tempest/scenario/test_large_ops.py b/tempest/scenario/test_large_ops.py
index ffb35fb..1a18be5 100644
--- a/tempest/scenario/test_large_ops.py
+++ b/tempest/scenario/test_large_ops.py
@@ -102,7 +102,7 @@
             **create_kwargs)
         # needed because of bug 1199788
         params = {'name': name}
-        server_list = self.servers_client.list_servers(params)
+        server_list = self.servers_client.list_servers(**params)
         self.servers = server_list['servers']
         for server in self.servers:
             # after deleting all servers - wait for all servers to clear
diff --git a/tempest/scenario/test_swift_telemetry_middleware.py b/tempest/scenario/test_swift_telemetry_middleware.py
index 302ccbe..29ce1a0 100644
--- a/tempest/scenario/test_swift_telemetry_middleware.py
+++ b/tempest/scenario/test_swift_telemetry_middleware.py
@@ -74,7 +74,7 @@
             called again.
             """
             results = self.telemetry_client.list_samples(
-                'storage.api.request')
+                'storage.objects.incoming.bytes')
             LOG.debug('got samples %s', results)
 
             # Extract container info from samples.
diff --git a/tempest/services/compute/json/servers_client.py b/tempest/services/compute/json/servers_client.py
index 2ecb2d4..7263014 100644
--- a/tempest/services/compute/json/servers_client.py
+++ b/tempest/services/compute/json/servers_client.py
@@ -149,28 +149,21 @@
         self.validate_response(schema.delete_server, resp, body)
         return service_client.ResponseBody(resp, body)
 
-    def list_servers(self, params=None):
+    def list_servers(self, detail=False, **params):
         """Lists all servers for a user."""
 
         url = 'servers'
+        _schema = schema.list_servers
+
+        if detail:
+            url += '/detail'
+            _schema = schema.list_servers_detail
         if params:
             url += '?%s' % urllib.urlencode(params)
 
         resp, body = self.get(url)
         body = json.loads(body)
-        self.validate_response(schema.list_servers, resp, body)
-        return service_client.ResponseBody(resp, body)
-
-    def list_servers_with_detail(self, params=None):
-        """Lists all servers in detail for a user."""
-
-        url = 'servers/detail'
-        if params:
-            url += '?%s' % urllib.urlencode(params)
-
-        resp, body = self.get(url)
-        body = json.loads(body)
-        self.validate_response(schema.list_servers_detail, resp, body)
+        self.validate_response(_schema, resp, body)
         return service_client.ResponseBody(resp, body)
 
     def wait_for_server_status(self, server_id, status, extra_timeout=0,
diff --git a/tempest/services/network/json/network_client.py b/tempest/services/network/json/network_client.py
index 13449ea..a903457 100644
--- a/tempest/services/network/json/network_client.py
+++ b/tempest/services/network/json/network_client.py
@@ -79,9 +79,9 @@
             if filters:
                 uri += '?' + urllib.urlencode(filters, doseq=1)
             resp, body = self.get(uri)
-            result = {plural_name: self.deserialize_list(body)}
+            body = self.deserialize_list(body)
             self.expected_success(200, resp.status)
-            return service_client.ResponseBody(resp, result)
+            return service_client.ResponseBody(resp, body)
 
         return _list
 
@@ -268,7 +268,7 @@
         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)}
+        body = self.deserialize_list(body)
         self.expected_success(201, resp.status)
         return service_client.ResponseBody(resp, body)
 
@@ -277,7 +277,7 @@
         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)}
+        body = self.deserialize_list(body)
         self.expected_success(201, resp.status)
         return service_client.ResponseBody(resp, body)
 
@@ -286,7 +286,7 @@
         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)}
+        body = self.deserialize_list(body)
         self.expected_success(201, resp.status)
         return service_client.ResponseBody(resp, body)
 
@@ -351,14 +351,7 @@
         return json.loads(body)
 
     def deserialize_list(self, body):
-        res = json.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]
+        return json.loads(body)
 
     def serialize(self, data):
         return json.dumps(data)
diff --git a/tempest/stress/cleanup.py b/tempest/stress/cleanup.py
index 29c4401..d9b430e 100644
--- a/tempest/stress/cleanup.py
+++ b/tempest/stress/cleanup.py
@@ -24,7 +24,7 @@
 def cleanup():
     admin_manager = clients.AdminManager()
 
-    body = admin_manager.servers_client.list_servers({"all_tenants": True})
+    body = admin_manager.servers_client.list_servers(all_tenants=True)
     LOG.info("Cleanup::remove %s servers" % len(body['servers']))
     for s in body['servers']:
         try:
diff --git a/tempest/test_discover/plugins.py b/tempest/test_discover/plugins.py
new file mode 100644
index 0000000..197bd0c
--- /dev/null
+++ b/tempest/test_discover/plugins.py
@@ -0,0 +1,56 @@
+# Copyright (c) 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.
+
+import abc
+
+import six
+import stevedore
+from tempest_lib.common.utils import misc
+
+
+@six.add_metaclass(abc.ABCMeta)
+class TempestPlugin(object):
+    """A TempestPlugin class provides the basic hooks for an external
+    plugin to provide tempest the necessary information to run the plugin.
+    """
+
+    @abc.abstractmethod
+    def load_tests(self):
+        """Method to return the information necessary to load the tests in the
+        plugin.
+
+        :return: a tuple with the first value being the test_dir and the second
+                 being the top_level
+        :rtype: tuple
+        """
+        return
+
+
+@misc.singleton
+class TempestTestPluginManager(object):
+    """Tempest test plugin manager class
+
+    This class is used to manage the lifecycle of external tempest test
+    plugins. It provides functions for getting set
+    """
+    def __init__(self):
+        self.ext_plugins = stevedore.ExtensionManager(
+            'tempest.test.plugins', invoke_on_load=True,
+            propagate_map_exceptions=True)
+
+    def get_plugin_load_tests_tuple(self):
+        load_tests_dict = {}
+        for plug in self.ext_plugins:
+            load_tests_dict[plug.name] = plug.obj.load_tests()
+        return load_tests_dict
diff --git a/tempest/test_discover/test_discover.py b/tempest/test_discover/test_discover.py
index 4a4b43a..a871d10 100644
--- a/tempest/test_discover/test_discover.py
+++ b/tempest/test_discover/test_discover.py
@@ -15,6 +15,8 @@
 import os
 import sys
 
+from tempest.test_discover import plugins
+
 if sys.version_info >= (2, 7):
     import unittest
 else:
@@ -22,9 +24,12 @@
 
 
 def load_tests(loader, tests, pattern):
+    ext_plugins = plugins.TempestTestPluginManager()
+
     suite = unittest.TestSuite()
     base_path = os.path.split(os.path.dirname(os.path.abspath(__file__)))[0]
     base_path = os.path.split(base_path)[0]
+    # Load local tempest tests
     for test_dir in ['./tempest/api', './tempest/scenario',
                      './tempest/thirdparty']:
         if not pattern:
@@ -32,4 +37,17 @@
         else:
             suite.addTests(loader.discover(test_dir, pattern=pattern,
                            top_level_dir=base_path))
+
+    plugin_load_tests = ext_plugins.get_plugin_load_tests_tuple()
+    if not plugin_load_tests:
+        return suite
+
+    # Load any installed plugin tests
+    for plugin in plugin_load_tests:
+        test_dir, top_path = plugin_load_tests[plugin]
+        if not pattern:
+            suite.addTests(loader.discover(test_dir, top_level=top_path))
+        else:
+            suite.addTests(loader.discover(test_dir, pattern=pattern,
+                                           top_level=top_path))
     return suite
diff --git a/tempest/tests/test_glance_http.py b/tempest/tests/test_glance_http.py
index 7850ee4..71aa395 100644
--- a/tempest/tests/test_glance_http.py
+++ b/tempest/tests/test_glance_http.py
@@ -137,7 +137,8 @@
         resp, body = self.client.raw_request('PUT', '/images', body=req_body)
         self.assertEqual(200, resp.status)
         self.assertEqual('fake_response_body', body.read())
-        httplib.HTTPConnection.send.assert_call_count(req_body.tell())
+        call_count = httplib.HTTPConnection.send.call_count
+        self.assertEqual(call_count - 1, req_body.tell())
 
     def test_get_connection_class_for_https(self):
         conn_class = self.client.get_connection_class('https')
diff --git a/test-requirements.txt b/test-requirements.txt
index 9bd3f46..2b3607d 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -10,4 +10,3 @@
 mock>=1.0
 coverage>=3.6
 oslotest>=1.5.1 # Apache-2.0
-stevedore>=1.5.0 # Apache-2.0