Merge "add test for creating a floating IP specifying an non-existent pool"
diff --git a/cli/__init__.py b/cli/__init__.py
index 2548f24..6ffe229 100644
--- a/cli/__init__.py
+++ b/cli/__init__.py
@@ -21,6 +21,7 @@
 
 from oslo.config import cfg
 
+import cli.output_parser
 import tempest.test
 
 
@@ -51,6 +52,7 @@
         super(ClientTestBase, cls).setUpClass()
 
     def __init__(self, *args, **kwargs):
+        self.parser = cli.output_parser
         super(ClientTestBase, self).__init__(*args, **kwargs)
 
     def nova(self, action, flags='', params='', admin=True, fail_ok=False):
@@ -58,6 +60,16 @@
         return self.cmd_with_auth(
             'nova', action, flags, params, admin, fail_ok)
 
+    def nova_manage(self, action, flags='', params='', fail_ok=False):
+        """Executes nova-manage command for the given action."""
+        return self.cmd(
+            'nova-manage', action, flags, params, fail_ok)
+
+    def keystone(self, action, flags='', params='', admin=True, fail_ok=False):
+        """Executes keystone command for the given action."""
+        return self.cmd_with_auth(
+            'keystone', 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."""
@@ -81,3 +93,9 @@
             LOG.error("command output:\n%s" % e.output)
             raise
         return result
+
+    def assertTableStruct(self, items, field_names):
+        """Verify that all items has keys listed in field_names."""
+        for item in items:
+            for field in field_names:
+                self.assertIn(field, item)
diff --git a/cli/output_parser.py b/cli/output_parser.py
new file mode 100644
index 0000000..840839b
--- /dev/null
+++ b/cli/output_parser.py
@@ -0,0 +1,168 @@
+# 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.
+
+"""Collection of utilities for parsing CLI clients output."""
+
+
+import logging
+import re
+
+
+LOG = logging.getLogger(__name__)
+
+
+delimiter_line = re.compile('^\+\-[\+\-]+\-\+$')
+
+
+def details_multiple(output_lines, with_label=False):
+    """Return list of dicts with item details from cli output tables.
+
+    If with_label is True, key '__label' is added to each items dict.
+    For more about 'label' see OutputParser.tables().
+    """
+    items = []
+    tables_ = tables(output_lines)
+    for table_ in tables_:
+        if 'Property' not in table_['headers'] \
+           or 'Value' not in table_['headers']:
+            raise Exception('Invalid structure of table with details')
+        item = {}
+        for value in table_['values']:
+            item[value[0]] = value[1]
+        if with_label:
+            item['__label'] = table_['label']
+        items.append(item)
+    return items
+
+
+def details(output_lines, with_label=False):
+    """Return dict with details of first item (table) found in output."""
+    items = details_multiple(output_lines, with_label)
+    return items[0]
+
+
+def listing(output_lines):
+    """Return list of dicts with basic item info parsed from cli output.
+    """
+
+    items = []
+    table_ = table(output_lines)
+    for row in table_['values']:
+        item = {}
+        for col_idx, col_key in enumerate(table_['headers']):
+            item[col_key] = row[col_idx]
+        items.append(item)
+    return items
+
+
+def tables(output_lines):
+    """Find all ascii-tables in output and parse them.
+
+    Return list of tables parsed from cli output as dicts.
+    (see OutputParser.table())
+
+    And, if found, label key (separated line preceding the table)
+    is added to each tables dict.
+    """
+    tables_ = []
+
+    table_ = []
+    label = None
+
+    start = False
+    header = False
+
+    if not isinstance(output_lines, list):
+        output_lines = output_lines.split('\n')
+
+    for line in output_lines:
+        if delimiter_line.match(line):
+            if not start:
+                start = True
+            elif not header:
+                # we are after head area
+                header = True
+            else:
+                # table ends here
+                start = header = None
+                table_.append(line)
+
+                parsed = table(table_)
+                parsed['label'] = label
+                tables_.append(parsed)
+
+                table_ = []
+                label = None
+                continue
+        if start:
+            table_.append(line)
+        else:
+            if label is None:
+                label = line
+            else:
+                LOG.warn('Invalid line between tables: %s' % line)
+    if len(table_) > 0:
+        LOG.warn('Missing end of table')
+
+    return tables_
+
+
+def table(output_lines):
+    """Parse single table from cli output.
+
+    Return dict with list of column names in 'headers' key and
+    rows in 'values' key.
+    """
+    table_ = {'headers': [], 'values': []}
+    columns = None
+
+    if not isinstance(output_lines, list):
+        output_lines = output_lines.split('\n')
+
+    for line in output_lines:
+        if delimiter_line.match(line):
+            columns = _table_columns(line)
+            continue
+        if '|' not in line:
+            LOG.warn('skipping invalid table line: %s' % line)
+            continue
+        row = []
+        for col in columns:
+            row.append(line[col[0]:col[1]].strip())
+        if table_['headers']:
+            table_['values'].append(row)
+        else:
+            table_['headers'] = row
+
+    return table_
+
+
+def _table_columns(first_table_row):
+    """Find column ranges in output line.
+
+    Return list of touples (start,end) for each column
+    detected by plus (+) characters in delimiter line.
+    """
+    positions = []
+    start = 1  # there is '+' at 0
+    while start < len(first_table_row):
+        end = first_table_row.find('+', start)
+        if end == -1:
+            break
+        positions.append((start, end))
+        start = end + 1
+    return positions
diff --git a/cli/simple_read_only/test_compute.py b/cli/simple_read_only/test_compute.py
index bcdd2c5..c904882 100644
--- a/cli/simple_read_only/test_compute.py
+++ b/cli/simple_read_only/test_compute.py
@@ -56,12 +56,13 @@
 
     def test_admin_absolute_limites(self):
         self.nova('absolute-limits')
+        self.nova('absolute-limits', params='--reserved')
 
     def test_admin_aggregate_list(self):
         self.nova('aggregate-list')
 
     def test_admin_availability_zone_list(self):
-        self.nova('availability-zone-list')
+        self.assertIn("internal", self.nova('availability-zone-list'))
 
     def test_admin_cloudpipe_list(self):
         self.nova('cloudpipe-list')
@@ -72,7 +73,7 @@
     def test_admin_dns_domains(self):
         self.nova('dns-domains')
 
-    @testtools.skip("needs parameters")
+    @testtools.skip("Test needs parameters, Bug #1157349")
     def test_admin_dns_list(self):
         self.nova('dns-list')
 
@@ -90,7 +91,7 @@
                           params='--flavor m1.tiny')
 
     def test_admin_flavor_list(self):
-        self.nova('flavor-list')
+        self.assertIn("Memory_MB", self.nova('flavor-list'))
 
     def test_admin_floating_ip_bulk_list(self):
         self.nova('floating-ip-bulk-list')
@@ -110,7 +111,7 @@
     def test_admin_image_list(self):
         self.nova('image-list')
 
-    @testtools.skip("needs parameters")
+    @testtools.skip("Test needs parameters, Bug #1157349")
     def test_admin_interface_list(self):
         self.nova('interface-list')
 
@@ -135,7 +136,7 @@
     def test_admin_secgroup_list(self):
         self.nova('secgroup-list')
 
-    @testtools.skip("needs parameters")
+    @testtools.skip("Test needs parameters, Bug #1157349")
     def test_admin_secgroup_list_rules(self):
         self.nova('secgroup-list-rules')
 
diff --git a/cli/simple_read_only/test_compute_manage.py b/cli/simple_read_only/test_compute_manage.py
new file mode 100644
index 0000000..5768c74
--- /dev/null
+++ b/cli/simple_read_only/test_compute_manage.py
@@ -0,0 +1,83 @@
+# 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 subprocess
+
+import testtools
+
+import cli
+
+
+LOG = logging.getLogger(__name__)
+
+
+class SimpleReadOnlyNovaManageTest(cli.ClientTestBase):
+
+    """
+    This is a first pass at a simple read only nova-manage test. This
+    only exercises client commands that are read only.
+
+    This should test commands:
+    * with and without optional parameters
+    * initially just check return codes, and later test command outputs
+
+    """
+
+    def test_admin_fake_action(self):
+        self.assertRaises(subprocess.CalledProcessError,
+                          self.nova_manage,
+                          'this-does-nova-exist')
+
+    #NOTE(jogo): Commands in order listed in 'nova-manage -h'
+
+    # test flags
+    def test_help_flag(self):
+        self.nova_manage('', '-h')
+
+    def test_version_flag(self):
+        self.assertNotEqual("", self.nova_manage('', '--version'))
+        self.assertEqual(self.nova_manage('version'),
+                         self.nova_manage('', '--version'))
+
+    def test_debug_flag(self):
+        self.assertNotEqual("", self.nova_manage('instance_type list',
+                            '--debug'))
+
+    def test_verbose_flag(self):
+        self.assertNotEqual("", self.nova_manage('instance_type list',
+                            '--verbose'))
+
+    # test actions
+    def test_version(self):
+        self.assertNotEqual("", self.nova_manage('version'))
+
+    def test_flavor_list(self):
+        self.assertNotEqual("", self.nova_manage('flavor list'))
+        self.assertNotEqual(self.nova_manage('instance_type list'),
+                            self.nova_manage('flavor list'))
+
+    def test_db_archive_deleted_rows(self):
+        # make sure command doesn't error out
+        self.nova_manage('db archive_deleted_rows 50')
+
+    def test_db_sync(self):
+        # make sure command doesn't error out
+        self.nova_manage('db sync')
+
+    def test_db_version(self):
+        self.assertNotEqual("", self.nova_manage('db version'))
diff --git a/cli/simple_read_only/test_keystone.py b/cli/simple_read_only/test_keystone.py
new file mode 100644
index 0000000..4b14c3c
--- /dev/null
+++ b/cli/simple_read_only/test_keystone.py
@@ -0,0 +1,109 @@
+# 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 cli
+
+
+LOG = logging.getLogger(__name__)
+
+
+class SimpleReadOnlyKeystoneClientTest(cli.ClientTestBase):
+    """Basic, read-only tests for Keystone 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_admin_fake_action(self):
+        self.assertRaises(subprocess.CalledProcessError,
+                          self.keystone,
+                          'this-does-not-exist')
+
+    def test_admin_catalog_list(self):
+        out = self.keystone('catalog')
+        catalog = self.parser.details_multiple(out, with_label=True)
+        for svc in catalog:
+            self.assertTrue(svc['__label'].startswith('Service:'))
+
+    def test_admin_endpoint_list(self):
+        out = self.keystone('endpoint-list')
+        endpoints = self.parser.listing(out)
+        self.assertTableStruct(endpoints, [
+            'id', 'region', 'publicurl', 'internalurl',
+            'adminurl', 'service_id'])
+
+    def test_admin_endpoint_service_match(self):
+        endpoints = self.parser.listing(self.keystone('endpoint-list'))
+        services = self.parser.listing(self.keystone('service-list'))
+        svc_by_id = {}
+        for svc in services:
+            svc_by_id[svc['id']] = svc
+        for endpoint in endpoints:
+            self.assertIn(endpoint['service_id'], svc_by_id)
+
+    def test_admin_role_list(self):
+        roles = self.parser.listing(self.keystone('role-list'))
+        self.assertTableStruct(roles, ['id', 'name'])
+
+    def test_admin_service_list(self):
+        services = self.parser.listing(self.keystone('service-list'))
+        self.assertTableStruct(services, ['id', 'name', 'type', 'description'])
+
+    def test_admin_tenant_list(self):
+        tenants = self.parser.listing(self.keystone('tenant-list'))
+        self.assertTableStruct(tenants, ['id', 'name', 'enabled'])
+
+    def test_admin_user_list(self):
+        users = self.parser.listing(self.keystone('user-list'))
+        self.assertTableStruct(users, [
+            'id', 'name', 'enabled', 'email'])
+
+    def test_admin_user_role_list(self):
+        user_roles = self.parser.listing(self.keystone('user-role-list'))
+        self.assertTableStruct(user_roles, [
+            'id', 'name', 'user_id', 'tenant_id'])
+
+    def test_admin_discover(self):
+        discovered = self.keystone('discover')
+        self.assertIn('Keystone found at http', discovered)
+        self.assertIn('supports version', discovered)
+
+    def test_admin_help(self):
+        help_text = self.keystone('help')
+        lines = help_text.split('\n')
+        self.assertTrue(lines[0].startswith('usage: keystone'))
+
+        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(('catalog', 'endpoint-list', 'help',
+                               'token-get', 'discover', 'bootstrap'))
+        self.assertFalse(wanted_commands - commands)
+
+    def test_admin_bashcompletion(self):
+        self.keystone('bash-completion')
diff --git a/tempest/common/rest_client.py b/tempest/common/rest_client.py
index 5ce3be6..d68b9ed 100644
--- a/tempest/common/rest_client.py
+++ b/tempest/common/rest_client.py
@@ -15,6 +15,7 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+import collections
 import hashlib
 import httplib2
 import json
@@ -60,6 +61,8 @@
                                        'location', 'proxy-authenticate',
                                        'retry-after', 'server',
                                        'vary', 'www-authenticate'))
+        dscv = self.config.identity.disable_ssl_certificate_validation
+        self.http_obj = httplib2.Http(disable_ssl_certificate_validation=dscv)
 
     def _set_auth(self):
         """
@@ -105,8 +108,6 @@
         params['headers'] = {'User-Agent': 'Test-Client', 'X-Auth-User': user,
                              'X-Auth-Key': password}
 
-        dscv = self.config.identity.disable_ssl_certificate_validation
-        self.http_obj = httplib2.Http(disable_ssl_certificate_validation=dscv)
         resp, body = self.http_obj.request(auth_url, 'GET', **params)
         try:
             return resp['x-auth-token'], resp['x-server-management-url']
@@ -132,8 +133,6 @@
             }
         }
 
-        dscv = self.config.identity.disable_ssl_certificate_validation
-        self.http_obj = httplib2.Http(disable_ssl_certificate_validation=dscv)
         headers = {'Content-Type': 'application/json'}
         body = json.dumps(creds)
         resp, body = self.http_obj.request(auth_url, 'POST',
@@ -192,6 +191,13 @@
     def copy(self, url, headers=None):
         return self.request('COPY', url, headers)
 
+    def get_versions(self):
+        resp, body = self.get('')
+        body = self._parse_resp(body)
+        body = body['versions']
+        versions = map(lambda x: x['id'], body)
+        return resp, versions
+
     def _log_request(self, method, req_url, headers, body):
         self.LOG.info('Request: ' + method + ' ' + req_url)
         if headers:
@@ -252,23 +258,14 @@
         # Usually RFC2616 says error responses SHOULD contain an explanation.
         # The warning is normal for SHOULD/SHOULD NOT case
 
-        # Likely it will cause error
-        if not body and resp.status >= 400:
+        # Likely it will cause an error
+        if not resp_body and resp.status >= 400:
             self.LOG.warning("status >= 400 response with empty body")
 
-    def request(self, method, url,
-                headers=None, body=None, depth=0):
+    def _request(self, method, url,
+                 headers=None, body=None):
         """A simple HTTP request interface."""
 
-        if (self.token is None) or (self.base_url is None):
-            self._set_auth()
-
-        dscv = self.config.identity.disable_ssl_certificate_validation
-        self.http_obj = httplib2.Http(disable_ssl_certificate_validation=dscv)
-        if headers is None:
-            headers = {}
-        headers['X-Auth-Token'] = self.token
-
         req_url = "%s/%s" % (self.base_url, url)
         self._log_request(method, req_url, headers, body)
         resp, resp_body = self.http_obj.request(req_url, method,
@@ -276,12 +273,37 @@
         self._log_response(resp, resp_body)
         self.response_checker(method, url, headers, body, resp, resp_body)
 
-        self._error_checker(method, url, headers, body, resp, resp_body, depth)
+        return resp, resp_body
 
+    def request(self, method, url,
+                headers=None, body=None):
+        retry = 0
+        if (self.token is None) or (self.base_url is None):
+            self._set_auth()
+
+        if headers is None:
+            headers = {}
+        headers['X-Auth-Token'] = self.token
+
+        resp, resp_body = self._request(method, url,
+                                        headers=headers, body=body)
+
+        while (resp.status == 413 and
+               'retry-after' in resp and
+                not self.is_absolute_limit(
+                    resp, self._parse_resp(resp_body)) and
+                retry < MAX_RECURSION_DEPTH):
+            retry += 1
+            delay = int(resp['retry-after'])
+            time.sleep(delay)
+            resp, resp_body = self._request(method, url,
+                                            headers=headers, body=body)
+        self._error_checker(method, url, headers, body,
+                            resp, resp_body)
         return resp, resp_body
 
     def _error_checker(self, method, url,
-                       headers, body, resp, resp_body, depth=0):
+                       headers, body, resp, resp_body):
 
         # NOTE(mtreinish): Check for httplib response from glance_http. The
         # object can't be used here because importing httplib breaks httplib2.
@@ -333,9 +355,10 @@
         if resp.status == 413:
             if parse_resp:
                 resp_body = self._parse_resp(resp_body)
-            #Checking whether Absolute/Rate limit
-            return self.check_over_limit(resp_body, method, url, headers, body,
-                                         depth)
+            if self.is_absolute_limit(resp, resp_body):
+                raise exceptions.OverLimit(resp_body)
+            else:
+                raise exceptions.RateLimitExceeded(resp_body)
 
         if resp.status == 422:
             if parse_resp:
@@ -365,34 +388,14 @@
                 resp_body = self._parse_resp(resp_body)
             raise exceptions.RestClientException(str(resp.status))
 
-    def check_over_limit(self, resp_body, method, url,
-                         headers, body, depth):
-        self.is_absolute_limit(resp_body['overLimit'])
-        return self.is_rate_limit_retry_max_recursion_depth(
-            resp_body['overLimit'], method, url, headers,
-            body, depth)
-
-    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):
-        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)
-            else:
-                raise exceptions.RateLimitExceeded(
-                    message=resp_body['overLimitFault']['message'],
-                    details=resp_body['overLimitFault']['details'])
-        else:
-            raise exceptions.OverLimit(resp_body['message'])
+    def is_absolute_limit(self, resp, resp_body):
+        if (not isinstance(resp_body, collections.Mapping) or
+            'retry-after' not in resp):
+            return True
+        over_limit = resp_body.get('overLimit', None)
+        if not over_limit:
+            return True
+        return 'exceed' in over_limit.get('message', 'blabla')
 
     def wait_for_resource_deletion(self, id):
         """Waits for a resource to be deleted."""
@@ -419,9 +422,8 @@
     def _parse_resp(self, body):
         return xml_to_json(etree.fromstring(body))
 
-    def check_over_limit(self, resp_body, method, url,
-                         headers, body, depth):
-        self.is_absolute_limit(resp_body)
-        return self.is_rate_limit_retry_max_recursion_depth(
-            resp_body, method, url, headers,
-            body, depth)
+    def is_absolute_limit(self, resp, resp_body):
+        if (not isinstance(resp_body, collections.Mapping) or
+            'retry-after' not in resp):
+            return True
+        return 'exceed' in resp_body.get('message', 'blabla')
diff --git a/tempest/services/compute/json/security_groups_client.py b/tempest/services/compute/json/security_groups_client.py
index 95f2831..7f430d8 100644
--- a/tempest/services/compute/json/security_groups_client.py
+++ b/tempest/services/compute/json/security_groups_client.py
@@ -95,3 +95,12 @@
     def delete_security_group_rule(self, group_rule_id):
         """Deletes the provided Security Group rule."""
         return self.delete('os-security-group-rules/%s' % str(group_rule_id))
+
+    def list_security_group_rules(self, security_group_id):
+        """List all rules for a security group."""
+        resp, body = self.get('os-security-groups')
+        body = json.loads(body)
+        for sg in body['security_groups']:
+            if sg['id'] == security_group_id:
+                return resp, sg['rules']
+        raise exceptions.NotFound('No such Security Group')
diff --git a/tempest/services/compute/xml/common.py b/tempest/services/compute/xml/common.py
index bbc4e38..4b1b11a 100644
--- a/tempest/services/compute/xml/common.py
+++ b/tempest/services/compute/xml/common.py
@@ -100,7 +100,8 @@
     """
     json = {}
     for attr in node.keys():
-        json[attr] = node.get(attr)
+        if not attr.startswith("xmlns"):
+            json[attr] = node.get(attr)
     if not node.getchildren():
         return node.text or json
     for child in node.getchildren():
diff --git a/tempest/services/compute/xml/security_groups_client.py b/tempest/services/compute/xml/security_groups_client.py
index ac70f1b..7db60a1 100644
--- a/tempest/services/compute/xml/security_groups_client.py
+++ b/tempest/services/compute/xml/security_groups_client.py
@@ -23,6 +23,7 @@
 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
+from tempest.services.compute.xml.common import XMLNS_11
 
 
 class SecurityGroupsClientXML(RestClientXML):
@@ -128,3 +129,16 @@
         """Deletes the provided Security Group rule."""
         return self.delete('os-security-group-rules/%s' %
                            str(group_rule_id), self.headers)
+
+    def list_security_group_rules(self, security_group_id):
+        """List all rules for a security group."""
+        url = "os-security-groups"
+        resp, body = self.get(url, self.headers)
+        body = etree.fromstring(body)
+        secgroups = body.getchildren()
+        for secgroup in secgroups:
+            if secgroup.get('id') == security_group_id:
+                node = secgroup.find('{%s}rules' % XMLNS_11)
+                rules = [xml_to_json(x) for x in node.getchildren()]
+                return resp, rules
+        raise exceptions.NotFound('No such Security Group')
diff --git a/tempest/services/compute/xml/servers_client.py b/tempest/services/compute/xml/servers_client.py
index fceeb28..008417b 100644
--- a/tempest/services/compute/xml/servers_client.py
+++ b/tempest/services/compute/xml/servers_client.py
@@ -401,6 +401,19 @@
     def remove_security_group(self, server_id, name):
         return self.action(server_id, 'removeSecurityGroup', None, name=name)
 
+    def live_migrate_server(self, server_id, dest_host, use_block_migration):
+        """This should be called with administrator privileges ."""
+
+        req_body = Element("os-migrateLive",
+                           xmlns=XMLNS_11,
+                           disk_over_commit=False,
+                           block_migration=use_block_migration,
+                           host=dest_host)
+
+        resp, body = self.post("servers/%s/action" % str(server_id),
+                               str(Document(req_body)), self.headers)
+        return resp, body
+
     def list_server_metadata(self, server_id):
         resp, body = self.get("servers/%s/metadata" % str(server_id),
                               self.headers)
diff --git a/tempest/services/image/v1/json/image_client.py b/tempest/services/image/v1/json/image_client.py
index 45e93e2..77c9cd2 100644
--- a/tempest/services/image/v1/json/image_client.py
+++ b/tempest/services/image/v1/json/image_client.py
@@ -121,28 +121,25 @@
         body = json.loads(''.join([c for c in body_iter]))
         return resp, body['image']
 
-    def create_image(self, name, container_format, disk_format, is_public=True,
-                     location=None, properties=None, data=None):
+    def create_image(self, name, container_format, disk_format, **kwargs):
         params = {
             "name": name,
             "container_format": container_format,
             "disk_format": disk_format,
-            "is_public": is_public,
         }
+
         headers = {}
 
-        if location is not None:
-            params['location'] = location
-
-        if properties is not None:
-            params['properties'] = properties
+        for option in ['is_public', 'location', 'properties']:
+            if option in kwargs:
+                params[option] = kwargs.get(option)
 
         headers.update(self._image_meta_to_headers(params))
 
-        if data is not None:
-            return self._create_with_data(headers, data)
+        if 'data' in kwargs:
+            return self._create_with_data(headers, kwargs.get('data'))
 
-        resp, body = self.post('v1/images', data, headers)
+        resp, body = self.post('v1/images', None, headers)
         body = json.loads(body)
         return resp, body['image']
 
diff --git a/tempest/services/image/v2/json/image_client.py b/tempest/services/image/v2/json/image_client.py
index bcae79b..2c50a8d 100644
--- a/tempest/services/image/v2/json/image_client.py
+++ b/tempest/services/image/v2/json/image_client.py
@@ -63,17 +63,20 @@
 
         jsonschema.validate(body, schema)
 
-    def create_image(self, name, container_format, disk_format, is_public=True,
-                     properties=None):
+    def create_image(self, name, container_format, disk_format, **kwargs):
         params = {
             "name": name,
             "container_format": container_format,
             "disk_format": disk_format,
         }
-        if is_public:
-            params["visibility"] = "public"
-        else:
-            params["visibility"] = "private"
+
+        for option in ['visibility']:
+            if option in kwargs:
+                value = kwargs.get(option)
+                if isinstance(value, dict) or isinstance(value, tuple):
+                    params.update(value)
+                else:
+                    params[option] = value
 
         data = json.dumps(params)
         self._validate_schema(data)
diff --git a/tempest/services/volume/xml/admin/volume_types_client.py b/tempest/services/volume/xml/admin/volume_types_client.py
index ba4ba67..49cbadb 100644
--- a/tempest/services/volume/xml/admin/volume_types_client.py
+++ b/tempest/services/volume/xml/admin/volume_types_client.py
@@ -149,11 +149,16 @@
         url = "types/%s/extra_specs" % str(vol_type_id)
         extra_specs = Element("extra_specs", xmlns=XMLNS_11)
         if extra_spec:
-            for key, value in extra_spec.items():
-                spec = Element('extra_spec')
-                spec.add_attr('key', key)
-                spec.append(Text(value))
-                extra_specs.append(spec)
+            if isinstance(extra_spec, list):
+                extra_specs.append(extra_spec)
+            else:
+                for key, value in extra_spec.items():
+                    spec = Element('extra_spec')
+                    spec.add_attr('key', key)
+                    spec.append(Text(value))
+                    extra_specs.append(spec)
+        else:
+            extra_specs = None
 
         resp, body = self.post(url, str(Document(extra_specs)),
                                self.headers)
diff --git a/tempest/tests/boto/test_s3_objects.py b/tempest/tests/boto/test_s3_objects.py
index d50dc45..c735215 100644
--- a/tempest/tests/boto/test_s3_objects.py
+++ b/tempest/tests/boto/test_s3_objects.py
@@ -35,7 +35,6 @@
         cls.os = clients.Manager()
         cls.client = cls.os.s3_client
 
-    @testtools.skip("Skipped until the Bug #1076534 is resolved")
     @attr(type='smoke')
     def test_create_get_delete_object(self):
         # S3 Create, get and delete object
diff --git a/tempest/tests/compute/admin/test_quotas.py b/tempest/tests/compute/admin/test_quotas.py
index 46a0c20..befcad3 100644
--- a/tempest/tests/compute/admin/test_quotas.py
+++ b/tempest/tests/compute/admin/test_quotas.py
@@ -44,7 +44,7 @@
         cls.default_quota_set = {'injected_file_content_bytes': 10240,
                                  'metadata_items': 128, 'injected_files': 5,
                                  'ram': 51200, 'floating_ips': 10,
-                                 'key_pairs': 100,
+                                 'fixed_ips': 10, 'key_pairs': 100,
                                  'injected_file_path_bytes': 255,
                                  'instances': 10, 'security_group_rules': 20,
                                  'cores': 20, 'security_groups': 10}
@@ -60,6 +60,9 @@
 
     @attr(type='smoke')
     def test_get_default_quotas(self):
+        # Tempest two step
+        self.skipTest('Skipped until the Bug 1125468 is resolved')
+
         # Admin can get the default resource quota set for a tenant
         expected_quota_set = self.default_quota_set.copy()
         expected_quota_set['id'] = self.demo_tenant_id
@@ -71,13 +74,16 @@
             self.fail("Admin could not get the default quota set for a tenant")
 
     def test_update_all_quota_resources_for_tenant(self):
+        # Tempest two step
+        self.skipTest('Skipped until the Bug 1125468 is resolved')
+
         # Admin can update all the resource quota limits for a tenant
         new_quota_set = {'injected_file_content_bytes': 20480,
                          'metadata_items': 256, 'injected_files': 10,
-                         'ram': 10240, 'floating_ips': 20, 'key_pairs': 200,
-                         'injected_file_path_bytes': 512, 'instances': 20,
-                         'security_group_rules': 20, 'cores': 2,
-                         'security_groups': 20}
+                         'ram': 10240, 'floating_ips': 20, 'fixed_ips': 10,
+                         'key_pairs': 200, 'injected_file_path_bytes': 512,
+                         'instances': 20, 'security_group_rules': 20,
+                         'cores': 2, 'security_groups': 20}
         try:
             # Update limits for all quota resources
             resp, quota_set = self.adm_client.update_quota_set(
@@ -97,6 +103,9 @@
 
     #TODO(afazekas): merge these test cases
     def test_get_updated_quotas(self):
+        # Tempest two step
+        self.skipTest('Skipped until the Bug 1125468 is resolved')
+
         # Verify that GET shows the updated quota set
         self.adm_client.update_quota_set(self.demo_tenant_id,
                                          ram='5120')
@@ -135,15 +144,10 @@
 
         self.adm_client.update_quota_set(self.demo_tenant_id,
                                          ram=mem_quota)
-        try:
-            self.create_server()
-        except exceptions.OverLimit:
-            pass
-        else:
-            self.fail("Could create servers over the memory quota limit")
-        finally:
-            self.adm_client.update_quota_set(self.demo_tenant_id,
-                                             ram=default_mem_quota)
+
+        self.addCleanup(self.adm_client.update_quota_set, self.demo_tenant_id,
+                        ram=default_mem_quota)
+        self.assertRaises(exceptions.OverLimit, self.create_server)
 
 #TODO(afazekas): Add test that tried to update the quota_set as a regular user
 
diff --git a/tempest/tests/compute/floating_ips/test_floating_ips_actions.py b/tempest/tests/compute/floating_ips/test_floating_ips_actions.py
index 78494c8..2b21710 100644
--- a/tempest/tests/compute/floating_ips/test_floating_ips_actions.py
+++ b/tempest/tests/compute/floating_ips/test_floating_ips_actions.py
@@ -33,10 +33,7 @@
         cls.servers_client = cls.servers_client
 
         #Server creation
-        resp, server = cls.servers_client.create_server('floating-server',
-                                                        cls.image_ref,
-                                                        cls.flavor_ref)
-        cls.servers_client.wait_for_server_status(server['id'], 'ACTIVE')
+        resp, server = cls.create_server(wait_until='ACTIVE')
         cls.server_id = server['id']
         resp, body = cls.servers_client.get_server(server['id'])
         #Floating IP creation
@@ -55,10 +52,8 @@
 
     @classmethod
     def tearDownClass(cls):
-        super(FloatingIPsTestJSON, cls).tearDownClass()
-        #Deleting the server which is created in this method
-        resp, body = cls.servers_client.delete_server(cls.server_id)
         #Deleting the floating IP which is created in this method
+        super(FloatingIPsTestJSON, cls).tearDownClass()
         resp, body = cls.client.delete_floating_ip(cls.floating_ip_id)
 
     @attr(type='positive')
diff --git a/tempest/tests/compute/images/test_image_metadata.py b/tempest/tests/compute/images/test_image_metadata.py
index 311ee8e..918075c 100644
--- a/tempest/tests/compute/images/test_image_metadata.py
+++ b/tempest/tests/compute/images/test_image_metadata.py
@@ -30,14 +30,9 @@
         cls.servers_client = cls.servers_client
         cls.client = cls.images_client
 
-        name = rand_name('server')
-        resp, server = cls.servers_client.create_server(name, cls.image_ref,
-                                                        cls.flavor_ref)
+        resp, server = cls.create_server(wait_until='ACTIVE')
         cls.server_id = server['id']
 
-        #Wait for the server to become active
-        cls.servers_client.wait_for_server_status(cls.server_id, 'ACTIVE')
-
         # Snapshot the server once to save time
         name = rand_name('image')
         resp, _ = cls.client.create_image(cls.server_id, name, {})
@@ -49,7 +44,6 @@
     @classmethod
     def tearDownClass(cls):
         cls.client.delete_image(cls.image_id)
-        cls.servers_client.delete_server(cls.server_id)
         super(ImagesMetadataTest, cls).tearDownClass()
 
     def setUp(self):
diff --git a/tempest/tests/compute/images/test_images.py b/tempest/tests/compute/images/test_images.py
index a61cef6..d9e4153 100644
--- a/tempest/tests/compute/images/test_images.py
+++ b/tempest/tests/compute/images/test_images.py
@@ -56,6 +56,14 @@
             self.image_ids.remove(image_id)
         super(ImagesTestJSON, self).tearDown()
 
+    def __create_image__(self, server_id, name, meta=None):
+        resp, body = self.client.create_image(server_id, name, meta)
+        image_id = parse_image_id(resp['location'])
+        self.client.wait_for_image_resp_code(image_id, 200)
+        self.client.wait_for_image_status(image_id, 'ACTIVE')
+        self.image_ids.append(image_id)
+        return resp, body
+
     @attr(type='negative')
     def test_create_image_from_deleted_server(self):
         # An image should not be created if the server instance is removed
@@ -63,43 +71,24 @@
 
         # Delete server before trying to create server
         self.servers_client.delete_server(server['id'])
-
-        try:
-            # Create a new image after server is deleted
-            name = rand_name('image')
-            meta = {'image_type': 'test'}
-            resp, body = self.client.create_image(server['id'], name, meta)
-
-        except Exception:
-            pass
-
-        else:
-            image_id = parse_image_id(resp['location'])
-            self.client.wait_for_image_resp_code(image_id, 200)
-            self.client.wait_for_image_status(image_id, 'ACTIVE')
-            self.client.delete_image(image_id)
-            self.fail("Should not create snapshot from deleted instance!")
+        self.servers_client.wait_for_server_termination(server['id'])
+        # Create a new image after server is deleted
+        name = rand_name('image')
+        meta = {'image_type': 'test'}
+        self.assertRaises(exceptions.NotFound,
+                          self.__create_image__,
+                          server['id'], name, meta)
 
     @attr(type='negative')
     def test_create_image_from_invalid_server(self):
         # An image should not be created with invalid server id
-        try:
-            # Create a new image with invalid server id
-            name = rand_name('image')
-            meta = {'image_type': 'test'}
-            resp = {}
-            resp['status'] = None
-            resp, body = self.client.create_image('!@#$%^&*()', name, meta)
-
-        except exceptions.NotFound:
-            pass
-
-        finally:
-            if (resp['status'] is not None):
-                image_id = parse_image_id(resp['location'])
-                resp, _ = self.client.delete_image(image_id)
-                self.fail("An image should not be created "
-                          "with invalid server id")
+        # Create a new image with invalid server id
+        name = rand_name('image')
+        meta = {'image_type': 'test'}
+        resp = {}
+        resp['status'] = None
+        self.assertRaises(exceptions.NotFound, self.__create_image__,
+                          '!@#$%^&*()', name, meta)
 
     @attr(type='negative')
     def test_create_image_when_server_is_terminating(self):
@@ -119,7 +108,7 @@
         self.assertRaises(exceptions.Duplicate, self.client.create_image,
                           server['id'], snapshot_name)
 
-    @testtools.skip("Until Bug 1039739 is fixed")
+    @testtools.skip("Until Bug #1039739 is fixed")
     @attr(type='negative')
     def test_create_image_when_server_is_rebooting(self):
         # Return error when creating an image of server that is rebooting
diff --git a/tempest/tests/compute/images/test_images_oneserver.py b/tempest/tests/compute/images/test_images_oneserver.py
index f7008f0..9412d39 100644
--- a/tempest/tests/compute/images/test_images_oneserver.py
+++ b/tempest/tests/compute/images/test_images_oneserver.py
@@ -58,7 +58,7 @@
             cls.alt_client = cls.alt_manager.images_client
 
     @attr(type='negative')
-    @testtools.skip("Until Bug 1006725 is fixed")
+    @testtools.skip("Until Bug #1006725 is fixed")
     def test_create_image_specify_multibyte_character_image_name(self):
         # Return an error if the image name has multi-byte characters
         snapshot_name = rand_name('\xef\xbb\xbf')
@@ -67,7 +67,7 @@
                           snapshot_name)
 
     @attr(type='negative')
-    @testtools.skip("Until Bug 1005423 is fixed")
+    @testtools.skip("Until Bug #1005423 is fixed")
     def test_create_image_specify_invalid_metadata(self):
         # Return an error when creating image with invalid metadata
         snapshot_name = rand_name('test-snap-')
@@ -76,7 +76,7 @@
                           self.server['id'], snapshot_name, meta)
 
     @attr(type='negative')
-    @testtools.skip("Until Bug 1005423 is fixed")
+    @testtools.skip("Until Bug #1005423 is fixed")
     def test_create_image_specify_metadata_over_limits(self):
         # Return an error when creating image with meta data over 256 chars
         snapshot_name = rand_name('test-snap-')
diff --git a/tempest/tests/compute/images/test_images_whitebox.py b/tempest/tests/compute/images/test_images_whitebox.py
index 8af812c..105a38a 100644
--- a/tempest/tests/compute/images/test_images_whitebox.py
+++ b/tempest/tests/compute/images/test_images_whitebox.py
@@ -36,15 +36,8 @@
 
     @classmethod
     def tearDownClass(cls):
-        """Terminate test instances created after a test is executed."""
-
-        for server in cls.servers:
-            cls.update_state(server['id'], "active", None)
-            resp, body = cls.servers_client.delete_server(server['id'])
-            if resp['status'] == '204':
-                cls.servers.remove(server)
-                cls.servers_client.wait_for_server_termination(server['id'])
-
+        """Delete images after a test is executed."""
+        super(ImagesWhiteboxTest, cls).tearDownClass()
         for image_id in cls.image_ids:
             cls.client.delete_image(image_id)
             cls.image_ids.remove(image_id)
diff --git a/tempest/tests/compute/keypairs/test_keypairs.py b/tempest/tests/compute/keypairs/test_keypairs.py
index b48b439..87c71aa 100644
--- a/tempest/tests/compute/keypairs/test_keypairs.py
+++ b/tempest/tests/compute/keypairs/test_keypairs.py
@@ -15,8 +15,6 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-import testtools
-
 from tempest.common.utils.data_utils import rand_name
 from tempest import exceptions
 from tempest.test import attr
@@ -82,7 +80,6 @@
         self.assertEqual(202, resp.status)
 
     @attr(type='positive')
-    @testtools.skip("Skipped until the Bug #980688 is resolved")
     def test_get_keypair_detail(self):
         # Keypair should be created, Got details by name and deleted
         k_name = rand_name('keypair-')
@@ -138,7 +135,6 @@
                           self.client.create_keypair, k_name, pub_key)
 
     @attr(type='negative')
-    @testtools.skip("Skipped until the Bug #1086980 is resolved")
     def test_keypair_delete_nonexistant_key(self):
         # Non-existant key deletion should throw a proper error
         k_name = rand_name("keypair-non-existant-")
diff --git a/tempest/tests/compute/security_groups/test_security_group_rules.py b/tempest/tests/compute/security_groups/test_security_group_rules.py
index dc85f4b..99d9a5d 100644
--- a/tempest/tests/compute/security_groups/test_security_group_rules.py
+++ b/tempest/tests/compute/security_groups/test_security_group_rules.py
@@ -232,6 +232,49 @@
                           self.client.delete_security_group_rule,
                           rand_name('999'))
 
+    @attr(type='positive')
+    def test_security_group_rules_list(self):
+        # Positive test: Created Security Group rules should be
+        # in the list of all rules
+        # Creating a Security Group to add rules to it
+        s_name = rand_name('securitygroup-')
+        s_description = rand_name('description-')
+        resp, securitygroup = \
+            self.client.create_security_group(s_name, s_description)
+        securitygroup_id = securitygroup['id']
+        # Delete the Security Group at the end of this method
+        self.addCleanup(self.client.delete_security_group, securitygroup_id)
+
+        # Add a first rule to the created Security Group
+        ip_protocol1 = 'tcp'
+        from_port1 = 22
+        to_port1 = 22
+        resp, rule = \
+            self.client.create_security_group_rule(securitygroup_id,
+                                                   ip_protocol1,
+                                                   from_port1, to_port1)
+        rule1_id = rule['id']
+        # Delete the Security Group rule1 at the end of this method
+        self.addCleanup(self.client.delete_security_group_rule, rule1_id)
+
+        # Add a second rule to the created Security Group
+        ip_protocol2 = 'icmp'
+        from_port2 = -1
+        to_port2 = -1
+        resp, rule = \
+            self.client.create_security_group_rule(securitygroup_id,
+                                                   ip_protocol2,
+                                                   from_port2, to_port2)
+        rule2_id = rule['id']
+        # Delete the Security Group rule2 at the end of this method
+        self.addCleanup(self.client.delete_security_group_rule, rule2_id)
+
+        # Get rules of the created Security Group
+        resp, rules = \
+            self.client.list_security_group_rules(securitygroup_id)
+        self.assertTrue(any([i for i in rules if i['id'] == rule1_id]))
+        self.assertTrue(any([i for i in rules if i['id'] == rule2_id]))
+
 
 class SecurityGroupRulesTestXML(SecurityGroupRulesTestJSON):
     _interface = 'xml'
diff --git a/tempest/tests/compute/servers/test_server_actions.py b/tempest/tests/compute/servers/test_server_actions.py
index 5046ec2..5634784 100644
--- a/tempest/tests/compute/servers/test_server_actions.py
+++ b/tempest/tests/compute/servers/test_server_actions.py
@@ -41,7 +41,7 @@
         # Check if the server is in a clean state after test
         try:
             self.client.wait_for_server_status(self.server_id, 'ACTIVE')
-        except exceptions:
+        except Exception:
             # Rebuild server if something happened to it during a test
             self.rebuild_servers()
 
@@ -87,7 +87,7 @@
             self.assertGreater(new_boot_time, boot_time)
 
     @attr(type='smoke')
-    @testtools.skip('Until bug 1014647 is dealt with.')
+    @testtools.skip('Until Bug #1014647 is dealt with.')
     def test_reboot_server_soft(self):
         # The server should be signaled to reboot gracefully
         if self.run_ssh:
@@ -239,7 +239,7 @@
                           '!@#$%^&*()', 10)
 
     @attr(type='positive')
-    @testtools.skip('Until tempest bug 1014683 is fixed.')
+    @testtools.skip('Until tempest Bug #1014683 is fixed.')
     def test_get_console_output_server_id_in_reboot_status(self):
         # Positive test:Should be able to GET the console output
         # for a given server_id in reboot status
diff --git a/tempest/tests/compute/servers/test_server_addresses.py b/tempest/tests/compute/servers/test_server_addresses.py
index 4807d1e..cb8e85e 100644
--- a/tempest/tests/compute/servers/test_server_addresses.py
+++ b/tempest/tests/compute/servers/test_server_addresses.py
@@ -29,16 +29,7 @@
         super(ServerAddressesTest, cls).setUpClass()
         cls.client = cls.servers_client
 
-        cls.name = rand_name('server')
-        resp, cls.server = cls.client.create_server(cls.name,
-                                                    cls.image_ref,
-                                                    cls.flavor_ref)
-        cls.client.wait_for_server_status(cls.server['id'], 'ACTIVE')
-
-    @classmethod
-    def tearDownClass(cls):
-        cls.client.delete_server(cls.server['id'])
-        super(ServerAddressesTest, cls).tearDownClass()
+        resp, cls.server = cls.create_server(wait_until='ACTIVE')
 
     @attr(type='negative', category='server-addresses')
     def test_list_server_addresses_invalid_server_id(self):
diff --git a/tempest/tests/compute/servers/test_server_metadata.py b/tempest/tests/compute/servers/test_server_metadata.py
index bc523de..69c0ad9 100644
--- a/tempest/tests/compute/servers/test_server_metadata.py
+++ b/tempest/tests/compute/servers/test_server_metadata.py
@@ -32,18 +32,10 @@
         resp, tenants = cls.admin_client.list_tenants()
         cls.tenant_id = [tnt['id'] for tnt in tenants if tnt['name'] ==
                          cls.client.tenant_name][0]
-        resp, server = cls.create_server(meta={})
+        resp, server = cls.create_server(meta={}, wait_until='ACTIVE')
 
         cls.server_id = server['id']
 
-        #Wait for the server to become active
-        cls.client.wait_for_server_status(cls.server_id, 'ACTIVE')
-
-    @classmethod
-    def tearDownClass(cls):
-        cls.client.delete_server(cls.server_id)
-        super(ServerMetadataTestJSON, cls).tearDownClass()
-
     def setUp(self):
         super(ServerMetadataTestJSON, self).setUp()
         meta = {'key1': 'value1', 'key2': 'value2'}
diff --git a/tempest/tests/compute/servers/test_server_rescue.py b/tempest/tests/compute/servers/test_server_rescue.py
index 70e3b7c..5fc730a 100644
--- a/tempest/tests/compute/servers/test_server_rescue.py
+++ b/tempest/tests/compute/servers/test_server_rescue.py
@@ -123,7 +123,7 @@
         self.servers_client.wait_for_server_status(self.server_id, 'ACTIVE')
 
     @attr(type='negative')
-    @testtools.skip("Skipped until BUG:1126163 is resolved")
+    @testtools.skip("Skipped until Bug #1126163 is resolved")
     def test_rescued_vm_reboot(self):
         self.assertRaises(exceptions.BadRequest, self.servers_client.reboot,
                           self.rescue_id, 'HARD')
@@ -136,6 +136,7 @@
                           self.image_ref_alt)
 
     @attr(type='positive')
+    @testtools.skip("Skipped due to Bug #1126187")
     def test_rescued_vm_attach_volume(self):
         client = self.volumes_extensions_client
 
@@ -164,7 +165,7 @@
         self.servers_client.wait_for_server_status(self.server_id, 'ACTIVE')
 
     @attr(type='positive')
-    @testtools.skip("Skipped until BUG:1126187 is resolved")
+    @testtools.skip("Skipped until Bug #1126187 is resolved")
     def test_rescued_vm_detach_volume(self):
         # Attach the volume to the server
         self.servers_client.attach_volume(self.server_id,
@@ -216,7 +217,7 @@
         self.servers_client.wait_for_server_status(self.server_id, 'ACTIVE')
 
     @attr(type='positive')
-    @testtools.skip("Skipped until BUG: 1126257 is resolved")
+    @testtools.skip("Skipped until Bug #1126257 is resolved")
     def test_rescued_vm_add_remove_security_group(self):
         #Add Security group
         resp, body = self.servers_client.add_security_group(self.server_id,
diff --git a/tempest/tests/compute/servers/test_servers.py b/tempest/tests/compute/servers/test_servers.py
index a8d28df..4796e86 100644
--- a/tempest/tests/compute/servers/test_servers.py
+++ b/tempest/tests/compute/servers/test_servers.py
@@ -28,111 +28,81 @@
         super(ServersTestJSON, cls).setUpClass()
         cls.client = cls.servers_client
 
+    def tearDown(self):
+        self.clear_servers()
+        super(ServersTestJSON, self).tearDown()
+
     @attr(type='positive')
     def test_create_server_with_admin_password(self):
         # If an admin password is provided on server creation, the server's
         # root password should be set to that password.
+        resp, server = self.create_server(adminPass='testpassword')
 
-        try:
-            server = None
-            resp, server = self.create_server(adminPass='testpassword')
-
-            #Verify the password is set correctly in the response
-            self.assertEqual('testpassword', server['adminPass'])
-
-        #Teardown
-        finally:
-            if server:
-                self.client.delete_server(server['id'])
+        # Verify the password is set correctly in the response
+        self.assertEqual('testpassword', server['adminPass'])
 
     def test_create_with_existing_server_name(self):
         # Creating a server with a name that already exists is allowed
 
         # TODO(sdague): clear out try, we do cleanup one layer up
-        try:
-            id1 = None
-            id2 = None
-            server_name = rand_name('server')
-            resp, server = self.create_server(name=server_name,
-                                              wait_until='ACTIVE')
-            id1 = server['id']
-            resp, server = self.create_server(name=server_name,
-                                              wait_until='ACTIVE')
-            id2 = server['id']
-            self.assertNotEqual(id1, id2, "Did not create a new server")
-            resp, server = self.client.get_server(id1)
-            name1 = server['name']
-            resp, server = self.client.get_server(id2)
-            name2 = server['name']
-            self.assertEqual(name1, name2)
-        finally:
-            for server_id in (id1, id2):
-                if server_id:
-                    self.client.delete_server(server_id)
+        server_name = rand_name('server')
+        resp, server = self.create_server(name=server_name,
+                                          wait_until='ACTIVE')
+        id1 = server['id']
+        resp, server = self.create_server(name=server_name,
+                                          wait_until='ACTIVE')
+        id2 = server['id']
+        self.assertNotEqual(id1, id2, "Did not create a new server")
+        resp, server = self.client.get_server(id1)
+        name1 = server['name']
+        resp, server = self.client.get_server(id2)
+        name2 = server['name']
+        self.assertEqual(name1, name2)
 
     @attr(type='positive')
     def test_create_specify_keypair(self):
         # Specify a keypair while creating a server
 
-        try:
-            server = None
-            key_name = rand_name('key')
-            resp, keypair = self.keypairs_client.create_keypair(key_name)
-            resp, body = self.keypairs_client.list_keypairs()
-            resp, server = self.create_server(key_name=key_name)
-            self.assertEqual('202', resp['status'])
-            self.client.wait_for_server_status(server['id'], 'ACTIVE')
-            resp, server = self.client.get_server(server['id'])
-            self.assertEqual(key_name, server['key_name'])
-        finally:
-            if server:
-                self.client.delete_server(server['id'])
+        key_name = rand_name('key')
+        resp, keypair = self.keypairs_client.create_keypair(key_name)
+        resp, body = self.keypairs_client.list_keypairs()
+        resp, server = self.create_server(key_name=key_name)
+        self.assertEqual('202', resp['status'])
+        self.client.wait_for_server_status(server['id'], 'ACTIVE')
+        resp, server = self.client.get_server(server['id'])
+        self.assertEqual(key_name, server['key_name'])
 
     @attr(type='positive')
     def test_update_server_name(self):
         # The server name should be changed to the the provided value
-        try:
-            server = None
-            resp, server = self.create_server(wait_until='ACTIVE')
+        resp, server = self.create_server(wait_until='ACTIVE')
 
-            #Update the server with a new name
-            resp, server = self.client.update_server(server['id'],
-                                                     name='newname')
-            self.assertEquals(200, resp.status)
-            self.client.wait_for_server_status(server['id'], 'ACTIVE')
+        # Update the server with a new name
+        resp, server = self.client.update_server(server['id'],
+                                                 name='newname')
+        self.assertEquals(200, resp.status)
+        self.client.wait_for_server_status(server['id'], 'ACTIVE')
 
-            #Verify the name of the server has changed
-            resp, server = self.client.get_server(server['id'])
-            self.assertEqual('newname', server['name'])
-
-        #Teardown
-        finally:
-            if server:
-                self.client.delete_server(server['id'])
+        # Verify the name of the server has changed
+        resp, server = self.client.get_server(server['id'])
+        self.assertEqual('newname', server['name'])
 
     @attr(type='positive')
     def test_update_access_server_address(self):
         # The server's access addresses should reflect the provided values
-        try:
-            server = None
-            resp, server = self.create_server(wait_until='ACTIVE')
+        resp, server = self.create_server(wait_until='ACTIVE')
 
-            #Update the IPv4 and IPv6 access addresses
-            resp, body = self.client.update_server(server['id'],
-                                                   accessIPv4='1.1.1.1',
-                                                   accessIPv6='::babe:202:202')
-            self.assertEqual(200, resp.status)
-            self.client.wait_for_server_status(server['id'], 'ACTIVE')
+        # Update the IPv4 and IPv6 access addresses
+        resp, body = self.client.update_server(server['id'],
+                                               accessIPv4='1.1.1.1',
+                                               accessIPv6='::babe:202:202')
+        self.assertEqual(200, resp.status)
+        self.client.wait_for_server_status(server['id'], 'ACTIVE')
 
-            #Verify the access addresses have been updated
-            resp, server = self.client.get_server(server['id'])
-            self.assertEqual('1.1.1.1', server['accessIPv4'])
-            self.assertEqual('::babe:202:202', server['accessIPv6'])
-
-        #Teardown
-        finally:
-            if server:
-                self.client.delete_server(server['id'])
+        # Verify the access addresses have been updated
+        resp, server = self.client.get_server(server['id'])
+        self.assertEqual('1.1.1.1', server['accessIPv4'])
+        self.assertEqual('::babe:202:202', server['accessIPv6'])
 
     def test_delete_server_while_in_building_state(self):
         # Delete a server while it's VM state is Building
diff --git a/tempest/tests/compute/servers/test_virtual_interfaces.py b/tempest/tests/compute/servers/test_virtual_interfaces.py
index 4c48366..476a556 100644
--- a/tempest/tests/compute/servers/test_virtual_interfaces.py
+++ b/tempest/tests/compute/servers/test_virtual_interfaces.py
@@ -30,20 +30,10 @@
     @classmethod
     def setUpClass(cls):
         super(VirtualInterfacesTestJSON, cls).setUpClass()
-        cls.name = rand_name('server')
         cls.client = cls.servers_client
-        resp, server = cls.servers_client.create_server(cls.name,
-                                                        cls.image_ref,
-                                                        cls.flavor_ref)
+        resp, server = cls.create_server(wait_until='ACTIVE')
         cls.server_id = server['id']
 
-        cls.servers_client.wait_for_server_status(cls.server_id, 'ACTIVE')
-
-    @classmethod
-    def tearDownClass(cls):
-        cls.servers_client.delete_server(cls.server_id)
-        super(VirtualInterfacesTestJSON, cls).tearDownClass()
-
     @attr(type='positive')
     def test_list_virtual_interfaces(self):
         # Positive test:Should be able to GET the virtual interfaces list
diff --git a/tempest/tests/compute/test_authorization.py b/tempest/tests/compute/test_authorization.py
index 52e457d..4ca197a 100644
--- a/tempest/tests/compute/test_authorization.py
+++ b/tempest/tests/compute/test_authorization.py
@@ -57,10 +57,7 @@
         cls.alt_security_client = cls.alt_manager.security_groups_client
 
         cls.alt_security_client._set_auth()
-        name = rand_name('server')
-        resp, server = cls.client.create_server(name, cls.image_ref,
-                                                cls.flavor_ref)
-        cls.client.wait_for_server_status(server['id'], 'ACTIVE')
+        resp, server = cls.create_server(wait_until='ACTIVE')
         resp, cls.server = cls.client.get_server(server['id'])
 
         name = rand_name('image')
@@ -92,7 +89,6 @@
     @classmethod
     def tearDownClass(cls):
         if compute.MULTI_USER:
-            cls.client.delete_server(cls.server['id'])
             cls.images_client.delete_image(cls.image['id'])
             cls.keypairs_client.delete_keypair(cls.keypairname)
             cls.security_client.delete_security_group(cls.security_group['id'])
@@ -207,7 +203,6 @@
                           self.alt_keypairs_client.get_keypair,
                           self.keypairname)
 
-    @testtools.skip("Skipped until the Bug #1086980 is resolved")
     def test_delete_keypair_of_alt_account_fails(self):
         # A DELETE request for another user's keypair should fail
         self.assertRaises(exceptions.NotFound,
@@ -316,60 +311,43 @@
         # A get metadata for another user's server should fail
         req_metadata = {'meta1': 'data1'}
         self.client.set_server_metadata(self.server['id'], req_metadata)
-        try:
-            resp, meta = \
-            self.alt_client.get_server_metadata_item(self.server['id'],
-                                                     'meta1')
-        except exceptions.NotFound:
-            pass
-        finally:
-            resp, body = \
-            self.client.delete_server_metadata_item(self.server['id'], 'meta1')
+        self.addCleanup(self.client.delete_server_metadata_item,
+                        self.server['id'], 'meta1')
+        self.assertRaises(exceptions.NotFound,
+                          self.alt_client.get_server_metadata_item,
+                          self.server['id'], 'meta1')
 
     def test_get_metadata_of_alt_account_image_fails(self):
         # A get metadata for another user's image should fail
         req_metadata = {'meta1': 'value1'}
+        self.addCleanup(self.images_client.delete_image_metadata_item,
+                        self.image['id'], 'meta1')
         self.images_client.set_image_metadata(self.image['id'],
                                               req_metadata)
-        try:
-            resp, meta = \
-            self.alt_images_client.get_image_metadata_item(self.image['id'],
-                                                           'meta1')
-        except exceptions.NotFound:
-            pass
-        finally:
-            resp, body = self.images_client.delete_image_metadata_item(
-                                self.image['id'], 'meta1')
+        self.assertRaises(exceptions.NotFound,
+                          self.alt_images_client.get_image_metadata_item,
+                          self.image['id'], 'meta1')
 
     def test_delete_metadata_of_alt_account_server_fails(self):
         # A delete metadata for another user's server should fail
         req_metadata = {'meta1': 'data1'}
+        self.addCleanup(self.client.delete_server_metadata_item,
+                        self.server['id'], 'meta1')
         self.client.set_server_metadata(self.server['id'], req_metadata)
-        try:
-            resp, body = \
-            self.alt_client.delete_server_metadata_item(self.server['id'],
-                                                        'meta1')
-        except exceptions.NotFound:
-            pass
-        finally:
-            resp, body = \
-            self.client.delete_server_metadata_item(self.server['id'], 'meta1')
+        self.assertRaises(exceptions.NotFound,
+                          self.alt_client.delete_server_metadata_item,
+                          self.server['id'], 'meta1')
 
     def test_delete_metadata_of_alt_account_image_fails(self):
         # A delete metadata for another user's image should fail
         req_metadata = {'meta1': 'data1'}
+        self.addCleanup(self.images_client.delete_image_metadata_item,
+                        self.image['id'], 'meta1')
         self.images_client.set_image_metadata(self.image['id'],
                                               req_metadata)
-        try:
-            resp, body = \
-            self.alt_images_client.delete_image_metadata_item(self.image['id'],
-                                                              'meta1')
-        except exceptions.NotFound:
-            pass
-        finally:
-            resp, body = \
-            self.images_client.delete_image_metadata_item(self.image['id'],
-                                                          'meta1')
+        self.assertRaises(exceptions.NotFound,
+                          self.alt_images_client.delete_image_metadata_item,
+                          self.image['id'], 'meta1')
 
     def test_get_console_output_of_alt_account_server_fails(self):
         # A Get Console Output for another user's server should fail
diff --git a/tempest/tests/compute/test_live_block_migration.py b/tempest/tests/compute/test_live_block_migration.py
index abaaf85..e5a7d5b 100644
--- a/tempest/tests/compute/test_live_block_migration.py
+++ b/tempest/tests/compute/test_live_block_migration.py
@@ -27,7 +27,7 @@
 
 
 @attr(category='live-migration')
-class LiveBlockMigrationTest(base.BaseComputeAdminTest):
+class LiveBlockMigrationTestJSON(base.BaseComputeAdminTest):
     _interface = 'json'
 
     live_migration_available = (
@@ -38,7 +38,7 @@
 
     @classmethod
     def setUpClass(cls):
-        super(LiveBlockMigrationTest, cls).setUpClass()
+        super(LiveBlockMigrationTestJSON, cls).setUpClass()
 
         cls.admin_hosts_client = cls.os_adm.hosts_client
         cls.admin_servers_client = cls.os_adm.servers_client
@@ -125,4 +125,8 @@
         for server_id in cls.created_server_ids:
             cls.servers_client.delete_server(server_id)
 
-        super(LiveBlockMigrationTest, cls).tearDownClass()
+        super(LiveBlockMigrationTestJSON, cls).tearDownClass()
+
+
+class LiveBlockMigrationTestXML(LiveBlockMigrationTestJSON):
+    _interface = 'xml'
diff --git a/tempest/tests/compute/test_quotas.py b/tempest/tests/compute/test_quotas.py
index dbff275..233d639 100644
--- a/tempest/tests/compute/test_quotas.py
+++ b/tempest/tests/compute/test_quotas.py
@@ -33,11 +33,14 @@
 
     @attr(type='smoke')
     def test_get_default_quotas(self):
+        # Tempest two step
+        self.skipTest('Skipped until the Bug 1125468 is resolved')
+
         # User can get the default quota set for it's tenant
         expected_quota_set = {'injected_file_content_bytes': 10240,
                               'metadata_items': 128, 'injected_files': 5,
                               'ram': 51200, 'floating_ips': 10,
-                              'key_pairs': 100,
+                              'fixed_ips': 10, 'key_pairs': 100,
                               'injected_file_path_bytes': 255, 'instances': 10,
                               'security_group_rules': 20, 'cores': 20,
                               'id': self.tenant_id, 'security_groups': 10}
diff --git a/tempest/tests/compute/volumes/test_attach_volume.py b/tempest/tests/compute/volumes/test_attach_volume.py
index 7c1a2d1..d9abe41 100644
--- a/tempest/tests/compute/volumes/test_attach_volume.py
+++ b/tempest/tests/compute/volumes/test_attach_volume.py
@@ -43,24 +43,15 @@
         self.servers_client.detach_volume(server_id, volume_id)
         self.volumes_client.wait_for_volume_status(volume_id, 'available')
 
-    def _delete(self, server, volume):
+    def _delete(self, volume):
         if self.volume:
             self.volumes_client.delete_volume(self.volume['id'])
             self.volume = None
-        if self.server:
-            self.servers_client.delete_server(self.server['id'])
-            self.server = None
 
     def _create_and_attach(self):
-        name = rand_name('server')
-
         # Start a server and wait for it to become ready
-        resp, server = self.servers_client.create_server(name,
-                                                         self.image_ref,
-                                                         self.flavor_ref,
-                                                         adminPass='password')
-        self.server = server
-        self.servers_client.wait_for_server_status(server['id'], 'ACTIVE')
+        resp, server = self.create_server(wait_until='ACTIVE',
+                                          adminPass='password')
 
         # Record addresses so that we can ssh later
         resp, server['addresses'] = \
@@ -118,7 +109,9 @@
         finally:
             if self.attached:
                 self._detach(server['id'], volume['id'])
-            self._delete(self.server, self.volume)
+            # NOTE(maurosr): here we do the cleanup for volume, servers are
+            # dealt on BaseComputeTest.tearDownClass
+            self._delete(self.volume)
 
 
 class AttachVolumeTestXML(AttachVolumeTestJSON):
diff --git a/tempest/tests/identity/admin/test_services.py b/tempest/tests/identity/admin/test_services.py
index caf57bd..35b2463 100644
--- a/tempest/tests/identity/admin/test_services.py
+++ b/tempest/tests/identity/admin/test_services.py
@@ -77,20 +77,17 @@
             services.append(service)
         service_ids = map(lambda x: x['id'], services)
 
+        def delete_services():
+            for service_id in service_ids:
+                self.client.delete_service(service_id)
+
+        self.addCleanup(delete_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 ServicesTestXML(ServicesTestJSON):
     _interface = 'xml'
diff --git a/tempest/tests/identity/admin/test_users.py b/tempest/tests/identity/admin/test_users.py
index 224272e..80c6fc9 100644
--- a/tempest/tests/identity/admin/test_users.py
+++ b/tempest/tests/identity/admin/test_users.py
@@ -77,7 +77,7 @@
                           self.data.tenant['id'], self.data.test_email)
 
     @attr(type='negative')
-    @testtools.skip("Until Bug 999084 is fixed")
+    @testtools.skip("Until Bug #999084 is fixed")
     def test_create_user_with_empty_password(self):
         # User with an empty password should not be created
         self.data.setup_test_tenant()
@@ -86,7 +86,7 @@
                           self.alt_email)
 
     @attr(type='nagative')
-    @testtools.skip("Until Bug 999084 is fixed")
+    @testtools.skip("Until Bug #999084 is fixed")
     def test_create_user_with_long_password(self):
         # User having password exceeding max length should not be created
         self.data.setup_test_tenant()
@@ -95,7 +95,7 @@
                           self.alt_email)
 
     @attr(type='negative')
-    @testtools.skip("Until Bug 999084 is fixed")
+    @testtools.skip("Until Bug #999084 is fixed")
     def test_create_user_with_invalid_email_format(self):
         # Email format should be validated while creating a user
         self.data.setup_test_tenant()
diff --git a/tempest/tests/image/base.py b/tempest/tests/image/base.py
new file mode 100644
index 0000000..65d81b6
--- /dev/null
+++ b/tempest/tests/image/base.py
@@ -0,0 +1,94 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# 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.
+
+import logging
+import time
+
+from tempest import clients
+from tempest.common.utils.data_utils import rand_name
+from tempest import exceptions
+import tempest.test
+
+LOG = logging.getLogger(__name__)
+
+
+class BaseImageTest(tempest.test.BaseTestCase):
+    """Base test class for Image API tests."""
+
+    @classmethod
+    def setUpClass(cls):
+        cls.os = clients.Manager()
+        cls.created_images = []
+
+    @classmethod
+    def tearDownClass(cls):
+        for image_id in cls.created_images:
+            try:
+                cls.client.delete_image(image_id)
+            except exceptions.NotFound:
+                pass
+
+        for image_id in cls.created_images:
+                cls.client.wait_for_resource_deletion(image_id)
+
+    @classmethod
+    def create_image(cls, **kwargs):
+        """Wrapper that returns a test image."""
+        name = rand_name(cls.__name__ + "-instance")
+
+        if 'name' in kwargs:
+            name = kwargs.pop('name')
+
+        container_format = kwargs.pop('container_format')
+        disk_format = kwargs.pop('disk_format')
+
+        resp, image = cls.client.create_image(name, container_format,
+                                              disk_format, **kwargs)
+        cls.created_images.append(image['id'])
+        return resp, image
+
+    @classmethod
+    def _check_version(cls, version):
+        __, versions = cls.client.get_versions()
+        if version == 'v2.0':
+            if 'v2.0' in versions:
+                return True
+        elif version == 'v1.0':
+            if 'v1.1' in versions or 'v1.0' in versions:
+                return True
+        return False
+
+
+class BaseV1ImageTest(BaseImageTest):
+
+    @classmethod
+    def setUpClass(cls):
+        super(BaseV1ImageTest, cls).setUpClass()
+        cls.client = cls.os.image_client
+        if not cls._check_version('v1.0'):
+            msg = "Glance API v1 not supported"
+            raise cls.skipException(msg)
+
+
+class BaseV2ImageTest(BaseImageTest):
+
+    @classmethod
+    def setUpClass(cls):
+        super(BaseV2ImageTest, cls).setUpClass()
+        cls.client = cls.os.image_client_v2
+        if not cls._check_version('v2.0'):
+            msg = "Glance API v2 not supported"
+            raise cls.skipException(msg)
diff --git a/tempest/tests/image/v1/test_image_members.py b/tempest/tests/image/v1/test_image_members.py
index 30fa6c6..92052fc 100644
--- a/tempest/tests/image/v1/test_image_members.py
+++ b/tempest/tests/image/v1/test_image_members.py
@@ -17,42 +17,32 @@
 import cStringIO as StringIO
 
 from tempest import clients
-import tempest.test
+from tempest.tests.image import base
 
 
-class ImageMembersTests(tempest.test.BaseTestCase):
+class ImageMembersTests(base.BaseV1ImageTest):
 
     @classmethod
     def setUpClass(cls):
-        cls.os = clients.Manager()
-        cls.client = cls.os.image_client
+        super(ImageMembersTests, cls).setUpClass()
         admin = clients.AdminManager(interface='json')
         cls.admin_client = admin.identity_client
-        cls.created_images = []
         cls.tenants = cls._get_tenants()
 
     @classmethod
-    def tearDownClass(cls):
-        for image_id in cls.created_images:
-            cls.client.delete_image(image_id)
-            cls.client.wait_for_resource_deletion(image_id)
-
-    @classmethod
     def _get_tenants(cls):
         resp, tenants = cls.admin_client.list_tenants()
         tenants = map(lambda x: x['id'], tenants)
         return tenants
 
-    def _create_image(self, name=None):
+    def _create_image(self):
         image_file = StringIO.StringIO('*' * 1024)
-        if name is not None:
-            name = 'New Standard Image with Members'
-        resp, image = self.client.create_image(name,
-                                               'bare', 'raw',
-                                               is_public=True, data=image_file)
+        resp, image = self.create_image(container_format='bare',
+                                        disk_format='raw',
+                                        is_public=True,
+                                        data=image_file)
         self.assertEquals(201, resp.status)
         image_id = image['id']
-        self.created_images.append(image_id)
         return image_id
 
     def test_add_image_member(self):
@@ -69,8 +59,7 @@
         image = self._create_image()
         resp = self.client.add_member(self.tenants[0], image)
         self.assertEquals(204, resp.status)
-        name = 'Shared Image'
-        share_image = self._create_image(name=name)
+        share_image = self._create_image()
         resp = self.client.add_member(self.tenants[0], share_image)
         self.assertEquals(204, resp.status)
         resp, body = self.client.get_shared_images(self.tenants[0])
@@ -81,8 +70,7 @@
         self.assertIn(image, images)
 
     def test_remove_member(self):
-        name = 'Shared Image for Delete Test'
-        image_id = self._create_image(name=name)
+        image_id = self._create_image()
         resp = self.client.add_member(self.tenants[0], image_id)
         self.assertEquals(204, resp.status)
         resp = self.client.delete_member(self.tenants[0], image_id)
diff --git a/tempest/tests/image/v1/test_images.py b/tempest/tests/image/v1/test_images.py
index 84bb650..af09b79 100644
--- a/tempest/tests/image/v1/test_images.py
+++ b/tempest/tests/image/v1/test_images.py
@@ -19,26 +19,12 @@
 
 from tempest import clients
 from tempest import exceptions
-import tempest.test
 from tempest.test import attr
+from tempest.tests.image import base
 
 
-class CreateRegisterImagesTest(tempest.test.BaseTestCase):
-
-    """
-    Here we test the registration and creation of images
-    """
-
-    @classmethod
-    def setUpClass(cls):
-        cls.os = clients.Manager()
-        cls.client = cls.os.image_client
-        cls.created_images = []
-
-    @classmethod
-    def tearDownClass(cls):
-        for image_id in cls.created_images:
-            cls.client.delete(image_id)
+class CreateRegisterImagesTest(base.BaseV1ImageTest):
+    """Here we test the registration and creation of images."""
 
     @attr(type='negative')
     def test_register_with_invalid_container_format(self):
@@ -55,19 +41,17 @@
     def test_register_then_upload(self):
         # Register, then upload an image
         properties = {'prop1': 'val1'}
-        resp, body = self.client.create_image('New Name', 'bare', 'raw',
-                                              is_public=True,
-                                              properties=properties)
+        resp, body = self.create_image(name='New Name',
+                                       container_format='bare',
+                                       disk_format='raw',
+                                       is_public=True,
+                                       properties=properties)
         self.assertTrue('id' in body)
         image_id = body.get('id')
         self.created_images.append(image_id)
-        self.assertTrue('name' in body)
         self.assertEqual('New Name', body.get('name'))
-        self.assertTrue('is_public' in body)
         self.assertTrue(body.get('is_public'))
-        self.assertTrue('status' in body)
         self.assertEqual('queued', body.get('status'))
-        self.assertTrue('properties' in body)
         for key, val in properties.items():
             self.assertEqual(val, body.get('properties')[key])
 
@@ -80,22 +64,20 @@
     @attr(type='image')
     def test_register_remote_image(self):
         # Register a new remote image
-        resp, body = self.client.create_image('New Remote Image', 'bare',
-                                              'raw', is_public=True,
-                                              location='http://example.com'
-                                                       '/someimage.iso')
+        resp, body = self.create_image(name='New Remote Image',
+                                       container_format='bare',
+                                       disk_format='raw', is_public=True,
+                                       location='http://example.com'
+                                                '/someimage.iso')
         self.assertTrue('id' in body)
         image_id = body.get('id')
         self.created_images.append(image_id)
-        self.assertTrue('name' in body)
         self.assertEqual('New Remote Image', body.get('name'))
-        self.assertTrue('is_public' in body)
         self.assertTrue(body.get('is_public'))
-        self.assertTrue('status' in body)
         self.assertEqual('active', body.get('status'))
 
 
-class ListImagesTest(tempest.test.BaseTestCase):
+class ListImagesTest(base.BaseV1ImageTest):
 
     """
     Here we test the listing of image information
@@ -103,9 +85,7 @@
 
     @classmethod
     def setUpClass(cls):
-        cls.os = clients.Manager()
-        cls.client = cls.os.image_client
-        cls.created_images = []
+        super(ListImagesTest, cls).setUpClass()
 
         # We add a few images here to test the listing functionality of
         # the images API
@@ -132,12 +112,6 @@
         cls.dup_set = set((img3, img4))
 
     @classmethod
-    def tearDownClass(cls):
-        for image_id in cls.created_images:
-            cls.client.delete_image(image_id)
-            cls.client.wait_for_resource_deletion(image_id)
-
-    @classmethod
     def _create_remote_image(cls, name, container_format, disk_format):
         """
         Create a new remote image and return the ID of the newly-registered
@@ -145,12 +119,12 @@
         """
         name = 'New Remote Image %s' % name
         location = 'http://example.com/someimage_%s.iso' % name
-        resp, image = cls.client.create_image(name,
-                                              container_format, disk_format,
-                                              is_public=True,
-                                              location=location)
+        resp, image = cls.create_image(name=name,
+                                       container_format=container_format,
+                                       disk_format=disk_format,
+                                       is_public=True,
+                                       location=location)
         image_id = image['id']
-        cls.created_images.append(image_id)
         return image_id
 
     @classmethod
@@ -163,11 +137,11 @@
         """
         image_file = StringIO.StringIO('*' * size)
         name = 'New Standard Image %s' % name
-        resp, image = cls.client.create_image(name,
-                                              container_format, disk_format,
-                                              is_public=True, data=image_file)
+        resp, image = cls.create_image(name=name,
+                                       container_format=container_format,
+                                       disk_format=disk_format,
+                                       is_public=True, data=image_file)
         image_id = image['id']
-        cls.created_images.append(image_id)
         return image_id
 
     @attr(type='image')
diff --git a/tempest/tests/image/v2/test_images.py b/tempest/tests/image/v2/test_images.py
index 9abf28d..19a7a95 100644
--- a/tempest/tests/image/v2/test_images.py
+++ b/tempest/tests/image/v2/test_images.py
@@ -21,27 +21,16 @@
 
 from tempest import clients
 from tempest import exceptions
-import tempest.test
 from tempest.test import attr
+from tempest.tests.image import base
 
 
-class CreateRegisterImagesTest(tempest.test.BaseTestCase):
+class CreateRegisterImagesTest(base.BaseV2ImageTest):
 
     """
     Here we test the registration and creation of images
     """
 
-    @classmethod
-    def setUpClass(cls):
-        cls.os = clients.Manager()
-        cls.client = cls.os.image_client_v2
-        cls.created_images = []
-
-    @classmethod
-    def tearDownClass(cls):
-        for image_id in cls.created_images:
-            cls.client.delete(image_id)
-
     @attr(type='negative')
     def test_register_with_invalid_container_format(self):
         # Negative tests for invalid data supplied to POST /images
@@ -56,8 +45,10 @@
     @attr(type='image')
     def test_register_then_upload(self):
         # Register, then upload an image
-        resp, body = self.client.create_image('New Name', 'bare', 'raw',
-                                              is_public=True)
+        resp, body = self.create_image(name='New Name',
+                                       container_format='bare',
+                                       disk_format='raw',
+                                       visibility='public')
         self.assertTrue('id' in body)
         image_id = body.get('id')
         self.created_images.append(image_id)
@@ -77,7 +68,7 @@
         self.assertEqual(1024, body.get('size'))
 
 
-class ListImagesTest(tempest.test.BaseTestCase):
+class ListImagesTest(base.BaseV2ImageTest):
 
     """
     Here we test the listing of image information
@@ -85,22 +76,13 @@
 
     @classmethod
     def setUpClass(cls):
-        cls.os = clients.Manager()
-        cls.client = cls.os.image_client_v2
-        cls.created_images = []
-
+        super(ListImagesTest, cls).setUpClass()
         # We add a few images here to test the listing functionality of
         # the images API
         for x in xrange(0, 10):
             cls.created_images.append(cls._create_standard_image(x))
 
     @classmethod
-    def tearDownClass(cls):
-        for image_id in cls.created_images:
-            cls.client.delete_image(image_id)
-            cls.client.wait_for_resource_deletion(image_id)
-
-    @classmethod
     def _create_standard_image(cls, number):
         """
         Create a new standard image and return the ID of the newly-registered
@@ -109,8 +91,9 @@
         """
         image_file = StringIO.StringIO('*' * random.randint(1024, 4096))
         name = 'New Standard Image %s' % number
-        resp, body = cls.client.create_image(name, 'bare', 'raw',
-                                             is_public=True)
+        resp, body = cls.create_image(name=name, container_format='bare',
+                                      disk_format='raw',
+                                      visibility='public')
         image_id = body['id']
         resp, body = cls.client.store_image(image_id, data=image_file)
 
diff --git a/tempest/tests/object_storage/test_container_sync.py b/tempest/tests/object_storage/test_container_sync.py
index dad6309..d5fa96c 100644
--- a/tempest/tests/object_storage/test_container_sync.py
+++ b/tempest/tests/object_storage/test_container_sync.py
@@ -61,7 +61,7 @@
             #Attempt to delete the container
             resp, _ = client[0].delete_container(cont_name)
 
-    @testtools.skip('Until Bug 1093743 is resolved.')
+    @testtools.skip('Until Bug #1093743 is resolved.')
     @attr(type='positive')
     def test_container_synchronization(self):
         #Container to container synchronization
diff --git a/tempest/tests/object_storage/test_object_expiry.py b/tempest/tests/object_storage/test_object_expiry.py
index 8411ef5..c12ec3d 100644
--- a/tempest/tests/object_storage/test_object_expiry.py
+++ b/tempest/tests/object_storage/test_object_expiry.py
@@ -55,7 +55,7 @@
         #Attempt to delete the container
         resp, _ = cls.container_client.delete_container(cls.container_name)
 
-    @testtools.skip('Until bug 1069849 is resolved.')
+    @testtools.skip('Until Bug #1069849 is resolved.')
     @attr(type='regression')
     def test_get_object_after_expiry_time(self):
         # GET object after expiry time
diff --git a/tempest/tests/object_storage/test_object_services.py b/tempest/tests/object_storage/test_object_services.py
index e0a2fbb..1edce92 100644
--- a/tempest/tests/object_storage/test_object_services.py
+++ b/tempest/tests/object_storage/test_object_services.py
@@ -326,7 +326,6 @@
                 self.assertIn('x-container-read', resp)
                 self.assertEqual(resp['x-container-read'], 'x')
 
-    @testtools.skip('Until Bug 1091669  is resolved.')
     @attr(type='smoke')
     def test_access_public_object_with_another_user_creds(self):
         #Make container public-readable, and access the object
@@ -591,7 +590,7 @@
                           self.container_name, object_name,
                           metadata=self.custom_headers)
 
-    @testtools.skip('Until bug 1097137 is resolved.')
+    @testtools.skip('Until Bug #1097137 is resolved.')
     @attr(type='positive')
     def test_get_object_using_temp_url(self):
         #Access object using temp url within expiry time
diff --git a/tempest/tests/volume/admin/test_volume_types_extra_specs_negative.py b/tempest/tests/volume/admin/test_volume_types_extra_specs_negative.py
index 6b274c6..f528cec 100644
--- a/tempest/tests/volume/admin/test_volume_types_extra_specs_negative.py
+++ b/tempest/tests/volume/admin/test_volume_types_extra_specs_negative.py
@@ -79,14 +79,12 @@
                           self.client.create_volume_type_extra_specs,
                           str(uuid.uuid4()), extra_specs)
 
-    @testtools.skip('Until bug 1090322 is fixed')
     def test_create_none_body(self):
         # Should not create volume type extra spec for none POST body.
         self.assertRaises(exceptions.BadRequest,
                           self.client.create_volume_type_extra_specs,
                           self.volume_type['id'], None)
 
-    @testtools.skip('Until bug 1090322 is fixed')
     def test_create_invalid_body(self):
         # Should not create volume type extra spec for invalid POST body.
         self.assertRaises(exceptions.BadRequest,
diff --git a/tempest/tests/volume/admin/test_volume_types_negative.py b/tempest/tests/volume/admin/test_volume_types_negative.py
index c706f3d..1b11d68 100644
--- a/tempest/tests/volume/admin/test_volume_types_negative.py
+++ b/tempest/tests/volume/admin/test_volume_types_negative.py
@@ -32,7 +32,6 @@
                           display_name=str(uuid.uuid4()),
                           volume_type=str(uuid.uuid4()))
 
-    @testtools.skip('Until bug 1090356 is fixed')
     def test_create_with_empty_name(self):
         # Should not be able to create volume type with an empty name.
         self.assertRaises(exceptions.BadRequest,
diff --git a/tools/find_stack_traces.py b/tools/find_stack_traces.py
new file mode 100755
index 0000000..e6c1990
--- /dev/null
+++ b/tools/find_stack_traces.py
@@ -0,0 +1,83 @@
+#!/usr/bin/env python
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 IBM Corp.
+# 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 gzip
+import re
+import StringIO
+import sys
+import urllib2
+
+
+def hunt_for_stacktrace(url):
+    """Return TRACE or ERROR lines out of logs."""
+    page = urllib2.urlopen(url)
+    buf = StringIO.StringIO(page.read())
+    f = gzip.GzipFile(fileobj=buf)
+    content = f.read()
+    traces = re.findall('^(.*? (TRACE|ERROR) .*?)$', content, re.MULTILINE)
+    tracelist = map(lambda x: x[0], traces)
+    # filter out log definitions as false possitives
+    return filter(lambda x: not re.search('logging_exception_prefix', x),
+                  tracelist)
+
+
+def log_url(url, log):
+    return "%s/%s" % (url, log)
+
+
+def collect_logs(url):
+    page = urllib2.urlopen(url)
+    content = page.read()
+    logs = re.findall('(screen-[\w-]+\.txt\.gz)</a>', content)
+    return logs
+
+
+def usage():
+    print """
+Usage: find_stack_traces.py <logurl>
+
+Hunts for stack traces in a devstack run. Must provide it a base log url
+from a tempest devstack run. Should start with http and end with /logs/.
+
+Returns a report listing stack traces out of the various files where
+they are found.
+"""
+    sys.exit(0)
+
+
+def main():
+    if len(sys.argv) == 2:
+        url = sys.argv[1]
+        loglist = collect_logs(url)
+
+        # probably wrong base url
+        if not loglist:
+            usage()
+
+        for log in loglist:
+            logurl = log_url(url, log)
+            traces = hunt_for_stacktrace(logurl)
+            if traces:
+                print "\n\nTRACES found in %s\n" % log
+                for line in traces:
+                    print line
+    else:
+        usage()
+
+if __name__ == '__main__':
+    main()
diff --git a/tools/pip-requires b/tools/pip-requires
index ee21065..e85cced 100644
--- a/tools/pip-requires
+++ b/tools/pip-requires
@@ -13,5 +13,4 @@
 testresources
 keyring
 testrepository
-
-http://tarballs.openstack.org/oslo-config/oslo.config-1.1.0b1.tar.gz#egg=oslo.config
+oslo.config>=1.1.0
diff --git a/tools/skip_tracker.py b/tools/skip_tracker.py
index e890e92..a4cf394 100755
--- a/tools/skip_tracker.py
+++ b/tools/skip_tracker.py
@@ -61,7 +61,7 @@
     """
     Return the skip tuples in a test file
     """
-    BUG_RE = re.compile(r'.*skip\(.*[bB]ug\s*(\d+)')
+    BUG_RE = re.compile(r'.*skip\(.*bug:*\s*\#*(\d+)', re.IGNORECASE)
     DEF_RE = re.compile(r'.*def (\w+)\(')
     bug_found = False
     results = []
@@ -89,6 +89,7 @@
     results = find_skips()
     unique_bugs = sorted(set([bug for (method, bug) in results]))
     unskips = []
+    duplicates = []
     info("Total bug skips found: %d", len(results))
     info("Total unique bugs causing skips: %d", len(unique_bugs))
     lp = launchpad.Launchpad.login_anonymously('grabbing bugs',
@@ -96,12 +97,26 @@
                                                LPCACHEDIR)
     for bug_no in unique_bugs:
         bug = lp.bugs[bug_no]
+        duplicate = bug.duplicate_of_link
+        if duplicate is not None:
+            dup_id = duplicate.split('/')[-1]
+            duplicates.append((bug_no, dup_id))
         for task in bug.bug_tasks:
             info("Bug #%7s (%12s - %12s)", bug_no,
                  task.importance, task.status)
             if task.status in ('Fix Released', 'Fix Committed'):
                 unskips.append(bug_no)
 
+    for bug_id, dup_id in duplicates:
+        if bug_id not in unskips:
+            dup_bug = lp.bugs[dup_id]
+            for task in dup_bug.bug_tasks:
+                info("Bug #%7s is a duplicate of Bug#%7s (%12s - %12s)",
+                     bug_id, dup_id, task.importance, task.status)
+                if task.status in ('Fix Released', 'Fix Committed'):
+                    unskips.append(bug_id)
+
+    unskips = sorted(set(unskips))
     if unskips:
         print "The following bugs have been fixed and the corresponding skips"
         print "should be removed from the test cases:"