Merge "Add missing config file read to tempest init"
diff --git a/tempest/api/compute/images/test_images.py b/tempest/api/compute/images/test_images.py
index 150e8af..3754637 100644
--- a/tempest/api/compute/images/test_images.py
+++ b/tempest/api/compute/images/test_images.py
@@ -49,6 +49,9 @@
                                               name=snapshot_name,
                                               wait_until='SAVING')
         self.client.delete_image(image['id'])
+        msg = ('The image with ID {image_id} failed to be deleted'
+               .format(image_id=image['id']))
+        self.assertTrue(self.client.is_resource_deleted(image['id']), msg)
 
     @test.idempotent_id('aaacd1d0-55a2-4ce8-818a-b5439df8adc9')
     def test_create_image_from_stopped_server(self):
diff --git a/tempest/api/volume/v2/test_volumes_list.py b/tempest/api/volume/v2/test_volumes_list.py
index 1fdcb49..c501ffc 100644
--- a/tempest/api/volume/v2/test_volumes_list.py
+++ b/tempest/api/volume/v2/test_volumes_list.py
@@ -42,22 +42,13 @@
         super(VolumesV2ListTestJSON, cls).resource_setup()
 
         # Create 3 test volumes
-        cls.volume_list = []
         cls.volume_id_list = []
         cls.metadata = {'Type': 'work'}
         for i in range(3):
             volume = cls.create_volume(metadata=cls.metadata)
             volume = cls.client.show_volume(volume['id'])['volume']
-            cls.volume_list.append(volume)
             cls.volume_id_list.append(volume['id'])
 
-    @classmethod
-    def resource_cleanup(cls):
-        # Delete the created volumes
-        for volid in cls.volume_id_list:
-            cls.delete_volume(cls.client, volid)
-        super(VolumesV2ListTestJSON, cls).resource_cleanup()
-
     @test.idempotent_id('2a7064eb-b9c3-429b-b888-33928fc5edd3')
     def test_volume_list_details_with_multiple_params(self):
         # List volumes detail using combined condition
diff --git a/tempest/clients.py b/tempest/clients.py
index 406f9d5..e14f6f8 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -97,19 +97,16 @@
         This uses `config.service_client_config` for all services to collect
         most configuration items needed to init the clients.
         """
-        # NOTE(andreaf) Configuration items will be passed in future patches
-        # into ClientFactory objects, but for now we update all the
-        # _set_*_client methods to consume them so we can verify that the
-        # configuration collected is correct
+        # NOTE(andreaf) Once all service clients in Tempest are migrated
+        # to tempest.lib, their configuration will be picked up from the
+        # registry, and this method will become redundant.
 
         configuration = {}
 
-        # Setup the parameters for all Tempest services.
+        # Setup the parameters for all Tempest services which are not in lib.
         # NOTE(andreaf) Since client.py is an internal module of Tempest,
         # it doesn't have to consider plugin configuration.
-        all_tempest_modules = (set(clients.tempest_modules()) |
-                               clients._tempest_internal_modules())
-        for service in all_tempest_modules:
+        for service in clients._tempest_internal_modules():
             try:
                 # NOTE(andreaf) Use the unversioned service name to fetch
                 # the configuration since configuration is not versioned.
diff --git a/tempest/cmd/verify_tempest_config.py b/tempest/cmd/verify_tempest_config.py
index 77b88f9..9947f2a 100644
--- a/tempest/cmd/verify_tempest_config.py
+++ b/tempest/cmd/verify_tempest_config.py
@@ -147,6 +147,10 @@
             contains_version('v2.', versions)):
         print_and_or_update('api_v2', 'volume-feature-enabled',
                             not CONF.volume_feature_enabled.api_v2, update)
+    if (CONF.volume_feature_enabled.api_v3 !=
+            contains_version('v3.', versions)):
+        print_and_or_update('api_v3', 'volume-feature-enabled',
+                            not CONF.volume_feature_enabled.api_v3, update)
 
 
 def verify_api_versions(os, service, update):
diff --git a/tempest/config.py b/tempest/config.py
index 84edede..3fd20ab 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -26,6 +26,7 @@
 import testtools
 
 from tempest.lib import exceptions
+from tempest.lib.services import clients
 from tempest.test_discover import plugins
 
 
@@ -1288,6 +1289,15 @@
             lockutils.set_defaults(lock_dir)
             self._config = TempestConfigPrivate(config_path=self._path)
 
+            # Pushing tempest internal service client configuration to the
+            # service clients register. Doing this in the config module ensures
+            # that the configuration is available by the time we register the
+            # service clients.
+            # NOTE(andreaf) This has to be done at the time the first
+            # attribute is accessed, to ensure all plugins have been already
+            # loaded, options registered, and _config is set.
+            _register_tempest_service_clients()
+
         return getattr(self._config, attr)
 
     def set_config_path(self, path):
@@ -1447,3 +1457,29 @@
     # Set service
     _parameters['service'] = getattr(options, 'catalog_type')
     return _parameters
+
+
+def _register_tempest_service_clients():
+    # Register tempest own service clients using the same mechanism used
+    # for external plugins.
+    # The configuration data is pushed to the registry so that automatic
+    # configuration of tempest own service clients is possible both for
+    # tempest as well as for the plugins.
+    service_clients = clients.tempest_modules()
+    registry = clients.ClientsRegistry()
+    all_clients = []
+    for service_client in service_clients:
+        module = service_clients[service_client]
+        configs = service_client.split('.')[0]
+        service_client_data = dict(
+            name=service_client.replace('.', '_'),
+            service_version=service_client,
+            module_path=module.__name__,
+            client_names=module.__all__,
+            **service_client_config(configs)
+        )
+        all_clients.append(service_client_data)
+    # NOTE(andreaf) Internal service clients do not actually belong
+    # to a plugin, so using '__tempest__' to indicate a virtual plugin
+    # which holds internal service clients.
+    registry.register_service_client('__tempest__', all_clients)
diff --git a/tempest/lib/services/clients.py b/tempest/lib/services/clients.py
index e782321..adf666b 100644
--- a/tempest/lib/services/clients.py
+++ b/tempest/lib/services/clients.py
@@ -18,7 +18,6 @@
 import importlib
 import inspect
 import logging
-import six
 
 from tempest.lib import auth
 from tempest.lib.common.utils import misc
@@ -90,11 +89,14 @@
                                                 plug_service_versions))
                 raise exceptions.PluginRegistrationException(
                     name=plugin_name, detailed_error=detailed_error)
-            if not plug_service_versions.isdisjoint(_tempest_modules):
+            # NOTE(andreaf) Once all tempest clients are stable, the following
+            # if will have to be removed.
+            if not plug_service_versions.isdisjoint(
+                    _tempest_internal_modules()):
                 detailed_error = (
                     'Plugin %s is trying to register a service %s already '
                     'claimed by a Tempest one' % (plugin_name,
-                                                  _tempest_modules &
+                                                  _tempest_internal_modules() &
                                                   plug_service_versions))
                 raise exceptions.PluginRegistrationException(
                     name=plugin_name, detailed_error=detailed_error)
@@ -351,15 +353,7 @@
             raise exceptions.UnknownServiceClient(
                 services=list(client_parameters.keys()))
 
-        # Register service clients owned by tempest
-        for service, module in six.iteritems(tempest_modules()):
-            attribute = service.replace('.', '_')
-            configs = service.split('.')[0]
-            self.register_service_client_module(
-                attribute, service, module.__name__,
-                module.__all__, **self.parameters[configs])
-
-        # Register service clients from plugins
+        # Register service clients from the registry (__tempest__ and plugins)
         clients_registry = ClientsRegistry()
         plugin_service_clients = clients_registry.get_service_clients()
         for plugin in plugin_service_clients:
@@ -432,6 +426,8 @@
 
     @property
     def registered_services(self):
+        # NOTE(andreaf) Once all tempest modules are stable this needs to
+        # be updated to remove _tempest_internal_modules
         return self._registered_services | _tempest_internal_modules()
 
     def _setup_parameters(self, parameters):
diff --git a/tempest/lib/services/network/networks_client.py b/tempest/lib/services/network/networks_client.py
index 7d75bf7..6b601ee 100755
--- a/tempest/lib/services/network/networks_client.py
+++ b/tempest/lib/services/network/networks_client.py
@@ -19,7 +19,7 @@
         """Creates a network.
 
         Available params: see http://developer.openstack.org/
-                              api-ref-networking-v2.html#createNetwork
+                              api-ref/networking/v2/index.html#create-network
         """
         uri = '/networks'
         post_data = {'network': kwargs}
@@ -29,7 +29,7 @@
         """Updates a network.
 
         Available params: see http://developer.openstack.org/
-                              api-ref-networking-v2.html#updateNetwork
+                              api-ref/networking/v2/index.html#update-network
         """
         uri = '/networks/%s' % network_id
         post_data = {'network': kwargs}
@@ -39,7 +39,7 @@
         """Shows details for a network.
 
         Available params: see http://developer.openstack.org/
-                              api-ref-networking-v2.html#showNetwork
+                              api-ref/networking/v2/index.html#show-network-details
         """
         uri = '/networks/%s' % network_id
         return self.show_resource(uri, **fields)
@@ -52,7 +52,7 @@
         """Lists networks to which the tenant has access.
 
         Available params: see http://developer.openstack.org/
-                              api-ref-networking-v2.html#listNetworks
+                              api-ref/networking/v2/index.html#list-networks
         """
         uri = '/networks'
         return self.list_resources(uri, **filters)
@@ -61,7 +61,7 @@
         """Create multiple networks in a single request.
 
         Available params: see http://developer.openstack.org/
-                              api-ref-networking-v2.html#bulkCreateNetwork
+                              api-ref/networking/v2/index.html#bulk-create-networks
         """
         uri = '/networks'
         return self.create_resource(uri, kwargs)
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
index 952c0c2..6da0570 100644
--- a/tempest/scenario/manager.py
+++ b/tempest/scenario/manager.py
@@ -127,6 +127,21 @@
             wait_dict['client'] = waiter_client
         self.cleanup_waits.append(wait_dict)
 
+    def _create_port(self, network_id, client=None, namestart='port-quotatest',
+                     **kwargs):
+        if not client:
+            client = self.ports_client
+        name = data_utils.rand_name(namestart)
+        result = client.create_port(
+            name=name,
+            network_id=network_id,
+            **kwargs)
+        self.assertIsNotNone(result, 'Unable to allocate port')
+        port = result['port']
+        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+                        client.delete_port, port['id'])
+        return port
+
     def _wait_for_cleanups(self):
         # To handle async delete actions, a list of waits is added
         # which will be iterated over as the last step of clearing the
@@ -209,18 +224,18 @@
                 networks = []
 
             # If there are no networks passed to us we look up
-            # for the project's private networks and create a port
-            # if there is only one private network. The same behaviour
-            # as we would expect when passing the call to the clients
-            # with no networks
+            # for the project's private networks and create a port.
+            # The same behaviour as we would expect when passing
+            # the call to the clients with no networks
             if not networks:
                 networks = clients.networks_client.list_networks(
-                    filters={'router:external': False})
-                self.assertEqual(1, len(networks),
-                                 "There is more than one"
-                                 " network for the tenant")
+                    **{'router:external': False, 'fields': 'id'})['networks']
+
+            # It's net['uuid'] if networks come from kwargs
+            # and net['id'] if they come from
+            # clients.networks_client.list_networks
             for net in networks:
-                net_id = net['uuid']
+                net_id = net.get('uuid', net['id'])
                 if 'port' not in net:
                     port = self._create_port(network_id=net_id,
                                              client=clients.ports_client,
@@ -813,21 +828,6 @@
 
         return subnet
 
-    def _create_port(self, network_id, client=None, namestart='port-quotatest',
-                     **kwargs):
-        if not client:
-            client = self.ports_client
-        name = data_utils.rand_name(namestart)
-        result = client.create_port(
-            name=name,
-            network_id=network_id,
-            **kwargs)
-        self.assertIsNotNone(result, 'Unable to allocate port')
-        port = result['port']
-        self.addCleanup(test_utils.call_and_ignore_notfound_exc,
-                        client.delete_port, port['id'])
-        return port
-
     def _get_server_port_id_and_ip4(self, server, ip_addr=None):
         ports = self._list_ports(device_id=server['id'], fixed_ip=ip_addr)
         # A port can have more then one IP address in some cases.
diff --git a/tempest/tests/cmd/test_verify_tempest_config.py b/tempest/tests/cmd/test_verify_tempest_config.py
index 70cbf87..00b4542 100644
--- a/tempest/tests/cmd/test_verify_tempest_config.py
+++ b/tempest/tests/cmd/test_verify_tempest_config.py
@@ -188,34 +188,54 @@
                                            False, True)
 
     @mock.patch('tempest.lib.common.http.ClosingHttp.request')
-    def test_verify_cinder_api_versions_no_v2(self, mock_request):
+    def test_verify_cinder_api_versions_no_v3(self, mock_request):
         self.useFixture(mockpatch.PatchObject(
             verify_tempest_config, '_get_unversioned_endpoint',
             return_value='http://fake_endpoint:5000'))
-        fake_resp = {'versions': [{'id': 'v1.0'}]}
+        fake_resp = {'versions': [{'id': 'v1.0'}, {'id': 'v2.0'}]}
         fake_resp = json.dumps(fake_resp)
         mock_request.return_value = (None, fake_resp)
         fake_os = mock.MagicMock()
         with mock.patch.object(verify_tempest_config,
                                'print_and_or_update') as print_mock:
             verify_tempest_config.verify_cinder_api_versions(fake_os, True)
-        print_mock.assert_called_once_with('api_v2', 'volume-feature-enabled',
-                                           False, True)
+        print_mock.assert_not_called()
+
+    @mock.patch('tempest.lib.common.http.ClosingHttp.request')
+    def test_verify_cinder_api_versions_no_v2(self, mock_request):
+        self.useFixture(mockpatch.PatchObject(
+            verify_tempest_config, '_get_unversioned_endpoint',
+            return_value='http://fake_endpoint:5000'))
+        fake_resp = {'versions': [{'id': 'v1.0'}, {'id': 'v3.0'}]}
+        fake_resp = json.dumps(fake_resp)
+        mock_request.return_value = (None, fake_resp)
+        fake_os = mock.MagicMock()
+        with mock.patch.object(verify_tempest_config,
+                               'print_and_or_update') as print_mock:
+            verify_tempest_config.verify_cinder_api_versions(fake_os, True)
+        print_mock.assert_any_call('api_v2', 'volume-feature-enabled',
+                                   False, True)
+        print_mock.assert_any_call('api_v3', 'volume-feature-enabled',
+                                   True, True)
+        self.assertEqual(2, print_mock.call_count)
 
     @mock.patch('tempest.lib.common.http.ClosingHttp.request')
     def test_verify_cinder_api_versions_no_v1(self, mock_request):
         self.useFixture(mockpatch.PatchObject(
             verify_tempest_config, '_get_unversioned_endpoint',
             return_value='http://fake_endpoint:5000'))
-        fake_resp = {'versions': [{'id': 'v2.0'}]}
+        fake_resp = {'versions': [{'id': 'v2.0'}, {'id': 'v3.0'}]}
         fake_resp = json.dumps(fake_resp)
         mock_request.return_value = (None, fake_resp)
         fake_os = mock.MagicMock()
         with mock.patch.object(verify_tempest_config,
                                'print_and_or_update') as print_mock:
             verify_tempest_config.verify_cinder_api_versions(fake_os, True)
-        print_mock.assert_called_once_with('api_v1', 'volume-feature-enabled',
-                                           False, True)
+        print_mock.assert_any_call('api_v1', 'volume-feature-enabled',
+                                   False, True)
+        print_mock.assert_any_call('api_v3', 'volume-feature-enabled',
+                                   True, True)
+        self.assertEqual(2, print_mock.call_count)
 
     def test_verify_glance_version_no_v2_with_v1_1(self):
         def fake_get_versions():