Add "region" config for each service

Added config items are:
    compute.region
    image.region
    network.region
    volume.region
    object_storage.region
    orchestration.region

RestClient decides target endpoints according to these values. If
these values are not set, the value of identity.region is used
instead.

Fixes bug 1210039

Change-Id: If2a01fae2893ee5740a94e97389164eb000538d7
diff --git a/etc/tempest.conf.sample b/etc/tempest.conf.sample
index cd57354..2ecace0 100644
--- a/etc/tempest.conf.sample
+++ b/etc/tempest.conf.sample
@@ -28,7 +28,8 @@
 uri = http://127.0.0.1:5000/v2.0/
 # URL for where to find the OpenStack V3 Identity API endpoint (Keystone)
 uri_v3 = http://127.0.0.1:5000/v3/
-# The identity region
+# The identity region. Also used as the other services' region name unless
+# they are set explicitly.
 region = RegionOne
 
 # This should be the username of a user WITHOUT administrative privileges
@@ -137,6 +138,11 @@
 # this value as "compute"
 catalog_type = compute
 
+# The name of a region for compute. If empty or commented-out, the value of
+# identity.region is used instead. If no such region is found in the service
+# catalog, the first found one is used.
+#region = RegionOne
+
 # Does the Compute API support creation of images?
 create_image_enabled = true
 
@@ -186,6 +192,11 @@
 # this value as "image"
 catalog_type = image
 
+# The name of a region for image. If empty or commented-out, the value of
+# identity.region is used instead. If no such region is found in the service
+# catalog, the first found one is used.
+#region = RegionOne
+
 # The version of the OpenStack Images API to use
 api_version = 1
 
@@ -201,6 +212,11 @@
 # Catalog type of the Neutron Service
 catalog_type = network
 
+# The name of a region for network. If empty or commented-out, the value of
+# identity.region is used instead. If no such region is found in the service
+# catalog, the first found one is used.
+#region = RegionOne
+
 # A large private cidr block from which to allocate smaller blocks for
 # tenant networks.
 tenant_network_cidr = 10.100.0.0/16
@@ -230,6 +246,10 @@
 # Unless you have a custom Keystone service catalog implementation, you
 # probably want to leave this value as "volume"
 catalog_type = volume
+# The name of a region for volume. If empty or commented-out, the value of
+# identity.region is used instead. If no such region is found in the service
+# catalog, the first found one is used.
+#region = RegionOne
 # The disk format to use when copying a volume to image
 disk_format = raw
 # Number of seconds to wait while looping to check the status of a
@@ -260,6 +280,11 @@
 # this value as "object-store"
 catalog_type = object-store
 
+# The name of a region for object storage. If empty or commented-out, the
+# value of identity.region is used instead. If no such region is found in
+# the service catalog, the first found one is used.
+#region = RegionOne
+
 # Number of seconds to time on waiting for a container to container
 # synchronization complete
 container_sync_timeout = 120
@@ -318,6 +343,16 @@
 build_interval = 1
 
 [orchestration]
+# The type of endpoint for an Orchestration API service. Unless you have a
+# custom Keystone service catalog implementation, you probably want to leave
+# this value as "orchestration"
+catalog_type = orchestration
+
+# The name of a region for orchestration. If empty or commented-out, the value
+# of identity.region is used instead. If no such region is found in the service
+# catalog, the first found one is used.
+#region = RegionOne
+
 # Status change wait interval
 build_interval = 1
 
diff --git a/tempest/cli/simple_read_only/test_cinder.py b/tempest/cli/simple_read_only/test_cinder.py
index 21acae8..3ff997a 100644
--- a/tempest/cli/simple_read_only/test_cinder.py
+++ b/tempest/cli/simple_read_only/test_cinder.py
@@ -114,4 +114,7 @@
         self.cinder('list', flags='--retries 3')
 
     def test_cinder_region_list(self):
-        self.cinder('list', flags='--os-region-name ' + self.identity.region)
+        region = self.config.volume.region
+        if not region:
+            region = self.config.identity.region
+        self.cinder('list', flags='--os-region-name ' + region)
diff --git a/tempest/common/rest_client.py b/tempest/common/rest_client.py
index 8dfff6e..81393a9 100644
--- a/tempest/common/rest_client.py
+++ b/tempest/common/rest_client.py
@@ -49,7 +49,17 @@
         self.service = None
         self.token = None
         self.base_url = None
-        self.region = {'compute': self.config.identity.region}
+        self.region = {}
+        for cfgname in dir(self.config):
+            # Find all config.FOO.catalog_type and assume FOO is a service.
+            cfg = getattr(self.config, cfgname)
+            catalog_type = getattr(cfg, 'catalog_type', None)
+            if not catalog_type:
+                continue
+            service_region = getattr(cfg, 'region', None)
+            if not service_region:
+                service_region = self.config.identity.region
+            self.region[catalog_type] = service_region
         self.endpoint_url = 'publicURL'
         self.headers = {'Content-Type': 'application/%s' % self.TYPE,
                         'Accept': 'application/%s' % self.TYPE}
diff --git a/tempest/config.py b/tempest/config.py
index b386968..1b52f5e 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -44,7 +44,10 @@
                help='Full URI of the OpenStack Identity API (Keystone), v3'),
     cfg.StrOpt('region',
                default='RegionOne',
-               help="The identity region name to use."),
+               help="The identity region name to use. Also used as the other "
+                    "services' region name unless they are set explicitly. "
+                    "If no such region is found in the service catalog, the "
+                    "first found one is used."),
     cfg.StrOpt('username',
                default='demo',
                help="Username to use for Nova API requests."),
@@ -200,6 +203,12 @@
     cfg.StrOpt('catalog_type',
                default='compute',
                help="Catalog type of the Compute service."),
+    cfg.StrOpt('region',
+               default='',
+               help="The compute region name to use. If empty, the value "
+                    "of identity.region is used instead. If no such region "
+                    "is found in the service catalog, the first found one is "
+                    "used."),
     cfg.StrOpt('path_to_private_key',
                default=None,
                help="Path to a private key file for SSH access to remote "
@@ -255,6 +264,12 @@
     cfg.StrOpt('catalog_type',
                default='image',
                help='Catalog type of the Image service.'),
+    cfg.StrOpt('region',
+               default='',
+               help="The image region name to use. If empty, the value "
+                    "of identity.region is used instead. If no such region "
+                    "is found in the service catalog, the first found one is "
+                    "used."),
     cfg.StrOpt('http_image',
                default='http://download.cirros-cloud.net/0.3.1/'
                'cirros-0.3.1-x86_64-uec.tar.gz',
@@ -275,6 +290,12 @@
     cfg.StrOpt('catalog_type',
                default='network',
                help='Catalog type of the Neutron service.'),
+    cfg.StrOpt('region',
+               default='',
+               help="The network region name to use. If empty, the value "
+                    "of identity.region is used instead. If no such region "
+                    "is found in the service catalog, the first found one is "
+                    "used."),
     cfg.StrOpt('tenant_network_cidr',
                default="10.100.0.0/16",
                help="The cidr block to allocate tenant networks from"),
@@ -315,6 +336,12 @@
     cfg.StrOpt('catalog_type',
                default='Volume',
                help="Catalog type of the Volume Service"),
+    cfg.StrOpt('region',
+               default='',
+               help="The volume region name to use. If empty, the value "
+                    "of identity.region is used instead. If no such region "
+                    "is found in the service catalog, the first found one is "
+                    "used."),
     cfg.BoolOpt('multi_backend_enabled',
                 default=False,
                 help="Runs Cinder multi-backend test (requires 2 backends)"),
@@ -349,6 +376,12 @@
     cfg.StrOpt('catalog_type',
                default='object-store',
                help="Catalog type of the Object-Storage service."),
+    cfg.StrOpt('region',
+               default='',
+               help="The object-storage region name to use. If empty, the "
+                    "value of identity.region is used instead. If no such "
+                    "region is found in the service catalog, the first found "
+                    "one is used."),
     cfg.StrOpt('container_sync_timeout',
                default=120,
                help="Number of seconds to time on waiting for a container"
@@ -380,6 +413,12 @@
     cfg.StrOpt('catalog_type',
                default='orchestration',
                help="Catalog type of the Orchestration service."),
+    cfg.StrOpt('region',
+               default='',
+               help="The orchestration region name to use. If empty, the "
+                    "value of identity.region is used instead. If no such "
+                    "region is found in the service catalog, the first found "
+                    "one is used."),
     cfg.BoolOpt('allow_tenant_isolation',
                 default=False,
                 help="Allows test cases to create/destroy tenants and "
diff --git a/tempest/services/botoclients.py b/tempest/services/botoclients.py
index 66fb7af..a689c89 100644
--- a/tempest/services/botoclients.py
+++ b/tempest/services/botoclients.py
@@ -111,7 +111,10 @@
         aws_secret = config.boto.aws_secret
         purl = urlparse.urlparse(config.boto.ec2_url)
 
-        region = boto.ec2.regioninfo.RegionInfo(name=config.identity.region,
+        region_name = config.compute.region
+        if not region_name:
+            region_name = config.identity.region
+        region = boto.ec2.regioninfo.RegionInfo(name=region_name,
                                                 endpoint=purl.hostname)
         port = purl.port
         if port is None: