Merge "Reuse v2 extension client for cinder v3"
diff --git a/tempest/api/identity/admin/v3/test_tokens.py b/tempest/api/identity/admin/v3/test_tokens.py
index 8c5e63b..1acc67d 100644
--- a/tempest/api/identity/admin/v3/test_tokens.py
+++ b/tempest/api/identity/admin/v3/test_tokens.py
@@ -145,7 +145,7 @@
 
     @decorators.idempotent_id('08ed85ce-2ba8-4864-b442-bcc61f16ae89')
     def test_get_available_project_scopes(self):
-        manager_project_id = self.manager.credentials.project_id
+        manager_project_id = self.os_primary.credentials.project_id
         admin_user_id = self.os_admin.credentials.user_id
         admin_role_id = self.get_role_by_name(CONF.identity.admin_role)['id']
 
diff --git a/tempest/lib/common/ssh.py b/tempest/lib/common/ssh.py
index 657c0c1..d4ec6ad 100644
--- a/tempest/lib/common/ssh.py
+++ b/tempest/lib/common/ssh.py
@@ -84,10 +84,6 @@
         ssh.set_missing_host_key_policy(
             paramiko.AutoAddPolicy())
         _start_time = time.time()
-        if self.proxy_client is not None:
-            proxy_chan = self._get_proxy_channel()
-        else:
-            proxy_chan = None
         if self.pkey is not None:
             LOG.info("Creating ssh connection to '%s:%d' as '%s'"
                      " with public key authentication",
@@ -98,6 +94,10 @@
                      self.host, self.port, self.username, str(self.password))
         attempts = 0
         while True:
+            if self.proxy_client is not None:
+                proxy_chan = self._get_proxy_channel()
+            else:
+                proxy_chan = None
             try:
                 ssh.connect(self.host, port=self.port, username=self.username,
                             password=self.password,
diff --git a/tempest/lib/services/identity/v3/services_client.py b/tempest/lib/services/identity/v3/services_client.py
index 17b0f24..7bbe850 100644
--- a/tempest/lib/services/identity/v3/services_client.py
+++ b/tempest/lib/services/identity/v3/services_client.py
@@ -76,7 +76,7 @@
         url = 'services'
         if params:
             url += '?%s' % urllib.urlencode(params)
-        resp, body = self.get('services')
+        resp, body = self.get(url)
         self.expected_success(200, resp.status)
         body = json.loads(body)
         return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/volume/v2/types_client.py b/tempest/lib/services/volume/v2/types_client.py
index 5d30615..af4fd8c 100644
--- a/tempest/lib/services/volume/v2/types_client.py
+++ b/tempest/lib/services/volume/v2/types_client.py
@@ -41,7 +41,7 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        http://developer.openstack.org/api-ref/block-storage/v2/#list-volume-types
+        https://developer.openstack.org/api-ref/block-storage/v2/#list-all-volume-types-for-v2
         """
         url = 'types'
         if params:
@@ -57,7 +57,7 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        http://developer.openstack.org/api-ref/block-storage/v2/#show-volume-type-details
+        https://developer.openstack.org/api-ref/block-storage/v2/#show-volume-type-details-for-v2
         """
         url = "types/%s" % volume_type_id
         resp, body = self.get(url)
@@ -70,7 +70,7 @@
 
         For a full list of available parameters, please refer to the official
         API reference:
-        http://developer.openstack.org/api-ref/block-storage/v2/#create-volume-type
+        https://developer.openstack.org/api-ref/block-storage/v2/#create-volume-type-for-v2
         """
         post_body = json.dumps({'volume_type': kwargs})
         resp, body = self.post('types', post_body)
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
index 2e55c2e..a486a29 100644
--- a/tempest/scenario/manager.py
+++ b/tempest/scenario/manager.py
@@ -47,40 +47,40 @@
     def setup_clients(cls):
         super(ScenarioTest, cls).setup_clients()
         # Clients (in alphabetical order)
-        cls.flavors_client = cls.manager.flavors_client
+        cls.flavors_client = cls.os_primary.flavors_client
         cls.compute_floating_ips_client = (
-            cls.manager.compute_floating_ips_client)
+            cls.os_primary.compute_floating_ips_client)
         if CONF.service_available.glance:
             # Check if glance v1 is available to determine which client to use.
             if CONF.image_feature_enabled.api_v1:
-                cls.image_client = cls.manager.image_client
+                cls.image_client = cls.os_primary.image_client
             elif CONF.image_feature_enabled.api_v2:
-                cls.image_client = cls.manager.image_client_v2
+                cls.image_client = cls.os_primary.image_client_v2
             else:
                 raise lib_exc.InvalidConfiguration(
                     'Either api_v1 or api_v2 must be True in '
                     '[image-feature-enabled].')
         # Compute image client
-        cls.compute_images_client = cls.manager.compute_images_client
-        cls.keypairs_client = cls.manager.keypairs_client
+        cls.compute_images_client = cls.os_primary.compute_images_client
+        cls.keypairs_client = cls.os_primary.keypairs_client
         # Nova security groups client
         cls.compute_security_groups_client = (
-            cls.manager.compute_security_groups_client)
+            cls.os_primary.compute_security_groups_client)
         cls.compute_security_group_rules_client = (
-            cls.manager.compute_security_group_rules_client)
-        cls.servers_client = cls.manager.servers_client
-        cls.interface_client = cls.manager.interfaces_client
+            cls.os_primary.compute_security_group_rules_client)
+        cls.servers_client = cls.os_primary.servers_client
+        cls.interface_client = cls.os_primary.interfaces_client
         # Neutron network client
-        cls.networks_client = cls.manager.networks_client
-        cls.ports_client = cls.manager.ports_client
-        cls.routers_client = cls.manager.routers_client
-        cls.subnets_client = cls.manager.subnets_client
-        cls.floating_ips_client = cls.manager.floating_ips_client
-        cls.security_groups_client = cls.manager.security_groups_client
+        cls.networks_client = cls.os_primary.networks_client
+        cls.ports_client = cls.os_primary.ports_client
+        cls.routers_client = cls.os_primary.routers_client
+        cls.subnets_client = cls.os_primary.subnets_client
+        cls.floating_ips_client = cls.os_primary.floating_ips_client
+        cls.security_groups_client = cls.os_primary.security_groups_client
         cls.security_group_rules_client = (
-            cls.manager.security_group_rules_client)
-        cls.volumes_client = cls.manager.volumes_v2_client
-        cls.snapshots_client = cls.manager.snapshots_v2_client
+            cls.os_primary.security_group_rules_client)
+        cls.volumes_client = cls.os_primary.volumes_v2_client
+        cls.snapshots_client = cls.os_primary.snapshots_v2_client
 
     # ## Test functions library
     #
@@ -133,7 +133,7 @@
 
         # Needed for the cross_tenant_traffic test:
         if clients is None:
-            clients = self.manager
+            clients = self.os_primary
 
         if name is None:
             name = data_utils.rand_name(self.__class__.__name__ + "-server")
diff --git a/tempest/scenario/test_server_multinode.py b/tempest/scenario/test_server_multinode.py
index 15f57d4..552ab27 100644
--- a/tempest/scenario/test_server_multinode.py
+++ b/tempest/scenario/test_server_multinode.py
@@ -13,7 +13,6 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-
 from tempest import config
 from tempest.lib import decorators
 from tempest.lib import exceptions
@@ -35,15 +34,6 @@
             raise cls.skipException(
                 "Less than 2 compute nodes, skipping multinode tests.")
 
-    @classmethod
-    def setup_clients(cls):
-        super(TestServerMultinode, cls).setup_clients()
-        # Use admin client by default
-        cls.manager = cls.os_admin
-        # this is needed so that we can use the availability_zone:host
-        # scheduler hint, which is admin_only by default
-        cls.servers_client = cls.os_admin.servers_client
-
     @decorators.idempotent_id('9cecbe35-b9d4-48da-a37e-7ce70aa43d30')
     @decorators.attr(type='smoke')
     @test.services('compute', 'network')
@@ -74,9 +64,13 @@
         for host in hosts[:CONF.compute.min_compute_nodes]:
             # by getting to active state here, this means this has
             # landed on the host in question.
+            # in order to use the availability_zone:host scheduler hint,
+            # admin client is need here.
             inst = self.create_server(
+                clients=self.os_admin,
                 availability_zone='%(zone)s:%(host_name)s' % host)
-            server = self.servers_client.show_server(inst['id'])['server']
+            server = self.os_admin.servers_client.show_server(
+                inst['id'])['server']
             # ensure server is located on the requested host
             self.assertEqual(host['host_name'], server['OS-EXT-SRV-ATTR:host'])
             servers.append(server)
diff --git a/tempest/tests/lib/services/network/test_subnets_client.py b/tempest/tests/lib/services/network/test_subnets_client.py
new file mode 100644
index 0000000..0aadf54
--- /dev/null
+++ b/tempest/tests/lib/services/network/test_subnets_client.py
@@ -0,0 +1,250 @@
+# Copyright 2017 AT&T Corporation.
+# 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 copy
+
+from tempest.lib.services.network import subnets_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestSubnetsClient(base.BaseServiceTest):
+
+    FAKE_SUBNET = {
+        "subnet": {
+            "name": "",
+            "enable_dhcp": True,
+            "network_id": "d32019d3-bc6e-4319-9c1d-6722fc136a22",
+            "segment_id": None,
+            "project_id": "4fd44f30292945e481c7b8a0c8908869",
+            "tenant_id": "4fd44f30292945e481c7b8a0c8908869",
+            "dns_nameservers": [],
+            "allocation_pools": [
+                {
+                    "start": "192.168.199.2",
+                    "end": "192.168.199.254"
+                }
+            ],
+            "host_routes": [],
+            "ip_version": 4,
+            "gateway_ip": "192.168.199.1",
+            "cidr": "192.168.199.0/24",
+            "id": "3b80198d-4f7b-4f77-9ef5-774d54e17126",
+            "created_at": "2016-10-10T14:35:47Z",
+            "description": "",
+            "ipv6_address_mode": None,
+            "ipv6_ra_mode": None,
+            "revision_number": 2,
+            "service_types": [],
+            "subnetpool_id": None,
+            "updated_at": "2016-10-10T14:35:47Z"
+        }
+    }
+
+    FAKE_UPDATED_SUBNET = {
+        "subnet": {
+            "name": "my_subnet",
+            "enable_dhcp": True,
+            "network_id": "db193ab3-96e3-4cb3-8fc5-05f4296d0324",
+            "segment_id": None,
+            "project_id": "26a7980765d0414dbc1fc1f88cdb7e6e",
+            "tenant_id": "26a7980765d0414dbc1fc1f88cdb7e6e",
+            "dns_nameservers": [],
+            "allocation_pools": [
+                {
+                    "start": "10.0.0.2",
+                    "end": "10.0.0.254"
+                }
+            ],
+            "host_routes": [],
+            "ip_version": 4,
+            "gateway_ip": "10.0.0.1",
+            "cidr": "10.0.0.0/24",
+            "id": "08eae331-0402-425a-923c-34f7cfe39c1b",
+            "description": ""
+        }
+    }
+
+    FAKE_SUBNETS = {
+        "subnets": [
+            {
+                "name": "private-subnet",
+                "enable_dhcp": True,
+                "network_id": "db193ab3-96e3-4cb3-8fc5-05f4296d0324",
+                "segment_id": None,
+                "project_id": "26a7980765d0414dbc1fc1f88cdb7e6e",
+                "tenant_id": "26a7980765d0414dbc1fc1f88cdb7e6e",
+                "dns_nameservers": [],
+                "allocation_pools": [
+                    {
+                        "start": "10.0.0.2",
+                        "end": "10.0.0.254"
+                    }
+                ],
+                "host_routes": [],
+                "ip_version": 4,
+                "gateway_ip": "10.0.0.1",
+                "cidr": "10.0.0.0/24",
+                "id": "08eae331-0402-425a-923c-34f7cfe39c1b",
+                "created_at": "2016-10-10T14:35:34Z",
+                "description": "",
+                "ipv6_address_mode": None,
+                "ipv6_ra_mode": None,
+                "revision_number": 2,
+                "service_types": [],
+                "subnetpool_id": None,
+                "updated_at": "2016-10-10T14:35:34Z"
+            },
+            {
+                "name": "my_subnet",
+                "enable_dhcp": True,
+                "network_id": "d32019d3-bc6e-4319-9c1d-6722fc136a22",
+                "segment_id": None,
+                "project_id": "4fd44f30292945e481c7b8a0c8908869",
+                "tenant_id": "4fd44f30292945e481c7b8a0c8908869",
+                "dns_nameservers": [],
+                "allocation_pools": [
+                    {
+                        "start": "192.0.0.2",
+                        "end": "192.255.255.254"
+                    }
+                ],
+                "host_routes": [],
+                "ip_version": 4,
+                "gateway_ip": "192.0.0.1",
+                "cidr": "192.0.0.0/8",
+                "id": "54d6f61d-db07-451c-9ab3-b9609b6b6f0b",
+                "created_at": "2016-10-10T14:35:47Z",
+                "description": "",
+                "ipv6_address_mode": None,
+                "ipv6_ra_mode": None,
+                "revision_number": 2,
+                "service_types": [],
+                "subnetpool_id": None,
+                "updated_at": "2016-10-10T14:35:47Z"
+            }
+        ]
+    }
+
+    FAKE_BULK_SUBNETS = copy.deepcopy(FAKE_SUBNETS)
+
+    FAKE_SUBNET_ID = "54d6f61d-db07-451c-9ab3-b9609b6b6f0b"
+
+    FAKE_NETWORK_ID = "d32019d3-bc6e-4319-9c1d-6722fc136a22"
+
+    def setUp(self):
+        super(TestSubnetsClient, self).setUp()
+        fake_auth = fake_auth_provider.FakeAuthProvider()
+        self.subnets_client = subnets_client.SubnetsClient(
+            fake_auth, 'compute', 'regionOne')
+
+    def _test_create_subnet(self, bytes_body=False):
+        self.check_service_client_function(
+            self.subnets_client.create_subnet,
+            'tempest.lib.common.rest_client.RestClient.post',
+            self.FAKE_SUBNET,
+            bytes_body,
+            201,
+            network_id=self.FAKE_NETWORK_ID,
+            ip_version=4,
+            cidr="192.168.199.0/24")
+
+    def _test_update_subnet(self, bytes_body=False):
+        self.check_service_client_function(
+            self.subnets_client.update_subnet,
+            'tempest.lib.common.rest_client.RestClient.put',
+            self.FAKE_UPDATED_SUBNET,
+            bytes_body,
+            200,
+            subnet_id=self.FAKE_SUBNET_ID,
+            name="fake_updated_subnet_name")
+
+    def _test_show_subnet(self, bytes_body=False):
+        self.check_service_client_function(
+            self.subnets_client.show_subnet,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_SUBNET,
+            bytes_body,
+            200,
+            subnet_id=self.FAKE_SUBNET_ID)
+
+    def _test_list_subnets(self, bytes_body=False):
+        self.check_service_client_function(
+            self.subnets_client.list_subnets,
+            'tempest.lib.common.rest_client.RestClient.get',
+            self.FAKE_SUBNETS,
+            bytes_body,
+            200)
+
+    def _test_create_bulk_subnets(self, bytes_body=False):
+        kwargs = {
+            "subnets": [
+                {
+                    "cidr": "192.168.199.0/24",
+                    "ip_version": 4,
+                    "network_id": "e6031bc2-901a-4c66-82da-f4c32ed89406"
+                },
+                {
+                    "cidr": "10.56.4.0/22",
+                    "ip_version": 4,
+                    "network_id": "64239a54-dcc4-4b39-920b-b37c2144effa"
+                }
+            ]
+        }
+        self.check_service_client_function(
+            self.subnets_client.create_bulk_subnets,
+            'tempest.lib.common.rest_client.RestClient.post',
+            self.FAKE_SUBNETS,
+            bytes_body,
+            201,
+            **kwargs)
+
+    def test_create_subnet_with_str_body(self):
+        self._test_create_subnet()
+
+    def test_create_subnet_with_bytes_body(self):
+        self._test_create_subnet(bytes_body=True)
+
+    def test_update_subnet_with_str_body(self):
+        self._test_update_subnet()
+
+    def test_update_subnet_with_bytes_body(self):
+        self._test_update_subnet(bytes_body=True)
+
+    def test_show_subnet_with_str_body(self):
+        self._test_show_subnet()
+
+    def test_show_subnet_with_bytes_body(self):
+        self._test_show_subnet(bytes_body=True)
+
+    def test_list_subnets_with_str_body(self):
+        self._test_list_subnets()
+
+    def test_list_subnets_with_bytes_body(self):
+        self._test_list_subnets(bytes_body=True)
+
+    def test_delete_subnet(self):
+        self.check_service_client_function(
+            self.subnets_client.delete_subnet,
+            'tempest.lib.common.rest_client.RestClient.delete',
+            {},
+            status=204,
+            subnet_id=self.FAKE_SUBNET_ID)
+
+    def test_create_bulk_subnets_with_str_body(self):
+        self._test_create_bulk_subnets()
+
+    def test_create_bulk_subnets_with_bytes_body(self):
+        self._test_create_bulk_subnets(bytes_body=True)