Merge "Add config file flag to javelin"
diff --git a/requirements.txt b/requirements.txt
index ab2903a..9a3b74d 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -7,11 +7,12 @@
 boto>=2.12.0,!=2.13.0
 paramiko>=1.13.0
 netaddr>=0.7.6
-python-glanceclient>=0.9.0
+python-ceilometerclient>=1.0.6
+python-glanceclient>=0.13.1
 python-keystoneclient>=0.9.0
 python-novaclient>=2.17.0
-python-neutronclient>=2.3.4,<3
-python-cinderclient>=1.0.6
+python-neutronclient>=2.3.5,<3
+python-cinderclient>=1.0.7
 python-heatclient>=0.2.9
 python-ironicclient
 python-saharaclient>=0.6.0
diff --git a/tempest/api/compute/images/test_image_metadata.py b/tempest/api/compute/images/test_image_metadata.py
index 91eb4c5..9036726 100644
--- a/tempest/api/compute/images/test_image_metadata.py
+++ b/tempest/api/compute/images/test_image_metadata.py
@@ -13,6 +13,8 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+import StringIO
+
 from tempest.api.compute import base
 from tempest.common.utils import data_utils
 from tempest import config
@@ -31,25 +33,21 @@
             skip_msg = ("%s skipped as glance is not available" % cls.__name__)
             raise cls.skipException(skip_msg)
 
+        cls.glance_client = cls.os.image_client
         cls.client = cls.images_client
         cls.image_id = None
 
-        resp, server = cls.create_test_server(wait_until='ACTIVE')
-        cls.server_id = server['id']
-
-        # Snapshot the server once to save time
         name = data_utils.rand_name('image')
-        resp, _ = cls.client.create_image(cls.server_id, name, {})
-        cls.image_id = resp['location'].rsplit('/', 1)[1]
-
+        resp, body = cls.glance_client.create_image(name=name,
+                                                    container_format='bare',
+                                                    disk_format='raw',
+                                                    is_public=False)
+        cls.image_id = body['id']
+        cls.images.append(cls.image_id)
+        image_file = StringIO.StringIO(('*' * 1024))
+        cls.glance_client.update_image(cls.image_id, data=image_file)
         cls.client.wait_for_image_status(cls.image_id, 'ACTIVE')
 
-    @classmethod
-    def tearDownClass(cls):
-        if cls.image_id:
-            cls.client.delete_image(cls.image_id)
-        super(ImagesMetadataTestJSON, cls).tearDownClass()
-
     def setUp(self):
         super(ImagesMetadataTestJSON, self).setUp()
         meta = {'key1': 'value1', 'key2': 'value2'}
diff --git a/tempest/api/compute/images/test_list_image_filters.py b/tempest/api/compute/images/test_list_image_filters.py
index 86ee4a4..f9350e1 100644
--- a/tempest/api/compute/images/test_list_image_filters.py
+++ b/tempest/api/compute/images/test_list_image_filters.py
@@ -13,7 +13,11 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+import StringIO
+import time
+
 from tempest.api.compute import base
+from tempest.common.utils import data_utils
 from tempest import config
 from tempest.openstack.common import log as logging
 from tempest import test
@@ -32,7 +36,34 @@
             skip_msg = ("%s skipped as glance is not available" % cls.__name__)
             raise cls.skipException(skip_msg)
         cls.client = cls.images_client
+        cls.glance_client = cls.os.image_client
 
+        def _create_image():
+            name = data_utils.rand_name('image')
+            _, body = cls.glance_client.create_image(name=name,
+                                                     container_format='bare',
+                                                     disk_format='raw',
+                                                     is_public=False)
+            image_id = body['id']
+            cls.images.append(image_id)
+            # Wait 1 second between creation and upload to ensure a delta
+            # between created_at and updated_at.
+            time.sleep(1)
+            image_file = StringIO.StringIO(('*' * 1024))
+            cls.glance_client.update_image(image_id, data=image_file)
+            cls.client.wait_for_image_status(image_id, 'ACTIVE')
+            _, body = cls.client.get_image(image_id)
+            return body
+
+        # Create non-snapshot images via glance
+        cls.image1 = _create_image()
+        cls.image1_id = cls.image1['id']
+        cls.image2 = _create_image()
+        cls.image2_id = cls.image2['id']
+        cls.image3 = _create_image()
+        cls.image3_id = cls.image3['id']
+
+        # Create instances and snapshots via nova
         try:
             resp, cls.server1 = cls.create_test_server()
             resp, cls.server2 = cls.create_test_server(wait_until='ACTIVE')
@@ -41,21 +72,21 @@
                                                       'ACTIVE')
 
             # Create images to be used in the filter tests
-            resp, cls.image1 = cls.create_image_from_server(
+            resp, cls.snapshot1 = cls.create_image_from_server(
                 cls.server1['id'], wait_until='ACTIVE')
-            cls.image1_id = cls.image1['id']
+            cls.snapshot1_id = cls.snapshot1['id']
 
             # Servers have a hidden property for when they are being imaged
             # Performing back-to-back create image calls on a single
             # server will sometimes cause failures
-            resp, cls.image3 = cls.create_image_from_server(
+            resp, cls.snapshot3 = cls.create_image_from_server(
                 cls.server2['id'], wait_until='ACTIVE')
-            cls.image3_id = cls.image3['id']
+            cls.snapshot3_id = cls.snapshot3['id']
 
             # Wait for the server to be active after the image upload
-            resp, cls.image2 = cls.create_image_from_server(
+            resp, cls.snapshot2 = cls.create_image_from_server(
                 cls.server1['id'], wait_until='ACTIVE')
-            cls.image2_id = cls.image2['id']
+            cls.snapshot2_id = cls.snapshot2['id']
         except Exception:
             LOG.exception('setUpClass failed')
             cls.tearDownClass()
@@ -89,11 +120,14 @@
         params = {'server': self.server1['id']}
         resp, images = self.client.list_images(params)
 
-        self.assertTrue(any([i for i in images if i['id'] == self.image1_id]),
+        self.assertTrue(any([i for i in images
+                             if i['id'] == self.snapshot1_id]),
                         "Failed to find image %s in images. Got images %s" %
                         (self.image1_id, images))
-        self.assertTrue(any([i for i in images if i['id'] == self.image2_id]))
-        self.assertFalse(any([i for i in images if i['id'] == self.image3_id]))
+        self.assertTrue(any([i for i in images
+                             if i['id'] == self.snapshot2_id]))
+        self.assertFalse(any([i for i in images
+                              if i['id'] == self.snapshot3_id]))
 
     @test.attr(type='gate')
     def test_list_images_filter_by_server_ref(self):
@@ -106,11 +140,11 @@
             resp, images = self.client.list_images(params)
 
             self.assertFalse(any([i for i in images
-                                  if i['id'] == self.image1_id]))
+                                  if i['id'] == self.snapshot1_id]))
             self.assertFalse(any([i for i in images
-                                  if i['id'] == self.image2_id]))
+                                  if i['id'] == self.snapshot2_id]))
             self.assertTrue(any([i for i in images
-                                 if i['id'] == self.image3_id]))
+                                 if i['id'] == self.snapshot3_id]))
 
     @test.attr(type='gate')
     def test_list_images_filter_by_type(self):
@@ -118,10 +152,14 @@
         params = {'type': 'snapshot'}
         resp, images = self.client.list_images(params)
 
-        self.assertTrue(any([i for i in images if i['id'] == self.image1_id]))
-        self.assertTrue(any([i for i in images if i['id'] == self.image2_id]))
-        self.assertTrue(any([i for i in images if i['id'] == self.image3_id]))
-        self.assertFalse(any([i for i in images if i['id'] == self.image_ref]))
+        self.assertTrue(any([i for i in images
+                             if i['id'] == self.snapshot1_id]))
+        self.assertTrue(any([i for i in images
+                             if i['id'] == self.snapshot2_id]))
+        self.assertTrue(any([i for i in images
+                             if i['id'] == self.snapshot3_id]))
+        self.assertFalse(any([i for i in images
+                              if i['id'] == self.image_ref]))
 
     @test.attr(type='gate')
     def test_list_images_limit_results(self):
@@ -184,11 +222,11 @@
             resp, images = self.client.list_images_with_detail(params)
 
             self.assertFalse(any([i for i in images
-                                  if i['id'] == self.image1_id]))
+                                  if i['id'] == self.snapshot1_id]))
             self.assertFalse(any([i for i in images
-                                  if i['id'] == self.image2_id]))
+                                  if i['id'] == self.snapshot2_id]))
             self.assertTrue(any([i for i in images
-                                 if i['id'] == self.image3_id]))
+                                 if i['id'] == self.snapshot3_id]))
 
     @test.attr(type='gate')
     def test_list_images_with_detail_filter_by_type(self):
@@ -197,10 +235,14 @@
         resp, images = self.client.list_images_with_detail(params)
         resp, image4 = self.client.get_image(self.image_ref)
 
-        self.assertTrue(any([i for i in images if i['id'] == self.image1_id]))
-        self.assertTrue(any([i for i in images if i['id'] == self.image2_id]))
-        self.assertTrue(any([i for i in images if i['id'] == self.image3_id]))
-        self.assertFalse(any([i for i in images if i['id'] == self.image_ref]))
+        self.assertTrue(any([i for i in images
+                             if i['id'] == self.snapshot1_id]))
+        self.assertTrue(any([i for i in images
+                             if i['id'] == self.snapshot2_id]))
+        self.assertTrue(any([i for i in images
+                             if i['id'] == self.snapshot3_id]))
+        self.assertFalse(any([i for i in images
+                              if i['id'] == self.image_ref]))
 
     @test.attr(type='gate')
     def test_list_images_with_detail_filter_by_changes_since(self):
diff --git a/tempest/api/identity/admin/v3/test_endpoints.py b/tempest/api/identity/admin/v3/test_endpoints.py
index 1548f89..6beb8f2 100644
--- a/tempest/api/identity/admin/v3/test_endpoints.py
+++ b/tempest/api/identity/admin/v3/test_endpoints.py
@@ -40,7 +40,7 @@
         cls.setup_endpoints = list()
         for i in range(2):
             region = data_utils.rand_name('region')
-            url = data_utils.rand_name('url')
+            url = data_utils.rand_url()
             interface = 'public'
             resp, endpoint = cls.client.create_endpoint(
                 cls.service_id, interface, url, region=region, enabled=True)
@@ -69,7 +69,7 @@
     @test.attr(type='gate')
     def test_create_list_delete_endpoint(self):
         region = data_utils.rand_name('region')
-        url = data_utils.rand_name('url')
+        url = data_utils.rand_url()
         interface = 'public'
         resp, endpoint =\
             self.client.create_endpoint(self.service_id, interface, url,
@@ -97,7 +97,7 @@
         # Creating an endpoint so as to check update endpoint
         # with new values
         region1 = data_utils.rand_name('region')
-        url1 = data_utils.rand_name('url')
+        url1 = data_utils.rand_url()
         interface1 = 'public'
         resp, endpoint_for_update =\
             self.client.create_endpoint(self.service_id, interface1,
@@ -114,7 +114,7 @@
         self.service_ids.append(service2['id'])
         # Updating endpoint with new values
         region2 = data_utils.rand_name('region')
-        url2 = data_utils.rand_name('url')
+        url2 = data_utils.rand_url()
         interface2 = 'internal'
         resp, endpoint = \
             self.client.update_endpoint(endpoint_for_update['id'],
diff --git a/tempest/api/identity/admin/v3/test_endpoints_negative.py b/tempest/api/identity/admin/v3/test_endpoints_negative.py
index 1d63cce..d728b1d 100644
--- a/tempest/api/identity/admin/v3/test_endpoints_negative.py
+++ b/tempest/api/identity/admin/v3/test_endpoints_negative.py
@@ -49,7 +49,7 @@
     def test_create_with_enabled_False(self):
         # Enabled should be a boolean, not a string like 'False'
         interface = 'public'
-        url = data_utils.rand_name('url')
+        url = data_utils.rand_url()
         region = data_utils.rand_name('region')
         self.assertRaises(exceptions.BadRequest, self.client.create_endpoint,
                           self.service_id, interface, url, region=region,
@@ -59,7 +59,7 @@
     def test_create_with_enabled_True(self):
         # Enabled should be a boolean, not a string like 'True'
         interface = 'public'
-        url = data_utils.rand_name('url')
+        url = data_utils.rand_url()
         region = data_utils.rand_name('region')
         self.assertRaises(exceptions.BadRequest, self.client.create_endpoint,
                           self.service_id, interface, url, region=region,
@@ -69,7 +69,7 @@
 
         # Create an endpoint
         region1 = data_utils.rand_name('region')
-        url1 = data_utils.rand_name('url')
+        url1 = data_utils.rand_url()
         interface1 = 'public'
         resp, endpoint_for_update = (
             self.client.create_endpoint(self.service_id, interface1,
diff --git a/tempest/api/identity/admin/v3/test_list_users.py b/tempest/api/identity/admin/v3/test_list_users.py
new file mode 100644
index 0000000..497c5ea
--- /dev/null
+++ b/tempest/api/identity/admin/v3/test_list_users.py
@@ -0,0 +1,100 @@
+# Copyright 2014 Hewlett-Packard 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.api.identity import base
+from tempest.common.utils import data_utils
+from tempest import test
+
+
+class UsersV3TestJSON(base.BaseIdentityV3AdminTest):
+    _interface = 'json'
+
+    def _list_users_with_params(self, params, key, expected, not_expected):
+        # Helper method to list users filtered with params and
+        # assert the response based on expected and not_expected
+        # expected: user expected in the list response
+        # not_expected: user, which should not be present in list response
+        _, body = self.client.get_users(params)
+        self.assertIn(expected[key], map(lambda x: x[key], body))
+        self.assertNotIn(not_expected[key],
+                         map(lambda x: x[key], body))
+
+    @classmethod
+    def setUpClass(cls):
+        super(UsersV3TestJSON, cls).setUpClass()
+        alt_user = data_utils.rand_name('test_user')
+        alt_password = data_utils.rand_name('pass')
+        cls.alt_email = alt_user + '@testmail.tm'
+        cls.data.setup_test_domain()
+        # Create user with Domain
+        u1_name = data_utils.rand_name('test_user')
+        _, cls.domain_enabled_user = cls.client.create_user(
+            u1_name, password=alt_password,
+            email=cls.alt_email, domain_id=cls.data.domain['id'])
+        cls.data.v3_users.append(cls.domain_enabled_user)
+        # Create default not enabled user
+        u2_name = data_utils.rand_name('test_user')
+        _, cls.non_domain_enabled_user = cls.client.create_user(
+            u2_name, password=alt_password,
+            email=cls.alt_email, enabled=False)
+        cls.data.v3_users.append(cls.non_domain_enabled_user)
+
+    @test.attr(type='gate')
+    def test_list_user_domains(self):
+        # List users with domain
+        params = {'domain_id': self.data.domain['id']}
+        self._list_users_with_params(params, 'domain_id',
+                                     self.domain_enabled_user,
+                                     self.non_domain_enabled_user)
+
+    @test.attr(type='gate')
+    def test_list_users_with_not_enabled(self):
+        # List the users with not enabled
+        params = {'enabled': False}
+        self._list_users_with_params(params, 'enabled',
+                                     self.non_domain_enabled_user,
+                                     self.domain_enabled_user)
+
+    @test.attr(type='gate')
+    def test_list_users_with_name(self):
+        # List users with name
+        params = {'name': self.domain_enabled_user['name']}
+        self._list_users_with_params(params, 'name',
+                                     self.domain_enabled_user,
+                                     self.non_domain_enabled_user)
+
+    @test.attr(type='gate')
+    def test_list_users(self):
+        # List users
+        _, body = self.client.get_users()
+        fetched_ids = [u['id'] for u in body]
+        missing_users = [u['id'] for u in self.data.v3_users
+                         if u['id'] not in fetched_ids]
+        self.assertEqual(0, len(missing_users),
+                         "Failed to find user %s in fetched list" %
+                         ', '.join(m_user for m_user in missing_users))
+
+    @test.attr(type='gate')
+    def test_get_user(self):
+        # Get a user detail
+        _, user = self.client.get_user(self.data.v3_users[0]['id'])
+        self.assertEqual(self.data.v3_users[0]['id'], user['id'])
+        self.assertEqual(self.data.v3_users[0]['name'], user['name'])
+        self.assertEqual(self.alt_email, user['email'])
+        self.assertEqual(self.data.domain['id'], user['domain_id'])
+
+
+class UsersV3TestXML(UsersV3TestJSON):
+    _interface = 'xml'
diff --git a/tempest/api/identity/base.py b/tempest/api/identity/base.py
index 0991576..8eb7d33 100644
--- a/tempest/api/identity/base.py
+++ b/tempest/api/identity/base.py
@@ -121,6 +121,7 @@
             self.v3_users = []
             self.projects = []
             self.v3_roles = []
+            self.domains = []
 
         @property
         def test_credentials(self):
@@ -185,6 +186,15 @@
             _, self.v3_role = self.client.create_role(self.test_role)
             self.v3_roles.append(self.v3_role)
 
+        def setup_test_domain(self):
+            """Set up a test domain."""
+            self.test_domain = data_utils.rand_name('test_domain')
+            self.test_description = data_utils.rand_name('desc')
+            _, self.domain = self.client.create_domain(
+                name=self.test_domain,
+                description=self.test_description)
+            self.domains.append(self.domain)
+
         def teardown_all(self):
             for user in self.users:
                 self.client.delete_user(user['id'])
@@ -198,3 +208,6 @@
                 self.client.delete_project(v3_project['id'])
             for v3_role in self.v3_roles:
                 self.client.delete_role(v3_role['id'])
+            for domain in self.domains:
+                self.client.update_domain(domain['id'], enabled=False)
+                self.client.delete_domain(domain['id'])
diff --git a/tempest/api_schema/compute/v2/volumes.py b/tempest/api_schema/compute/v2/volumes.py
index 1af951f..541d3ff 100644
--- a/tempest/api_schema/compute/v2/volumes.py
+++ b/tempest/api_schema/compute/v2/volumes.py
@@ -26,7 +26,7 @@
                     'availabilityZone': {'type': 'string'},
                     'createdAt': {'type': 'string'},
                     'displayDescription': {'type': ['string', 'null']},
-                    'volumeType': {'type': 'string'},
+                    'volumeType': {'type': ['string', 'null']},
                     'snapshotId': {'type': ['string', 'null']},
                     'metadata': {'type': 'object'},
                     'size': {'type': 'integer'},
@@ -74,7 +74,7 @@
                         'availabilityZone': {'type': 'string'},
                         'createdAt': {'type': 'string'},
                         'displayDescription': {'type': ['string', 'null']},
-                        'volumeType': {'type': 'string'},
+                        'volumeType': {'type': ['string', 'null']},
                         'snapshotId': {'type': ['string', 'null']},
                         'metadata': {'type': 'object'},
                         'size': {'type': 'integer'},
diff --git a/tempest/cli/simple_read_only/test_glance.py b/tempest/cli/simple_read_only/test_glance.py
index 9869483..6b3f763 100644
--- a/tempest/cli/simple_read_only/test_glance.py
+++ b/tempest/cli/simple_read_only/test_glance.py
@@ -76,7 +76,7 @@
         commands = set(commands)
         wanted_commands = set(('image-create', 'image-delete', 'help',
                                'image-download', 'image-show', 'image-update',
-                               'member-add', 'member-create', 'member-delete',
+                               'member-create', 'member-delete',
                                'member-list'))
         self.assertFalse(wanted_commands - commands)
 
diff --git a/tempest/common/utils/data_utils.py b/tempest/common/utils/data_utils.py
index a0a88dd..174e557 100644
--- a/tempest/common/utils/data_utils.py
+++ b/tempest/common/utils/data_utils.py
@@ -34,6 +34,11 @@
         return randbits
 
 
+def rand_url():
+    randbits = str(random.randint(1, 0x7fffffff))
+    return 'https://url-' + randbits + '.com'
+
+
 def rand_int_id(start=0, end=0x7fffffff):
     return random.randint(start, end)
 
diff --git a/tempest/common/waiters.py b/tempest/common/waiters.py
index d8474a0..d242c14 100644
--- a/tempest/common/waiters.py
+++ b/tempest/common/waiters.py
@@ -22,16 +22,6 @@
 LOG = logging.getLogger(__name__)
 
 
-def _console_dump(client, server_id):
-    try:
-        resp, output = client.get_console_output(server_id, None)
-        LOG.debug("Console Output for Server %s:\n%s" % (
-            server_id, output))
-    except exceptions.NotFound:
-        LOG.debug("Server %s: doesn't have a console" % server_id)
-        pass
-
-
 # NOTE(afazekas): This function needs to know a token and a subject.
 def wait_for_server_status(client, server_id, status, ready_wait=True,
                            extra_timeout=0, raise_on_error=True):
@@ -81,10 +71,12 @@
                      '/'.join((old_status, str(old_task_state))),
                      '/'.join((server_status, str(task_state))),
                      time.time() - start_time)
-
         if (server_status == 'ERROR') and raise_on_error:
-            _console_dump(client, server_id)
-            raise exceptions.BuildErrorException(server_id=server_id)
+            if 'fault' in body:
+                raise exceptions.BuildErrorException(body['fault'],
+                                                    server_id=server_id)
+            else:
+                raise exceptions.BuildErrorException(server_id=server_id)
 
         timed_out = int(time.time()) - start_time >= timeout
 
@@ -99,11 +91,9 @@
                         'timeout': timeout})
             message += ' Current status: %s.' % server_status
             message += ' Current task state: %s.' % task_state
-
             caller = misc_utils.find_test_caller()
             if caller:
                 message = '(%s) %s' % (caller, message)
-            _console_dump(client, server_id)
             raise exceptions.TimeoutException(message)
         old_status = server_status
         old_task_state = task_state
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
index ca79325..aa24c31 100644
--- a/tempest/scenario/manager.py
+++ b/tempest/scenario/manager.py
@@ -53,6 +53,32 @@
 LOG_cinder_client.addHandler(log.NullHandler())
 
 
+class ScenarioTest(tempest.test.BaseTestCase):
+
+    @classmethod
+    def setUpClass(cls):
+        super(ScenarioTest, cls).setUpClass()
+        cls.isolated_creds = isolated_creds.IsolatedCreds(
+            cls.__name__, tempest_client=True,
+            network_resources=cls.network_resources)
+        cls.manager = clients.Manager(
+            credentials=cls.credentials()
+        )
+
+    @classmethod
+    def _get_credentials(cls, get_creds, ctype):
+        if CONF.compute.allow_tenant_isolation:
+            creds = get_creds()
+        else:
+            creds = auth.get_default_credentials(ctype)
+        return creds
+
+    @classmethod
+    def credentials(cls):
+        return cls._get_credentials(cls.isolated_creds.get_primary_creds,
+                                    'user')
+
+
 class OfficialClientTest(tempest.test.BaseTestCase):
     """
     Official Client test base class for scenario testing.
diff --git a/tempest/services/__init__.py b/tempest/services/__init__.py
index e7bec60..e69de29 100644
--- a/tempest/services/__init__.py
+++ b/tempest/services/__init__.py
@@ -1,37 +0,0 @@
-# Copyright 2012 OpenStack Foundation
-# 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.
-
-"""
-Base Service class, which acts as a descriptor for an OpenStack service
-in the test environment
-"""
-
-
-class Service(object):
-
-    def __init__(self, config):
-        """
-        Initializes the service.
-
-        :param config: `tempest.config.Config` object
-        """
-        self.config = config
-
-    def get_client(self):
-        """
-        Returns a client object that may be used to query
-        the service API.
-        """
-        raise NotImplementedError
diff --git a/tempest/services/identity/v3/json/identity_client.py b/tempest/services/identity/v3/json/identity_client.py
index 8c72dfa..329f026 100644
--- a/tempest/services/identity/v3/json/identity_client.py
+++ b/tempest/services/identity/v3/json/identity_client.py
@@ -14,6 +14,7 @@
 #    under the License.
 
 import json
+import urllib
 
 from tempest.common import rest_client
 from tempest import config
@@ -83,9 +84,12 @@
         body = json.loads(body)
         return resp, body['projects']
 
-    def get_users(self):
+    def get_users(self, params=None):
         """Get the list of users."""
-        resp, body = self.get("users")
+        url = 'users'
+        if params:
+            url += '?%s' % urllib.urlencode(params)
+        resp, body = self.get(url)
         self.expected_success(200, resp.status)
         body = json.loads(body)
         return resp, body['users']
diff --git a/tempest/services/identity/v3/xml/identity_client.py b/tempest/services/identity/v3/xml/identity_client.py
index 242b032..3790f13 100644
--- a/tempest/services/identity/v3/xml/identity_client.py
+++ b/tempest/services/identity/v3/xml/identity_client.py
@@ -14,6 +14,7 @@
 #    under the License.
 
 import json
+import urllib
 
 from lxml import etree
 
@@ -76,6 +77,14 @@
                 array.append(common.xml_to_json(child))
         return array
 
+    def _parse_users(self, node):
+        array = []
+        for child in node.getchildren():
+            tag_list = child.tag.split('}', 1)
+            if tag_list[1] == "user":
+                array.append(common.xml_to_json(child))
+        return array
+
     def _parse_array(self, node):
         array = []
         for child in node.getchildren():
@@ -137,11 +146,14 @@
         body = self._parse_projects(etree.fromstring(body))
         return resp, body
 
-    def get_users(self):
+    def get_users(self, params=None):
         """Get the list of users."""
-        resp, body = self.get("users")
+        url = 'users'
+        if params:
+            url += '?%s' % urllib.urlencode(params)
+        resp, body = self.get(url)
         self.expected_success(200, resp.status)
-        body = self._parse_array(etree.fromstring(body))
+        body = self._parse_users(etree.fromstring(body))
         return resp, body
 
     def get_user(self, user_id):