Merge "Import data_utils from tempest.lib.common.utils"
diff --git a/doc/source/conf.py b/doc/source/conf.py
index 201d387..4e7a72d 100644
--- a/doc/source/conf.py
+++ b/doc/source/conf.py
@@ -22,10 +22,8 @@
 # All configuration values have a default; values that are commented out
 # serve to show the default.
 
-import sys
 import os
 import subprocess
-import warnings
 
 import openstackdocstheme
 
diff --git a/releasenotes/notes/migrate-object-storage-as-stable-interface-42014c7b43ecb254.yaml b/releasenotes/notes/migrate-object-storage-as-stable-interface-42014c7b43ecb254.yaml
new file mode 100644
index 0000000..72b8e26
--- /dev/null
+++ b/releasenotes/notes/migrate-object-storage-as-stable-interface-42014c7b43ecb254.yaml
@@ -0,0 +1,10 @@
+---
+features:
+  - |
+    Define below object storage service clients as libraries.
+    Add new service clients to the library interface so the
+    other projects can use these modules as stable libraries
+    without any maintenance changes.
+
+      * bulk_middleware_client
+      * capabilities_client
diff --git a/releasenotes/notes/remove-support-of-py34-7d59fdb431fefe24.yaml b/releasenotes/notes/remove-support-of-py34-7d59fdb431fefe24.yaml
new file mode 100644
index 0000000..093228a
--- /dev/null
+++ b/releasenotes/notes/remove-support-of-py34-7d59fdb431fefe24.yaml
@@ -0,0 +1,5 @@
+---
+deprecations:
+  - |
+    Remove the support of python3.4, because in Ubuntu Xenial only
+    python3.5 is available (python3.4 is restricted to <= Mitaka).
diff --git a/setup.cfg b/setup.cfg
index f52137e..04bb29f 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -16,7 +16,6 @@
     Programming Language :: Python :: 2
     Programming Language :: Python :: 2.7
     Programming Language :: Python :: 3
-    Programming Language :: Python :: 3.4
     Programming Language :: Python :: 3.5
 
 [files]
diff --git a/tempest/lib/exceptions.py b/tempest/lib/exceptions.py
index cdb8be9..c538c72 100644
--- a/tempest/lib/exceptions.py
+++ b/tempest/lib/exceptions.py
@@ -44,6 +44,9 @@
     def __str__(self):
         return self._error_string
 
+    def __repr__(self):
+        return self._error_string
+
 
 class RestClientException(TempestException,
                           testtools.TestCase.failureException):
diff --git a/tempest/lib/services/clients.py b/tempest/lib/services/clients.py
index 5f230b7..4fa7a7a 100644
--- a/tempest/lib/services/clients.py
+++ b/tempest/lib/services/clients.py
@@ -17,10 +17,12 @@
 import copy
 import importlib
 import inspect
+import sys
 import warnings
 
 from debtcollector import removals
 from oslo_log import log as logging
+import testtools
 
 from tempest.lib import auth
 from tempest.lib.common.utils import misc
@@ -85,6 +87,7 @@
     extra_service_versions = set([])
     _tempest_modules = set(tempest_modules())
     plugin_services = ClientsRegistry().get_service_clients()
+    name_conflicts = []
     for plugin_name in plugin_services:
         plug_service_versions = set([x['service_version'] for x in
                                      plugin_services[plugin_name]])
@@ -96,8 +99,8 @@
                     'claimed by another one' % (plugin_name,
                                                 extra_service_versions &
                                                 plug_service_versions))
-                raise exceptions.PluginRegistrationException(
-                    name=plugin_name, detailed_error=detailed_error)
+                name_conflicts.append(exceptions.PluginRegistrationException(
+                    name=plugin_name, detailed_error=detailed_error))
             # NOTE(andreaf) Once all tempest clients are stable, the following
             # if will have to be removed.
             if not plug_service_versions.isdisjoint(
@@ -107,9 +110,14 @@
                     'claimed by a Tempest one' % (plugin_name,
                                                   _tempest_internal_modules() &
                                                   plug_service_versions))
-                raise exceptions.PluginRegistrationException(
-                    name=plugin_name, detailed_error=detailed_error)
+                name_conflicts.append(exceptions.PluginRegistrationException(
+                    name=plugin_name, detailed_error=detailed_error))
         extra_service_versions |= plug_service_versions
+    if name_conflicts:
+        LOG.error(
+            'Failed to list available modules due to name conflicts: %s',
+            name_conflicts)
+        raise testtools.MultipleExceptions(*name_conflicts)
     return _tempest_modules | extra_service_versions
 
 
@@ -375,6 +383,7 @@
         # Register service clients from the registry (__tempest__ and plugins)
         clients_registry = ClientsRegistry()
         plugin_service_clients = clients_registry.get_service_clients()
+        registration_errors = []
         for plugin in plugin_service_clients:
             service_clients = plugin_service_clients[plugin]
             # Each plugin returns a list of service client parameters
@@ -385,10 +394,12 @@
                 try:
                     self.register_service_client_module(**service_client)
                 except Exception:
+                    registration_errors.append(sys.exc_info())
                     LOG.exception(
                         'Failed to register service client from plugin %s '
                         'with parameters %s', plugin, service_client)
-                    raise
+        if registration_errors:
+            raise testtools.MultipleExceptions(*registration_errors)
 
     def register_service_client_module(self, name, service_version,
                                        module_path, client_names, **kwargs):
diff --git a/tempest/lib/services/network/versions_client.py b/tempest/lib/services/network/versions_client.py
index a9c3bbf..f87fe87 100644
--- a/tempest/lib/services/network/versions_client.py
+++ b/tempest/lib/services/network/versions_client.py
@@ -15,7 +15,6 @@
 import time
 
 from oslo_serialization import jsonutils as json
-from six.moves import urllib
 
 from tempest.lib.services.network import base
 
@@ -25,9 +24,7 @@
     def list_versions(self):
         """Do a GET / to fetch available API version information."""
 
-        endpoint = self.base_url
-        url = urllib.parse.urlparse(endpoint)
-        version_url = '%s://%s/' % (url.scheme, url.netloc)
+        version_url = self._get_base_version_url()
 
         # Note: we do a raw_request here because we want to use
         # an unversioned URL, not "v2/$project_id/".
diff --git a/tempest/lib/services/object_storage/__init__.py b/tempest/lib/services/object_storage/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/lib/services/object_storage/__init__.py
diff --git a/tempest/services/object_storage/bulk_middleware_client.py b/tempest/lib/services/object_storage/bulk_middleware_client.py
similarity index 100%
rename from tempest/services/object_storage/bulk_middleware_client.py
rename to tempest/lib/services/object_storage/bulk_middleware_client.py
diff --git a/tempest/services/object_storage/capabilities_client.py b/tempest/lib/services/object_storage/capabilities_client.py
similarity index 100%
rename from tempest/services/object_storage/capabilities_client.py
rename to tempest/lib/services/object_storage/capabilities_client.py
diff --git a/tempest/services/object_storage/__init__.py b/tempest/services/object_storage/__init__.py
index 1738566..a2f0992 100644
--- a/tempest/services/object_storage/__init__.py
+++ b/tempest/services/object_storage/__init__.py
@@ -12,11 +12,11 @@
 # License for the specific language governing permissions and limitations under
 # the License.
 
-from tempest.services.object_storage.account_client import AccountClient
-from tempest.services.object_storage.bulk_middleware_client import \
+from tempest.lib.services.object_storage.bulk_middleware_client import \
     BulkMiddlewareClient
-from tempest.services.object_storage.capabilities_client import \
+from tempest.lib.services.object_storage.capabilities_client import \
     CapabilitiesClient
+from tempest.services.object_storage.account_client import AccountClient
 from tempest.services.object_storage.container_client import ContainerClient
 from tempest.services.object_storage.object_client import ObjectClient
 
diff --git a/tempest/tests/lib/services/object_storage/__init__.py b/tempest/tests/lib/services/object_storage/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/tests/lib/services/object_storage/__init__.py
diff --git a/tempest/tests/services/object_storage/test_bulk_middleware_client.py b/tempest/tests/lib/services/object_storage/test_bulk_middleware_client.py
similarity index 96%
rename from tempest/tests/services/object_storage/test_bulk_middleware_client.py
rename to tempest/tests/lib/services/object_storage/test_bulk_middleware_client.py
index 163b48e..08028c3 100644
--- a/tempest/tests/services/object_storage/test_bulk_middleware_client.py
+++ b/tempest/tests/lib/services/object_storage/test_bulk_middleware_client.py
@@ -12,7 +12,7 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from tempest.services.object_storage import bulk_middleware_client
+from tempest.lib.services.object_storage import bulk_middleware_client
 from tempest.tests.lib import fake_auth_provider
 from tempest.tests.lib.services import base
 
diff --git a/tempest/tests/services/object_storage/test_capabilities_client.py b/tempest/tests/lib/services/object_storage/test_capabilities_client.py
similarity index 96%
rename from tempest/tests/services/object_storage/test_capabilities_client.py
rename to tempest/tests/lib/services/object_storage/test_capabilities_client.py
index 5279bf4..b7f972a 100644
--- a/tempest/tests/services/object_storage/test_capabilities_client.py
+++ b/tempest/tests/lib/services/object_storage/test_capabilities_client.py
@@ -14,7 +14,7 @@
 #    under the License.
 
 
-from tempest.services.object_storage import capabilities_client
+from tempest.lib.services.object_storage import capabilities_client
 from tempest.tests.lib import fake_auth_provider
 from tempest.tests.lib.services import base
 
diff --git a/tempest/tests/lib/services/test_clients.py b/tempest/tests/lib/services/test_clients.py
index a837199..6d0f27a 100644
--- a/tempest/tests/lib/services/test_clients.py
+++ b/tempest/tests/lib/services/test_clients.py
@@ -16,6 +16,7 @@
 
 import fixtures
 import mock
+import six
 import testtools
 
 from tempest.lib import auth
@@ -258,6 +259,58 @@
             clients.ServiceClients(creds, identity_uri=uri,
                                    client_parameters=params)
 
+    def test___init___plugin_service_clients_cannot_load(self):
+        creds = fake_credentials.FakeKeystoneV3Credentials()
+        uri = 'fake_uri'
+        fake_service_clients = {
+            'service1': [{'name': 'client1',
+                          'service_version': 'client1.v1',
+                          'module_path': 'I cannot load this',
+                          'client_names': ['SomeClient1']}],
+            'service2': [{'name': 'client2',
+                          'service_version': 'client2.v1',
+                          'module_path': 'This neither',
+                          'client_names': ['SomeClient1']}]}
+        msg = "(?=.*{0})(?=.*{1})".format(
+            *[x[1][0]['module_path'] for x in six.iteritems(
+                fake_service_clients)])
+        self.useFixture(fixtures.MockPatchObject(
+            clients.ClientsRegistry(), 'get_service_clients',
+            return_value=fake_service_clients))
+        with testtools.ExpectedException(
+                testtools.MultipleExceptions, value_re=msg):
+            clients.ServiceClients(creds, identity_uri=uri)
+
+    def test___init___plugin_service_clients_name_conflict(self):
+        creds = fake_credentials.FakeKeystoneV3Credentials()
+        uri = 'fake_uri'
+        fake_service_clients = {
+            'serviceA': [{'name': 'client1',
+                          'service_version': 'client1.v1',
+                          'module_path': 'fake_path_1',
+                          'client_names': ['SomeClient1']}],
+            'serviceB': [{'name': 'client1',
+                          'service_version': 'client1.v2',
+                          'module_path': 'fake_path_2',
+                          'client_names': ['SomeClient2']}],
+            'serviceC': [{'name': 'client1',
+                          'service_version': 'client1.v1',
+                          'module_path': 'fake_path_2',
+                          'client_names': ['SomeClient1']}],
+            'serviceD': [{'name': 'client1',
+                          'service_version': 'client1.v2',
+                          'module_path': 'fake_path_2',
+                          'client_names': ['SomeClient2']}]}
+        msg = "(?=.*{0})(?=.*{1})".format(
+            *[x[1][0]['service_version'] for x in six.iteritems(
+                fake_service_clients)])
+        self.useFixture(fixtures.MockPatchObject(
+            clients.ClientsRegistry(), 'get_service_clients',
+            return_value=fake_service_clients))
+        with testtools.ExpectedException(
+                testtools.MultipleExceptions, value_re=msg):
+            clients.ServiceClients(creds, identity_uri=uri)
+
     def _get_manager(self, init_region='fake_region'):
         # Get a manager to invoke _setup_parameters on
         creds = fake_credentials.FakeKeystoneV2Credentials()