Merge "Fix SSH host to floating IP from fixed IP"
diff --git a/README.rst b/README.rst
index 992d19b..da0f5f3 100644
--- a/README.rst
+++ b/README.rst
@@ -40,7 +40,7 @@
     $> nosetests tempest
 
 To run one single test  ::
-    $> nosetests -sv tempest.tests.compute.servers.test_server_actions.py:
+    $> nosetests -sv tempest.api.compute.servers.test_server_actions.py:
        ServerActionsTestJSON.test_rebuild_nonexistent_server
 
 Configuration
diff --git a/etc/tempest.conf.sample b/etc/tempest.conf.sample
index f23c8f2..f5e51cd 100644
--- a/etc/tempest.conf.sample
+++ b/etc/tempest.conf.sample
@@ -339,6 +339,10 @@
 # ssh username for the image file
 ssh_user = cirros
 
+# specifies how many resources to request at once. Used for large operations
+# testing."
+large_ops_number = 0
+
 [cli]
 # Enable cli tests
 enabled = True
diff --git a/requirements.txt b/requirements.txt
index b3c706b..06aa9f3 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -12,7 +12,7 @@
 python-keystoneclient>=0.2.0
 python-novaclient>=2.10.0
 python-neutronclient>=2.2.3,<3.0.0
-python-cinderclient>=1.0.4,<2
+python-cinderclient>=1.0.4
 testresources
 keyring
 testrepository
diff --git a/tempest/api/compute/images/test_images_oneserver.py b/tempest/api/compute/images/test_images_oneserver.py
index 4163245..7740cfc 100644
--- a/tempest/api/compute/images/test_images_oneserver.py
+++ b/tempest/api/compute/images/test_images_oneserver.py
@@ -40,7 +40,6 @@
     def setUpClass(cls):
         super(ImagesOneServerTestJSON, cls).setUpClass()
         cls.client = cls.images_client
-        cls.servers_client = cls.servers_client
 
         try:
             resp, cls.server = cls.create_server(wait_until='ACTIVE')
@@ -104,6 +103,10 @@
         self.assertRaises(exceptions.NotFound,
                           self.alt_client.delete_image, image_id)
 
+    def _get_default_flavor_disk_size(self, flavor_id):
+        resp, flavor = self.flavors_client.get_flavor_details(flavor_id)
+        return flavor['disk']
+
     @testtools.skipUnless(compute.CREATE_IMAGE_ENABLED,
                           'Environment unable to create images.')
     @attr(type='smoke')
@@ -123,10 +126,15 @@
         self.assertEqual(name, image['name'])
         self.assertEqual('test', image['metadata']['image_type'])
 
-        # Verify minRAM and minDisk values are the same as the original image
         resp, original_image = self.client.get_image(self.image_ref)
-        self.assertEqual(original_image['minRam'], image['minRam'])
-        self.assertEqual(original_image['minDisk'], image['minDisk'])
+
+        # Verify minRAM is the same as the original image
+        self.assertEqual(image['minRam'], original_image['minRam'])
+
+        # Verify minDisk is the same as the original image or the flavor size
+        flavor_disk_size = self._get_default_flavor_disk_size(self.flavor_ref)
+        self.assertIn(str(image['minDisk']),
+                      (str(original_image['minDisk']), str(flavor_disk_size)))
 
         # Verify the image was deleted correctly
         resp, body = self.client.delete_image(image_id)
diff --git a/tempest/api/compute/servers/test_server_actions.py b/tempest/api/compute/servers/test_server_actions.py
index 9f97f4f..8b76f7f 100644
--- a/tempest/api/compute/servers/test_server_actions.py
+++ b/tempest/api/compute/servers/test_server_actions.py
@@ -112,7 +112,7 @@
         meta = {'rebuild': 'server'}
         new_name = rand_name('server')
         file_contents = 'Test server rebuild.'
-        personality = [{'path': '/etc/rebuild.txt',
+        personality = [{'path': 'rebuild.txt',
                        'contents': base64.b64encode(file_contents)}]
         password = 'rebuildPassw0rd'
         resp, rebuilt_server = self.client.rebuild(self.server_id,
@@ -253,6 +253,24 @@
         lines = len(output.split('\n'))
         self.assertEqual(lines, 10)
 
+    @attr(type='gate')
+    def test_pause_unpause_server(self):
+        resp, server = self.client.pause_server(self.server_id)
+        self.assertEqual(202, resp.status)
+        self.client.wait_for_server_status(self.server_id, 'PAUSED')
+        resp, server = self.client.unpause_server(self.server_id)
+        self.assertEqual(202, resp.status)
+        self.client.wait_for_server_status(self.server_id, 'ACTIVE')
+
+    @attr(type='gate')
+    def test_suspend_resume_server(self):
+        resp, server = self.client.suspend_server(self.server_id)
+        self.assertEqual(202, resp.status)
+        self.client.wait_for_server_status(self.server_id, 'SUSPENDED')
+        resp, server = self.client.resume_server(self.server_id)
+        self.assertEqual(202, resp.status)
+        self.client.wait_for_server_status(self.server_id, 'ACTIVE')
+
     @classmethod
     def rebuild_servers(cls):
         # Destroy any existing server and creates a new one
diff --git a/tempest/api/identity/admin/v3/test_domains.py b/tempest/api/identity/admin/v3/test_domains.py
index 8d019fe..3d40eb3 100644
--- a/tempest/api/identity/admin/v3/test_domains.py
+++ b/tempest/api/identity/admin/v3/test_domains.py
@@ -15,6 +15,7 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+
 from tempest.api.identity import base
 from tempest.common.utils.data_utils import rand_name
 from tempest.test import attr
@@ -49,6 +50,48 @@
         missing_doms = [d for d in domain_ids if d not in fetched_ids]
         self.assertEqual(0, len(missing_doms))
 
+    @attr(type='smoke')
+    def test_create_update_delete_domain(self):
+        d_name = rand_name('domain-')
+        d_desc = rand_name('domain-desc-')
+        resp_1, domain = self.v3_client.create_domain(
+            d_name, description=d_desc)
+        self.assertEqual(resp_1['status'], '201')
+        self.addCleanup(self._delete_domain, domain['id'])
+        self.assertIn('id', domain)
+        self.assertIn('description', domain)
+        self.assertIn('name', domain)
+        self.assertIn('enabled', domain)
+        self.assertIn('links', domain)
+        self.assertIsNotNone(domain['id'])
+        self.assertEqual(d_name, domain['name'])
+        self.assertEqual(d_desc, domain['description'])
+        if self._interface == "json":
+            self.assertEqual(True, domain['enabled'])
+        else:
+            self.assertEqual('true', str(domain['enabled']).lower())
+        new_desc = rand_name('new-desc-')
+        new_name = rand_name('new-name-')
+
+        resp_2, updated_domain = self.v3_client.update_domain(
+            domain['id'], name=new_name, description=new_desc)
+        self.assertEqual(resp_2['status'], '200')
+        self.assertIn('id', updated_domain)
+        self.assertIn('description', updated_domain)
+        self.assertIn('name', updated_domain)
+        self.assertIn('enabled', updated_domain)
+        self.assertIn('links', updated_domain)
+        self.assertIsNotNone(updated_domain['id'])
+        self.assertEqual(new_name, updated_domain['name'])
+        self.assertEqual(new_desc, updated_domain['description'])
+        self.assertEqual('true', str(updated_domain['enabled']).lower())
+
+        resp_3, fetched_domain = self.v3_client.get_domain(domain['id'])
+        self.assertEqual(resp_3['status'], '200')
+        self.assertEqual(new_name, fetched_domain['name'])
+        self.assertEqual(new_desc, fetched_domain['description'])
+        self.assertEqual('true', str(fetched_domain['enabled']).lower())
+
 
 class DomainsTestXML(DomainsTestJSON):
     _interface = 'xml'
diff --git a/tempest/api/identity/admin/v3/test_tokens.py b/tempest/api/identity/admin/v3/test_tokens.py
new file mode 100644
index 0000000..2a20493
--- /dev/null
+++ b/tempest/api/identity/admin/v3/test_tokens.py
@@ -0,0 +1,57 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 OpenStack, LLC
+# 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.data_utils import rand_name
+from tempest import exceptions
+from tempest.test import attr
+
+
+class UsersTestJSON(base.BaseIdentityAdminTest):
+    _interface = 'json'
+
+    @attr(type='smoke')
+    def test_tokens(self):
+        # Valid user's token is authenticated
+        # Create a User
+        u_name = rand_name('user-')
+        u_desc = '%s-description' % u_name
+        u_email = '%s@testmail.tm' % u_name
+        u_password = rand_name('pass-')
+        resp, user = self.v3_client.create_user(
+            u_name, description=u_desc, password=u_password,
+            email=u_email)
+        self.assertTrue(resp['status'].startswith('2'))
+        self.addCleanup(self.v3_client.delete_user, user['id'])
+        # Perform Authentication
+        resp, body = self.v3_token.auth(user['id'], u_password)
+        self.assertEqual(resp['status'], '201')
+        subject_token = resp['x-subject-token']
+        # Perform GET Token
+        resp, token_details = self.v3_client.get_token(subject_token)
+        self.assertEqual(resp['status'], '200')
+        self.assertEqual(resp['x-subject-token'], subject_token)
+        self.assertEqual(token_details['user']['id'], user['id'])
+        self.assertEqual(token_details['user']['name'], u_name)
+        # Perform Delete Token
+        resp, _ = self.v3_client.delete_token(subject_token)
+        self.assertRaises(exceptions.Unauthorized, self.v3_client.get_token,
+                          subject_token)
+
+
+class UsersTestXML(UsersTestJSON):
+    _interface = 'xml'
diff --git a/tempest/api/identity/base.py b/tempest/api/identity/base.py
index db55509..1237ce4 100644
--- a/tempest/api/identity/base.py
+++ b/tempest/api/identity/base.py
@@ -32,6 +32,7 @@
         cls.v3_client = os.identity_v3_client
         cls.service_client = os.service_client
         cls.policy_client = os.policy_client
+        cls.v3_token = os.token_v3_client
 
         if not cls.client.has_admin_extensions():
             raise cls.skipException("Admin extensions disabled")
diff --git a/tempest/api/object_storage/test_object_services.py b/tempest/api/object_storage/test_object_services.py
index 2f52a65..6136216 100644
--- a/tempest/api/object_storage/test_object_services.py
+++ b/tempest/api/object_storage/test_object_services.py
@@ -340,18 +340,19 @@
     def test_object_upload_in_segments(self):
         # create object
         object_name = rand_name(name='LObject')
-        data = arbitrary_string(size=len(object_name),
-                                base_text=object_name)
+        data = arbitrary_string()
         segments = 10
-        self.object_client.create_object(self.container_name,
-                                         object_name, data)
-        # uploading 10 segments
-        for i in range(segments):
+        data_segments = [data + str(i) for i in xrange(segments)]
+        # uploading segments
+        for i in xrange(segments):
             resp, _ = self.object_client.create_object_segments(
-                self.container_name, object_name, i, data)
-        # creating a manifest file (metadata update)
+                self.container_name, object_name, i, data_segments[i])
+            self.assertEqual(resp['status'], '201')
+        # creating a manifest file
         metadata = {'X-Object-Manifest': '%s/%s/'
                     % (self.container_name, object_name)}
+        self.object_client.create_object(self.container_name,
+                                         object_name, data='')
         resp, _ = self.object_client.update_object_metadata(
             self.container_name, object_name, metadata, metadata_prefix='')
         resp, _ = self.object_client.list_object_metadata(
@@ -363,7 +364,7 @@
         # downloading the object
         resp, body = self.object_client.get_object(
             self.container_name, object_name)
-        self.assertEqual(data * segments, body)
+        self.assertEqual(''.join(data_segments), body)
 
     @attr(type='gate')
     def test_get_object_if_different(self):
diff --git a/tempest/api/orchestration/base.py b/tempest/api/orchestration/base.py
index 6d6000a..ffa534a 100644
--- a/tempest/api/orchestration/base.py
+++ b/tempest/api/orchestration/base.py
@@ -12,7 +12,7 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-import logging
+from tempest.common import log as logging
 import time
 
 from tempest import clients
diff --git a/tempest/api/orchestration/stacks/test_instance_cfn_init.py b/tempest/api/orchestration/stacks/test_instance_cfn_init.py
index add8588..4f22158 100644
--- a/tempest/api/orchestration/stacks/test_instance_cfn_init.py
+++ b/tempest/api/orchestration/stacks/test_instance_cfn_init.py
@@ -13,7 +13,7 @@
 #    under the License.
 
 import json
-import logging
+from tempest.common import log as logging
 import testtools
 
 from tempest.api.orchestration import base
@@ -46,6 +46,13 @@
 Resources:
   CfnUser:
     Type: AWS::IAM::User
+  SmokeSecurityGroup:
+    Type: AWS::EC2::SecurityGroup
+    Properties:
+      GroupDescription: Enable only ping and SSH access
+      SecurityGroupIngress:
+      - {CidrIp: 0.0.0.0/0, FromPort: '-1', IpProtocol: icmp, ToPort: '-1'}
+      - {CidrIp: 0.0.0.0/0, FromPort: '22', IpProtocol: tcp, ToPort: '22'}
   SmokeKeys:
     Type: AWS::IAM::AccessKey
     Properties:
@@ -79,6 +86,8 @@
       ImageId: {Ref: ImageId}
       InstanceType: {Ref: InstanceType}
       KeyName: {Ref: KeyName}
+      SecurityGroups:
+      - {Ref: SmokeSecurityGroup}
       UserData:
         Fn::Base64:
           Fn::Join:
diff --git a/tempest/api/orchestration/stacks/test_stacks.py b/tempest/api/orchestration/stacks/test_stacks.py
index 5fed581..15979ed 100644
--- a/tempest/api/orchestration/stacks/test_stacks.py
+++ b/tempest/api/orchestration/stacks/test_stacks.py
@@ -12,7 +12,7 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-import logging
+from tempest.common import log as logging
 
 from tempest.api.orchestration import base
 from tempest.common.utils.data_utils import rand_name
diff --git a/tempest/api/volume/test_volumes_actions.py b/tempest/api/volume/test_volumes_actions.py
index cd5ab34..56a3006 100644
--- a/tempest/api/volume/test_volumes_actions.py
+++ b/tempest/api/volume/test_volumes_actions.py
@@ -27,7 +27,7 @@
     def setUpClass(cls):
         super(VolumesActionsTest, cls).setUpClass()
         cls.client = cls.volumes_client
-        cls.servers_client = cls.servers_client
+        cls.image_client = cls.os.image_client
 
         # Create a test shared instance and volume for attach/detach tests
         srv_name = rand_name('Instance-')
@@ -93,3 +93,16 @@
         finally:
             self.client.detach_volume(self.volume['id'])
             self.client.wait_for_volume_status(self.volume['id'], 'available')
+
+    @attr(type='gate')
+    def test_volume_upload(self):
+        # NOTE(gfidente): the volume uploaded in Glance comes from setUpClass,
+        # it is shared with the other tests. After it is uploaded in Glance,
+        # there is no way to delete it from Cinder, so we delete it from Glance
+        # using the Glance image_client and from Cinder via tearDownClass.
+        image_name = rand_name('Image-')
+        resp, body = self.client.upload_volume(self.volume['id'], image_name)
+        image_id = body["image_id"]
+        self.addCleanup(self.image_client.delete_image, image_id)
+        self.assertEqual(202, resp.status)
+        self.image_client.wait_for_image_status(image_id, 'active')
diff --git a/tempest/cli/__init__.py b/tempest/cli/__init__.py
index 5bbedfd..0e1d6db 100644
--- a/tempest/cli/__init__.py
+++ b/tempest/cli/__init__.py
@@ -15,7 +15,6 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-import logging
 import os
 import shlex
 import subprocess
@@ -23,6 +22,7 @@
 from oslo.config import cfg
 
 import tempest.cli.output_parser
+from tempest.common import log as logging
 import tempest.test
 
 
@@ -77,6 +77,11 @@
         return self.cmd_with_auth(
             'glance', action, flags, params, admin, fail_ok)
 
+    def cinder(self, action, flags='', params='', admin=True, fail_ok=False):
+        """Executes cinder command for the given action."""
+        return self.cmd_with_auth(
+            'cinder', action, flags, params, admin, fail_ok)
+
     def cmd_with_auth(self, cmd, action, flags='', params='',
                       admin=True, fail_ok=False):
         """Executes given command with auth attributes appended."""
diff --git a/tempest/cli/output_parser.py b/tempest/cli/output_parser.py
index 840839b..3ee3098 100644
--- a/tempest/cli/output_parser.py
+++ b/tempest/cli/output_parser.py
@@ -18,7 +18,8 @@
 """Collection of utilities for parsing CLI clients output."""
 
 
-import logging
+from tempest.common import log as logging
+
 import re
 
 
diff --git a/tempest/cli/simple_read_only/test_cinder.py b/tempest/cli/simple_read_only/test_cinder.py
new file mode 100644
index 0000000..e9ce87b
--- /dev/null
+++ b/tempest/cli/simple_read_only/test_cinder.py
@@ -0,0 +1,117 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 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.
+
+import logging
+import re
+import subprocess
+
+import tempest.cli
+
+LOG = logging.getLogger(__name__)
+
+
+class SimpleReadOnlyCinderClientTest(tempest.cli.ClientTestBase):
+    """Basic, read-only tests for Cinder CLI client.
+
+    Checks return values and output of read-only commands.
+    These tests do not presume any content, nor do they create
+    their own. They only verify the structure of output if present.
+    """
+
+    def test_cinder_fake_action(self):
+        self.assertRaises(subprocess.CalledProcessError,
+                          self.cinder,
+                          'this-does-not-exist')
+
+    def test_cinder_absolute_limit_list(self):
+        roles = self.parser.listing(self.cinder('absolute-limits'))
+        self.assertTableStruct(roles, ['Name', 'Value'])
+
+    def test_cinder_backup_list(self):
+        self.cinder('backup-list')
+
+    def test_cinder_extra_specs_list(self):
+        self.cinder('extra-specs-list')
+
+    def test_cinder_volumes_list(self):
+        self.cinder('list')
+
+    def test_cinder_quota_class_show(self):
+        """This CLI can accept and string as param."""
+        roles = self.parser.listing(self.cinder('quota-class-show',
+                                                params='abc'))
+        self.assertTableStruct(roles, ['Property', 'Value'])
+
+    def test_cinder_quota_defaults(self):
+        """This CLI can accept and string as param."""
+        roles = self.parser.listing(self.cinder('quota-defaults',
+                                                params=self.identity.
+                                                admin_tenant_name))
+        self.assertTableStruct(roles, ['Property', 'Value'])
+
+    def test_cinder_quota_show(self):
+        """This CLI can accept and string as param."""
+        roles = self.parser.listing(self.cinder('quota-show',
+                                                params=self.identity.
+                                                admin_tenant_name))
+        self.assertTableStruct(roles, ['Property', 'Value'])
+
+    def test_cinder_rate_limits(self):
+        self.cinder('rate-limits')
+
+    def test_cinder_snapshot_list(self):
+        self.cinder('snapshot-list')
+
+    def test_cinder_type_list(self):
+        self.cinder('type-list')
+
+    def test_cinder_list_extensions(self):
+        self.cinder('list-extensions')
+        roles = self.parser.listing(self.cinder('list-extensions'))
+        self.assertTableStruct(roles, ['Name', 'Summary', 'Alias', 'Updated'])
+
+    def test_admin_help(self):
+        help_text = self.cinder('help')
+        lines = help_text.split('\n')
+        self.assertTrue(lines[0].startswith('usage: cinder'))
+
+        commands = []
+        cmds_start = lines.index('Positional arguments:')
+        cmds_end = lines.index('Optional arguments:')
+        command_pattern = re.compile('^ {4}([a-z0-9\-\_]+)')
+        for line in lines[cmds_start:cmds_end]:
+            match = command_pattern.match(line)
+            if match:
+                commands.append(match.group(1))
+        commands = set(commands)
+        wanted_commands = set(('absolute-limits', 'list', 'help',
+                               'quota-show', 'type-list', 'snapshot-list'))
+        self.assertFalse(wanted_commands - commands)
+
+     # Optional arguments:
+
+    def test_cinder_version(self):
+        self.cinder('', flags='--version')
+
+    def test_cinder_debug_list(self):
+        self.cinder('list', flags='--debug')
+
+    def test_cinder_retries_list(self):
+        self.cinder('list', flags='--retries 3')
+
+    def test_cinder_region_list(self):
+        self.cinder('list', flags='--os-region-name ' + self.identity.region)
diff --git a/tempest/cli/simple_read_only/test_compute.py b/tempest/cli/simple_read_only/test_compute.py
index fa64561..561fd00 100644
--- a/tempest/cli/simple_read_only/test_compute.py
+++ b/tempest/cli/simple_read_only/test_compute.py
@@ -15,13 +15,13 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-import logging
 import subprocess
 
 from oslo.config import cfg
 import testtools
 
 import tempest.cli
+from tempest.common import log as logging
 
 
 CONF = cfg.CONF
diff --git a/tempest/cli/simple_read_only/test_compute_manage.py b/tempest/cli/simple_read_only/test_compute_manage.py
index a788c8b..802a206 100644
--- a/tempest/cli/simple_read_only/test_compute_manage.py
+++ b/tempest/cli/simple_read_only/test_compute_manage.py
@@ -15,10 +15,10 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-import logging
 import subprocess
 
 import tempest.cli
+from tempest.common import log as logging
 
 
 LOG = logging.getLogger(__name__)
diff --git a/tempest/cli/simple_read_only/test_glance.py b/tempest/cli/simple_read_only/test_glance.py
index b3b3eb7..fa77e8a 100644
--- a/tempest/cli/simple_read_only/test_glance.py
+++ b/tempest/cli/simple_read_only/test_glance.py
@@ -15,11 +15,11 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-import logging
 import re
 import subprocess
 
 import tempest.cli
+from tempest.common import log as logging
 
 
 LOG = logging.getLogger(__name__)
diff --git a/tempest/cli/simple_read_only/test_keystone.py b/tempest/cli/simple_read_only/test_keystone.py
index 45d519b..3bc8b3e 100644
--- a/tempest/cli/simple_read_only/test_keystone.py
+++ b/tempest/cli/simple_read_only/test_keystone.py
@@ -15,11 +15,11 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-import logging
 import re
 import subprocess
 
 import tempest.cli
+from tempest.common import log as logging
 
 
 LOG = logging.getLogger(__name__)
diff --git a/tempest/clients.py b/tempest/clients.py
index d7a740a..5efce98 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -75,12 +75,14 @@
     EndPointClientJSON
 from tempest.services.identity.v3.json.identity_client import \
     IdentityV3ClientJSON
+from tempest.services.identity.v3.json.identity_client import V3TokenClientJSON
 from tempest.services.identity.v3.json.policy_client import PolicyClientJSON
 from tempest.services.identity.v3.json.service_client import \
     ServiceClientJSON
 from tempest.services.identity.v3.xml.endpoints_client import EndPointClientXML
 from tempest.services.identity.v3.xml.identity_client import \
     IdentityV3ClientXML
+from tempest.services.identity.v3.xml.identity_client import V3TokenClientXML
 from tempest.services.identity.v3.xml.policy_client import PolicyClientXML
 from tempest.services.identity.v3.xml.service_client import \
     ServiceClientXML
@@ -239,6 +241,11 @@
     "xml": HypervisorClientXML,
 }
 
+V3_TOKEN_CLIENT = {
+    "json": V3TokenClientJSON,
+    "xml": V3TokenClientXML,
+}
+
 
 class Manager(object):
 
@@ -319,6 +326,7 @@
                 TENANT_USAGES_CLIENT[interface](*client_args)
             self.policy_client = POLICY_CLIENT[interface](*client_args)
             self.hypervisor_client = HYPERVISOR_CLIENT[interface](*client_args)
+            self.token_v3_client = V3_TOKEN_CLIENT[interface](*client_args)
 
             if client_args_v3_auth:
                 self.servers_client_v3_auth = SERVERS_CLIENTS[interface](
diff --git a/tempest/config.py b/tempest/config.py
index 3018cd2..2e56628 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -523,7 +523,12 @@
                help='AKI image file name'),
     cfg.StrOpt('ssh_user',
                default='cirros',
-               help='ssh username for the image file')
+               help='ssh username for the image file'),
+    cfg.IntOpt(
+        'large_ops_number',
+        default=0,
+        help="specifies how many resources to request at once. Used "
+        "for large operations testing.")
 ]
 
 
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
index e3a23bb..fe6fbf5 100644
--- a/tempest/scenario/manager.py
+++ b/tempest/scenario/manager.py
@@ -16,7 +16,6 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-import logging
 import subprocess
 
 # Default client libs
@@ -30,6 +29,7 @@
 
 
 from tempest.api.network import common as net_common
+from tempest.common import log as logging
 from tempest.common import ssh
 from tempest.common.utils.data_utils import rand_name
 from tempest import exceptions
diff --git a/tempest/scenario/test_large_ops.py b/tempest/scenario/test_large_ops.py
new file mode 100644
index 0000000..1f75e2f
--- /dev/null
+++ b/tempest/scenario/test_large_ops.py
@@ -0,0 +1,103 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 NEC Corporation
+# 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.common import log as logging
+from tempest.common.utils.data_utils import rand_name
+from tempest.scenario import manager
+
+
+LOG = logging.getLogger(__name__)
+
+
+class TestLargeOpsScenario(manager.OfficialClientTest):
+
+    """
+    Test large operations.
+
+    This test below:
+    * Spin up multiple instances in one nova call
+    * as a regular user
+    * TODO: same thing for cinder
+
+    """
+
+    def _wait_for_server_status(self, status):
+        for server in self.servers:
+            self.status_timeout(
+                self.compute_client.servers, server.id, status)
+
+    def _wait_for_volume_status(self, status):
+        volume_id = self.volume.id
+        self.status_timeout(
+            self.volume_client.volumes, volume_id, status)
+
+    def _image_create(self, name, fmt, path, properties={}):
+        name = rand_name('%s-' % name)
+        image_file = open(path, 'rb')
+        self.addCleanup(image_file.close)
+        params = {
+            'name': name,
+            'container_format': fmt,
+            'disk_format': fmt,
+            'is_public': 'True',
+        }
+        params.update(properties)
+        image = self.image_client.images.create(**params)
+        self.addCleanup(self.image_client.images.delete, image)
+        self.assertEqual("queued", image.status)
+        image.update(data=image_file)
+        return image.id
+
+    def glance_image_create(self):
+        aki_img_path = self.config.scenario.img_dir + "/" + \
+            self.config.scenario.aki_img_file
+        ari_img_path = self.config.scenario.img_dir + "/" + \
+            self.config.scenario.ari_img_file
+        ami_img_path = self.config.scenario.img_dir + "/" + \
+            self.config.scenario.ami_img_file
+        LOG.debug("paths: ami: %s, ari: %s, aki: %s"
+                  % (ami_img_path, ari_img_path, aki_img_path))
+        kernel_id = self._image_create('scenario-aki', 'aki', aki_img_path)
+        ramdisk_id = self._image_create('scenario-ari', 'ari', ari_img_path)
+        properties = {
+            'properties': {'kernel_id': kernel_id, 'ramdisk_id': ramdisk_id}
+        }
+        self.image = self._image_create('scenario-ami', 'ami',
+                                        path=ami_img_path,
+                                        properties=properties)
+
+    def nova_boot(self):
+        def delete(servers):
+            [x.delete() for x in servers]
+
+        name = rand_name('scenario-server-')
+        client = self.compute_client
+        flavor_id = self.config.compute.flavor_ref
+        self.servers = client.servers.create(
+            name=name, image=self.image,
+            flavor=flavor_id,
+            min_count=self.config.scenario.large_ops_number)
+        # needed because of bug 1199788
+        self.servers = [x for x in client.servers.list() if name in x.name]
+        self.addCleanup(delete, self.servers)
+        self._wait_for_server_status('ACTIVE')
+
+    def test_large_ops_scenario(self):
+        if self.config.scenario.large_ops_number < 1:
+            return
+        self.glance_image_create()
+        self.nova_boot()
diff --git a/tempest/scenario/test_minimum_basic.py b/tempest/scenario/test_minimum_basic.py
index a55bbb2..2097f50 100644
--- a/tempest/scenario/test_minimum_basic.py
+++ b/tempest/scenario/test_minimum_basic.py
@@ -15,7 +15,7 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-import logging
+from tempest.common import log as logging
 
 from tempest.common.utils.data_utils import rand_name
 from tempest.common.utils.linux.remote_client import RemoteClient
diff --git a/tempest/scenario/test_snapshot_pattern.py b/tempest/scenario/test_snapshot_pattern.py
index eb6ed35..76fac82 100644
--- a/tempest/scenario/test_snapshot_pattern.py
+++ b/tempest/scenario/test_snapshot_pattern.py
@@ -15,7 +15,7 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-import logging
+from tempest.common import log as logging
 
 from tempest.common.utils.data_utils import rand_name
 from tempest.common.utils.linux.remote_client import RemoteClient
diff --git a/tempest/services/compute/xml/servers_client.py b/tempest/services/compute/xml/servers_client.py
index 1ec4df0..f2cca72 100644
--- a/tempest/services/compute/xml/servers_client.py
+++ b/tempest/services/compute/xml/servers_client.py
@@ -163,6 +163,22 @@
         server = self._parse_server(etree.fromstring(body))
         return resp, server
 
+    def suspend_server(self, server_id, **kwargs):
+        """Suspends the provided server."""
+        return self.action(server_id, 'suspend', None, **kwargs)
+
+    def resume_server(self, server_id, **kwargs):
+        """Un-suspends the provided server."""
+        return self.action(server_id, 'resume', None, **kwargs)
+
+    def pause_server(self, server_id, **kwargs):
+        """Pauses the provided server."""
+        return self.action(server_id, 'pause', None, **kwargs)
+
+    def unpause_server(self, server_id, **kwargs):
+        """Un-pauses the provided server."""
+        return self.action(server_id, 'unpause', None, **kwargs)
+
     def delete_server(self, server_id):
         """Deletes the given server."""
         return self.delete("servers/%s" % str(server_id))
diff --git a/tempest/services/identity/v3/json/identity_client.py b/tempest/services/identity/v3/json/identity_client.py
index adbdc83..56a1a72 100644
--- a/tempest/services/identity/v3/json/identity_client.py
+++ b/tempest/services/identity/v3/json/identity_client.py
@@ -208,3 +208,61 @@
         resp, body = self.get('domains/%s' % domain_id)
         body = json.loads(body)
         return resp, body['domain']
+
+    def get_token(self, resp_token):
+        """Get token details."""
+        headers = {'X-Subject-Token': resp_token}
+        resp, body = self.get("auth/tokens", headers=headers)
+        body = json.loads(body)
+        return resp, body['token']
+
+    def delete_token(self, resp_token):
+        """Deletes token."""
+        headers = {'X-Subject-Token': resp_token}
+        resp, body = self.delete("auth/tokens", headers=headers)
+        return resp, body
+
+
+class V3TokenClientJSON(RestClient):
+
+    def __init__(self, config, username, password, auth_url, tenant_name=None):
+        super(V3TokenClientJSON, self).__init__(config, username, password,
+                                                auth_url, tenant_name)
+        self.service = self.config.identity.catalog_type
+        self.endpoint_url = 'adminURL'
+
+        auth_url = config.identity.uri
+
+        if 'tokens' not in auth_url:
+            auth_url = auth_url.rstrip('/') + '/tokens'
+
+        self.auth_url = auth_url
+        self.config = config
+
+    def auth(self, user_id, password):
+        creds = {
+            'auth': {
+                'identity': {
+                    'methods': ['password'],
+                    'password': {
+                        'user': {
+                            'id': user_id,
+                            'password': password
+                        }
+                    }
+                }
+            }
+        }
+        headers = {'Content-Type': 'application/json'}
+        body = json.dumps(creds)
+        resp, body = self.post("auth/tokens", headers=headers, body=body)
+        return resp, body
+
+    def request(self, method, url, headers=None, body=None, wait=None):
+        """Overriding the existing HTTP request in super class rest_client."""
+        self._set_auth()
+        self.base_url = self.base_url.replace(urlparse(self.base_url).path,
+                                              "/v3")
+        return super(V3TokenClientJSON, self).request(method, url,
+                                                      headers=headers,
+                                                      body=body)
diff --git a/tempest/services/identity/v3/xml/identity_client.py b/tempest/services/identity/v3/xml/identity_client.py
index 708ee28..571b491 100644
--- a/tempest/services/identity/v3/xml/identity_client.py
+++ b/tempest/services/identity/v3/xml/identity_client.py
@@ -22,9 +22,9 @@
 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 Text
 from tempest.services.compute.xml.common import xml_to_json
 
-
 XMLNS = "http://docs.openstack.org/identity/api/v3"
 
 
@@ -241,3 +241,65 @@
         resp, body = self.get('domains/%s' % domain_id, self.headers)
         body = self._parse_body(etree.fromstring(body))
         return resp, body
+
+    def get_token(self, resp_token):
+        """GET a Token Details."""
+        headers = {'Content-Type': 'application/xml',
+                   'Accept': 'application/xml',
+                   'X-Subject-Token': resp_token}
+        resp, body = self.get("auth/tokens", headers=headers)
+        body = self._parse_body(etree.fromstring(body))
+        return resp, body
+
+    def delete_token(self, resp_token):
+        """Delete a Given Token."""
+        headers = {'X-Subject-Token': resp_token}
+        resp, body = self.delete("auth/tokens", headers=headers)
+        return resp, body
+
+
+class V3TokenClientXML(RestClientXML):
+
+    def __init__(self, config, username, password, auth_url, tenant_name=None):
+        super(V3TokenClientXML, self).__init__(config, username, password,
+                                               auth_url, tenant_name)
+        self.service = self.config.identity.catalog_type
+        self.endpoint_url = 'adminURL'
+
+        auth_url = config.identity.uri
+
+        if 'tokens' not in auth_url:
+            auth_url = auth_url.rstrip('/') + '/tokens'
+
+        self.auth_url = auth_url
+        self.config = config
+
+    def auth(self, user_id, password):
+        user = Element('user',
+                       id=user_id,
+                       password=password)
+        password = Element('password')
+        password.append(user)
+
+        method = Element('method')
+        method.append(Text('password'))
+        methods = Element('methods')
+        methods.append(method)
+        identity = Element('identity')
+        identity.append(methods)
+        identity.append(password)
+        auth = Element('auth')
+        auth.append(identity)
+        headers = {'Content-Type': 'application/xml'}
+        resp, body = self.post("auth/tokens", headers=headers,
+                               body=str(Document(auth)))
+        return resp, body
+
+    def request(self, method, url, headers=None, body=None, wait=None):
+        """Overriding the existing HTTP request in super class rest_client."""
+        self._set_auth()
+        self.base_url = self.base_url.replace(urlparse(self.base_url).path,
+                                              "/v3")
+        return super(V3TokenClientXML, self).request(method, url,
+                                                     headers=headers,
+                                                     body=body)
diff --git a/tempest/services/volume/json/volumes_client.py b/tempest/services/volume/json/volumes_client.py
index 87c0eba..c22b398 100644
--- a/tempest/services/volume/json/volumes_client.py
+++ b/tempest/services/volume/json/volumes_client.py
@@ -85,6 +85,17 @@
         """Deletes the Specified Volume."""
         return self.delete("volumes/%s" % str(volume_id))
 
+    def upload_volume(self, volume_id, image_name):
+        """Uploads a volume in Glance."""
+        post_body = {
+            'image_name': image_name,
+        }
+        post_body = json.dumps({'os-volume_upload_image': post_body})
+        url = 'volumes/%s/action' % (volume_id)
+        resp, body = self.post(url, post_body, self.headers)
+        body = json.loads(body)
+        return resp, body['os-volume_upload_image']
+
     def attach_volume(self, volume_id, instance_uuid, mountpoint):
         """Attaches a volume to a given instance on a given mountpoint."""
         post_body = {
diff --git a/tempest/test.py b/tempest/test.py
index 6be37be..d7008a7 100644
--- a/tempest/test.py
+++ b/tempest/test.py
@@ -137,7 +137,7 @@
             thing = things.get(thing_id)
             new_status = thing.status
             if new_status == 'ERROR':
-                self.fail("%s failed to get to expected status."
+                self.fail("%s failed to get to expected status. "
                           "In ERROR state."
                           % thing)
             elif new_status == expected_status:
diff --git a/tools/install_venv_common.py b/tools/install_venv_common.py
index 42a44e8..f428c1e 100644
--- a/tools/install_venv_common.py
+++ b/tools/install_venv_common.py
@@ -34,12 +34,13 @@
 
 class InstallVenv(object):
 
-    def __init__(self, root, venv, pip_requires, test_requires, py_version,
+    def __init__(self, root, venv, requirements,
+                 test_requirements, py_version,
                  project):
         self.root = root
         self.venv = venv
-        self.pip_requires = pip_requires
-        self.test_requires = test_requires
+        self.requirements = requirements
+        self.test_requirements = test_requirements
         self.py_version = py_version
         self.project = project
 
@@ -75,11 +76,13 @@
     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)
+            return Fedora(
+                self.root, self.venv, self.requirements,
+                self.test_requirements, self.py_version, self.project)
         else:
-            return Distro(self.root, self.venv, self.pip_requires,
-                          self.test_requires, self.py_version, self.project)
+            return Distro(
+                self.root, self.venv, self.requirements,
+                self.test_requirements, self.py_version, self.project)
 
     def check_dependencies(self):
         self.get_distro().install_virtualenv()
@@ -98,11 +101,6 @@
             else:
                 self.run_command(['virtualenv', '-q', self.venv])
             print('done.')
-            print('Installing pip in venv...', end=' ')
-            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
@@ -116,20 +114,12 @@
         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')
+        # setuptools.
+        self.pip_install('pip>=1.3')
+        self.pip_install('setuptools')
 
-        # 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)
+        self.pip_install('-r', self.requirements)
+        self.pip_install('-r', self.test_requirements)
 
     def post_process(self):
         self.get_distro().post_process()