Merge "Remove unused TYPE from base_quotas_client"
diff --git a/doc/source/index.rst b/doc/source/index.rst
index f1ede06..6abe9dc 100644
--- a/doc/source/index.rst
+++ b/doc/source/index.rst
@@ -50,7 +50,6 @@
 
    account_generator
    cleanup
-   javelin
    subunit_describe_calls
    workspace
    run
diff --git a/doc/source/javelin.rst b/doc/source/javelin.rst
deleted file mode 100644
index 01090ca..0000000
--- a/doc/source/javelin.rst
+++ /dev/null
@@ -1,5 +0,0 @@
-----------------------------------------------------------
-Javelin2 - How to check that resources survived an upgrade
-----------------------------------------------------------
-
-.. automodule:: tempest.cmd.javelin
diff --git a/etc/javelin-resources.yaml.sample b/etc/javelin-resources.yaml.sample
deleted file mode 100644
index 1565686..0000000
--- a/etc/javelin-resources.yaml.sample
+++ /dev/null
@@ -1,63 +0,0 @@
-tenants:
-  - javelin
-  - discuss
-
-users:
-  - name: javelin
-    pass: gungnir
-    tenant: javelin
-  - name: javelin2
-    pass: gungnir2
-    tenant: discuss
-
-secgroups:
-  - name: secgroup1
-    owner: javelin
-    description: SecurityGroup1
-    rules:
-      - 'icmp -1 -1 0.0.0.0/0'
-      - 'tcp 22 22 0.0.0.0/0'
-  - name: secgroup2
-    owner: javelin2
-    description: SecurityGroup2
-    rules:
-      - 'tcp 80 80 0.0.0.0/0'
-
-images:
-  - name: cirros1
-    owner: javelin
-    imgdir: images
-    file: cirros.img
-    container_format: bare
-    disk_format: qcow2
-  - name: cirros2
-    owner: javelin2
-    imgdir: files/images/cirros-0.3.2-x86_64-uec
-    file: cirros-0.3.2-x86_64-blank.img
-    container_format: ami
-    disk_format: ami
-    aki: cirros-0.3.2-x86_64-vmlinuz
-    ari: cirros-0.3.2-x86_64-initrd
-
-networks:
-  - name: network1
-    owner: javelin
-  - name: network2
-    owner: javelin2
-
-subnets:
-  - name: net1-subnet1
-    range: 10.1.0.0/24
-    network: network1
-    owner: javelin
-  - name: net2-subnet2
-    range: 192.168.1.0/24
-    network: network2
-    owner: javelin2
-
-objects:
-  - container: container1
-    name: object1
-    owner: javelin
-    file: /etc/hosts
-    swift_role: Member
diff --git a/releasenotes/notes/add-httptimeout-in-restclient-ax78061900e3f3d7.yaml b/releasenotes/notes/add-httptimeout-in-restclient-ax78061900e3f3d7.yaml
new file mode 100644
index 0000000..a360f8e
--- /dev/null
+++ b/releasenotes/notes/add-httptimeout-in-restclient-ax78061900e3f3d7.yaml
@@ -0,0 +1,7 @@
+---
+features:
+  - RestClient now supports setting timeout in urllib3.poolmanager.
+    Clients will use CONF.service_clients.http_timeout for timeout
+    value to wait for http request to response.
+  - KeystoneAuthProvider will accept http_timeout and will use it in
+    get_credentials.
diff --git a/releasenotes/notes/plugin-service-client-registration-00b19a2dd4935ba0.yaml b/releasenotes/notes/plugin-service-client-registration-00b19a2dd4935ba0.yaml
new file mode 100644
index 0000000..64f729a
--- /dev/null
+++ b/releasenotes/notes/plugin-service-client-registration-00b19a2dd4935ba0.yaml
@@ -0,0 +1,12 @@
+---
+features:
+  - A new optional interface `TempestPlugin.get_service_clients`
+    is available to plugins. It allows them to declare
+    any service client they implement. For now this is used by
+    tempest only, for auto-registration of service clients
+    in the new class `ServiceClients`.
+  - A new singleton class `clients.ClientsRegistry` is
+    available. It holds the service clients registration data
+    from all plugins. It is used by `ServiceClients` for
+    auto-registration of the service clients implemented
+    in plugins.
diff --git a/releasenotes/notes/remove-javelin-276f62d04f7e4a1d.yaml b/releasenotes/notes/remove-javelin-276f62d04f7e4a1d.yaml
new file mode 100644
index 0000000..8e893b8
--- /dev/null
+++ b/releasenotes/notes/remove-javelin-276f62d04f7e4a1d.yaml
@@ -0,0 +1,5 @@
+---
+upgrade:
+  - The previously deprecated Javelin utility has been removed from Tempest.
+    As an alternative Ansible can be used to construct similar yaml workflows
+    to what Javelin used to provide.
diff --git a/requirements.txt b/requirements.txt
index 058ea00..0f8e94d 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -13,7 +13,7 @@
 oslo.i18n>=2.1.0 # Apache-2.0
 oslo.log>=1.14.0 # Apache-2.0
 oslo.serialization>=1.10.0 # Apache-2.0
-oslo.utils>=3.15.0 # Apache-2.0
+oslo.utils>=3.16.0 # Apache-2.0
 six>=1.9.0 # MIT
 fixtures>=3.0.0 # Apache-2.0/BSD
 testscenarios>=0.4 # Apache-2.0/BSD
diff --git a/setup.cfg b/setup.cfg
index 2a3000d..91ede20 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -27,7 +27,6 @@
 [entry_points]
 console_scripts =
     verify-tempest-config = tempest.cmd.verify_tempest_config:main
-    javelin2 = tempest.cmd.javelin:main
     run-tempest-stress = tempest.cmd.run_stress:main
     tempest-cleanup = tempest.cmd.cleanup:main
     tempest-account-generator = tempest.cmd.account_generator:main
diff --git a/tempest/api/baremetal/admin/base.py b/tempest/api/baremetal/admin/base.py
index f7891dd..2d3f190 100644
--- a/tempest/api/baremetal/admin/base.py
+++ b/tempest/api/baremetal/admin/base.py
@@ -96,7 +96,7 @@
 
     @classmethod
     @creates('chassis')
-    def create_chassis(cls, description=None, expect_errors=False):
+    def create_chassis(cls, description=None):
         """Wrapper utility for creating test chassis.
 
         :param description: A description of the chassis. if not supplied,
diff --git a/tempest/api/compute/security_groups/test_security_groups_negative.py b/tempest/api/compute/security_groups/test_security_groups_negative.py
index 5125e2b..e6abf28 100644
--- a/tempest/api/compute/security_groups/test_security_groups_negative.py
+++ b/tempest/api/compute/security_groups/test_security_groups_negative.py
@@ -44,9 +44,11 @@
             security_group_id.append(body[i]['id'])
         # Generate a non-existent security group id
         while True:
-            non_exist_id = data_utils.rand_int_id(start=999)
-            if self.neutron_available:
+            if (self.neutron_available and
+                test.is_extension_enabled('security-group', 'network')):
                 non_exist_id = data_utils.rand_uuid()
+            else:
+                non_exist_id = data_utils.rand_int_id(start=999)
             if non_exist_id not in security_group_id:
                 break
         return non_exist_id
diff --git a/tempest/api/compute/servers/test_attach_interfaces.py b/tempest/api/compute/servers/test_attach_interfaces.py
index d02f86f..7c12bf9 100644
--- a/tempest/api/compute/servers/test_attach_interfaces.py
+++ b/tempest/api/compute/servers/test_attach_interfaces.py
@@ -21,6 +21,7 @@
 from tempest.common import waiters
 from tempest import config
 from tempest import exceptions
+from tempest.lib import decorators
 from tempest.lib import exceptions as lib_exc
 from tempest import test
 
@@ -272,6 +273,7 @@
                 break
         self.servers_client.remove_fixed_ip(server['id'], address=fixed_ip)
 
+    @decorators.skip_because(bug='1607714')
     @test.idempotent_id('2f3a0127-95c7-4977-92d2-bc5aec602fb4')
     def test_reassign_port_between_servers(self):
         """Tests the following:
diff --git a/tempest/api/image/v2/test_images_negative.py b/tempest/api/image/v2/test_images_negative.py
index 14de8fd..f60fb0c 100644
--- a/tempest/api/image/v2/test_images_negative.py
+++ b/tempest/api/image/v2/test_images_negative.py
@@ -29,7 +29,7 @@
         ** get image with image_id=NULL
         ** get the deleted image
         ** delete non-existent image
-        ** delete rimage with  image_id=NULL
+        ** delete image with image_id=NULL
         ** delete the deleted image
      """
 
diff --git a/tempest/api/network/test_networks.py b/tempest/api/network/test_networks.py
index 07c80a2..3825f84 100644
--- a/tempest/api/network/test_networks.py
+++ b/tempest/api/network/test_networks.py
@@ -559,7 +559,7 @@
         # Verifies Subnet GW is set in IPv6
         self.assertEqual(subnet1['gateway_ip'], ipv6_gateway)
         # Verifies Subnet GW is None in IPv4
-        self.assertEqual(subnet2['gateway_ip'], None)
+        self.assertIsNone(subnet2['gateway_ip'])
         # Verifies all 2 subnets in the same network
         body = self.subnets_client.list_subnets()
         subnets = [sub['id'] for sub in body['subnets']
diff --git a/tempest/api/volume/admin/test_volume_types.py b/tempest/api/volume/admin/test_volume_types.py
index 27f6ccb..8eae085 100644
--- a/tempest/api/volume/admin/test_volume_types.py
+++ b/tempest/api/volume/admin/test_volume_types.py
@@ -88,18 +88,21 @@
         # Create/get volume type.
         body = {}
         name = data_utils.rand_name("volume-type")
+        description = data_utils.rand_name("volume-type-description")
         proto = CONF.volume.storage_protocol
         vendor = CONF.volume.vendor_name
         extra_specs = {"storage_protocol": proto,
                        "vendor_name": vendor}
-        body = self.create_volume_type(
-            name=name,
-            extra_specs=extra_specs)
+        body = self.create_volume_type(description=description, name=name,
+                                       extra_specs=extra_specs)
         self.assertIn('id', body)
         self.assertIn('name', body)
-        self.assertEqual(body['name'], name,
+        self.assertEqual(name, body['name'],
                          "The created volume_type name is not equal "
                          "to the requested name")
+        self.assertEqual(description, body['description'],
+                         "The created volume_type_description name is "
+                         "not equal to the requested name")
         self.assertTrue(body['id'] is not None,
                         "Field volume_type id is empty or not found.")
         fetched_volume_type = self.admin_volume_types_client.show_volume_type(
diff --git a/tempest/api/volume/v2/test_volumes_list.py b/tempest/api/volume/v2/test_volumes_list.py
index 5117e6c..1fdcb49 100644
--- a/tempest/api/volume/v2/test_volumes_list.py
+++ b/tempest/api/volume/v2/test_volumes_list.py
@@ -174,9 +174,9 @@
 
             # If cannot follow make sure it's because we have finished
             else:
-                self.assertListEqual([], remaining or [],
-                                     'No more pages reported, but still '
-                                     'missing ids %s' % remaining)
+                self.assertEqual([], remaining or [],
+                                 'No more pages reported, but still '
+                                 'missing ids %s' % remaining)
                 break
 
     @test.idempotent_id('e9138a2c-f67b-4796-8efa-635c196d01de')
diff --git a/tempest/clients.py b/tempest/clients.py
index fd010f2..e070637 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -22,9 +22,6 @@
 from tempest import exceptions
 from tempest.lib import auth
 from tempest.lib import exceptions as lib_exc
-from tempest.lib.services import compute
-from tempest.lib.services import image
-from tempest.lib.services import network
 from tempest import service_clients
 from tempest.services import baremetal
 from tempest.services import data_processing
@@ -127,133 +124,84 @@
         return configuration
 
     def _set_network_clients(self):
-        params = self.parameters['network']
-        self.network_agents_client = network.AgentsClient(
-            self.auth_provider, **params)
-        self.network_extensions_client = network.ExtensionsClient(
-            self.auth_provider, **params)
-        self.networks_client = network.NetworksClient(
-            self.auth_provider, **params)
-        self.subnetpools_client = network.SubnetpoolsClient(
-            self.auth_provider, **params)
-        self.subnets_client = network.SubnetsClient(
-            self.auth_provider, **params)
-        self.ports_client = network.PortsClient(
-            self.auth_provider, **params)
-        self.network_quotas_client = network.QuotasClient(
-            self.auth_provider, **params)
-        self.floating_ips_client = network.FloatingIPsClient(
-            self.auth_provider, **params)
-        self.metering_labels_client = network.MeteringLabelsClient(
-            self.auth_provider, **params)
-        self.metering_label_rules_client = network.MeteringLabelRulesClient(
-            self.auth_provider, **params)
-        self.routers_client = network.RoutersClient(
-            self.auth_provider, **params)
-        self.security_group_rules_client = network.SecurityGroupRulesClient(
-            self.auth_provider, **params)
-        self.security_groups_client = network.SecurityGroupsClient(
-            self.auth_provider, **params)
-        self.network_versions_client = network.NetworkVersionsClient(
-            self.auth_provider, **params)
+        self.network_agents_client = self.network.AgentsClient()
+        self.network_extensions_client = self.network.ExtensionsClient()
+        self.networks_client = self.network.NetworksClient()
+        self.subnetpools_client = self.network.SubnetpoolsClient()
+        self.subnets_client = self.network.SubnetsClient()
+        self.ports_client = self.network.PortsClient()
+        self.network_quotas_client = self.network.QuotasClient()
+        self.floating_ips_client = self.network.FloatingIPsClient()
+        self.metering_labels_client = self.network.MeteringLabelsClient()
+        self.metering_label_rules_client = (
+            self.network.MeteringLabelRulesClient())
+        self.routers_client = self.network.RoutersClient()
+        self.security_group_rules_client = (
+            self.network.SecurityGroupRulesClient())
+        self.security_groups_client = self.network.SecurityGroupsClient()
+        self.network_versions_client = self.network.NetworkVersionsClient()
 
     def _set_image_clients(self):
         if CONF.service_available.glance:
-            params = self.parameters['image']
-            self.image_client = image.v1.ImagesClient(
-                self.auth_provider, **params)
-            self.image_member_client = image.v1.ImageMembersClient(
-                self.auth_provider, **params)
-
-            self.image_client_v2 = image.v2.ImagesClient(
-                self.auth_provider, **params)
-            self.image_member_client_v2 = image.v2.ImageMembersClient(
-                self.auth_provider, **params)
-            self.namespaces_client = image.v2.NamespacesClient(
-                self.auth_provider, **params)
-            self.resource_types_client = image.v2.ResourceTypesClient(
-                self.auth_provider, **params)
-            self.schemas_client = image.v2.SchemasClient(
-                self.auth_provider, **params)
+            self.image_client = self.image_v1.ImagesClient()
+            self.image_member_client = self.image_v1.ImageMembersClient()
+            self.image_client_v2 = self.image_v2.ImagesClient()
+            self.image_member_client_v2 = self.image_v2.ImageMembersClient()
+            self.namespaces_client = self.image_v2.NamespacesClient()
+            self.resource_types_client = self.image_v2.ResourceTypesClient()
+            self.schemas_client = self.image_v2.SchemasClient()
 
     def _set_compute_clients(self):
-        params = self.parameters['compute']
-
-        self.agents_client = compute.AgentsClient(self.auth_provider, **params)
-        self.compute_networks_client = compute.NetworksClient(
-            self.auth_provider, **params)
-        self.migrations_client = compute.MigrationsClient(self.auth_provider,
-                                                          **params)
+        self.agents_client = self.compute.AgentsClient()
+        self.compute_networks_client = self.compute.NetworksClient()
+        self.migrations_client = self.compute.MigrationsClient()
         self.security_group_default_rules_client = (
-            compute.SecurityGroupDefaultRulesClient(self.auth_provider,
-                                                    **params))
-        self.certificates_client = compute.CertificatesClient(
-            self.auth_provider, **params)
-        self.servers_client = compute.ServersClient(
-            self.auth_provider,
-            enable_instance_password=CONF.compute_feature_enabled
-                .enable_instance_password,
-            **params)
-        self.server_groups_client = compute.ServerGroupsClient(
-            self.auth_provider, **params)
-        self.limits_client = compute.LimitsClient(self.auth_provider, **params)
-        self.compute_images_client = compute.ImagesClient(self.auth_provider,
-                                                          **params)
-        self.keypairs_client = compute.KeyPairsClient(self.auth_provider,
-                                                      **params)
-        self.quotas_client = compute.QuotasClient(self.auth_provider, **params)
-        self.quota_classes_client = compute.QuotaClassesClient(
-            self.auth_provider, **params)
-        self.flavors_client = compute.FlavorsClient(self.auth_provider,
-                                                    **params)
-        self.extensions_client = compute.ExtensionsClient(self.auth_provider,
-                                                          **params)
-        self.floating_ip_pools_client = compute.FloatingIPPoolsClient(
-            self.auth_provider, **params)
-        self.floating_ips_bulk_client = compute.FloatingIPsBulkClient(
-            self.auth_provider, **params)
-        self.compute_floating_ips_client = compute.FloatingIPsClient(
-            self.auth_provider, **params)
+            self.compute.SecurityGroupDefaultRulesClient())
+        self.certificates_client = self.compute.CertificatesClient()
+        eip = CONF.compute_feature_enabled.enable_instance_password
+        self.servers_client = self.compute.ServersClient(
+            enable_instance_password=eip)
+        self.server_groups_client = self.compute.ServerGroupsClient()
+        self.limits_client = self.compute.LimitsClient()
+        self.compute_images_client = self.compute.ImagesClient()
+        self.keypairs_client = self.compute.KeyPairsClient()
+        self.quotas_client = self.compute.QuotasClient()
+        self.quota_classes_client = self.compute.QuotaClassesClient()
+        self.flavors_client = self.compute.FlavorsClient()
+        self.extensions_client = self.compute.ExtensionsClient()
+        self.floating_ip_pools_client = self.compute.FloatingIPPoolsClient()
+        self.floating_ips_bulk_client = self.compute.FloatingIPsBulkClient()
+        self.compute_floating_ips_client = self.compute.FloatingIPsClient()
         self.compute_security_group_rules_client = (
-            compute.SecurityGroupRulesClient(self.auth_provider, **params))
-        self.compute_security_groups_client = compute.SecurityGroupsClient(
-            self.auth_provider, **params)
-        self.interfaces_client = compute.InterfacesClient(self.auth_provider,
-                                                          **params)
-        self.fixed_ips_client = compute.FixedIPsClient(self.auth_provider,
-                                                       **params)
-        self.availability_zone_client = compute.AvailabilityZoneClient(
-            self.auth_provider, **params)
-        self.aggregates_client = compute.AggregatesClient(self.auth_provider,
-                                                          **params)
-        self.services_client = compute.ServicesClient(self.auth_provider,
-                                                      **params)
-        self.tenant_usages_client = compute.TenantUsagesClient(
-            self.auth_provider, **params)
-        self.hosts_client = compute.HostsClient(self.auth_provider, **params)
-        self.hypervisor_client = compute.HypervisorClient(self.auth_provider,
-                                                          **params)
+            self.compute.SecurityGroupRulesClient())
+        self.compute_security_groups_client = (
+            self.compute.SecurityGroupsClient())
+        self.interfaces_client = self.compute.InterfacesClient()
+        self.fixed_ips_client = self.compute.FixedIPsClient()
+        self.availability_zone_client = self.compute.AvailabilityZoneClient()
+        self.aggregates_client = self.compute.AggregatesClient()
+        self.services_client = self.compute.ServicesClient()
+        self.tenant_usages_client = self.compute.TenantUsagesClient()
+        self.hosts_client = self.compute.HostsClient()
+        self.hypervisor_client = self.compute.HypervisorClient()
         self.instance_usages_audit_log_client = (
-            compute.InstanceUsagesAuditLogClient(self.auth_provider, **params))
-        self.tenant_networks_client = compute.TenantNetworksClient(
-            self.auth_provider, **params)
-        self.baremetal_nodes_client = compute.BaremetalNodesClient(
-            self.auth_provider, **params)
+            self.compute.InstanceUsagesAuditLogClient())
+        self.tenant_networks_client = self.compute.TenantNetworksClient()
+        self.baremetal_nodes_client = self.compute.BaremetalNodesClient()
 
         # NOTE: The following client needs special timeout values because
         # the API is a proxy for the other component.
-        params_volume = copy.deepcopy(params)
-        # Optional parameters
+        params_volume = {}
         for _key in ('build_interval', 'build_timeout'):
             _value = self.parameters['volume'].get(_key)
             if _value:
                 params_volume[_key] = _value
-        self.volumes_extensions_client = compute.VolumesClient(
-            self.auth_provider, **params_volume)
-        self.compute_versions_client = compute.VersionsClient(
-            self.auth_provider, **params_volume)
-        self.snapshots_extensions_client = compute.SnapshotsClient(
-            self.auth_provider, **params_volume)
+        self.volumes_extensions_client = self.compute.VolumesClient(
+            **params_volume)
+        self.compute_versions_client = self.compute.VersionsClient(
+            **params_volume)
+        self.snapshots_extensions_client = self.compute.SnapshotsClient(
+            **params_volume)
 
     def _set_identity_clients(self):
         params = self.parameters['identity']
diff --git a/tempest/cmd/javelin.py b/tempest/cmd/javelin.py
deleted file mode 100755
index a9e5167..0000000
--- a/tempest/cmd/javelin.py
+++ /dev/null
@@ -1,1131 +0,0 @@
-#!/usr/bin/env python
-#
-# 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.
-
-"""Javelin is a tool for creating, verifying, and deleting a small set of
-resources in a declarative way.
-
-Javelin is meant to be used as a way to validate quickly that resources can
-survive an upgrade process.
-
-Authentication
---------------
-
-Javelin will be creating (and removing) users and tenants so it needs the admin
-credentials of your cloud to operate properly. The corresponding info can be
-given the usual way, either through CLI options or environment variables.
-
-You're probably familiar with these, but just in case::
-
-    +----------+------------------+----------------------+
-    | Param    | CLI              | Environment Variable |
-    +----------+------------------+----------------------+
-    | Username | --os-username    | OS_USERNAME          |
-    | Password | --os-password    | OS_PASSWORD          |
-    | Tenant   | --os-tenant-name | OS_TENANT_NAME       |
-    +----------+------------------+----------------------+
-
-
-Runtime Arguments
------------------
-
-**-m/--mode**: (Required) Has to be one of 'check', 'create' or 'destroy'. It
-indicates which actions javelin is going to perform.
-
-**-r/--resources**: (Required) The path to a YAML file describing the resources
-used by Javelin.
-
-**-d/--devstack-base**: (Required) The path to the devstack repo used to
-retrieve artefacts (like images) that will be referenced in the resource files.
-
-**-c/--config-file**: (Optional) The path to a valid Tempest config file
-describing your cloud. Javelin may use this to determine if certain services
-are enabled and modify its behavior accordingly.
-
-
-Resource file
--------------
-
-The resource file is a valid YAML file describing the resources that will be
-created, checked and destroyed by javelin. Here's a canonical example of a
-resource file::
-
-  tenants:
-    - javelin
-    - discuss
-
-  users:
-    - name: javelin
-      pass: gungnir
-      tenant: javelin
-    - name: javelin2
-      pass: gungnir2
-      tenant: discuss
-
-  # resources that we want to create
-  images:
-    - name: javelin_cirros
-      owner: javelin
-      file: cirros-0.3.2-x86_64-blank.img
-      disk_format: ami
-      container_format: ami
-      aki: cirros-0.3.2-x86_64-vmlinuz
-      ari: cirros-0.3.2-x86_64-initrd
-
-  servers:
-    - name: peltast
-      owner: javelin
-      flavor: m1.small
-      image: javelin_cirros
-      floating_ip_pool: public
-    - name: hoplite
-      owner: javelin
-      flavor: m1.medium
-      image: javelin_cirros
-
-
-An important piece of the resource definition is the *owner* field, which is
-the user (that we've created) that is the owner of that resource. All
-operations on that resource will happen as that regular user to ensure that
-admin level access does not mask issues.
-
-The check phase will act like a unit test, using well known assert methods to
-verify that the correct resources exist.
-
-"""
-
-import argparse
-import collections
-import datetime
-import os
-import sys
-import unittest
-
-import netaddr
-from oslo_log import log as logging
-import six
-import yaml
-
-from tempest.common import identity
-from tempest.common import waiters
-from tempest import config
-from tempest.lib import auth
-from tempest.lib import exceptions as lib_exc
-from tempest.lib.services.compute import flavors_client
-from tempest.lib.services.compute import floating_ips_client
-from tempest.lib.services.compute import security_group_rules_client
-from tempest.lib.services.compute import security_groups_client
-from tempest.lib.services.compute import servers_client
-from tempest.lib.services.identity.v2 import roles_client
-from tempest.lib.services.identity.v2 import tenants_client
-from tempest.lib.services.identity.v2 import users_client
-from tempest.lib.services.image.v2 import images_client
-from tempest.lib.services.network import networks_client
-from tempest.lib.services.network import ports_client
-from tempest.lib.services.network import routers_client
-from tempest.lib.services.network import subnets_client
-from tempest.services.identity.v2.json import identity_client
-from tempest.services.object_storage import container_client
-from tempest.services.object_storage import object_client
-from tempest.services.volume.v1.json import volumes_client
-
-CONF = config.CONF
-OPTS = {}
-USERS = {}
-RES = collections.defaultdict(list)
-
-LOG = None
-
-JAVELIN_START = datetime.datetime.utcnow()
-
-
-class OSClient(object):
-    _creds = None
-    identity = None
-    servers = None
-
-    def __init__(self, user, pw, tenant):
-        default_params = {
-            'disable_ssl_certificate_validation':
-                CONF.identity.disable_ssl_certificate_validation,
-            'ca_certs': CONF.identity.ca_certificates_file,
-            'trace_requests': CONF.debug.trace_requests
-        }
-        default_params_with_timeout_values = {
-            'build_interval': CONF.compute.build_interval,
-            'build_timeout': CONF.compute.build_timeout
-        }
-        default_params_with_timeout_values.update(default_params)
-
-        compute_params = {
-            'service': CONF.compute.catalog_type,
-            'region': CONF.compute.region or CONF.identity.region,
-            'endpoint_type': CONF.compute.endpoint_type,
-            'build_interval': CONF.compute.build_interval,
-            'build_timeout': CONF.compute.build_timeout
-        }
-        compute_params.update(default_params)
-
-        object_storage_params = {
-            'service': CONF.object_storage.catalog_type,
-            'region': CONF.object_storage.region or CONF.identity.region,
-            'endpoint_type': CONF.object_storage.endpoint_type
-        }
-        object_storage_params.update(default_params)
-
-        _creds = auth.KeystoneV2Credentials(
-            username=user,
-            password=pw,
-            tenant_name=tenant)
-        auth_provider_params = {
-            'disable_ssl_certificate_validation':
-                CONF.identity.disable_ssl_certificate_validation,
-            'ca_certs': CONF.identity.ca_certificates_file,
-            'trace_requests': CONF.debug.trace_requests
-        }
-        _auth = auth.KeystoneV2AuthProvider(
-            _creds, CONF.identity.uri, **auth_provider_params)
-        self.identity = identity_client.IdentityClient(
-            _auth,
-            CONF.identity.catalog_type,
-            CONF.identity.region,
-            endpoint_type='adminURL',
-            **default_params_with_timeout_values)
-        self.tenants = tenants_client.TenantsClient(
-            _auth,
-            CONF.identity.catalog_type,
-            CONF.identity.region,
-            endpoint_type='adminURL',
-            **default_params_with_timeout_values)
-        self.roles = roles_client.RolesClient(
-            _auth,
-            CONF.identity.catalog_type,
-            CONF.identity.region,
-            endpoint_type='adminURL',
-            **default_params_with_timeout_values)
-        self.users = users_client.UsersClient(
-            _auth,
-            CONF.identity.catalog_type,
-            CONF.identity.region,
-            endpoint_type='adminURL',
-            **default_params_with_timeout_values)
-        self.servers = servers_client.ServersClient(_auth,
-                                                    **compute_params)
-        self.flavors = flavors_client.FlavorsClient(_auth,
-                                                    **compute_params)
-        self.floating_ips = floating_ips_client.FloatingIPsClient(
-            _auth, **compute_params)
-        self.secgroups = security_groups_client.SecurityGroupsClient(
-            _auth, **compute_params)
-        self.secrules = security_group_rules_client.SecurityGroupRulesClient(
-            _auth, **compute_params)
-        self.objects = object_client.ObjectClient(_auth,
-                                                  **object_storage_params)
-        self.containers = container_client.ContainerClient(
-            _auth, **object_storage_params)
-        self.images = images_client.ImagesClient(
-            _auth,
-            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,
-            **default_params)
-        self.volumes = volumes_client.VolumesClient(
-            _auth,
-            CONF.volume.catalog_type,
-            CONF.volume.region or CONF.identity.region,
-            endpoint_type=CONF.volume.endpoint_type,
-            build_interval=CONF.volume.build_interval,
-            build_timeout=CONF.volume.build_timeout,
-            **default_params)
-        self.networks = networks_client.NetworksClient(
-            _auth,
-            CONF.network.catalog_type,
-            CONF.network.region or CONF.identity.region,
-            endpoint_type=CONF.network.endpoint_type,
-            build_interval=CONF.network.build_interval,
-            build_timeout=CONF.network.build_timeout,
-            **default_params)
-        self.ports = ports_client.PortsClient(
-            _auth,
-            CONF.network.catalog_type,
-            CONF.network.region or CONF.identity.region,
-            endpoint_type=CONF.network.endpoint_type,
-            build_interval=CONF.network.build_interval,
-            build_timeout=CONF.network.build_timeout,
-            **default_params)
-        self.routers = routers_client.RoutersClient(
-            _auth,
-            CONF.network.catalog_type,
-            CONF.network.region or CONF.identity.region,
-            endpoint_type=CONF.network.endpoint_type,
-            build_interval=CONF.network.build_interval,
-            build_timeout=CONF.network.build_timeout,
-            **default_params)
-        self.subnets = subnets_client.SubnetsClient(
-            _auth,
-            CONF.network.catalog_type,
-            CONF.network.region or CONF.identity.region,
-            endpoint_type=CONF.network.endpoint_type,
-            build_interval=CONF.network.build_interval,
-            build_timeout=CONF.network.build_timeout,
-            **default_params)
-
-
-def load_resources(fname):
-    """Load the expected resources from a yaml file."""
-    return yaml.load(open(fname, 'r'))
-
-
-def keystone_admin():
-    return OSClient(OPTS.os_username, OPTS.os_password, OPTS.os_tenant_name)
-
-
-def client_for_user(name):
-    LOG.debug("Entering client_for_user")
-    if name in USERS:
-        user = USERS[name]
-        LOG.debug("Created client for user %s" % user)
-        return OSClient(user['name'], user['pass'], user['tenant'])
-    else:
-        LOG.error("%s not found in USERS: %s" % (name, USERS))
-
-
-###################
-#
-# TENANTS
-#
-###################
-
-
-def create_tenants(tenants):
-    """Create tenants from resource definition.
-
-    Don't create the tenants if they already exist.
-    """
-    admin = keystone_admin()
-    body = admin.tenants.list_tenants()['tenants']
-    existing = [x['name'] for x in body]
-    for tenant in tenants:
-        if tenant not in existing:
-            admin.tenants.create_tenant(name=tenant)['tenant']
-        else:
-            LOG.warning("Tenant '%s' already exists in this environment"
-                        % tenant)
-
-
-def destroy_tenants(tenants):
-    admin = keystone_admin()
-    for tenant in tenants:
-        tenant_id = identity.get_tenant_by_name(admin.tenant, tenant)['id']
-        admin.tenants.delete_tenant(tenant_id)
-
-##############
-#
-# USERS
-#
-##############
-
-
-def _users_for_tenant(users, tenant):
-    u_for_t = []
-    for user in users:
-        for n in user:
-            if user[n]['tenant'] == tenant:
-                u_for_t.append(user[n])
-    return u_for_t
-
-
-def _tenants_from_users(users):
-    tenants = set()
-    for user in users:
-        for n in user:
-            tenants.add(user[n]['tenant'])
-    return tenants
-
-
-def _assign_swift_role(user, swift_role):
-    admin = keystone_admin()
-    roles = admin.roles.list_roles()
-    role = next(r for r in roles if r['name'] == swift_role)
-    LOG.debug(USERS[user])
-    try:
-        admin.roles.create_user_role_on_project(
-            USERS[user]['tenant_id'],
-            USERS[user]['id'],
-            role['id'])
-    except lib_exc.Conflict:
-        # don't care if it's already assigned
-        pass
-
-
-def create_users(users):
-    """Create tenants from resource definition.
-
-    Don't create the tenants if they already exist.
-    """
-    global USERS
-    LOG.info("Creating users")
-    admin = keystone_admin()
-    for u in users:
-        try:
-            tenant = identity.get_tenant_by_name(admin.tenants, u['tenant'])
-        except lib_exc.NotFound:
-            LOG.error("Tenant: %s - not found" % u['tenant'])
-            continue
-        try:
-            identity.get_user_by_username(admin.tenants,
-                                          tenant['id'], u['name'])
-            LOG.warning("User '%s' already exists in this environment"
-                        % u['name'])
-        except lib_exc.NotFound:
-            admin.users.create_user(
-                name=u['name'], password=u['pass'],
-                tenantId=tenant['id'],
-                email="%s@%s" % (u['name'], tenant['id']),
-                enabled=True)
-
-
-def destroy_users(users):
-    admin = keystone_admin()
-    for user in users:
-        tenant_id = identity.get_tenant_by_name(admin.tenants,
-                                                user['tenant'])['id']
-        user_id = identity.get_user_by_username(admin.tenants,
-                                                tenant_id, user['name'])['id']
-        admin.users.delete_user(user_id)
-
-
-def collect_users(users):
-    global USERS
-    LOG.info("Collecting users")
-    admin = keystone_admin()
-    for u in users:
-        tenant = identity.get_tenant_by_name(admin.tenants, u['tenant'])
-        u['tenant_id'] = tenant['id']
-        USERS[u['name']] = u
-        body = identity.get_user_by_username(admin.tenants,
-                                             tenant['id'], u['name'])
-        USERS[u['name']]['id'] = body['id']
-
-
-class JavelinCheck(unittest.TestCase):
-    def __init__(self, users, resources):
-        super(JavelinCheck, self).__init__()
-        self.users = users
-        self.res = resources
-
-    def runTest(self, *args):
-        pass
-
-    def _ping_ip(self, ip_addr, count, namespace=None):
-        if namespace is None:
-            ping_cmd = "ping -c1 " + ip_addr
-        else:
-            ping_cmd = "sudo ip netns exec %s ping -c1 %s" % (namespace,
-                                                              ip_addr)
-        for current in range(count):
-            return_code = os.system(ping_cmd)
-            if return_code is 0:
-                break
-        self.assertNotEqual(current, count - 1,
-                            "Server is not pingable at %s" % ip_addr)
-
-    def check(self):
-        self.check_users()
-        self.check_objects()
-        self.check_servers()
-        self.check_volumes()
-        self.check_secgroups()
-
-        # validate neutron is enabled and ironic disabled:
-        # Tenant network isolation is not supported when using ironic.
-        # "admin" has set up a neutron flat network environment within a shared
-        # fixed network for all tenants to use.
-        # In this case, network/subnet/router creation can be skipped and the
-        # server booted the same as nova network.
-        if (CONF.service_available.neutron and
-                not CONF.baremetal.driver_enabled):
-            self.check_networking()
-
-    def check_users(self):
-        """Check that the users we expect to exist, do.
-
-        We don't use the resource list for this because we need to validate
-        that things like tenantId didn't drift across versions.
-        """
-        LOG.info("checking users")
-        for name, user in six.iteritems(self.users):
-            client = keystone_admin()
-            found = client.users.show_user(user['id'])['user']
-            self.assertEqual(found['name'], user['name'])
-            self.assertEqual(found['tenantId'], user['tenant_id'])
-
-            # also ensure we can auth with that user, and do something
-            # on the cloud. We don't care about the results except that it
-            # remains authorized.
-            client = client_for_user(user['name'])
-            client.servers.list_servers()
-
-    def check_objects(self):
-        """Check that the objects created are still there."""
-        if not self.res.get('objects'):
-            return
-        LOG.info("checking objects")
-        for obj in self.res['objects']:
-            client = client_for_user(obj['owner'])
-            r, contents = client.objects.get_object(
-                obj['container'], obj['name'])
-            source = _file_contents(obj['file'])
-            self.assertEqual(contents, source)
-
-    def check_servers(self):
-        """Check that the servers are still up and running."""
-        if not self.res.get('servers'):
-            return
-        LOG.info("checking servers")
-        for server in self.res['servers']:
-            client = client_for_user(server['owner'])
-            found = _get_server_by_name(client, server['name'])
-            self.assertIsNotNone(
-                found,
-                "Couldn't find expected server %s" % server['name'])
-
-            found = client.servers.show_server(found['id'])['server']
-            # validate neutron is enabled and ironic disabled:
-            if (CONF.service_available.neutron and
-                    not CONF.baremetal.driver_enabled):
-                _floating_is_alive = False
-                for network_name, body in found['addresses'].items():
-                    for addr in body:
-                        ip = addr['addr']
-                        # Use floating IP, fixed IP or other type to
-                        # reach the server.
-                        # This is useful in multi-node environment.
-                        if CONF.validation.connect_method == 'floating':
-                            if addr.get('OS-EXT-IPS:type',
-                                        'floating') == 'floating':
-                                self._ping_ip(ip, 60)
-                                _floating_is_alive = True
-                        elif CONF.validation.connect_method == 'fixed':
-                            if addr.get('OS-EXT-IPS:type',
-                                        'fixed') == 'fixed':
-                                namespace = _get_router_namespace(client,
-                                                                  network_name)
-                                self._ping_ip(ip, 60, namespace)
-                        else:
-                            self._ping_ip(ip, 60)
-                # If CONF.validation.connect_method is floating, validate
-                # that the floating IP is attached to the server and the
-                # the server is pingable.
-                if CONF.validation.connect_method == 'floating':
-                    self.assertTrue(_floating_is_alive,
-                                    "Server %s has no floating IP." %
-                                    server['name'])
-            else:
-                addr = found['addresses']['private'][0]['addr']
-                self._ping_ip(addr, 60)
-
-    def check_secgroups(self):
-        """Check that the security groups still exist."""
-        LOG.info("Checking security groups")
-        for secgroup in self.res['secgroups']:
-            client = client_for_user(secgroup['owner'])
-            found = _get_resource_by_name(client.secgroups, 'security_groups',
-                                          secgroup['name'])
-            self.assertIsNotNone(
-                found,
-                "Couldn't find expected secgroup %s" % secgroup['name'])
-
-    def check_volumes(self):
-        """Check that the volumes are still there and attached."""
-        if not self.res.get('volumes'):
-            return
-        LOG.info("checking volumes")
-        for volume in self.res['volumes']:
-            client = client_for_user(volume['owner'])
-            vol_body = _get_volume_by_name(client, volume['name'])
-            self.assertIsNotNone(
-                vol_body,
-                "Couldn't find expected volume %s" % volume['name'])
-
-            # Verify that a volume's attachment retrieved
-            server_id = _get_server_by_name(client, volume['server'])['id']
-            attachment = client.volumes.get_attachment_from_volume(vol_body)
-            self.assertEqual(vol_body['id'], attachment['volume_id'])
-            self.assertEqual(server_id, attachment['server_id'])
-
-    def check_networking(self):
-        """Check that the networks are still there."""
-        for res_type in ('networks', 'subnets', 'routers'):
-            for res in self.res[res_type]:
-                client = client_for_user(res['owner'])
-                found = _get_resource_by_name(client.networks, res_type,
-                                              res['name'])
-                self.assertIsNotNone(
-                    found,
-                    "Couldn't find expected resource %s" % res['name'])
-
-
-#######################
-#
-# OBJECTS
-#
-#######################
-
-
-def _file_contents(fname):
-    with open(fname, 'r') as f:
-        return f.read()
-
-
-def create_objects(objects):
-    if not objects:
-        return
-    LOG.info("Creating objects")
-    for obj in objects:
-        LOG.debug("Object %s" % obj)
-        swift_role = obj.get('swift_role', 'Member')
-        _assign_swift_role(obj['owner'], swift_role)
-        client = client_for_user(obj['owner'])
-        client.containers.create_container(obj['container'])
-        client.objects.create_object(
-            obj['container'], obj['name'],
-            _file_contents(obj['file']))
-
-
-def destroy_objects(objects):
-    for obj in objects:
-        client = client_for_user(obj['owner'])
-        r, body = client.objects.delete_object(obj['container'], obj['name'])
-        if not (200 <= int(r['status']) < 299):
-            raise ValueError("unable to destroy object: [%s] %s" % (r, body))
-
-
-#######################
-#
-# IMAGES
-#
-#######################
-
-
-def _resolve_image(image, imgtype):
-    name = image[imgtype]
-    fname = os.path.join(OPTS.devstack_base, image['imgdir'], name)
-    return name, fname
-
-
-def _get_image_by_name(client, name):
-    body = client.images.list_images()
-    for image in body:
-        if name == image['name']:
-            return image
-    return None
-
-
-def create_images(images):
-    if not images:
-        return
-    LOG.info("Creating images")
-    for image in images:
-        client = client_for_user(image['owner'])
-
-        # DEPRECATED: 'format' was used for ami images
-        # Use 'disk_format' and 'container_format' instead
-        if 'format' in image:
-            LOG.warning("Deprecated: 'format' is deprecated for images "
-                        "description. Please use 'disk_format' and 'container_"
-                        "format' instead.")
-            image['disk_format'] = image['format']
-            image['container_format'] = image['format']
-
-        # only upload a new image if the name isn't there
-        if _get_image_by_name(client, image['name']):
-            LOG.info("Image '%s' already exists" % image['name'])
-            continue
-
-        # special handling for 3 part image
-        extras = {}
-        if image['disk_format'] == 'ami':
-            name, fname = _resolve_image(image, 'aki')
-            aki = client.images.create_image(
-                'javelin_' + name, 'aki', 'aki')
-            client.images.store_image_file(aki.get('id'), open(fname, 'r'))
-            extras['kernel_id'] = aki.get('id')
-
-            name, fname = _resolve_image(image, 'ari')
-            ari = client.images.create_image(
-                'javelin_' + name, 'ari', 'ari')
-            client.images.store_image_file(ari.get('id'), open(fname, 'r'))
-            extras['ramdisk_id'] = ari.get('id')
-
-        _, fname = _resolve_image(image, 'file')
-        body = client.images.create_image(
-            image['name'], image['container_format'],
-            image['disk_format'], **extras)
-        image_id = body.get('id')
-        client.images.store_image_file(image_id, open(fname, 'r'))
-
-
-def destroy_images(images):
-    if not images:
-        return
-    LOG.info("Destroying images")
-    for image in images:
-        client = client_for_user(image['owner'])
-
-        response = _get_image_by_name(client, image['name'])
-        if not response:
-            LOG.info("Image '%s' does not exist" % image['name'])
-            continue
-        client.images.delete_image(response['id'])
-
-
-#######################
-#
-# NETWORKS
-#
-#######################
-
-def _get_router_namespace(client, network):
-    network_id = _get_resource_by_name(client.networks,
-                                       'networks', network)['id']
-    n_body = client.routers.list_routers()
-    for router in n_body['routers']:
-        router_id = router['id']
-        r_body = client.ports.list_ports(device_id=router_id)
-        for port in r_body['ports']:
-            if port['network_id'] == network_id:
-                return "qrouter-%s" % router_id
-
-
-def _get_resource_by_name(client, resource, name):
-    get_resources = getattr(client, 'list_%s' % resource)
-    if get_resources is None:
-        raise AttributeError("client doesn't have method list_%s" % resource)
-    # Until all tempest client methods are changed to return only one value,
-    # we cannot assume they all have the same signature so we need to discard
-    # the unused response first value it two values are being returned.
-    body = get_resources()
-    if isinstance(body, tuple):
-        body = body[1]
-    if isinstance(body, dict):
-        body = body[resource]
-    for res in body:
-        if name == res['name']:
-            return res
-    raise ValueError('%s not found in %s resources' % (name, resource))
-
-
-def create_networks(networks):
-    LOG.info("Creating networks")
-    for network in networks:
-        client = client_for_user(network['owner'])
-
-        # only create a network if the name isn't here
-        body = client.networks.list_networks()
-        if any(item['name'] == network['name'] for item in body['networks']):
-            LOG.warning("Duplicated network name: %s" % network['name'])
-            continue
-
-        client.networks.create_network(name=network['name'])
-
-
-def destroy_networks(networks):
-    LOG.info("Destroying subnets")
-    for network in networks:
-        client = client_for_user(network['owner'])
-        network_id = _get_resource_by_name(client.networks, 'networks',
-                                           network['name'])['id']
-        client.networks.delete_network(network_id)
-
-
-def create_subnets(subnets):
-    LOG.info("Creating subnets")
-    for subnet in subnets:
-        client = client_for_user(subnet['owner'])
-
-        network = _get_resource_by_name(client.networks, 'networks',
-                                        subnet['network'])
-        ip_version = netaddr.IPNetwork(subnet['range']).version
-        # ensure we don't overlap with another subnet in the network
-        try:
-            client.networks.create_subnet(network_id=network['id'],
-                                          cidr=subnet['range'],
-                                          name=subnet['name'],
-                                          ip_version=ip_version)
-        except lib_exc.BadRequest as e:
-            is_overlapping_cidr = 'overlaps with another subnet' in str(e)
-            if not is_overlapping_cidr:
-                raise
-
-
-def destroy_subnets(subnets):
-    LOG.info("Destroying subnets")
-    for subnet in subnets:
-        client = client_for_user(subnet['owner'])
-        subnet_id = _get_resource_by_name(client.subnets,
-                                          'subnets', subnet['name'])['id']
-        client.subnets.delete_subnet(subnet_id)
-
-
-def create_routers(routers):
-    LOG.info("Creating routers")
-    for router in routers:
-        client = client_for_user(router['owner'])
-
-        # only create a router if the name isn't here
-        body = client.routers.list_routers()
-        if any(item['name'] == router['name'] for item in body['routers']):
-            LOG.warning("Duplicated router name: %s" % router['name'])
-            continue
-
-        client.networks.create_router(name=router['name'])
-
-
-def destroy_routers(routers):
-    LOG.info("Destroying routers")
-    for router in routers:
-        client = client_for_user(router['owner'])
-        router_id = _get_resource_by_name(client.networks,
-                                          'routers', router['name'])['id']
-        for subnet in router['subnet']:
-            subnet_id = _get_resource_by_name(client.networks,
-                                              'subnets', subnet)['id']
-            client.routers.remove_router_interface(router_id,
-                                                   subnet_id=subnet_id)
-        client.routers.delete_router(router_id)
-
-
-def add_router_interface(routers):
-    for router in routers:
-        client = client_for_user(router['owner'])
-        router_id = _get_resource_by_name(client.networks,
-                                          'routers', router['name'])['id']
-
-        for subnet in router['subnet']:
-            subnet_id = _get_resource_by_name(client.networks,
-                                              'subnets', subnet)['id']
-            # connect routers to their subnets
-            client.routers.add_router_interface(router_id,
-                                                subnet_id=subnet_id)
-        # connect routers to external network if set to "gateway"
-        if router['gateway']:
-            if CONF.network.public_network_id:
-                ext_net = CONF.network.public_network_id
-                client.routers.update_router(
-                    router_id, set_enable_snat=True,
-                    external_gateway_info={"network_id": ext_net})
-            else:
-                raise ValueError('public_network_id is not configured.')
-
-
-#######################
-#
-# SERVERS
-#
-#######################
-
-def _get_server_by_name(client, name):
-    body = client.servers.list_servers()
-    for server in body['servers']:
-        if name == server['name']:
-            return server
-    return None
-
-
-def _get_flavor_by_name(client, name):
-    body = client.flavors.list_flavors()['flavors']
-    for flavor in body:
-        if name == flavor['name']:
-            return flavor
-    return None
-
-
-def create_servers(servers):
-    if not servers:
-        return
-    LOG.info("Creating servers")
-    for server in servers:
-        client = client_for_user(server['owner'])
-
-        if _get_server_by_name(client, server['name']):
-            LOG.info("Server '%s' already exists" % server['name'])
-            continue
-
-        image_id = _get_image_by_name(client, server['image'])['id']
-        flavor_id = _get_flavor_by_name(client, server['flavor'])['id']
-        # validate neutron is enabled and ironic disabled
-        kwargs = dict()
-        if (CONF.service_available.neutron and
-                not CONF.baremetal.driver_enabled and server.get('networks')):
-            get_net_id = lambda x: (_get_resource_by_name(
-                client.networks, 'networks', x)['id'])
-            kwargs['networks'] = [{'uuid': get_net_id(network)}
-                                  for network in server['networks']]
-        body = client.servers.create_server(
-            name=server['name'], imageRef=image_id, flavorRef=flavor_id,
-            **kwargs)['server']
-        server_id = body['id']
-        client.servers.wait_for_server_status(server_id, 'ACTIVE')
-        # create security group(s) after server spawning
-        for secgroup in server['secgroups']:
-            client.servers.add_security_group(server_id, name=secgroup)
-        if CONF.validation.connect_method == 'floating':
-            floating_ip_pool = server.get('floating_ip_pool')
-            floating_ip = client.floating_ips.create_floating_ip(
-                pool_name=floating_ip_pool)['floating_ip']
-            client.floating_ips.associate_floating_ip_to_server(
-                floating_ip['ip'], server_id)
-
-
-def destroy_servers(servers):
-    if not servers:
-        return
-    LOG.info("Destroying servers")
-    for server in servers:
-        client = client_for_user(server['owner'])
-
-        response = _get_server_by_name(client, server['name'])
-        if not response:
-            LOG.info("Server '%s' does not exist" % server['name'])
-            continue
-
-        # TODO(EmilienM): disassociate floating IP from server and release it.
-        client.servers.delete_server(response['id'])
-        waiters.wait_for_server_termination(client.servers, response['id'],
-                                            ignore_error=True)
-
-
-def create_secgroups(secgroups):
-    LOG.info("Creating security groups")
-    for secgroup in secgroups:
-        client = client_for_user(secgroup['owner'])
-
-        # only create a security group if the name isn't here
-        # i.e. a security group may be used by another server
-        # only create a router if the name isn't here
-        body = client.secgroups.list_security_groups()['security_groups']
-        if any(item['name'] == secgroup['name'] for item in body):
-            LOG.warning("Security group '%s' already exists" %
-                        secgroup['name'])
-            continue
-
-        body = client.secgroups.create_security_group(
-            name=secgroup['name'],
-            description=secgroup['description'])['security_group']
-        secgroup_id = body['id']
-        # for each security group, create the rules
-        for rule in secgroup['rules']:
-            ip_proto, from_port, to_port, cidr = rule.split()
-            client.secrules.create_security_group_rule(
-                parent_group_id=secgroup_id, ip_protocol=ip_proto,
-                from_port=from_port, to_port=to_port, cidr=cidr)
-
-
-def destroy_secgroups(secgroups):
-    LOG.info("Destroying security groups")
-    for secgroup in secgroups:
-        client = client_for_user(secgroup['owner'])
-        sg_id = _get_resource_by_name(client.secgroups,
-                                      'security_groups',
-                                      secgroup['name'])
-        # sg rules are deleted automatically
-        client.secgroups.delete_security_group(sg_id['id'])
-
-
-#######################
-#
-# VOLUMES
-#
-#######################
-
-def _get_volume_by_name(client, name):
-    body = client.volumes.list_volumes()['volumes']
-    for volume in body:
-        if name == volume['display_name']:
-            return volume
-    return None
-
-
-def create_volumes(volumes):
-    if not volumes:
-        return
-    LOG.info("Creating volumes")
-    for volume in volumes:
-        client = client_for_user(volume['owner'])
-
-        # only create a volume if the name isn't here
-        if _get_volume_by_name(client, volume['name']):
-            LOG.info("volume '%s' already exists" % volume['name'])
-            continue
-
-        size = volume['gb']
-        v_name = volume['name']
-        body = client.volumes.create_volume(size=size,
-                                            display_name=v_name)['volume']
-        waiters.wait_for_volume_status(client.volumes, body['id'], 'available')
-
-
-def destroy_volumes(volumes):
-    for volume in volumes:
-        client = client_for_user(volume['owner'])
-        volume_id = _get_volume_by_name(client, volume['name'])['id']
-        client.volumes.detach_volume(volume_id)
-        client.volumes.delete_volume(volume_id)
-
-
-def attach_volumes(volumes):
-    for volume in volumes:
-        client = client_for_user(volume['owner'])
-        server_id = _get_server_by_name(client, volume['server'])['id']
-        volume_id = _get_volume_by_name(client, volume['name'])['id']
-        device = volume['device']
-        client.volumes.attach_volume(volume_id,
-                                     instance_uuid=server_id,
-                                     mountpoint=device)
-
-
-#######################
-#
-# MAIN LOGIC
-#
-#######################
-
-def create_resources():
-    LOG.info("Creating Resources")
-    # first create keystone level resources, and we need to be admin
-    # for this.
-    create_tenants(RES['tenants'])
-    create_users(RES['users'])
-    collect_users(RES['users'])
-
-    # next create resources in a well known order
-    create_objects(RES['objects'])
-    create_images(RES['images'])
-
-    # validate neutron is enabled and ironic is disabled
-    if CONF.service_available.neutron and not CONF.baremetal.driver_enabled:
-        create_networks(RES['networks'])
-        create_subnets(RES['subnets'])
-        create_routers(RES['routers'])
-        add_router_interface(RES['routers'])
-
-    create_secgroups(RES['secgroups'])
-    create_volumes(RES['volumes'])
-
-    # Only attempt attaching the volumes if servers are defined in the
-    # resource file
-    if 'servers' in RES:
-        create_servers(RES['servers'])
-        attach_volumes(RES['volumes'])
-
-
-def destroy_resources():
-    LOG.info("Destroying Resources")
-    # Destroy in inverse order of create
-    destroy_servers(RES['servers'])
-    destroy_images(RES['images'])
-    destroy_objects(RES['objects'])
-    destroy_volumes(RES['volumes'])
-    if CONF.service_available.neutron and not CONF.baremetal.driver_enabled:
-        destroy_routers(RES['routers'])
-        destroy_subnets(RES['subnets'])
-        destroy_networks(RES['networks'])
-    destroy_secgroups(RES['secgroups'])
-    destroy_users(RES['users'])
-    destroy_tenants(RES['tenants'])
-    LOG.warning("Destroy mode incomplete")
-
-
-def get_options():
-    global OPTS
-    parser = argparse.ArgumentParser(
-        description='Create and validate a fixed set of OpenStack resources')
-    parser.add_argument('-m', '--mode',
-                        metavar='<create|check|destroy>',
-                        required=True,
-                        help=('One of (create, check, destroy)'))
-    parser.add_argument('-r', '--resources',
-                        required=True,
-                        metavar='resourcefile.yaml',
-                        help='Resources definition yaml file')
-
-    parser.add_argument(
-        '-d', '--devstack-base',
-        required=True,
-        metavar='/opt/stack/old',
-        help='Devstack base directory for retrieving artifacts')
-    parser.add_argument(
-        '-c', '--config-file',
-        metavar='/etc/tempest.conf',
-        help='path to javelin2(tempest) config file')
-
-    # auth bits, letting us also just source the devstack openrc
-    parser.add_argument('--os-username',
-                        metavar='<auth-user-name>',
-                        default=os.environ.get('OS_USERNAME'),
-                        help=('Defaults to env[OS_USERNAME].'))
-    parser.add_argument('--os-password',
-                        metavar='<auth-password>',
-                        default=os.environ.get('OS_PASSWORD'),
-                        help=('Defaults to env[OS_PASSWORD].'))
-    parser.add_argument('--os-tenant-name',
-                        metavar='<auth-tenant-name>',
-                        default=os.environ.get('OS_TENANT_NAME'),
-                        help=('Defaults to env[OS_TENANT_NAME].'))
-
-    OPTS = parser.parse_args()
-    if OPTS.mode not in ('create', 'check', 'destroy'):
-        print("ERROR: Unknown mode -m %s\n" % OPTS.mode)
-        parser.print_help()
-        sys.exit(1)
-    if OPTS.config_file:
-        config.CONF.set_config_path(OPTS.config_file)
-
-
-def setup_logging():
-    global LOG
-    logging.setup(CONF, __name__)
-    LOG = logging.getLogger(__name__)
-
-
-def main():
-    print("Javelin is deprecated and will be removed from Tempest in the "
-          "future.")
-    global RES
-    get_options()
-    setup_logging()
-    RES.update(load_resources(OPTS.resources))
-
-    if OPTS.mode == 'create':
-        create_resources()
-        # Make sure the resources we just created actually work
-        checker = JavelinCheck(USERS, RES)
-        checker.check()
-    elif OPTS.mode == 'check':
-        collect_users(RES['users'])
-        checker = JavelinCheck(USERS, RES)
-        checker.check()
-    elif OPTS.mode == 'destroy':
-        collect_users(RES['users'])
-        destroy_resources()
-    else:
-        LOG.error('Unknown mode %s' % OPTS.mode)
-        return 1
-    LOG.info('javelin2 successfully finished')
-    return 0
-
-if __name__ == "__main__":
-    sys.exit(main())
diff --git a/tempest/cmd/resources.yaml b/tempest/cmd/resources.yaml
deleted file mode 100644
index 5c62ee3..0000000
--- a/tempest/cmd/resources.yaml
+++ /dev/null
@@ -1,95 +0,0 @@
-# This is a yaml description for the most basic definitions
-# of what should exist across the resource boundary. Perhaps
-# one day this will grow into a Heat resource template, but as
-# Heat isn't a known working element in the upgrades, we do
-# this much simpler thing for now.
-
-tenants:
-  - javelin
-  - discuss
-
-users:
-  - name: javelin
-    pass: gungnir
-    tenant: javelin
-  - name: javelin2
-    pass: gungnir2
-    tenant: discuss
-
-secgroups:
-  - name: angon
-    owner: javelin
-    description: angon
-    rules:
-      - 'icmp -1 -1 0.0.0.0/0'
-      - 'tcp 22 22 0.0.0.0/0'
-  - name: baobab
-    owner: javelin
-    description: baobab
-    rules:
-      - 'tcp 80 80 0.0.0.0/0'
-
-# resources that we want to create
-images:
-  - name: javelin_cirros
-    owner: javelin
-    imgdir: files/images/cirros-0.3.2-x86_64-uec
-    file: cirros-0.3.2-x86_64-blank.img
-    format: ami
-    aki: cirros-0.3.2-x86_64-vmlinuz
-    ari: cirros-0.3.2-x86_64-initrd
-volumes:
-  - name: assegai
-    server: peltast
-    owner: javelin
-    gb: 1
-    device: /dev/vdb
-  - name: pifpouf
-    server: hoplite
-    owner: javelin
-    gb: 2
-    device: /dev/vdb
-networks:
-  - name: world1
-    owner: javelin
-  - name: world2
-    owner: javelin
-subnets:
-  - name: subnet1
-    range: 10.1.0.0/24
-    network: world1
-    owner: javelin
-  - name: subnet2
-    range: 192.168.1.0/24
-    network: world2
-    owner: javelin
-routers:
-  - name: connector
-    owner: javelin
-    gateway: true
-    subnet:
-      - subnet1
-      - subnet2
-servers:
-  - name: peltast
-    owner: javelin
-    flavor: m1.small
-    image: javelin_cirros
-    networks:
-      - world1
-    secgroups:
-      - angon
-      - baobab
-  - name: hoplite
-    owner: javelin
-    flavor: m1.medium
-    image: javelin_cirros
-    networks:
-      - world2
-    secgroups:
-      - angon
-objects:
-  - container: jc1
-    name: javelin1
-    owner: javelin
-    file: /etc/hosts
diff --git a/tempest/config.py b/tempest/config.py
index 0c2b913..6bae021 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -173,6 +173,16 @@
                      "a domain scoped token to use admin APIs")
 ]
 
+service_clients_group = cfg.OptGroup(name='service-clients',
+                                     title="Service Clients Options")
+
+ServiceClientsGroup = [
+    cfg.IntOpt('http_timeout',
+               default=60,
+               help='Timeout in seconds to wait for the http request to '
+                    'return'),
+]
+
 identity_feature_group = cfg.OptGroup(name='identity-feature-enabled',
                                       title='Enabled Identity Features')
 
@@ -1119,6 +1129,7 @@
     (compute_group, ComputeGroup),
     (compute_features_group, ComputeFeaturesGroup),
     (identity_group, IdentityGroup),
+    (service_clients_group, ServiceClientsGroup),
     (identity_feature_group, IdentityFeatureGroup),
     (image_group, ImageGroup),
     (image_feature_group, ImageFeaturesGroup),
@@ -1184,6 +1195,7 @@
         self.compute = _CONF.compute
         self.compute_feature_enabled = _CONF['compute-feature-enabled']
         self.identity = _CONF.identity
+        self.service_clients = _CONF['service-clients']
         self.identity_feature_enabled = _CONF['identity-feature-enabled']
         self.image = _CONF.image
         self.image_feature_enabled = _CONF['image-feature-enabled']
@@ -1372,6 +1384,7 @@
         * `disable_ssl_certificate_validation`
         * `ca_certs`
         * `trace_requests`
+        * `http_timeout`
 
     The dict returned by this does not fit a few service clients:
 
@@ -1393,7 +1406,8 @@
         'disable_ssl_certificate_validation':
             CONF.identity.disable_ssl_certificate_validation,
         'ca_certs': CONF.identity.ca_certificates_file,
-        'trace_requests': CONF.debug.trace_requests
+        'trace_requests': CONF.debug.trace_requests,
+        'http_timeout': CONF.service_clients.http_timeout
     }
 
     if service_client_name is None:
diff --git a/tempest/lib/auth.py b/tempest/lib/auth.py
index 54a7002..1857a43 100644
--- a/tempest/lib/auth.py
+++ b/tempest/lib/auth.py
@@ -260,11 +260,13 @@
 
     def __init__(self, credentials, auth_url,
                  disable_ssl_certificate_validation=None,
-                 ca_certs=None, trace_requests=None, scope='project'):
+                 ca_certs=None, trace_requests=None, scope='project',
+                 http_timeout=None):
         super(KeystoneAuthProvider, self).__init__(credentials, scope)
         self.dscv = disable_ssl_certificate_validation
         self.ca_certs = ca_certs
         self.trace_requests = trace_requests
+        self.http_timeout = http_timeout
         self.auth_url = auth_url
         self.auth_client = self._auth_client(auth_url)
 
@@ -342,7 +344,8 @@
     def _auth_client(self, auth_url):
         return json_v2id.TokenClient(
             auth_url, disable_ssl_certificate_validation=self.dscv,
-            ca_certs=self.ca_certs, trace_requests=self.trace_requests)
+            ca_certs=self.ca_certs, trace_requests=self.trace_requests,
+            http_timeout=self.http_timeout)
 
     def _auth_params(self):
         """Auth parameters to be passed to the token request
@@ -429,7 +432,8 @@
     def _auth_client(self, auth_url):
         return json_v3id.V3TokenClient(
             auth_url, disable_ssl_certificate_validation=self.dscv,
-            ca_certs=self.ca_certs, trace_requests=self.trace_requests)
+            ca_certs=self.ca_certs, trace_requests=self.trace_requests,
+            http_timeout=self.http_timeout)
 
     def _auth_params(self):
         """Auth parameters to be passed to the token request
@@ -595,7 +599,7 @@
 
 def get_credentials(auth_url, fill_in=True, identity_version='v2',
                     disable_ssl_certificate_validation=None, ca_certs=None,
-                    trace_requests=None, **kwargs):
+                    trace_requests=None, http_timeout=None, **kwargs):
     """Builds a credentials object based on the configured auth_version
 
     :param auth_url (string): Full URI of the OpenStack Identity API(Keystone)
@@ -611,6 +615,8 @@
     :param ca_certs: CA certificate bundle for validation of certificates
            in SSL API requests to the auth system
     :param trace_requests: trace in log API requests to the auth system
+    :param http_timeout: timeout in seconds to wait for the http request to
+           return
     :param kwargs (dict): Dict of credential key/value pairs
 
     Examples:
@@ -634,7 +640,8 @@
         dscv = disable_ssl_certificate_validation
         auth_provider = auth_provider_class(
             creds, auth_url, disable_ssl_certificate_validation=dscv,
-            ca_certs=ca_certs, trace_requests=trace_requests)
+            ca_certs=ca_certs, trace_requests=trace_requests,
+            http_timeout=http_timeout)
         creds = auth_provider.fill_credentials()
     return creds
 
diff --git a/tempest/lib/common/http.py b/tempest/lib/common/http.py
index dffc5f9..86ea26e 100644
--- a/tempest/lib/common/http.py
+++ b/tempest/lib/common/http.py
@@ -18,7 +18,7 @@
 
 class ClosingHttp(urllib3.poolmanager.PoolManager):
     def __init__(self, disable_ssl_certificate_validation=False,
-                 ca_certs=None):
+                 ca_certs=None, timeout=None):
         kwargs = {}
 
         if disable_ssl_certificate_validation:
@@ -29,6 +29,9 @@
             kwargs['cert_reqs'] = 'CERT_REQUIRED'
             kwargs['ca_certs'] = ca_certs
 
+        if timeout:
+            kwargs['timeout'] = timeout
+
         super(ClosingHttp, self).__init__(**kwargs)
 
     def request(self, url, method, *args, **kwargs):
diff --git a/tempest/lib/common/rest_client.py b/tempest/lib/common/rest_client.py
index 1b0f53a..7e1a442 100644
--- a/tempest/lib/common/rest_client.py
+++ b/tempest/lib/common/rest_client.py
@@ -66,6 +66,8 @@
                          TLS server cert
     :param str trace_request: Regex to use for specifying logging the entirety
                               of the request and response payload
+    :param str http_timeout: Timeout in seconds to wait for the http request to
+                             return
     """
     TYPE = "json"
 
@@ -78,7 +80,7 @@
                  endpoint_type='publicURL',
                  build_interval=1, build_timeout=60,
                  disable_ssl_certificate_validation=False, ca_certs=None,
-                 trace_requests='', name=None):
+                 trace_requests='', name=None, http_timeout=None):
         self.auth_provider = auth_provider
         self.service = service
         self.region = region
@@ -99,7 +101,8 @@
                                        'vary', 'www-authenticate'))
         dscv = disable_ssl_certificate_validation
         self.http_obj = http.ClosingHttp(
-            disable_ssl_certificate_validation=dscv, ca_certs=ca_certs)
+            disable_ssl_certificate_validation=dscv, ca_certs=ca_certs,
+            timeout=http_timeout)
 
     def _get_type(self):
         return self.TYPE
diff --git a/tempest/lib/exceptions.py b/tempest/lib/exceptions.py
index 5ca78f9..de2d713 100644
--- a/tempest/lib/exceptions.py
+++ b/tempest/lib/exceptions.py
@@ -229,3 +229,13 @@
 
 class UnknownServiceClient(TempestException):
     message = "Service clients named %(services)s are not known"
+
+
+class ServiceClientRegistrationException(TempestException):
+    message = ("Error registering module %(name)s in path %(module_path)s, "
+               "with service %(service_version)s and clients "
+               "%(client_names)s: %(detailed_error)s")
+
+
+class PluginRegistrationException(TempestException):
+    message = "Error registering plugin %(name)s: %(detailed_error)s"
diff --git a/tempest/lib/services/clients.py b/tempest/lib/services/clients.py
new file mode 100644
index 0000000..8054e62
--- /dev/null
+++ b/tempest/lib/services/clients.py
@@ -0,0 +1,36 @@
+# Copyright (c) 2016 Hewlett-Packard Enterprise Development Company, L.P.
+# 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.
+
+from tempest.lib.common.utils import misc
+from tempest.lib import exceptions
+
+
+@misc.singleton
+class ClientsRegistry(object):
+    """Registry of all service clients available from plugins"""
+
+    def __init__(self):
+        self._service_clients = {}
+
+    def register_service_client(self, plugin_name, service_client_data):
+        if plugin_name in self._service_clients:
+            detailed_error = 'Clients for plugin %s already registered'
+            raise exceptions.PluginRegistrationException(
+                name=plugin_name,
+                detailed_error=detailed_error % plugin_name)
+        self._service_clients[plugin_name] = service_client_data
+
+    def get_service_clients(self):
+        return self._service_clients
diff --git a/tempest/lib/services/identity/v2/token_client.py b/tempest/lib/services/identity/v2/token_client.py
index 5716027..a5d7c86 100644
--- a/tempest/lib/services/identity/v2/token_client.py
+++ b/tempest/lib/services/identity/v2/token_client.py
@@ -22,11 +22,11 @@
 class TokenClient(rest_client.RestClient):
 
     def __init__(self, auth_url, disable_ssl_certificate_validation=None,
-                 ca_certs=None, trace_requests=None):
+                 ca_certs=None, trace_requests=None, **kwargs):
         dscv = disable_ssl_certificate_validation
         super(TokenClient, self).__init__(
             None, None, None, disable_ssl_certificate_validation=dscv,
-            ca_certs=ca_certs, trace_requests=trace_requests)
+            ca_certs=ca_certs, trace_requests=trace_requests, **kwargs)
 
         if auth_url is None:
             raise exceptions.IdentityError("Couldn't determine auth_url")
diff --git a/tempest/lib/services/identity/v3/token_client.py b/tempest/lib/services/identity/v3/token_client.py
index 964d43f..c1f7e7b 100644
--- a/tempest/lib/services/identity/v3/token_client.py
+++ b/tempest/lib/services/identity/v3/token_client.py
@@ -22,11 +22,11 @@
 class V3TokenClient(rest_client.RestClient):
 
     def __init__(self, auth_url, disable_ssl_certificate_validation=None,
-                 ca_certs=None, trace_requests=None):
+                 ca_certs=None, trace_requests=None, **kwargs):
         dscv = disable_ssl_certificate_validation
         super(V3TokenClient, self).__init__(
             None, None, None, disable_ssl_certificate_validation=dscv,
-            ca_certs=ca_certs, trace_requests=trace_requests)
+            ca_certs=ca_certs, trace_requests=trace_requests, **kwargs)
 
         if auth_url is None:
             raise exceptions.IdentityError("Couldn't determine auth_url")
diff --git a/tempest/service_clients.py b/tempest/service_clients.py
index 252ebf4..a5bc86e 100644
--- a/tempest/service_clients.py
+++ b/tempest/service_clients.py
@@ -17,9 +17,23 @@
 import copy
 import importlib
 import inspect
+import logging
 
 from tempest.lib import auth
 from tempest.lib import exceptions
+from tempest.lib.services import clients
+from tempest.lib.services import compute
+from tempest.lib.services import image
+from tempest.lib.services import network
+
+LOG = logging.getLogger(__name__)
+
+client_modules_by_service_name = {
+    'compute': compute,
+    'image.v1': image.v1,
+    'image.v2': image.v2,
+    'network': network
+}
 
 
 def tempest_modules():
@@ -33,9 +47,48 @@
 
 
 def available_modules():
-    """List of service client modules available in Tempest and plugins"""
-    # TODO(andreaf) For now this returns only tempest_modules
-    return tempest_modules()
+    """List of service client modules available in Tempest and plugins
+
+    The list of available modules can be used for automatic configuration.
+
+    :raise PluginRegistrationException: if a plugin exposes a service_version
+        already defined by Tempest or another plugin.
+
+    Examples:
+
+        >>> from tempest import config
+        >>> params = {}
+        >>> for service_version in available_modules():
+        >>>     service = service_version.split('.')[0]
+        >>>     params[service] = config.service_client_config(service)
+        >>> service_clients = ServiceClients(creds, identity_uri,
+        >>>                                  client_parameters=params)
+    """
+    extra_service_versions = set([])
+    plugin_services = clients.ClientsRegistry().get_service_clients()
+    for plugin_name in plugin_services:
+        plug_service_versions = set([x['service_version'] for x in
+                                     plugin_services[plugin_name]])
+        # If a plugin exposes a duplicate service_version raise an exception
+        if plug_service_versions:
+            if not plug_service_versions.isdisjoint(extra_service_versions):
+                detailed_error = (
+                    'Plugin %s is trying to register a service %s already '
+                    'claimed by another one' % (plugin_name,
+                                                extra_service_versions &
+                                                plug_service_versions))
+                raise exceptions.PluginRegistrationException(
+                    name=plugin_name, detailed_error=detailed_error)
+            if not plug_service_versions.isdisjoint(tempest_modules()):
+                detailed_error = (
+                    'Plugin %s is trying to register a service %s already '
+                    'claimed by a Tempest one' % (plugin_name,
+                                                  tempest_modules() &
+                                                  plug_service_versions))
+                raise exceptions.PluginRegistrationException(
+                    name=plugin_name, detailed_error=detailed_error)
+        extra_service_versions |= plug_service_versions
+    return tempest_modules() | extra_service_versions
 
 
 class ClientsFactory(object):
@@ -49,22 +102,21 @@
     ClientsFactory can be used directly, or consumed via the `ServiceClients`
     class, which manages the authorization part.
     """
-    # TODO(andreaf) This version includes ClientsFactory but it does not
-    # use it yet in ServiceClients
 
     def __init__(self, module_path, client_names, auth_provider, **kwargs):
         """Initialises the client factory
 
-        :param module_path Path to module that includes all service clients.
+        :param module_path: Path to module that includes all service clients.
             All service client classes must be exposed by a single module.
             If they are separated in different modules, defining __all__
             in the root module can help, similar to what is done by service
             clients in tempest.
-        :param client_names List or set of names of the service client classes.
-        :param auth_provider The auth provider used to initialise client.
-        :param kwargs Parameters to be passed to all clients. Parameters values
-            can be overwritten when clients are initialised, but parameters
-            cannot be deleted.
+        :param client_names: List or set of names of the service client
+            classes.
+        :param auth_provider: The auth provider used to initialise client.
+        :param kwargs: Parameters to be passed to all clients. Parameters
+            values can be overwritten when clients are initialised, but
+            parameters cannot be deleted.
         :raise ImportError if the specified module_path cannot be imported
 
         Example:
@@ -187,11 +239,11 @@
                              mandatory parameter, and it will so soon.
         :param region: Default value of region for service clients.
         :param scope: default scope for tokens produced by the auth provider
-        :param disable_ssl_certificate_validation Applies to auth and to all
+        :param disable_ssl_certificate_validation: Applies to auth and to all
                                                   service clients.
-        :param ca_certs Applies to auth and to all service clients.
-        :param trace_requests Applies to auth and to all service clients.
-        :param client_parameters Dictionary with parameters for service
+        :param ca_certs: Applies to auth and to all service clients.
+        :param trace_requests: Applies to auth and to all service clients.
+        :param client_parameters: Dictionary with parameters for service
             clients. Keys of the dictionary are the service client service
             name, as declared in `service_clients.available_modules()` except
             for the version. Values are dictionaries of parameters that are
@@ -206,6 +258,7 @@
             >>> client_parameters['service_y'] = params_service_y
 
         """
+        self._registered_services = set([])
         self.credentials = credentials
         self.identity_uri = identity_uri
         if not identity_uri:
@@ -247,6 +300,94 @@
             raise exceptions.UnknownServiceClient(
                 services=list(client_parameters.keys()))
 
+        # Register service clients owned by tempest
+        for service in tempest_modules():
+            if service in list(client_modules_by_service_name):
+                attribute = service.replace('.', '_')
+                configs = service.split('.')[0]
+                module = client_modules_by_service_name[service]
+                self.register_service_client_module(
+                    attribute, service, module.__name__,
+                    module.__all__, **self.parameters[configs])
+
+        # Register service clients from plugins
+        clients_registry = clients.ClientsRegistry()
+        plugin_service_clients = clients_registry.get_service_clients()
+        for plugin in plugin_service_clients:
+            service_clients = plugin_service_clients[plugin]
+            # Each plugin returns a list of service client parameters
+            for service_client in service_clients:
+                # NOTE(andreaf) If a plugin cannot register, stop the
+                # registration process, log some details to help
+                # troubleshooting, and re-raise
+                try:
+                    self.register_service_client_module(**service_client)
+                except Exception:
+                    LOG.exception(
+                        'Failed to register service client from plugin %s '
+                        'with parameters %s' % (plugin, service_client))
+                    raise
+
+    def register_service_client_module(self, name, service_version,
+                                       module_path, client_names, **kwargs):
+        """Register a service client module
+
+        Initiates a client factory for the specified module, using this
+        class auth_provider, and accessible via a `name` attribute in the
+        service client.
+
+        :param name: Name used to access the client
+        :param service_version: Name of the service complete with version.
+            Used to track registered services. When a plugin implements it,
+            it can be used by other plugins to obtain their configuration.
+        :param module_path: Path to module that includes all service clients.
+            All service client classes must be exposed by a single module.
+            If they are separated in different modules, defining __all__
+            in the root module can help, similar to what is done by service
+            clients in tempest.
+        :param client_names: List or set of names of service client classes.
+        :param kwargs: Extra optional parameters to be passed to all clients.
+            ServiceClient provides defaults for region, dscv, ca_certs and
+            trace_requests.
+        :raise ServiceClientRegistrationException: if the provided name is
+            already in use or if service_version is already registered.
+        :raise ImportError: if module_path cannot be imported.
+        """
+        if hasattr(self, name):
+            using_name = getattr(self, name)
+            detailed_error = 'Module name already in use: %s' % using_name
+            raise exceptions.ServiceClientRegistrationException(
+                name=name, service_version=service_version,
+                module_path=module_path, client_names=client_names,
+                detailed_error=detailed_error)
+        if service_version in self.registered_services:
+            detailed_error = 'Service %s already registered.' % service_version
+            raise exceptions.ServiceClientRegistrationException(
+                name=name, service_version=service_version,
+                module_path=module_path, client_names=client_names,
+                detailed_error=detailed_error)
+        params = dict(region=self.region,
+                      disable_ssl_certificate_validation=self.dscv,
+                      ca_certs=self.ca_certs,
+                      trace_requests=self.trace_requests)
+        params.update(kwargs)
+        # Instantiate the client factory
+        _factory = ClientsFactory(module_path=module_path,
+                                  client_names=client_names,
+                                  auth_provider=self.auth_provider,
+                                  **params)
+        # Adds the client factory to the service_client
+        setattr(self, name, _factory)
+        # Add the name of the new service in self.SERVICES for discovery
+        self._registered_services.add(service_version)
+
+    @property
+    def registered_services(self):
+        # TODO(andreaf) Temporary set needed until all services are migrated
+        _non_migrated_services = tempest_modules() - set(
+            client_modules_by_service_name)
+        return self._registered_services | _non_migrated_services
+
     def _setup_parameters(self, parameters):
         """Setup default values for client parameters
 
diff --git a/tempest/test_discover/plugins.py b/tempest/test_discover/plugins.py
index d604b28..cfb0c7f 100644
--- a/tempest/test_discover/plugins.py
+++ b/tempest/test_discover/plugins.py
@@ -19,6 +19,7 @@
 import stevedore
 
 from tempest.lib.common.utils import misc
+from tempest.lib.services import clients
 
 LOG = logging.getLogger(__name__)
 
@@ -62,6 +63,54 @@
         """
         return []
 
+    def get_service_clients(self):
+        """Get a list of the service clients for registration
+
+        If the plugin implements service clients for one or more APIs, it
+        may return their details by this method for automatic registration
+        in any ServiceClients object instantiated by tests.
+        The default implementation returns an empty list.
+
+        :return list of dictionaries. Each element of the list represents
+            the service client for an API. Each dict must define all
+            parameters required for the invocation of
+            `service_clients.ServiceClients.register_service_client_module`.
+        :rtype: list
+
+        Example:
+
+            >>>  # Example implementation with one service client
+            >>>  myservice_config = config.service_client_config('myservice')
+            >>>  params = {
+            >>>     'name': 'myservice',
+            >>>     'service_version': 'myservice',
+            >>>     'module_path': 'myservice_tempest_tests.services',
+            >>>     'client_names': ['API1Client', 'API2Client'],
+            >>>  }
+            >>>  params.update(myservice_config)
+            >>> return [params]
+
+            >>>  # Example implementation with two service clients
+            >>>  foo1_config = config.service_client_config('foo')
+            >>>  params_foo1 = {
+            >>>     'name': 'foo_v1',
+            >>>     'service_version': 'foo.v1',
+            >>>     'module_path': 'bar_tempest_tests.services.foo.v1',
+            >>>     'client_names': ['API1Client', 'API2Client'],
+            >>>  }
+            >>>  params_foo1.update(foo_config)
+            >>>  foo2_config = config.service_client_config('foo')
+            >>>  params_foo2 = {
+            >>>     'name': 'foo_v2',
+            >>>     'service_version': 'foo.v2',
+            >>>     'module_path': 'bar_tempest_tests.services.foo.v2',
+            >>>     'client_names': ['API1Client', 'API2Client'],
+            >>>  }
+            >>>  params_foo2.update(foo2_config)
+            >>> return [params_foo1, params_foo2]
+        """
+        return []
+
 
 @misc.singleton
 class TempestTestPluginManager(object):
@@ -75,6 +124,7 @@
             'tempest.test_plugins', invoke_on_load=True,
             propagate_map_exceptions=True,
             on_load_failure_callback=self.failure_hook)
+        self._register_service_clients()
 
     @staticmethod
     def failure_hook(_, ep, err):
@@ -102,3 +152,13 @@
             if opt_list:
                 plugin_options.extend(opt_list)
         return plugin_options
+
+    def _register_service_clients(self):
+        registry = clients.ClientsRegistry()
+        for plug in self.ext_plugins:
+            try:
+                registry.register_service_client(
+                    plug.name, plug.obj.get_service_clients())
+            except Exception:
+                LOG.exception('Plugin %s raised an exception trying to run '
+                              'get_service_clients' % plug.name)
diff --git a/tempest/tests/cmd/test_javelin.py b/tempest/tests/cmd/test_javelin.py
deleted file mode 100644
index 5ec9720..0000000
--- a/tempest/tests/cmd/test_javelin.py
+++ /dev/null
@@ -1,422 +0,0 @@
-#!/usr/bin/env python
-#
-#    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 mock
-from oslotest import mockpatch
-
-from tempest.cmd import javelin
-from tempest.lib import exceptions as lib_exc
-from tempest.tests import base
-
-
-class JavelinUnitTest(base.TestCase):
-
-    def setUp(self):
-        super(JavelinUnitTest, self).setUp()
-        javelin.LOG = mock.MagicMock()
-        self.fake_client = mock.MagicMock()
-        self.fake_object = mock.MagicMock()
-
-    def test_load_resources(self):
-        with mock.patch('six.moves.builtins.open', mock.mock_open(),
-                        create=True) as open_mock:
-            with mock.patch('yaml.load', mock.MagicMock(),
-                            create=True) as load_mock:
-                javelin.load_resources(self.fake_object)
-                load_mock.assert_called_once_with(open_mock(self.fake_object))
-
-    def test_keystone_admin(self):
-        self.useFixture(mockpatch.PatchObject(javelin, "OSClient"))
-        javelin.OPTS = self.fake_object
-        javelin.keystone_admin()
-        javelin.OSClient.assert_called_once_with(
-            self.fake_object.os_username,
-            self.fake_object.os_password,
-            self.fake_object.os_tenant_name)
-
-    def test_client_for_user(self):
-        fake_user = mock.MagicMock()
-        javelin.USERS = {fake_user['name']: fake_user}
-        self.useFixture(mockpatch.PatchObject(javelin, "OSClient"))
-        javelin.client_for_user(fake_user['name'])
-        javelin.OSClient.assert_called_once_with(
-            fake_user['name'], fake_user['pass'], fake_user['tenant'])
-
-    def test_client_for_non_existing_user(self):
-        fake_non_existing_user = self.fake_object
-        fake_user = mock.MagicMock()
-        javelin.USERS = {fake_user['name']: fake_user}
-        self.useFixture(mockpatch.PatchObject(javelin, "OSClient"))
-        javelin.client_for_user(fake_non_existing_user['name'])
-        self.assertFalse(javelin.OSClient.called)
-
-    def test_attach_volumes(self):
-        self.useFixture(mockpatch.PatchObject(javelin, "client_for_user",
-                                              return_value=self.fake_client))
-
-        self.useFixture(mockpatch.PatchObject(
-            javelin, "_get_volume_by_name",
-            return_value=self.fake_object.volume))
-
-        self.useFixture(mockpatch.PatchObject(
-            javelin, "_get_server_by_name",
-            return_value=self.fake_object.server))
-
-        javelin.attach_volumes([self.fake_object])
-
-        mocked_function = self.fake_client.volumes.attach_volume
-        mocked_function.assert_called_once_with(
-            self.fake_object.volume['id'],
-            instance_uuid=self.fake_object.server['id'],
-            mountpoint=self.fake_object['device'])
-
-
-class TestCreateResources(JavelinUnitTest):
-    def test_create_tenants(self):
-
-        self.fake_client.tenants.list_tenants.return_value = {'tenants': []}
-        self.useFixture(mockpatch.PatchObject(javelin, "keystone_admin",
-                                              return_value=self.fake_client))
-
-        javelin.create_tenants([self.fake_object['name']])
-
-        mocked_function = self.fake_client.tenants.create_tenant
-        mocked_function.assert_called_once_with(name=self.fake_object['name'])
-
-    def test_create_duplicate_tenant(self):
-        self.fake_client.tenants.list_tenants.return_value = {'tenants': [
-            {'name': self.fake_object['name']}]}
-        self.useFixture(mockpatch.PatchObject(javelin, "keystone_admin",
-                                              return_value=self.fake_client))
-
-        javelin.create_tenants([self.fake_object['name']])
-
-        mocked_function = self.fake_client.tenants.create_tenant
-        self.assertFalse(mocked_function.called)
-
-    def test_create_users(self):
-        self.useFixture(mockpatch.Patch(
-                        'tempest.common.identity.get_tenant_by_name',
-                        return_value=self.fake_object['tenant']))
-        self.useFixture(mockpatch.Patch(
-                        'tempest.common.identity.get_user_by_username',
-                        side_effect=lib_exc.NotFound("user is not found")))
-        self.useFixture(mockpatch.PatchObject(javelin, "keystone_admin",
-                                              return_value=self.fake_client))
-
-        javelin.create_users([self.fake_object])
-
-        fake_tenant_id = self.fake_object['tenant']['id']
-        fake_email = "%s@%s" % (self.fake_object['user'], fake_tenant_id)
-        mocked_function = self.fake_client.users.create_user
-        mocked_function.assert_called_once_with(
-            name=self.fake_object['name'],
-            password=self.fake_object['password'],
-            tenantId=fake_tenant_id,
-            email=fake_email,
-            enabled=True)
-
-    def test_create_user_missing_tenant(self):
-        self.useFixture(mockpatch.Patch(
-                        'tempest.common.identity.get_tenant_by_name',
-                        side_effect=lib_exc.NotFound("tenant is not found")))
-        self.useFixture(mockpatch.PatchObject(javelin, "keystone_admin",
-                                              return_value=self.fake_client))
-
-        javelin.create_users([self.fake_object])
-
-        mocked_function = self.fake_client.users.create_user
-        self.assertFalse(mocked_function.called)
-
-    def test_create_objects(self):
-
-        self.useFixture(mockpatch.PatchObject(javelin, "client_for_user",
-                                              return_value=self.fake_client))
-        self.useFixture(mockpatch.PatchObject(javelin, "_assign_swift_role"))
-        self.useFixture(mockpatch.PatchObject(javelin, "_file_contents",
-                        return_value=self.fake_object.content))
-
-        javelin.create_objects([self.fake_object])
-
-        mocked_function = self.fake_client.containers.create_container
-        mocked_function.assert_called_once_with(self.fake_object['container'])
-        mocked_function = self.fake_client.objects.create_object
-        mocked_function.assert_called_once_with(self.fake_object['container'],
-                                                self.fake_object['name'],
-                                                self.fake_object.content)
-
-    def test_create_images(self):
-        self.fake_client.images.create_image.return_value = \
-            self.fake_object['body']
-
-        self.useFixture(mockpatch.PatchObject(javelin, "client_for_user",
-                                              return_value=self.fake_client))
-        self.useFixture(mockpatch.PatchObject(javelin, "_get_image_by_name",
-                                              return_value=[]))
-        self.useFixture(mockpatch.PatchObject(javelin, "_resolve_image",
-                                              return_value=(None, None)))
-
-        with mock.patch('six.moves.builtins.open', mock.mock_open(),
-                        create=True) as open_mock:
-            javelin.create_images([self.fake_object])
-
-        mocked_function = self.fake_client.images.create_image
-        mocked_function.assert_called_once_with(self.fake_object['name'],
-                                                self.fake_object['format'],
-                                                self.fake_object['format'])
-
-        mocked_function = self.fake_client.images.store_image_file
-        fake_image_id = self.fake_object['body'].get('id')
-        mocked_function.assert_called_once_with(fake_image_id, open_mock())
-
-    def test_create_networks(self):
-        self.fake_client.networks.list_networks.return_value = {
-            'networks': []}
-
-        self.useFixture(mockpatch.PatchObject(javelin, "client_for_user",
-                                              return_value=self.fake_client))
-
-        javelin.create_networks([self.fake_object])
-
-        mocked_function = self.fake_client.networks.create_network
-        mocked_function.assert_called_once_with(name=self.fake_object['name'])
-
-    def test_create_subnet(self):
-
-        fake_network = self.fake_object['network']
-
-        self.useFixture(mockpatch.PatchObject(javelin, "client_for_user",
-                                              return_value=self.fake_client))
-        self.useFixture(mockpatch.PatchObject(javelin, "_get_resource_by_name",
-                                              return_value=fake_network))
-
-        fake_netaddr = mock.MagicMock()
-        self.useFixture(mockpatch.PatchObject(javelin, "netaddr",
-                                              return_value=fake_netaddr))
-        fake_version = javelin.netaddr.IPNetwork().version
-
-        javelin.create_subnets([self.fake_object])
-
-        mocked_function = self.fake_client.networks.create_subnet
-        mocked_function.assert_called_once_with(network_id=fake_network['id'],
-                                                cidr=self.fake_object['range'],
-                                                name=self.fake_object['name'],
-                                                ip_version=fake_version)
-
-    @mock.patch("tempest.common.waiters.wait_for_volume_status")
-    def test_create_volumes(self, mock_wait_for_volume_status):
-        self.useFixture(mockpatch.PatchObject(javelin, "client_for_user",
-                                              return_value=self.fake_client))
-        self.useFixture(mockpatch.PatchObject(javelin, "_get_volume_by_name",
-                                              return_value=None))
-        self.fake_client.volumes.create_volume.return_value = \
-            self.fake_object.body
-
-        javelin.create_volumes([self.fake_object])
-
-        mocked_function = self.fake_client.volumes.create_volume
-        mocked_function.assert_called_once_with(
-            size=self.fake_object['gb'],
-            display_name=self.fake_object['name'])
-        mock_wait_for_volume_status.assert_called_once_with(
-            self.fake_client.volumes, self.fake_object.body['volume']['id'],
-            'available')
-
-    @mock.patch("tempest.common.waiters.wait_for_volume_status")
-    def test_create_volume_existing(self, mock_wait_for_volume_status):
-        self.useFixture(mockpatch.PatchObject(javelin, "client_for_user",
-                                              return_value=self.fake_client))
-        self.useFixture(mockpatch.PatchObject(javelin, "_get_volume_by_name",
-                                              return_value=self.fake_object))
-        self.fake_client.volumes.create_volume.return_value = \
-            self.fake_object.body
-
-        javelin.create_volumes([self.fake_object])
-
-        mocked_function = self.fake_client.volumes.create_volume
-        self.assertFalse(mocked_function.called)
-        self.assertFalse(mock_wait_for_volume_status.called)
-
-    def test_create_router(self):
-
-        self.fake_client.routers.list_routers.return_value = {'routers': []}
-        self.useFixture(mockpatch.PatchObject(javelin, "client_for_user",
-                                              return_value=self.fake_client))
-
-        javelin.create_routers([self.fake_object])
-
-        mocked_function = self.fake_client.networks.create_router
-        mocked_function.assert_called_once_with(name=self.fake_object['name'])
-
-    def test_create_router_existing(self):
-        self.fake_client.routers.list_routers.return_value = {
-            'routers': [self.fake_object]}
-        self.useFixture(mockpatch.PatchObject(javelin, "client_for_user",
-                                              return_value=self.fake_client))
-
-        javelin.create_routers([self.fake_object])
-
-        mocked_function = self.fake_client.networks.create_router
-        self.assertFalse(mocked_function.called)
-
-    def test_create_secgroup(self):
-        self.useFixture(mockpatch.PatchObject(javelin, "client_for_user",
-                                              return_value=self.fake_client))
-        self.fake_client.secgroups.list_security_groups.return_value = (
-            {'security_groups': []})
-        self.fake_client.secgroups.create_security_group.return_value = \
-            {'security_group': {'id': self.fake_object['secgroup_id']}}
-
-        javelin.create_secgroups([self.fake_object])
-
-        mocked_function = self.fake_client.secgroups.create_security_group
-        mocked_function.assert_called_once_with(
-            name=self.fake_object['name'],
-            description=self.fake_object['description'])
-
-
-class TestDestroyResources(JavelinUnitTest):
-
-    def test_destroy_tenants(self):
-
-        fake_tenant = self.fake_object['tenant']
-        fake_auth = self.fake_client
-        self.useFixture(mockpatch.Patch(
-                        'tempest.common.identity.get_tenant_by_name',
-                        return_value=fake_tenant))
-        self.useFixture(mockpatch.PatchObject(javelin, "keystone_admin",
-                                              return_value=fake_auth))
-        javelin.destroy_tenants([fake_tenant])
-
-        mocked_function = fake_auth.tenants.delete_tenant
-        mocked_function.assert_called_once_with(fake_tenant['id'])
-
-    def test_destroy_users(self):
-
-        fake_user = self.fake_object['user']
-        fake_tenant = self.fake_object['tenant']
-
-        fake_auth = self.fake_client
-        fake_auth.tenants.list_tenants.return_value = \
-            {'tenants': [fake_tenant]}
-        fake_auth.users.list_users.return_value = {'users': [fake_user]}
-
-        self.useFixture(mockpatch.Patch(
-                        'tempest.common.identity.get_user_by_username',
-                        return_value=fake_user))
-        self.useFixture(mockpatch.PatchObject(javelin, "keystone_admin",
-                                              return_value=fake_auth))
-
-        javelin.destroy_users([fake_user])
-
-        mocked_function = fake_auth.users.delete_user
-        mocked_function.assert_called_once_with(fake_user['id'])
-
-    def test_destroy_objects(self):
-
-        self.fake_client.objects.delete_object.return_value = \
-            {'status': "200"}, ""
-        self.useFixture(mockpatch.PatchObject(javelin, "client_for_user",
-                                              return_value=self.fake_client))
-        javelin.destroy_objects([self.fake_object])
-
-        mocked_function = self.fake_client.objects.delete_object
-        mocked_function.asswert_called_once(self.fake_object['container'],
-                                            self.fake_object['name'])
-
-    def test_destroy_images(self):
-
-        self.useFixture(mockpatch.PatchObject(javelin, "client_for_user",
-                                              return_value=self.fake_client))
-        self.useFixture(mockpatch.PatchObject(javelin, "_get_image_by_name",
-                        return_value=self.fake_object['image']))
-
-        javelin.destroy_images([self.fake_object])
-
-        mocked_function = self.fake_client.images.delete_image
-        mocked_function.assert_called_once_with(
-            self.fake_object['image']['id'])
-
-    def test_destroy_networks(self):
-
-        self.useFixture(mockpatch.PatchObject(javelin, "client_for_user",
-                                              return_value=self.fake_client))
-        self.useFixture(mockpatch.PatchObject(
-            javelin, "_get_resource_by_name",
-            return_value=self.fake_object['resource']))
-
-        javelin.destroy_networks([self.fake_object])
-
-        mocked_function = self.fake_client.networks.delete_network
-        mocked_function.assert_called_once_with(
-            self.fake_object['resource']['id'])
-
-    def test_destroy_volumes(self):
-        self.useFixture(mockpatch.PatchObject(javelin, "client_for_user",
-                                              return_value=self.fake_client))
-
-        self.useFixture(mockpatch.PatchObject(
-            javelin, "_get_volume_by_name",
-            return_value=self.fake_object.volume))
-
-        javelin.destroy_volumes([self.fake_object])
-
-        mocked_function = self.fake_client.volumes.detach_volume
-        mocked_function.assert_called_once_with(self.fake_object.volume['id'])
-        mocked_function = self.fake_client.volumes.delete_volume
-        mocked_function.assert_called_once_with(self.fake_object.volume['id'])
-
-    def test_destroy_subnets(self):
-
-        self.useFixture(mockpatch.PatchObject(javelin, "client_for_user",
-                                              return_value=self.fake_client))
-        fake_subnet_id = self.fake_object['subnet_id']
-        self.useFixture(mockpatch.PatchObject(javelin, "_get_resource_by_name",
-                                              return_value={
-                                                  'id': fake_subnet_id}))
-
-        javelin.destroy_subnets([self.fake_object])
-
-        mocked_function = self.fake_client.subnets.delete_subnet
-        mocked_function.assert_called_once_with(fake_subnet_id)
-
-    def test_destroy_routers(self):
-        self.useFixture(mockpatch.PatchObject(javelin, "client_for_user",
-                                              return_value=self.fake_client))
-
-        # this function is used on 2 different occasions in the code
-        def _fake_get_resource_by_name(*args):
-            if args[1] == "routers":
-                return {"id": self.fake_object['router_id']}
-            elif args[1] == "subnets":
-                return {"id": self.fake_object['subnet_id']}
-        javelin._get_resource_by_name = _fake_get_resource_by_name
-
-        javelin.destroy_routers([self.fake_object])
-
-        mocked_function = self.fake_client.routers.delete_router
-        mocked_function.assert_called_once_with(
-            self.fake_object['router_id'])
-
-    def test_destroy_secgroup(self):
-        self.useFixture(mockpatch.PatchObject(javelin, "client_for_user",
-                                              return_value=self.fake_client))
-        fake_secgroup = {'id': self.fake_object['id']}
-        self.useFixture(mockpatch.PatchObject(javelin, "_get_resource_by_name",
-                                              return_value=fake_secgroup))
-
-        javelin.destroy_secgroups([self.fake_object])
-
-        mocked_function = self.fake_client.secgroups.delete_security_group
-        mocked_function.assert_called_once_with(self.fake_object['id'])
diff --git a/tempest/tests/fake_tempest_plugin.py b/tempest/tests/fake_tempest_plugin.py
index f718d0b..56aae1e 100644
--- a/tempest/tests/fake_tempest_plugin.py
+++ b/tempest/tests/fake_tempest_plugin.py
@@ -18,6 +18,7 @@
 
 class FakePlugin(plugins.TempestPlugin):
     expected_load_test = ["my/test/path", "/home/dir"]
+    expected_service_clients = [{'foo': 'bar'}]
 
     def load_tests(self):
         return self.expected_load_test
@@ -28,6 +29,9 @@
     def get_opt_lists(self):
         return []
 
+    def get_service_clients(self):
+        return self.expected_service_clients
+
 
 class FakeStevedoreObj(object):
     obj = FakePlugin()
@@ -38,3 +42,26 @@
 
     def __init__(self, name='Test1'):
         self._name = name
+
+
+class FakePluginNoServiceClients(plugins.TempestPlugin):
+
+    def load_tests(self):
+        return []
+
+    def register_opts(self, conf):
+        return
+
+    def get_opt_lists(self):
+        return []
+
+
+class FakeStevedoreObjNoServiceClients(object):
+    obj = FakePluginNoServiceClients()
+
+    @property
+    def name(self):
+        return self._name
+
+    def __init__(self, name='Test2'):
+        self._name = name
diff --git a/tempest/tests/lib/test_auth.py b/tempest/tests/lib/test_auth.py
index 12590a3..6da7e41 100644
--- a/tempest/tests/lib/test_auth.py
+++ b/tempest/tests/lib/test_auth.py
@@ -244,7 +244,7 @@
         # The original headers where empty
         self.assertNotEqual(url, self.target_url)
         self.assertIsNone(headers)
-        self.assertEqual(body, None)
+        self.assertIsNone(body)
 
     def _test_request_with_alt_part_without_alt_data_no_change(self, body):
         """Test empty alternate auth data with no effect
diff --git a/tempest/tests/negative/test_negative_generators.py b/tempest/tests/negative/test_negative_generators.py
index 78fd80d..2e45ef7 100644
--- a/tempest/tests/negative/test_negative_generators.py
+++ b/tempest/tests/negative/test_negative_generators.py
@@ -146,5 +146,5 @@
             schema_under_test = copy.copy(valid_schema)
             expected_result = \
                 self.generator.generate_payload(test, schema_under_test)
-            self.assertEqual(expected_result, None)
+            self.assertIsNone(expected_result)
             self._validate_result(valid_schema, schema_under_test)
diff --git a/tempest/tests/test_service_clients.py b/tempest/tests/test_service_clients.py
index 26cc93f..3d8b360 100644
--- a/tempest/tests/test_service_clients.py
+++ b/tempest/tests/test_service_clients.py
@@ -263,3 +263,84 @@
         for _key in _params.keys():
             self.assertEqual(expected_params[_key],
                              _params[_key])
+
+    def test_register_service_client_module(self):
+        expected_params = {'fake_param1': 'fake_value1',
+                           'fake_param2': 'fake_value2'}
+        _manager = self._get_manager(init_region='fake_region_default')
+        # Mock after the _manager is setup to preserve the call count
+        factory_mock = self.useFixture(fixtures.MockPatch(
+            'tempest.service_clients.ClientsFactory')).mock
+        _manager.register_service_client_module(
+            name='fake_module',
+            service_version='fake_service',
+            module_path='fake.path.to.module',
+            client_names=[],
+            **expected_params)
+        self.assertThat(_manager, has_attribute('fake_module'))
+        # Assert called once, without check for exact parameters
+        self.assertTrue(factory_mock.called)
+        self.assertEqual(1, factory_mock.call_count)
+        # Assert expected params are in with their values
+        actual_kwargs = factory_mock.call_args[1]
+        self.assertIn('region', actual_kwargs)
+        self.assertEqual('fake_region_default', actual_kwargs['region'])
+        for param in expected_params:
+            self.assertIn(param, actual_kwargs)
+            self.assertEqual(expected_params[param], actual_kwargs[param])
+        # Assert the new service is registered
+        self.assertIn('fake_service', _manager._registered_services)
+
+    def test_register_service_client_module_override_default(self):
+        new_region = 'new_region'
+        expected_params = {'fake_param1': 'fake_value1',
+                           'fake_param2': 'fake_value2',
+                           'region': new_region}
+        _manager = self._get_manager(init_region='fake_region_default')
+        # Mock after the _manager is setup to preserve the call count
+        factory_mock = self.useFixture(fixtures.MockPatch(
+            'tempest.service_clients.ClientsFactory')).mock
+        _manager.register_service_client_module(
+            name='fake_module',
+            service_version='fake_service',
+            module_path='fake.path.to.module',
+            client_names=[],
+            **expected_params)
+        self.assertThat(_manager, has_attribute('fake_module'))
+        # Assert called once, without check for exact parameters
+        self.assertTrue(factory_mock.called)
+        self.assertEqual(1, factory_mock.call_count)
+        # Assert expected params are in with their values
+        actual_kwargs = factory_mock.call_args[1]
+        self.assertIn('region', actual_kwargs)
+        self.assertEqual(new_region, actual_kwargs['region'])
+        for param in expected_params:
+            self.assertIn(param, actual_kwargs)
+            self.assertEqual(expected_params[param], actual_kwargs[param])
+        # Assert the new service is registered
+        self.assertIn('fake_service', _manager._registered_services)
+
+    def test_register_service_client_module_duplicate_name(self):
+        self.useFixture(fixtures.MockPatch(
+            'tempest.service_clients.ClientsFactory'))
+        _manager = self._get_manager()
+        name_owner = 'this_is_a_string'
+        setattr(_manager, 'fake_module', name_owner)
+        expected_error = '.*' + name_owner
+        with testtools.ExpectedException(
+                exceptions.ServiceClientRegistrationException, expected_error):
+            _manager.register_service_client_module(
+                name='fake_module', module_path='fake.path.to.module',
+                service_version='fake_service', client_names=[])
+
+    def test_register_service_client_module_duplicate_service(self):
+        self.useFixture(fixtures.MockPatch(
+            'tempest.service_clients.ClientsFactory'))
+        _manager = self._get_manager()
+        duplicate_service = 'fake_service1'
+        expected_error = '.*' + duplicate_service
+        with testtools.ExpectedException(
+                exceptions.ServiceClientRegistrationException, expected_error):
+            _manager.register_service_client_module(
+                name='fake_module', module_path='fake.path.to.module',
+                service_version=duplicate_service, client_names=[])
diff --git a/tempest/tests/test_tempest_plugin.py b/tempest/tests/test_tempest_plugin.py
index c07e98c..dd50125 100644
--- a/tempest/tests/test_tempest_plugin.py
+++ b/tempest/tests/test_tempest_plugin.py
@@ -13,6 +13,7 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+from tempest.lib.services import clients
 from tempest.test_discover import plugins
 from tempest.tests import base
 from tempest.tests import fake_tempest_plugin as fake_plugin
@@ -42,3 +43,39 @@
                          result['fake01'])
         self.assertEqual(fake_plugin.FakePlugin.expected_load_test,
                          result['fake02'])
+
+    def test__register_service_clients_with_one_plugin(self):
+        registry = clients.ClientsRegistry()
+        manager = plugins.TempestTestPluginManager()
+        fake_obj = fake_plugin.FakeStevedoreObj()
+        manager.ext_plugins = [fake_obj]
+        manager._register_service_clients()
+        expected_result = fake_plugin.FakePlugin.expected_service_clients
+        registered_clients = registry.get_service_clients()
+        self.assertIn(fake_obj.name, registered_clients)
+        self.assertEqual(expected_result, registered_clients[fake_obj.name])
+
+    def test__get_service_clients_with_two_plugins(self):
+        registry = clients.ClientsRegistry()
+        manager = plugins.TempestTestPluginManager()
+        obj1 = fake_plugin.FakeStevedoreObj('fake01')
+        obj2 = fake_plugin.FakeStevedoreObj('fake02')
+        manager.ext_plugins = [obj1, obj2]
+        manager._register_service_clients()
+        expected_result = fake_plugin.FakePlugin.expected_service_clients
+        registered_clients = registry.get_service_clients()
+        self.assertIn('fake01', registered_clients)
+        self.assertIn('fake02', registered_clients)
+        self.assertEqual(expected_result, registered_clients['fake01'])
+        self.assertEqual(expected_result, registered_clients['fake02'])
+
+    def test__register_service_clients_one_plugin_no_service_clients(self):
+        registry = clients.ClientsRegistry()
+        manager = plugins.TempestTestPluginManager()
+        fake_obj = fake_plugin.FakeStevedoreObjNoServiceClients()
+        manager.ext_plugins = [fake_obj]
+        manager._register_service_clients()
+        expected_result = []
+        registered_clients = registry.get_service_clients()
+        self.assertIn(fake_obj.name, registered_clients)
+        self.assertEqual(expected_result, registered_clients[fake_obj.name])