Merge "Move the singleton to a common location"
diff --git a/etc/TEMPEST_README.txt b/etc/TEMPEST_README.txt
index 4f907a0..50fa688 100644
--- a/etc/TEMPEST_README.txt
+++ b/etc/TEMPEST_README.txt
@@ -2,13 +2,6 @@
 -rename the /etc/tempest.conf.sample file to tempest.conf
 -Set the fields in the file to values relevant to your system
 -Set the "authentication" value (basic or keystone_v2 currently supported)
--from the root directory of the project, run "nosetests tempest/tests" to
- run all tests
-
-TODO:
-Use virtualenv to install all needed packages. Till then, the following
-packages must be installed:
--httplib2
--testtools
--paramiko
--nose
\ No newline at end of file
+-From the root directory of the project, run "./run_tests.sh" this will
+create the venv to install the project dependencies and run nosetests tempest
+to run all the tests
diff --git a/etc/tempest.conf.sample b/etc/tempest.conf.sample
index e1196f0..43277d6 100644
--- a/etc/tempest.conf.sample
+++ b/etc/tempest.conf.sample
@@ -107,9 +107,6 @@
 # Level to log Compute API request/response details.
 log_level = ERROR
 
-# Whitebox options for compute. Whitebox options enable the
-# whitebox test cases, which look at internal Nova database state,
-# SSH into VMs to check instance state, etc.
 
 # Run live migration tests (requires 2 hosts)
 live_migration_available = false
@@ -123,6 +120,12 @@
 # are forced to skip, regardless of the extension status
 disk_config_enabled_override = true
 
+
+[whitebox]
+# Whitebox options for compute. Whitebox options enable the
+# whitebox test cases, which look at internal Nova database state,
+# SSH into VMs to check instance state, etc.
+
 # Should we run whitebox tests for Compute?
 whitebox_enabled = true
 
diff --git a/openstack-common.conf b/openstack-common.conf
index a75279f..0c9e43e 100644
--- a/openstack-common.conf
+++ b/openstack-common.conf
@@ -1,7 +1,7 @@
 [DEFAULT]
 
 # The list of modules to copy from openstack-common
-modules=setup,cfg,iniparser
+modules=setup,cfg,iniparser,install_venv_common
 
 # The base module to hold the copy of openstack.common
 base=tempest
diff --git a/setup.cfg b/setup.cfg
index c28d129..b522f3a 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,7 +1,5 @@
 [nosetests]
 # NOTE(jkoelker) To run the test suite under nose install the following
 #                coverage http://pypi.python.org/pypi/coverage
-#                tissue http://pypi.python.org/pypi/tissue (pep8 checker)
 #                openstack-nose https://github.com/openstack-dev/openstack-nose
 verbosity=2
-detailed-errors=1
diff --git a/tempest/clients.py b/tempest/clients.py
index 29e83bf..7a7ea13 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -32,7 +32,7 @@
 from tempest.services.compute.json.security_groups_client import \
     SecurityGroupsClientJSON
 from tempest.services.compute.json.keypairs_client import KeyPairsClientJSON
-from tempest.services.compute.json.quotas_client import QuotasClient
+from tempest.services.compute.json.quotas_client import QuotasClientJSON
 from tempest.services.compute.json.volumes_extensions_client import \
     VolumesExtensionsClientJSON
 from tempest.services.compute.json.console_output_client import \
@@ -44,6 +44,7 @@
 from tempest.services.compute.xml.images_client import ImagesClientXML
 from tempest.services.compute.xml.keypairs_client import KeyPairsClientXML
 from tempest.services.compute.xml.limits_client import LimitsClientXML
+from tempest.services.compute.xml.quotas_client import QuotasClientXML
 from tempest.services.compute.xml.security_groups_client \
     import SecurityGroupsClientXML
 from tempest.services.compute.xml.servers_client import ServersClientXML
@@ -79,6 +80,11 @@
     "xml": KeyPairsClientXML,
 }
 
+QUOTAS_CLIENTS = {
+    "json": QuotasClientJSON,
+    "xml": QuotasClientXML,
+}
+
 SERVERS_CLIENTS = {
     "json": ServersClientJSON,
     "xml": ServersClientXML,
@@ -180,6 +186,7 @@
             self.limits_client = LIMITS_CLIENTS[interface](*client_args)
             self.images_client = IMAGES_CLIENTS[interface](*client_args)
             self.keypairs_client = KEYPAIRS_CLIENTS[interface](*client_args)
+            self.quotas_client = QUOTAS_CLIENTS[interface](*client_args)
             self.flavors_client = FLAVORS_CLIENTS[interface](*client_args)
             ext_cli = EXTENSIONS_CLIENTS[interface](*client_args)
             self.extensions_client = ext_cli
@@ -196,7 +203,6 @@
         except KeyError:
             msg = "Unsupported interface type `%s'" % interface
             raise exceptions.InvalidConfiguration(msg)
-        self.quotas_client = QuotasClient(*client_args)
         self.network_client = NetworkClient(*client_args)
         self.account_client = AccountClient(*client_args)
         self.container_client = ContainerClient(*client_args)
diff --git a/tempest/common/rest_client.py b/tempest/common/rest_client.py
index 5710f4c..824198f 100644
--- a/tempest/common/rest_client.py
+++ b/tempest/common/rest_client.py
@@ -269,18 +269,9 @@
         if resp.status == 413:
             resp_body = self._parse_resp(resp_body)
             self._log(req_url, body, resp, resp_body)
-            if 'overLimit' in resp_body:
-                raise exceptions.OverLimit(resp_body['overLimit']['message'])
-            elif 'exceeded' in resp_body['message']:
-                raise exceptions.OverLimit(resp_body['message'])
-            elif depth < MAX_RECURSION_DEPTH:
-                delay = resp['Retry-After'] if 'Retry-After' in resp else 60
-                time.sleep(int(delay))
-                return self.request(method, url, headers, body, depth + 1)
-            else:
-                raise exceptions.RateLimitExceeded(
-                    message=resp_body['overLimitFault']['message'],
-                    details=resp_body['overLimitFault']['details'])
+            #Checking whether Absolute/Rate limit
+            return self.check_over_limit(resp_body, method, url, headers, body,
+                                         depth, wait)
 
         if resp.status in (500, 501):
             resp_body = self._parse_resp(resp_body)
@@ -309,6 +300,36 @@
 
         return resp, resp_body
 
+    def check_over_limit(self, resp_body, method, url,
+                         headers, body, depth, wait):
+        self.is_absolute_limit(resp_body['overLimit'])
+        return self.is_rate_limit_retry_max_recursion_depth(
+            resp_body['overLimit'], method, url, headers,
+            body, depth, wait)
+
+    def is_absolute_limit(self, resp_body):
+        if 'exceeded' in resp_body['message']:
+            raise exceptions.OverLimit(resp_body['message'])
+        else:
+            return
+
+    def is_rate_limit_retry_max_recursion_depth(self, resp_body, method,
+                                                url, headers, body, depth,
+                                                wait):
+        if 'retryAfter' in resp_body:
+            if depth < MAX_RECURSION_DEPTH:
+                delay = resp_body['retryAfter']
+                time.sleep(int(delay))
+                return self.request(method, url, headers=headers,
+                                    body=body,
+                                    depth=depth + 1, wait=wait)
+            else:
+                raise exceptions.RateLimitExceeded(
+                    message=resp_body['overLimitFault']['message'],
+                    details=resp_body['overLimitFault']['details'])
+        else:
+            raise exceptions.OverLimit(resp_body['message'])
+
     def wait_for_resource_deletion(self, id):
         """Waits for a resource to be deleted."""
         start_time = int(time.time())
@@ -323,7 +344,9 @@
         """
         Subclasses override with specific deletion detection.
         """
-        return False
+        message = ('"%s" does not implement is_resource_deleted'
+                   % self.__class__.__name__)
+        raise NotImplementedError(message)
 
 
 class RestClientXML(RestClient):
@@ -331,3 +354,10 @@
 
     def _parse_resp(self, body):
         return xml_to_json(etree.fromstring(body))
+
+    def check_over_limit(self, resp_body, method, url,
+                         headers, body, depth, wait):
+        self.is_absolute_limit(resp_body)
+        return self.is_rate_limit_retry_max_recursion_depth(
+            resp_body, method, url, headers,
+            body, depth, wait)
diff --git a/tempest/config.py b/tempest/config.py
index 3b425ca..f6fcd90 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -172,22 +172,6 @@
                 default=True,
                 help="If false, skip config tests regardless of the "
                      "extension status"),
-    cfg.BoolOpt('whitebox_enabled',
-                default=False,
-                help="Does the test environment support whitebox tests for "
-                     "Compute?"),
-    cfg.StrOpt('db_uri',
-               default=None,
-               help="Connection string to the database of Compute service"),
-    cfg.StrOpt('source_dir',
-               default="/opt/stack/nova",
-               help="Path of nova source directory"),
-    cfg.StrOpt('config_path',
-               default='/etc/nova/nova.conf',
-               help="Path of nova configuration file"),
-    cfg.StrOpt('bin_dir',
-               default="/usr/local/bin/",
-               help="Directory containing nova binaries such as nova-manage"),
 ]
 
 
@@ -220,6 +204,35 @@
         conf.register_opt(opt, group='compute-admin')
 
 
+whitebox_group = cfg.OptGroup(name='whitebox',
+                              title="Whitebox Options")
+
+WhiteboxGroup = [
+    cfg.BoolOpt('whitebox_enabled',
+                default=False,
+                help="Does the test environment support whitebox tests for "
+                     "Compute?"),
+    cfg.StrOpt('db_uri',
+               default=None,
+               help="Connection string to the database of Compute service"),
+    cfg.StrOpt('source_dir',
+               default="/opt/stack/nova",
+               help="Path of nova source directory"),
+    cfg.StrOpt('config_path',
+               default='/etc/nova/nova.conf',
+               help="Path of nova configuration file"),
+    cfg.StrOpt('bin_dir',
+               default="/usr/local/bin/",
+               help="Directory containing nova binaries such as nova-manage"),
+]
+
+
+def register_whitebox_opts(conf):
+    conf.register_group(whitebox_group)
+    for opt in WhiteboxGroup:
+        conf.register_opt(opt, group='whitebox')
+
+
 image_group = cfg.OptGroup(name='image',
                            title="Image Service Options")
 
@@ -407,6 +420,7 @@
 
         register_compute_opts(cfg.CONF)
         register_identity_opts(cfg.CONF)
+        register_whitebox_opts(cfg.CONF)
         register_image_opts(cfg.CONF)
         register_network_opts(cfg.CONF)
         register_volume_opts(cfg.CONF)
@@ -414,6 +428,7 @@
         register_boto_opts(cfg.CONF)
         register_compute_admin_opts(cfg.CONF)
         self.compute = cfg.CONF.compute
+        self.whitebox = cfg.CONF.whitebox
         self.identity = cfg.CONF.identity
         self.images = cfg.CONF.image
         self.network = cfg.CONF.network
diff --git a/tempest/manager.py b/tempest/manager.py
index 4137ec3..cb1e52d 100644
--- a/tempest/manager.py
+++ b/tempest/manager.py
@@ -55,7 +55,7 @@
 VolumesExtensionsClient = volumes_extensions_client.VolumesExtensionsClientJSON
 VolumesClient = volumes_client.VolumesClientJSON
 ConsoleOutputsClient = console_output_client.ConsoleOutputsClientJSON
-QuotasClient = quotas_client.QuotasClient
+QuotasClient = quotas_client.QuotasClientJSON
 
 LOG = logging.getLogger(__name__)
 
diff --git a/tempest/services/compute/admin/json/quotas_client.py b/tempest/services/compute/admin/json/quotas_client.py
index 625d4d4..0a4bd72 100644
--- a/tempest/services/compute/admin/json/quotas_client.py
+++ b/tempest/services/compute/admin/json/quotas_client.py
@@ -17,14 +17,14 @@
 
 import json
 
-from tempest.services.compute.json.quotas_client import QuotasClient
+from tempest.services.compute.json.quotas_client import QuotasClientJSON
 
 
-class AdminQuotasClient(QuotasClient):
+class AdminQuotasClientJSON(QuotasClientJSON):
 
     def __init__(self, config, username, password, auth_url, tenant_name=None):
-        super(AdminQuotasClient, self).__init__(config, username, password,
-                                                auth_url, tenant_name)
+        super(AdminQuotasClientJSON, self).__init__(config, username, password,
+                                                    auth_url, tenant_name)
 
     def update_quota_set(self, tenant_id, injected_file_content_bytes=None,
                          metadata_items=None, ram=None, floating_ips=None,
diff --git a/tempest/services/compute/admin/xml/__init__.py b/tempest/services/compute/admin/xml/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/services/compute/admin/xml/__init__.py
diff --git a/tempest/services/compute/admin/xml/quotas_client.py b/tempest/services/compute/admin/xml/quotas_client.py
new file mode 100644
index 0000000..d567a9c
--- /dev/null
+++ b/tempest/services/compute/admin/xml/quotas_client.py
@@ -0,0 +1,88 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2012 NTT Data
+# 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.
+
+import urllib
+
+from lxml import etree
+
+from tempest.common.rest_client import RestClientXML
+from tempest.services.compute.xml.common import Document
+from tempest.services.compute.xml.common import Element
+from tempest.services.compute.xml.common import xml_to_json
+from tempest.services.compute.xml.common import XMLNS_11
+from tempest.services.compute.xml.quotas_client import QuotasClientXML
+
+
+class AdminQuotasClientXML(QuotasClientXML):
+
+    def __init__(self, config, username, password, auth_url, tenant_name=None):
+        super(AdminQuotasClientXML, self).__init__(config, username, password,
+                                                   auth_url, tenant_name)
+
+    def update_quota_set(self, tenant_id, injected_file_content_bytes=None,
+                         metadata_items=None, ram=None, floating_ips=None,
+                         key_pairs=None, instances=None,
+                         security_group_rules=None, injected_files=None,
+                         cores=None, injected_file_path_bytes=None,
+                         security_groups=None):
+        """
+        Updates the tenant's quota limits for one or more resources
+        """
+        post_body = Element("quota_set",
+                            xmlns=XMLNS_11)
+
+        if injected_file_content_bytes is not None:
+            post_body.add_attr('injected_file_content_bytes',
+                               injected_file_content_bytes)
+
+        if metadata_items is not None:
+            post_body.add_attr('metadata_items', metadata_items)
+
+        if ram is not None:
+            post_body.add_attr('ram', ram)
+
+        if floating_ips is not None:
+            post_body.add_attr('floating_ips', floating_ips)
+
+        if key_pairs is not None:
+            post_body.add_attr('key_pairs', key_pairs)
+
+        if instances is not None:
+            post_body.add_attr('instances', instances)
+
+        if security_group_rules is not None:
+            post_body.add_attr('security_group_rules', security_group_rules)
+
+        if injected_files is not None:
+            post_body.add_attr('injected_files', injected_files)
+
+        if cores is not None:
+            post_body.add_attr('cores', cores)
+
+        if injected_file_path_bytes is not None:
+            post_body.add_attr('injected_file_path_bytes',
+                               injected_file_path_bytes)
+
+        if security_groups is not None:
+            post_body.add_attr('security_groups', security_groups)
+
+        resp, body = self.put('os-quota-sets/%s' % str(tenant_id),
+                              str(Document(post_body)),
+                              self.headers)
+        body = xml_to_json(etree.fromstring(body))
+        body = self._format_quota(body)
+        return resp, body
diff --git a/tempest/services/compute/json/quotas_client.py b/tempest/services/compute/json/quotas_client.py
index 543b015..a95ff1c 100644
--- a/tempest/services/compute/json/quotas_client.py
+++ b/tempest/services/compute/json/quotas_client.py
@@ -20,11 +20,11 @@
 from tempest.common.rest_client import RestClient
 
 
-class QuotasClient(RestClient):
+class QuotasClientJSON(RestClient):
 
     def __init__(self, config, username, password, auth_url, tenant_name=None):
-        super(QuotasClient, self).__init__(config, username, password,
-                                           auth_url, tenant_name)
+        super(QuotasClientJSON, self).__init__(config, username, password,
+                                               auth_url, tenant_name)
         self.service = self.config.compute.catalog_type
 
     def get_quota_set(self, tenant_id):
diff --git a/tempest/services/compute/xml/quotas_client.py b/tempest/services/compute/xml/quotas_client.py
new file mode 100644
index 0000000..8978214
--- /dev/null
+++ b/tempest/services/compute/xml/quotas_client.py
@@ -0,0 +1,58 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2012 NTT Data
+# 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.
+
+import urllib
+
+from lxml import etree
+
+from tempest.common.rest_client import RestClientXML
+from tempest.services.compute.xml.common import Document
+from tempest.services.compute.xml.common import Element
+from tempest.services.compute.xml.common import xml_to_json
+from tempest.services.compute.xml.common import XMLNS_11
+
+
+class QuotasClientXML(RestClientXML):
+
+    def __init__(self, config, username, password, auth_url, tenant_name=None):
+        super(QuotasClientXML, self).__init__(config, username, password,
+                                              auth_url, tenant_name)
+        self.service = self.config.compute.catalog_type
+
+    def _format_quota(self, q):
+        quota = {}
+        for k, v in q.items():
+            try:
+                v = int(v)
+            except ValueError:
+                pass
+
+            quota[k] = v
+
+        return quota
+
+    def _parse_array(self, node):
+        return [self._format_quota(xml_to_json(x)) for x in node]
+
+    def get_quota_set(self, tenant_id):
+        """List the quota set for a tenant."""
+
+        url = 'os-quota-sets/%s' % str(tenant_id)
+        resp, body = self.get(url, self.headers)
+        body = xml_to_json(etree.fromstring(body))
+        body = self._format_quota(body)
+        return resp, body
diff --git a/tempest/services/identity/json/identity_client.py b/tempest/services/identity/json/identity_client.py
index 9cc30d9..403a3ac 100644
--- a/tempest/services/identity/json/identity_client.py
+++ b/tempest/services/identity/json/identity_client.py
@@ -197,6 +197,12 @@
         body = json.loads(body)
         return resp, body['OS-KSADM:service']
 
+    def list_services(self):
+        """List Service - Returns Services."""
+        resp, body = self.get('/OS-KSADM/services/')
+        body = json.loads(body)
+        return resp, body['OS-KSADM:services']
+
     def delete_service(self, service_id):
         """Delete Service."""
         url = '/OS-KSADM/services/%s' % service_id
diff --git a/tempest/services/identity/xml/identity_client.py b/tempest/services/identity/xml/identity_client.py
index 02be91e..f79c3d5 100644
--- a/tempest/services/identity/xml/identity_client.py
+++ b/tempest/services/identity/xml/identity_client.py
@@ -226,6 +226,12 @@
         body = self._parse_body(etree.fromstring(body))
         return resp, body
 
+    def list_services(self):
+        """Returns services."""
+        resp, body = self.get('OS-KSADM/services', self.headers)
+        body = self._parse_array(etree.fromstring(body))
+        return resp, body
+
     def get_service(self, service_id):
         """Get Service."""
         url = '/OS-KSADM/services/%s' % service_id
diff --git a/tempest/testboto.py b/tempest/testboto.py
index 7031fe2..ab79f3c 100644
--- a/tempest/testboto.py
+++ b/tempest/testboto.py
@@ -202,8 +202,10 @@
     s3_error_code.server = ServerError()
     s3_error_code.client = ClientError()
     valid_image_state = set(('available', 'pending', 'failed'))
+    #NOTE(afazekas): 'paused' is not valid status in EC2, but it does not have
+    # a good mapping, because it uses memory, but not really a running machine
     valid_instance_state = set(('pending', 'running', 'shutting-down',
-                                'terminated', 'stopping', 'stopped'))
+                                'terminated', 'stopping', 'stopped', 'paused'))
     valid_volume_status = set(('creating', 'available', 'in-use',
                                'deleting', 'deleted', 'error'))
     valid_snapshot_status = set(('pending', 'completed', 'error'))
diff --git a/tempest/tests/compute/__init__.py b/tempest/tests/compute/__init__.py
index 08e5091..5b59a70 100644
--- a/tempest/tests/compute/__init__.py
+++ b/tempest/tests/compute/__init__.py
@@ -29,7 +29,7 @@
 CREATE_IMAGE_ENABLED = CONFIG.compute.create_image_enabled
 RESIZE_AVAILABLE = CONFIG.compute.resize_available
 CHANGE_PASSWORD_AVAILABLE = CONFIG.compute.change_password_available
-WHITEBOX_ENABLED = CONFIG.compute.whitebox_enabled
+WHITEBOX_ENABLED = CONFIG.whitebox.whitebox_enabled
 DISK_CONFIG_ENABLED = False
 DISK_CONFIG_ENABLED_OVERRIDE = CONFIG.compute.disk_config_enabled_override
 FLAVOR_EXTRA_DATA_ENABLED = False
diff --git a/tempest/tests/compute/admin/test_quotas.py b/tempest/tests/compute/admin/test_quotas.py
index 6a7a5ea..b2b515a 100644
--- a/tempest/tests/compute/admin/test_quotas.py
+++ b/tempest/tests/compute/admin/test_quotas.py
@@ -18,23 +18,21 @@
 from nose.plugins.attrib import attr
 
 from tempest import exceptions
-from tempest.services.compute.admin.json import quotas_client as adm_quotas
-from tempest.tests.compute.base import BaseComputeTest
+from tempest.services.compute.admin.json \
+    import quotas_client as adm_quotas_json
+from tempest.services.compute.admin.xml import quotas_client as adm_quotas_xml
+from tempest.tests import compute
+from tempest.tests.compute import base
 
 
-class QuotasTest(BaseComputeTest):
+class QuotasAdminTestBase(object):
 
     @classmethod
     def setUpClass(cls):
-        super(QuotasTest, cls).setUpClass()
-        c_adm_user = cls.config.compute_admin.username
-        c_adm_pass = cls.config.compute_admin.password
-        c_adm_tenant = cls.config.compute_admin.tenant_name
-        auth_url = cls.config.identity.uri
-
-        cls.adm_client = adm_quotas.AdminQuotasClient(cls.config, c_adm_user,
-                                                      c_adm_pass, auth_url,
-                                                      c_adm_tenant)
+        cls.c_adm_user = cls.config.compute_admin.username
+        cls.c_adm_pass = cls.config.compute_admin.password
+        cls.c_adm_tenant = cls.config.compute_admin.tenant_name
+        cls.auth_url = cls.config.identity.uri
         cls.client = cls.os.quotas_client
         cls.identity_admin_client = cls._get_identity_admin_client()
         resp, tenants = cls.identity_admin_client.list_tenants()
@@ -63,7 +61,6 @@
                 cls.servers_client.delete_server(server['id'])
             except exceptions.NotFound:
                 continue
-        super(QuotasTest, cls).tearDownClass()
 
     @attr(type='smoke')
     def test_get_default_quotas(self):
@@ -155,3 +152,43 @@
         finally:
             self.adm_client.update_quota_set(self.demo_tenant_id,
                                              ram=default_mem_quota)
+
+
+class QuotasAdminTestJSON(QuotasAdminTestBase, base.BaseComputeAdminTestJSON,
+                          base.BaseComputeTest):
+
+    @classmethod
+    def setUpClass(cls):
+        base.BaseComputeAdminTestJSON.setUpClass()
+        base.BaseComputeTest.setUpClass()
+        super(QuotasAdminTestJSON, cls).setUpClass()
+
+        cls.adm_client = adm_quotas_json.AdminQuotasClientJSON(
+            cls.config, cls.c_adm_user, cls.c_adm_pass,
+            cls.auth_url, cls.c_adm_tenant)
+
+    @classmethod
+    def tearDownClass(cls):
+        super(QuotasAdminTestJSON, cls).tearDownClass()
+        base.BaseComputeTest.tearDownClass()
+
+
+class QuotasAdminTestXML(QuotasAdminTestBase, base.BaseComputeAdminTestXML,
+                         base.BaseComputeTest):
+
+    @classmethod
+    def setUpClass(cls):
+        base.BaseComputeAdminTestXML.setUpClass()
+        base.BaseComputeTest.setUpClass()
+        super(QuotasAdminTestXML, cls).setUpClass()
+
+        cls.adm_client = adm_quotas_xml.AdminQuotasClientXML(cls.config,
+                                                             cls.c_adm_user,
+                                                             cls.c_adm_pass,
+                                                             cls.auth_url,
+                                                             cls.c_adm_tenant)
+
+    @classmethod
+    def tearDownClass(cls):
+        super(QuotasAdminTestXML, cls).tearDownClass()
+        base.BaseComputeTest.tearDownClass()
diff --git a/tempest/tests/compute/servers/test_server_addresses.py b/tempest/tests/compute/servers/test_server_addresses.py
index 6e819a2..6b0f7ae 100644
--- a/tempest/tests/compute/servers/test_server_addresses.py
+++ b/tempest/tests/compute/servers/test_server_addresses.py
@@ -74,9 +74,9 @@
 
         # We do not know the exact network configuration, but an instance
         # should at least have a single public or private address
-        self.assertGreaterEqual(len(addresses), 1)
+        self.assertTrue(len(addresses) >= 1)
         for network_name, network_addresses in addresses.iteritems():
-            self.assertGreaterEqual(len(network_addresses), 1)
+            self.assertTrue(len(network_addresses) >= 1)
             for address in network_addresses:
                 self.assertTrue(address['addr'])
                 self.assertTrue(address['version'])
diff --git a/tempest/tests/compute/test_quotas.py b/tempest/tests/compute/test_quotas.py
index 3dc2515..9306351 100644
--- a/tempest/tests/compute/test_quotas.py
+++ b/tempest/tests/compute/test_quotas.py
@@ -17,14 +17,13 @@
 
 from nose.plugins.attrib import attr
 
-from tempest.tests.compute.base import BaseComputeTest
+from tempest.tests.compute import base
 
 
-class QuotasTest(BaseComputeTest):
+class QuotasTestBase(object):
 
     @classmethod
     def setUpClass(cls):
-        super(QuotasTest, cls).setUpClass()
         cls.client = cls.quotas_client
         cls.admin_client = cls._get_identity_admin_client()
         resp, tenants = cls.admin_client.list_tenants()
@@ -47,3 +46,19 @@
             self.assertSequenceEqual(expected_quota_set, quota_set)
         except Exception:
             self.fail("Quota set for tenant did not have default limits")
+
+
+class QuotasTestJSON(QuotasTestBase, base.BaseComputeTestJSON):
+
+    @classmethod
+    def setUpClass(cls):
+        base.BaseComputeTestJSON.setUpClass()
+        super(QuotasTestJSON, cls).setUpClass()
+
+
+class QuotasTestXML(QuotasTestBase, base.BaseComputeTestXML):
+
+    @classmethod
+    def setUpClass(cls):
+        base.BaseComputeTestXML.setUpClass()
+        super(QuotasTestXML, cls).setUpClass()
diff --git a/tempest/tests/identity/admin/test_services.py b/tempest/tests/identity/admin/test_services.py
index 5261b9d..73f4a90 100644
--- a/tempest/tests/identity/admin/test_services.py
+++ b/tempest/tests/identity/admin/test_services.py
@@ -16,6 +16,9 @@
 #    under the License.
 
 
+from nose.plugins.attrib import attr
+import unittest2 as unittest
+
 from tempest.common.utils.data_utils import rand_name
 from tempest import exceptions
 from tempest.tests.identity import base
@@ -30,8 +33,8 @@
             name = rand_name('service-')
             type = rand_name('type--')
             description = rand_name('description-')
-            resp, service_data = \
-            self.client.create_service(name, type, description=description)
+            resp, service_data = self.client.create_service(
+                name, type, description=description)
             self.assertTrue(resp['status'].startswith('2'))
             #Verifying response body of create service
             self.assertTrue('id' in service_data)
@@ -63,6 +66,32 @@
             self.assertRaises(exceptions.NotFound, self.client.get_service,
                               service_data['id'])
 
+    def test_list_services(self):
+        # Create, List, Verify and Delete Services
+        services = []
+        for _ in xrange(3):
+            name = rand_name('service-')
+            type = rand_name('type--')
+            description = rand_name('description-')
+            resp, service = self.client.create_service(
+                name, type, description=description)
+            services.append(service)
+        service_ids = map(lambda x: x['id'], services)
+
+        # List and Verify Services
+        resp, body = self.client.list_services()
+        self.assertTrue(resp['status'].startswith('2'))
+        found = [service for service in body if service['id'] in service_ids]
+        self.assertEqual(len(found), len(services), 'Services not found')
+
+        # Delete Services
+        for service in services:
+            resp, body = self.client.delete_service(service['id'])
+            self.assertTrue(resp['status'].startswith('2'))
+        resp, body = self.client.list_services()
+        found = [service for service in body if service['id'] in service_ids]
+        self.assertFalse(any(found), 'Services failed to delete')
+
 
 class ServicesTestJSON(base.BaseIdentityAdminTestJSON, ServicesTestBase):
     @classmethod
diff --git a/tempest/tests/network/test_network_basic_ops.py b/tempest/tests/network/test_network_basic_ops.py
index bdebced..d297c98 100644
--- a/tempest/tests/network/test_network_basic_ops.py
+++ b/tempest/tests/network/test_network_basic_ops.py
@@ -186,9 +186,9 @@
         cls.check_preconditions()
         cfg = cls.config.network
         cls.tenant_id = cls.manager._get_identity_client(
-            cfg.username,
-            cfg.password,
-            cfg.tenant_name).tenant_id
+            cls.config.identity.username,
+            cls.config.identity.password,
+            cls.config.identity.tenant_name).tenant_id
         # TODO(mnewby) Consider looking up entities as needed instead
         # of storing them as collections on the class.
         cls.keypairs = {}
diff --git a/tempest/tests/object_storage/test_container_sync.py b/tempest/tests/object_storage/test_container_sync.py
index 597fd86..d612880 100644
--- a/tempest/tests/object_storage/test_container_sync.py
+++ b/tempest/tests/object_storage/test_container_sync.py
@@ -103,7 +103,7 @@
             self.assertEqual(resp['status'], '200',
                              'Error listing the destination container`s'
                              ' "%s" contents' % (self.containers[0]))
-            object_list_0 = {obj['name']: obj for obj in object_list_0}
+            object_list_0 = dict((obj['name'], obj) for obj in object_list_0)
             # get second container content
             resp, object_list_1 = \
                 cont_client[1].\
@@ -111,7 +111,7 @@
             self.assertEqual(resp['status'], '200',
                              'Error listing the destination container`s'
                              ' "%s" contents' % (self.containers[1]))
-            object_list_1 = {obj['name']: obj for obj in object_list_1}
+            object_list_1 = dict((obj['name'], obj) for obj in object_list_1)
             # check that containers is not empty and has equal keys()
             # or wait for next attepmt
             if not object_list_0 or not object_list_1 or \
diff --git a/tempest/whitebox.py b/tempest/whitebox.py
index 03ad63b..b7a1e68 100644
--- a/tempest/whitebox.py
+++ b/tempest/whitebox.py
@@ -63,9 +63,9 @@
         super(ComputeWhiteboxTest, cls).setUpClass()
 
         # Add some convenience attributes that tests use...
-        cls.nova_dir = cls.config.compute.source_dir
-        cls.compute_bin_dir = cls.config.compute.bin_dir
-        cls.compute_config_path = cls.config.compute.config_path
+        cls.nova_dir = cls.config.whitebox.source_dir
+        cls.compute_bin_dir = cls.config.whitebox.bin_dir
+        cls.compute_config_path = cls.config.whitebox.config_path
         cls.servers_client = cls.manager.servers_client
         cls.images_client = cls.manager.images_client
         cls.flavors_client = cls.manager.flavors_client
@@ -126,7 +126,7 @@
                        }
 
         try:
-            engine = create_engine(cls.config.compute.db_uri, **engine_args)
+            engine = create_engine(cls.config.whitebox.db_uri, **engine_args)
             connection = engine.connect()
             meta = MetaData()
             meta.reflect(bind=engine)
diff --git a/tools/install_venv.py b/tools/install_venv.py
index 28275ba..52dbe39 100644
--- a/tools/install_venv.py
+++ b/tools/install_venv.py
@@ -5,6 +5,7 @@
 # All Rights Reserved.
 #
 # Copyright 2010 OpenStack, LLC
+# Copyright 2013 IBM Corp.
 #
 #    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
@@ -25,123 +26,10 @@
 import subprocess
 import sys
 
-
-ROOT = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
-VENV = os.path.join(ROOT, '.venv')
-PIP_REQUIRES = os.path.join(ROOT, 'tools', 'pip-requires')
-TEST_REQUIRES = os.path.join(ROOT, 'tools', 'test-requires')
-PY_VERSION = "python%s.%s" % (sys.version_info[0], sys.version_info[1])
+import install_venv_common as install_venv
 
 
-def die(message, *args):
-    print >> sys.stderr, message % args
-    sys.exit(1)
-
-
-def check_python_version():
-    if sys.version_info < (2, 6):
-        die("Need Python Version >= 2.6")
-
-
-def run_command_with_code(cmd, redirect_output=True, check_exit_code=True):
-    """Runs a command in an out-of-process shell.
-
-    Returns the output of that command.  Working directory is ROOT.
-    """
-    if redirect_output:
-        stdout = subprocess.PIPE
-    else:
-        stdout = None
-
-    proc = subprocess.Popen(cmd, cwd=ROOT, stdout=stdout)
-    output = proc.communicate()[0]
-    if check_exit_code and proc.returncode != 0:
-        die('Command "%s" failed.\n%s', ' '.join(cmd), output)
-    return (output, proc.returncode)
-
-
-def run_command(cmd, redirect_output=True, check_exit_code=True):
-    return run_command_with_code(cmd, redirect_output, check_exit_code)[0]
-
-
-class Distro(object):
-
-    def check_cmd(self, cmd):
-        return bool(run_command(['which', cmd], check_exit_code=False).strip())
-
-    def install_virtualenv(self):
-        if self.check_cmd('virtualenv'):
-            return
-
-        if self.check_cmd('easy_install'):
-            print 'Installing virtualenv via easy_install...',
-            if run_command(['easy_install', 'virtualenv']):
-                print 'Succeeded'
-                return
-            else:
-                print 'Failed'
-
-        die('ERROR: virtualenv not found.\n\nTempest development'
-            ' requires virtualenv, please install it using your'
-            ' favorite package management tool')
-
-    def post_process(self):
-        """Any distribution-specific post-processing gets done here.
-
-        In particular, this is useful for applying patches to code inside
-        the venv.
-        """
-        pass
-
-
-class Fedora(Distro):
-    """This covers Fedora-based distributions.
-
-    Includes: Fedora, RHEL, Scientific Linux"""
-
-    def check_pkg(self, pkg):
-        return run_command_with_code(['rpm', '-q', pkg],
-                                     check_exit_code=False)[1] == 0
-
-    def yum_install(self, pkg, **kwargs):
-        print "Attempting to install '%s' via yum" % pkg
-        run_command(['sudo', 'yum', 'install', '-y', pkg], **kwargs)
-
-    def apply_patch(self, originalfile, patchfile):
-        run_command(['patch', originalfile, patchfile])
-
-    def install_virtualenv(self):
-        if self.check_cmd('virtualenv'):
-            return
-
-        if not self.check_pkg('python-virtualenv'):
-            self.yum_install('python-virtualenv', check_exit_code=False)
-
-        super(Fedora, self).install_virtualenv()
-
-    def post_process(self):
-        """Workaround for a bug in eventlet.
-
-        This currently affects RHEL6.1, but the fix can safely be
-        applied to all RHEL and Fedora distributions.
-
-        This can be removed when the fix is applied upstream.
-
-        Nova: https://bugs.launchpad.net/nova/+bug/884915
-        Upstream: https://bitbucket.org/which_linden/eventlet/issue/89
-        """
-
-        # Install "patch" program if it's not there
-        if not self.check_pkg('patch'):
-            self.yum_install('patch')
-
-        # Apply the eventlet patch
-        self.apply_patch(os.path.join(VENV, 'lib', PY_VERSION, 'site-packages',
-                                      'eventlet/green/subprocess.py'),
-                         'contrib/redhat-eventlet.patch')
-
-
-class CentOS(Fedora):
+class CentOS(install_venv.Fedora):
     """This covers CentOS."""
 
     def post_process(self):
@@ -149,73 +37,6 @@
             self.yum.install('openssl-devel', check_exit_code=False)
 
 
-def get_distro():
-    if os.path.exists('/etc/redhat-release'):
-        with open('/etc/redhat-release') as rh_release:
-            if 'CentOS' in rh_release.read():
-                return CentOS()
-        return Fedora()
-
-    if os.path.exists('/etc/fedora-release'):
-        return Fedora()
-
-    return Distro()
-
-
-def check_dependencies():
-    get_distro().install_virtualenv()
-
-
-def create_virtualenv(venv=VENV, no_site_packages=True):
-    """Creates the virtual environment and installs PIP.
-
-    Creates the virtual environment and installs PIP only into the
-    virtual environment.
-    """
-    print 'Creating venv...',
-    if no_site_packages:
-        run_command(['virtualenv', '-q', '--no-site-packages', VENV])
-    else:
-        run_command(['virtualenv', '-q', VENV])
-    print 'done.'
-    print 'Installing pip in virtualenv...',
-    if not run_command(['tools/with_venv.sh', 'easy_install',
-                        'pip>1.0']).strip():
-        die("Failed to install pip.")
-    print 'done.'
-
-
-def pip_install(*args):
-    run_command(['tools/with_venv.sh',
-                 'pip', 'install', '--upgrade'] + list(args),
-                redirect_output=False)
-
-
-def install_dependencies(venv=VENV):
-    print 'Installing dependencies with pip (this can take a while)...'
-
-    # First things first, make sure our venv has the latest pip and distribute.
-    # NOTE: we keep pip at version 1.1 since the most recent version causes
-    # the .venv creation to fail. See:
-    # https://bugs.launchpad.net/nova/+bug/1047120
-    pip_install('pip==1.1')
-    pip_install('distribute')
-
-    # Install greenlet by hand - just listing it in the requires file does not
-    # get it in stalled in the right order
-    pip_install('greenlet')
-
-    pip_install('-r', PIP_REQUIRES)
-    pip_install('-r', TEST_REQUIRES)
-
-    # Install tempest into the virtual_env. No more path munging!
-    run_command([os.path.join(venv, 'bin/python'), 'setup.py', 'develop'])
-
-
-def post_process():
-    get_distro().post_process()
-
-
 def print_help():
     help = """
     Tempest development environment setup is complete.
@@ -238,23 +59,25 @@
     print help
 
 
-def parse_args():
-    """Parses command-line arguments."""
-    parser = optparse.OptionParser()
-    parser.add_option("-n", "--no-site-packages", dest="no_site_packages",
-                      default=False, action="store_true",
-                      help="Do not inherit packages from global Python"
-                           " install")
-    return parser.parse_args()
-
-
 def main(argv):
-    (options, args) = parse_args()
-    check_python_version()
-    check_dependencies()
-    create_virtualenv(no_site_packages=options.no_site_packages)
-    install_dependencies()
-    post_process()
+    root = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
+    venv = os.path.join(root, '.venv')
+    pip_requires = os.path.join(root, 'tools', 'pip-requires')
+    test_requires = os.path.join(root, 'tools', 'test-requires')
+    py_version = "python%s.%s" % (sys.version_info[0], sys.version_info[1])
+    project = 'Tempest'
+    install = install_venv.InstallVenv(root, venv, pip_requires, test_requires,
+                                       py_version, project)
+    if os.path.exists('/etc/redhat-release'):
+        with open('/etc/redhat-release') as rh_release:
+            if 'CentOS' in rh_release.read():
+                install_venv.Fedora = CentOS
+    options = install.parse_args(argv)
+    install.check_python_version()
+    install.check_dependencies()
+    install.create_virtualenv(no_site_packages=options.no_site_packages)
+    install.install_dependencies()
+    install.post_process()
     print_help()
 
 if __name__ == '__main__':
diff --git a/tools/install_venv_common.py b/tools/install_venv_common.py
new file mode 100644
index 0000000..3dbcafe
--- /dev/null
+++ b/tools/install_venv_common.py
@@ -0,0 +1,225 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 OpenStack, LLC
+# Copyright 2013 IBM Corp.
+#
+#    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.
+
+"""Provides methods needed by installation script for OpenStack development
+virtual environments.
+
+Synced in from openstack-common
+"""
+
+import os
+import subprocess
+import sys
+
+from tempest.openstack.common import cfg
+
+
+class InstallVenv(object):
+
+    def __init__(self, root, venv, pip_requires, test_requires, py_version,
+                 project):
+        self.root = root
+        self.venv = venv
+        self.pip_requires = pip_requires
+        self.test_requires = test_requires
+        self.py_version = py_version
+        self.project = project
+
+    def die(self, message, *args):
+        print >> sys.stderr, message % args
+        sys.exit(1)
+
+    def check_python_version(self):
+        if sys.version_info < (2, 6):
+            self.die("Need Python Version >= 2.6")
+
+    def run_command_with_code(self, cmd, redirect_output=True,
+                              check_exit_code=True):
+        """Runs a command in an out-of-process shell.
+
+        Returns the output of that command. Working directory is ROOT.
+        """
+        if redirect_output:
+            stdout = subprocess.PIPE
+        else:
+            stdout = None
+
+        proc = subprocess.Popen(cmd, cwd=self.root, stdout=stdout)
+        output = proc.communicate()[0]
+        if check_exit_code and proc.returncode != 0:
+            self.die('Command "%s" failed.\n%s', ' '.join(cmd), output)
+        return (output, proc.returncode)
+
+    def run_command(self, cmd, redirect_output=True, check_exit_code=True):
+        return self.run_command_with_code(cmd, redirect_output,
+                                          check_exit_code)[0]
+
+    def get_distro(self):
+        if (os.path.exists('/etc/fedora-release') or
+                os.path.exists('/etc/redhat-release')):
+            return Fedora(self.root, self.venv, self.pip_requires,
+                          self.test_requires, self.py_version, self.project)
+        else:
+            return Distro(self.root, self.venv, self.pip_requires,
+                          self.test_requires, self.py_version, self.project)
+
+    def check_dependencies(self):
+        self.get_distro().install_virtualenv()
+
+    def create_virtualenv(self, no_site_packages=True):
+        """Creates the virtual environment and installs PIP.
+
+        Creates the virtual environment and installs PIP only into the
+        virtual environment.
+        """
+        if not os.path.isdir(self.venv):
+            print 'Creating venv...',
+            if no_site_packages:
+                self.run_command(['virtualenv', '-q', '--no-site-packages',
+                                 self.venv])
+            else:
+                self.run_command(['virtualenv', '-q', self.venv])
+            print 'done.'
+            print 'Installing pip in virtualenv...',
+            if not self.run_command(['tools/with_venv.sh', 'easy_install',
+                                    'pip>1.0']).strip():
+                self.die("Failed to install pip.")
+            print 'done.'
+        else:
+            print "venv already exists..."
+            pass
+
+    def pip_install(self, *args):
+        self.run_command(['tools/with_venv.sh',
+                         'pip', 'install', '--upgrade'] + list(args),
+                         redirect_output=False)
+
+    def install_dependencies(self):
+        print 'Installing dependencies with pip (this can take a while)...'
+
+        # First things first, make sure our venv has the latest pip and
+        # distribute.
+        # NOTE: we keep pip at version 1.1 since the most recent version causes
+        # the .venv creation to fail. See:
+        # https://bugs.launchpad.net/nova/+bug/1047120
+        self.pip_install('pip==1.1')
+        self.pip_install('distribute')
+
+        # Install greenlet by hand - just listing it in the requires file does
+        # not
+        # get it installed in the right order
+        self.pip_install('greenlet')
+
+        self.pip_install('-r', self.pip_requires)
+        self.pip_install('-r', self.test_requires)
+
+    def post_process(self):
+        self.get_distro().post_process()
+
+    def parse_args(self, argv):
+        """Parses command-line arguments."""
+        cli_opts = [
+            cfg.BoolOpt('no-site-packages',
+                        default=False,
+                        short='n',
+                        help="Do not inherit packages from global Python"
+                             "install"),
+        ]
+        CLI = cfg.ConfigOpts()
+        CLI.register_cli_opts(cli_opts)
+        CLI(argv[1:])
+        return CLI
+
+
+class Distro(InstallVenv):
+
+    def check_cmd(self, cmd):
+        return bool(self.run_command(['which', cmd],
+                    check_exit_code=False).strip())
+
+    def install_virtualenv(self):
+        if self.check_cmd('virtualenv'):
+            return
+
+        if self.check_cmd('easy_install'):
+            print 'Installing virtualenv via easy_install...',
+            if self.run_command(['easy_install', 'virtualenv']):
+                print 'Succeeded'
+                return
+            else:
+                print 'Failed'
+
+        self.die('ERROR: virtualenv not found.\n\n%s development'
+                 ' requires virtualenv, please install it using your'
+                 ' favorite package management tool' % self.project)
+
+    def post_process(self):
+        """Any distribution-specific post-processing gets done here.
+
+        In particular, this is useful for applying patches to code inside
+        the venv.
+        """
+        pass
+
+
+class Fedora(Distro):
+    """This covers all Fedora-based distributions.
+
+    Includes: Fedora, RHEL, CentOS, Scientific Linux
+    """
+
+    def check_pkg(self, pkg):
+        return self.run_command_with_code(['rpm', '-q', pkg],
+                                          check_exit_code=False)[1] == 0
+
+    def yum_install(self, pkg, **kwargs):
+        print "Attempting to install '%s' via yum" % pkg
+        self.run_command(['sudo', 'yum', 'install', '-y', pkg], **kwargs)
+
+    def apply_patch(self, originalfile, patchfile):
+        self.run_command(['patch', originalfile, patchfile])
+
+    def install_virtualenv(self):
+        if self.check_cmd('virtualenv'):
+            return
+
+        if not self.check_pkg('python-virtualenv'):
+            self.yum_install('python-virtualenv', check_exit_code=False)
+
+        super(Fedora, self).install_virtualenv()
+
+    def post_process(self):
+        """Workaround for a bug in eventlet.
+
+        This currently affects RHEL6.1, but the fix can safely be
+        applied to all RHEL and Fedora distributions.
+
+        This can be removed when the fix is applied upstream.
+
+        Nova: https://bugs.launchpad.net/nova/+bug/884915
+        Upstream: https://bitbucket.org/which_linden/eventlet/issue/89
+        """
+
+        # Install "patch" program if it's not there
+        if not self.check_pkg('patch'):
+            self.yum_install('patch')
+
+        # Apply the eventlet patch
+        self.apply_patch(os.path.join(self.venv, 'lib', self.py_version,
+                                      'site-packages',
+                                      'eventlet/green/subprocess.py'),
+                         'contrib/redhat-eventlet.patch')