Merge "Separate namespaces_client from v2 images_client"
diff --git a/doc/source/configuration.rst b/doc/source/configuration.rst
index 9a7ce15..32cd3ef 100644
--- a/doc/source/configuration.rst
+++ b/doc/source/configuration.rst
@@ -205,6 +205,8 @@
 
 Enabling Remote Access to Created Servers
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+Network Creation/Usage for Servers
+""""""""""""""""""""""""""""""""""
 When Tempest creates servers for testing, some tests require being able to
 connect those servers. Depending on the configuration of the cloud, the methods
 for doing this can be different. In certain configurations it is required to
@@ -214,23 +216,8 @@
 run. This section covers the different methods of configuring Tempest to provide
 a network when creating servers.
 
-The ``validation`` group gathers all the connection options to remotely access the
-created servers.
-
-To enable remote access to servers, at least the three following options need to be
-set:
-
-* The ``run_validation`` option needs be set to ``true``.
-
-* The ``connect_method`` option. Two connect methods are available: ``fixed`` and
-  ``floating``, the later being set by default.
-
-* The ``auth_method`` option. Currently, only authentication by keypair is
-  available.
-
-
 Fixed Network Name
-""""""""""""""""""
+''''''''''''''''''
 This is the simplest method of specifying how networks should be used. You can
 just specify a single network name/label to use for all server creations. The
 limitation with this is that all projects and users must be able to see
@@ -252,7 +239,7 @@
 
 
 Accounts File
-"""""""""""""
+'''''''''''''
 If you are using an accounts file to provide credentials for running Tempest
 then you can leverage it to also specify which network should be used with
 server creations on a per project and user pair basis. This provides
@@ -277,7 +264,7 @@
 
 
 With Dynamic Credentials
-""""""""""""""""""""""""
+''''''''''''''''''''''''
 With dynamic credentials enabled and using nova-network, your only option for
 configuration is to either set a fixed network name or not. However, in most
 cases it shouldn't matter because nova-network should have no problem booting a
@@ -302,6 +289,34 @@
 network available for the server creation, or use ``fixed_network_name`` to
 inform Tempest which network to use.
 
+SSH Connection Configuration
+""""""""""""""""""""""""""""
+There are also several different ways to actually establish a connection and
+authenticate/login on the server. After a server is booted with a provided
+network there are still details needed to know how to actually connect to
+the server. The ``validation`` group gathers all the options regarding
+connecting to and remotely accessing the created servers.
+
+To enable remote access to servers, there are 3 options at a minimum that are used:
+
+ #. ``run_validation``
+ #. ``connect_method``
+ #. ``auth_method``
+
+The ``run_validation`` is used to enable or disable ssh connectivity for
+all tests (with the exception of scenario tests which do not have a flag for
+enabling or disabling ssh) To enable ssh connectivity this needs be set to ``true``.
+
+The ``connect_method`` option is used to tell tempest what kind of IP to use for
+establishing a connection to the server. Two methods are available: ``fixed``
+and ``floating``, the later being set by default. If this is set to floating
+tempest will create a floating ip for the server before attempted to connect
+to it. The IP for the floating ip is what is used for the connection.
+
+For the ``auth_method`` option there is currently, only one valid option,
+``keypair``. With this set to ``keypair`` tempest will create an ssh keypair
+and use that for authenticating against the created server.
+
 Configuring Available Services
 ------------------------------
 OpenStack is really a constellation of several different projects which
diff --git a/tempest/api/image/base.py b/tempest/api/image/base.py
index adc978f..6e2713a 100644
--- a/tempest/api/image/base.py
+++ b/tempest/api/image/base.py
@@ -93,12 +93,14 @@
     @classmethod
     def setup_clients(cls):
         super(BaseV1ImageMembersTest, cls).setup_clients()
+        cls.image_member_client = cls.os.image_member_client
+        cls.alt_image_member_client = cls.os_alt.image_member_client
         cls.alt_img_cli = cls.os_alt.image_client
 
     @classmethod
     def resource_setup(cls):
         super(BaseV1ImageMembersTest, cls).resource_setup()
-        cls.alt_tenant_id = cls.alt_img_cli.tenant_id
+        cls.alt_tenant_id = cls.alt_image_member_client.tenant_id
 
     def _create_image(self):
         image_file = moves.cStringIO(data_utils.random_bytes())
diff --git a/tempest/api/image/v1/test_image_members.py b/tempest/api/image/v1/test_image_members.py
index eb6969b..0bad96a 100644
--- a/tempest/api/image/v1/test_image_members.py
+++ b/tempest/api/image/v1/test_image_members.py
@@ -22,8 +22,8 @@
     @test.idempotent_id('1d6ef640-3a20-4c84-8710-d95828fdb6ad')
     def test_add_image_member(self):
         image = self._create_image()
-        self.client.add_member(self.alt_tenant_id, image)
-        body = self.client.list_image_members(image)
+        self.image_member_client.add_member(self.alt_tenant_id, image)
+        body = self.image_member_client.list_image_members(image)
         members = body['members']
         members = map(lambda x: x['member_id'], members)
         self.assertIn(self.alt_tenant_id, members)
@@ -33,10 +33,11 @@
     @test.idempotent_id('6a5328a5-80e8-4b82-bd32-6c061f128da9')
     def test_get_shared_images(self):
         image = self._create_image()
-        self.client.add_member(self.alt_tenant_id, image)
+        self.image_member_client.add_member(self.alt_tenant_id, image)
         share_image = self._create_image()
-        self.client.add_member(self.alt_tenant_id, share_image)
-        body = self.client.list_shared_images(self.alt_tenant_id)
+        self.image_member_client.add_member(self.alt_tenant_id, share_image)
+        body = self.image_member_client.list_shared_images(
+            self.alt_tenant_id)
         images = body['shared_images']
         images = map(lambda x: x['image_id'], images)
         self.assertIn(share_image, images)
@@ -45,8 +46,8 @@
     @test.idempotent_id('a76a3191-8948-4b44-a9d6-4053e5f2b138')
     def test_remove_member(self):
         image_id = self._create_image()
-        self.client.add_member(self.alt_tenant_id, image_id)
-        self.client.delete_member(self.alt_tenant_id, image_id)
-        body = self.client.list_image_members(image_id)
+        self.image_member_client.add_member(self.alt_tenant_id, image_id)
+        self.image_member_client.delete_member(self.alt_tenant_id, image_id)
+        body = self.image_member_client.list_image_members(image_id)
         members = body['members']
         self.assertEqual(0, len(members), str(members))
diff --git a/tempest/api/image/v1/test_image_members_negative.py b/tempest/api/image/v1/test_image_members_negative.py
index 16a4ba6..d46a836 100644
--- a/tempest/api/image/v1/test_image_members_negative.py
+++ b/tempest/api/image/v1/test_image_members_negative.py
@@ -25,7 +25,8 @@
     def test_add_member_with_non_existing_image(self):
         # Add member with non existing image.
         non_exist_image = data_utils.rand_uuid()
-        self.assertRaises(lib_exc.NotFound, self.client.add_member,
+        self.assertRaises(lib_exc.NotFound,
+                          self.image_member_client.add_member,
                           self.alt_tenant_id, non_exist_image)
 
     @test.attr(type=['negative'])
@@ -33,7 +34,8 @@
     def test_delete_member_with_non_existing_image(self):
         # Delete member with non existing image.
         non_exist_image = data_utils.rand_uuid()
-        self.assertRaises(lib_exc.NotFound, self.client.delete_member,
+        self.assertRaises(lib_exc.NotFound,
+                          self.image_member_client.delete_member,
                           self.alt_tenant_id, non_exist_image)
 
     @test.attr(type=['negative'])
@@ -42,7 +44,8 @@
         # Delete member with non existing tenant.
         image_id = self._create_image()
         non_exist_tenant = data_utils.rand_uuid_hex()
-        self.assertRaises(lib_exc.NotFound, self.client.delete_member,
+        self.assertRaises(lib_exc.NotFound,
+                          self.image_member_client.delete_member,
                           non_exist_tenant, image_id)
 
     @test.attr(type=['negative'])
diff --git a/tempest/clients.py b/tempest/clients.py
index f98ca00..06d3a74 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -132,6 +132,7 @@
 from tempest.services.identity.v3.json.users_clients import \
     UsersClient as UsersV3Client
 from tempest.services.image.v1.json.images_client import ImagesClient
+from tempest.services.image.v1.json.members_client import MembersClient
 from tempest.services.image.v2.json.images_client import \
     ImagesClient as ImagesV2Client
 from tempest.services.image.v2.json.members_client import MembersClient \
@@ -335,6 +336,14 @@
                 build_interval=CONF.image.build_interval,
                 build_timeout=CONF.image.build_timeout,
                 **self.default_params)
+            self.image_member_client = MembersClient(
+                self.auth_provider,
+                CONF.image.catalog_type,
+                CONF.image.region or CONF.identity.region,
+                endpoint_type=CONF.image.endpoint_type,
+                build_interval=CONF.image.build_interval,
+                build_timeout=CONF.image.build_timeout,
+                **self.default_params)
             self.image_client_v2 = ImagesV2Client(
                 self.auth_provider,
                 CONF.image.catalog_type,
diff --git a/tempest/services/image/v1/json/images_client.py b/tempest/services/image/v1/json/images_client.py
index 63fb59d..4ffaf3b 100644
--- a/tempest/services/image/v1/json/images_client.py
+++ b/tempest/services/image/v1/json/images_client.py
@@ -188,36 +188,3 @@
     def resource_type(self):
         """Returns the primary type of resource this client works with."""
         return 'image_meta'
-
-    def list_image_members(self, image_id):
-        url = 'images/%s/members' % image_id
-        resp, body = self.get(url)
-        self.expected_success(200, resp.status)
-        body = json.loads(body)
-        return rest_client.ResponseBody(resp, body)
-
-    def list_shared_images(self, tenant_id):
-        """List shared images with the specified tenant"""
-        url = 'shared-images/%s' % tenant_id
-        resp, body = self.get(url)
-        self.expected_success(200, resp.status)
-        body = json.loads(body)
-        return rest_client.ResponseBody(resp, body)
-
-    def add_member(self, member_id, image_id, **kwargs):
-        """Add a member to an image.
-
-        Available params: see http://developer.openstack.org/
-                              api-ref-image-v1.html#addMember-v1
-        """
-        url = 'images/%s/members/%s' % (image_id, member_id)
-        body = json.dumps({'member': kwargs})
-        resp, __ = self.put(url, body)
-        self.expected_success(204, resp.status)
-        return rest_client.ResponseBody(resp)
-
-    def delete_member(self, member_id, image_id):
-        url = 'images/%s/members/%s' % (image_id, member_id)
-        resp, __ = self.delete(url)
-        self.expected_success(204, resp.status)
-        return rest_client.ResponseBody(resp)
diff --git a/tempest/services/image/v1/json/members_client.py b/tempest/services/image/v1/json/members_client.py
new file mode 100644
index 0000000..95cee1c
--- /dev/null
+++ b/tempest/services/image/v1/json/members_client.py
@@ -0,0 +1,63 @@
+#    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_serialization import jsonutils as json
+
+from tempest.lib.common import rest_client
+
+
+class MembersClient(rest_client.RestClient):
+    api_version = "v1"
+
+    def list_image_members(self, image_id):
+        """List all members of an image."""
+        url = 'images/%s/members' % image_id
+        resp, body = self.get(url)
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def list_shared_images(self, tenant_id):
+        """List image memberships for the given tenant.
+
+        Available params: see http://developer.openstack.org/
+                              api-ref-image-v1.html#listSharedImages-v1
+        """
+
+        url = 'shared-images/%s' % tenant_id
+        resp, body = self.get(url)
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def add_member(self, member_id, image_id, **kwargs):
+        """Add a member to an image.
+
+        Available params: see http://developer.openstack.org/
+                              api-ref-image-v1.html#addMember-v1
+        """
+        url = 'images/%s/members/%s' % (image_id, member_id)
+        body = json.dumps({'member': kwargs})
+        resp, __ = self.put(url, body)
+        self.expected_success(204, resp.status)
+        return rest_client.ResponseBody(resp)
+
+    def delete_member(self, member_id, image_id):
+        """Removes a membership from the image.
+
+        Available params: see http://developer.openstack.org/
+                              api-ref-image-v1.html#removeMember-v1
+        """
+        url = 'images/%s/members/%s' % (image_id, member_id)
+        resp, __ = self.delete(url)
+        self.expected_success(204, resp.status)
+        return rest_client.ResponseBody(resp)