Merge "Don't include boto tests as part of smoke tests"
diff --git a/etc/tempest.conf.sample b/etc/tempest.conf.sample
index 23d4ebc..9051310 100644
--- a/etc/tempest.conf.sample
+++ b/etc/tempest.conf.sample
@@ -327,6 +327,11 @@
 # (integer value)
 #shelved_offload_time=0
 
+# Unallocated floating IP range, which will be used to test
+# the floating IP bulk feature for CRUD operation. (string
+# value)
+#floating_ip_range=10.0.0.0/29
+
 # Allows test cases to create/destroy tenants and users. This
 # option enables isolated test cases and better parallel
 # execution, but also requires that OpenStack Identity API
diff --git a/openstack-common.conf b/openstack-common.conf
index 38d58ee..a9a6b0b 100644
--- a/openstack-common.conf
+++ b/openstack-common.conf
@@ -7,6 +7,7 @@
 module=log
 module=importlib
 module=fixture
+module=versionutils
 
 # The base module to hold the copy of openstack.common
 base=tempest
diff --git a/tempest/api/compute/admin/test_flavors.py b/tempest/api/compute/admin/test_flavors.py
index a8a9bb4..18866e5 100644
--- a/tempest/api/compute/admin/test_flavors.py
+++ b/tempest/api/compute/admin/test_flavors.py
@@ -231,7 +231,7 @@
         flavor_name = data_utils.rand_name(self.flavor_name_prefix)
         new_flavor_id = data_utils.rand_int_id(start=1000)
 
-            # Create the flavor
+        # Create the flavor
         resp, flavor = self.client.create_flavor(flavor_name,
                                                  self.ram, self.vcpus,
                                                  self.disk,
diff --git a/tempest/api/compute/admin/test_floating_ips_bulk.py b/tempest/api/compute/admin/test_floating_ips_bulk.py
new file mode 100644
index 0000000..8f875d2
--- /dev/null
+++ b/tempest/api/compute/admin/test_floating_ips_bulk.py
@@ -0,0 +1,82 @@
+# Copyright 2014 NEC Technologies India Ltd.
+# 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 netaddr
+
+from tempest.api.compute import base
+from tempest import config
+from tempest import test
+
+CONF = config.CONF
+
+
+class FloatingIPsBulkAdminTestJSON(base.BaseV2ComputeAdminTest):
+    """
+    Tests Floating IPs Bulk APIs Create, List and  Delete that
+    require admin privileges.
+    API documentation - http://docs.openstack.org/api/openstack-compute/2/
+    content/ext-os-floating-ips-bulk.html
+    """
+
+    @classmethod
+    @test.safe_setup
+    def setUpClass(cls):
+        super(FloatingIPsBulkAdminTestJSON, cls).setUpClass()
+        cls.client = cls.os_adm.floating_ips_client
+        cls.ip_range = CONF.compute.floating_ip_range
+        cls.verify_unallocated_floating_ip_range(cls.ip_range)
+
+    @classmethod
+    def verify_unallocated_floating_ip_range(cls, ip_range):
+        # Verify whether configure floating IP range is not already allocated.
+        _, body = cls.client.list_floating_ips_bulk()
+        allocated_ips_list = map(lambda x: x['address'], body)
+        for ip_addr in netaddr.IPNetwork(ip_range).iter_hosts():
+            if str(ip_addr) in allocated_ips_list:
+                msg = ("Configured unallocated floating IP range is already "
+                       "allocated. Configure the correct unallocated range "
+                       "as 'floating_ip_range'")
+                raise cls.skipException(msg)
+        return
+
+    def _delete_floating_ips_bulk(self, ip_range):
+        try:
+            self.client.delete_floating_ips_bulk(ip_range)
+        except Exception:
+            pass
+
+    @test.attr(type='gate')
+    def test_create_list_delete_floating_ips_bulk(self):
+        # Create, List  and delete the Floating IPs Bulk
+        pool = 'test_pool'
+        # NOTE(GMann): Reserving the IP range but those are not attached
+        # anywhere. Using the below mentioned interface which is not ever
+        # expected to be used. Clean Up has been done for created IP range
+        interface = 'eth0'
+        resp, body = self.client.create_floating_ips_bulk(self.ip_range,
+                                                          pool,
+                                                          interface)
+
+        self.assertEqual(200, resp.status)
+        self.addCleanup(self._delete_floating_ips_bulk, self.ip_range)
+        self.assertEqual(self.ip_range, body['ip_range'])
+        resp, ips_list = self.client.list_floating_ips_bulk()
+        self.assertEqual(200, resp.status)
+        self.assertNotEqual(0, len(body))
+        for ip in netaddr.IPNetwork(self.ip_range).iter_hosts():
+            self.assertIn(str(ip), map(lambda x: x['address'], ips_list))
+        resp, body = self.client.delete_floating_ips_bulk(self.ip_range)
+        self.assertEqual(200, resp.status)
+        self.assertEqual(self.ip_range, body)
diff --git a/tempest/api/compute/images/test_images_negative.py b/tempest/api/compute/images/test_images_negative.py
index ae00ae2..6163f4d 100644
--- a/tempest/api/compute/images/test_images_negative.py
+++ b/tempest/api/compute/images/test_images_negative.py
@@ -40,7 +40,7 @@
         # Delete server before trying to create server
         self.servers_client.delete_server(server['id'])
         self.servers_client.wait_for_server_termination(server['id'])
-       # Create a new image after server is deleted
+        # Create a new image after server is deleted
         name = data_utils.rand_name('image')
         meta = {'image_type': 'test'}
         self.assertRaises(exceptions.NotFound,
diff --git a/tempest/api/compute/v3/admin/test_flavors.py b/tempest/api/compute/v3/admin/test_flavors.py
index 2a4fc02..8a4e3cf 100644
--- a/tempest/api/compute/v3/admin/test_flavors.py
+++ b/tempest/api/compute/v3/admin/test_flavors.py
@@ -229,7 +229,7 @@
         flavor_name = data_utils.rand_name(self.flavor_name_prefix)
         new_flavor_id = data_utils.rand_int_id(start=1000)
 
-            # Create the flavor
+        # Create the flavor
         resp, flavor = self.client.create_flavor(flavor_name,
                                                  self.ram, self.vcpus,
                                                  self.disk,
diff --git a/tempest/api/data_processing/test_cluster_templates.py b/tempest/api/data_processing/test_cluster_templates.py
index c08d6ba..e5c6303 100644
--- a/tempest/api/data_processing/test_cluster_templates.py
+++ b/tempest/api/data_processing/test_cluster_templates.py
@@ -143,4 +143,4 @@
         # delete the cluster template by id
         resp = self.client.delete_cluster_template(template_id)[0]
         self.assertEqual(204, resp.status)
-        #TODO(ylobankov): check that cluster template is really deleted
+        # TODO(ylobankov): check that cluster template is really deleted
diff --git a/tempest/api/identity/admin/test_services.py b/tempest/api/identity/admin/test_services.py
index 0472e07..5926488 100644
--- a/tempest/api/identity/admin/test_services.py
+++ b/tempest/api/identity/admin/test_services.py
@@ -101,7 +101,7 @@
         # List and Verify Services
         resp, body = self.client.list_services()
         self.assertEqual(200, resp.status)
-        found = [service for service in body if service['id'] in service_ids]
+        found = [serv for serv in body if serv['id'] in service_ids]
         self.assertEqual(len(found), len(services), 'Services not found')
 
 
diff --git a/tempest/api/identity/admin/test_tenants.py b/tempest/api/identity/admin/test_tenants.py
index b989664..93734d2 100644
--- a/tempest/api/identity/admin/test_tenants.py
+++ b/tempest/api/identity/admin/test_tenants.py
@@ -36,7 +36,7 @@
         tenant_ids = map(lambda x: x['id'], tenants)
         resp, body = self.client.list_tenants()
         self.assertEqual(200, resp.status)
-        found = [tenant for tenant in body if tenant['id'] in tenant_ids]
+        found = [t for t in body if t['id'] in tenant_ids]
         self.assertEqual(len(found), len(tenants), 'Tenants not created')
 
         for tenant in tenants:
diff --git a/tempest/api/identity/admin/v3/test_projects.py b/tempest/api/identity/admin/v3/test_projects.py
index 31a0ddd..79717b1 100644
--- a/tempest/api/identity/admin/v3/test_projects.py
+++ b/tempest/api/identity/admin/v3/test_projects.py
@@ -171,13 +171,13 @@
 
     @test.attr(type='gate')
     def test_associate_user_to_project(self):
-        #Associate a user to a project
-        #Create a Project
+        # Associate a user to a project
+        # Create a Project
         p_name = data_utils.rand_name('project-')
         resp, project = self.client.create_project(p_name)
         self.data.projects.append(project)
 
-        #Create a User
+        # Create a User
         u_name = data_utils.rand_name('user-')
         u_desc = u_name + 'description'
         u_email = u_name + '@testmail.tm'
@@ -191,7 +191,7 @@
 
         # Get User To validate the user details
         resp, new_user_get = self.client.get_user(user['id'])
-        #Assert response body of GET
+        # Assert response body of GET
         self.assertEqual(u_name, new_user_get['name'])
         self.assertEqual(u_desc, new_user_get['description'])
         self.assertEqual(project['id'],
diff --git a/tempest/api/network/test_load_balancer.py b/tempest/api/network/test_load_balancer.py
index 673fc47..db24e0d 100644
--- a/tempest/api/network/test_load_balancer.py
+++ b/tempest/api/network/test_load_balancer.py
@@ -259,7 +259,7 @@
         self.assertEqual('200', resp['status'])
         member = body['member']
         for key, value in member.iteritems():
-             # 'status' should not be confirmed in api tests
+            # 'status' should not be confirmed in api tests
             if key != 'status':
                 self.assertEqual(self.member[key], value)
 
@@ -340,7 +340,7 @@
         self.assertEqual('200', resp['status'])
         health_monitor = body['health_monitor']
         for key, value in health_monitor.iteritems():
-             # 'status' should not be confirmed in api tests
+            # 'status' should not be confirmed in api tests
             if key != 'status':
                 self.assertEqual(self.health_monitor[key], value)
 
diff --git a/tempest/api/network/test_security_groups_negative.py b/tempest/api/network/test_security_groups_negative.py
index 0b86398..53c9d12 100644
--- a/tempest/api/network/test_security_groups_negative.py
+++ b/tempest/api/network/test_security_groups_negative.py
@@ -55,7 +55,7 @@
     def test_create_security_group_rule_with_bad_protocol(self):
         group_create_body, _ = self._create_security_group()
 
-        #Create rule with bad protocol name
+        # Create rule with bad protocol name
         pname = 'bad_protocol_name'
         self.assertRaises(
             exceptions.BadRequest, self.client.create_security_group_rule,
@@ -66,7 +66,7 @@
     def test_create_security_group_rule_with_invalid_ports(self):
         group_create_body, _ = self._create_security_group()
 
-        #Create rule with invalid ports
+        # Create rule with invalid ports
         states = [(-16, 80, 'Invalid value for port -16'),
                   (80, 79, 'port_range_min must be <= port_range_max'),
                   (80, 65536, 'Invalid value for port 65536'),
diff --git a/tempest/api/orchestration/stacks/test_nova_keypair_resources.py b/tempest/api/orchestration/stacks/test_nova_keypair_resources.py
index cb70d07..a81a540 100644
--- a/tempest/api/orchestration/stacks/test_nova_keypair_resources.py
+++ b/tempest/api/orchestration/stacks/test_nova_keypair_resources.py
@@ -68,13 +68,13 @@
         output_map = {}
         for outputs in stack['outputs']:
             output_map[outputs['output_key']] = outputs['output_value']
-        #Test that first key generated public and private keys
+        # Test that first key generated public and private keys
         self.assertTrue('KeyPair_PublicKey' in output_map)
         self.assertTrue("Generated" in output_map['KeyPair_PublicKey'])
         self.assertTrue('KeyPair_PrivateKey' in output_map)
         self.assertTrue('-----BEGIN' in output_map['KeyPair_PrivateKey'])
-        #Test that second key generated public key, and private key is not
-        #in the output due to save_private_key = false
+        # Test that second key generated public key, and private key is not
+        # in the output due to save_private_key = false
         self.assertTrue('KeyPairDontSavePrivate_PublicKey' in output_map)
         self.assertTrue('Generated' in
                         output_map['KeyPairDontSavePrivate_PublicKey'])
diff --git a/tempest/api/volume/admin/test_volume_types_extra_specs_negative.py b/tempest/api/volume/admin/test_volume_types_extra_specs_negative.py
index d3a052e..da421dc 100644
--- a/tempest/api/volume/admin/test_volume_types_extra_specs_negative.py
+++ b/tempest/api/volume/admin/test_volume_types_extra_specs_negative.py
@@ -29,9 +29,9 @@
         super(ExtraSpecsNegativeTest, cls).setUpClass()
         vol_type_name = data_utils.rand_name('Volume-type-')
         cls.extra_specs = {"spec1": "val1"}
-        resp, cls.volume_type = cls.client.create_volume_type(vol_type_name,
-                                                              extra_specs=
-                                                              cls.extra_specs)
+        resp, cls.volume_type = cls.client.create_volume_type(
+            vol_type_name,
+            extra_specs=cls.extra_specs)
 
     @classmethod
     def tearDownClass(cls):
diff --git a/tempest/api/volume/test_volumes_get.py b/tempest/api/volume/test_volumes_get.py
index 58da440..2745b95 100644
--- a/tempest/api/volume/test_volumes_get.py
+++ b/tempest/api/volume/test_volumes_get.py
@@ -39,7 +39,7 @@
     def _is_true(self, val):
         # NOTE(jdg): Temporary conversion method to get cinder patch
         # merged.  Then we'll make this strict again and
-        #specifically check "true" or "false"
+        # specifically check "true" or "false"
         if val in ['true', 'True', True]:
             return True
         else:
@@ -121,19 +121,19 @@
         new_volume = {}
         new_v_desc = data_utils.rand_name('@#$%^* description')
         resp, new_volume = \
-            self.client.create_volume(size=1,
-                                      display_description=new_v_desc,
-                                      availability_zone=
-                                      volume['availability_zone'])
+            self.client.create_volume(
+                size=1,
+                display_description=new_v_desc,
+                availability_zone=volume['availability_zone'])
         self.assertEqual(200, resp.status)
         self.assertIn('id', new_volume)
         self.addCleanup(self._delete_volume, new_volume['id'])
         self.client.wait_for_volume_status(new_volume['id'], 'available')
         resp, update_volume = \
-            self.client.update_volume(new_volume['id'],
-                                      display_name=volume['display_name'],
-                                      display_description=
-                                      volume['display_description'])
+            self.client.update_volume(
+                new_volume['id'],
+                display_name=volume['display_name'],
+                display_description=volume['display_description'])
         self.assertEqual(200, resp.status)
 
         # NOTE(jdg): Revert back to strict true/false checking
diff --git a/tempest/api_schema/compute/servers.py b/tempest/api_schema/compute/servers.py
index 33992b1..14e9ce9 100644
--- a/tempest/api_schema/compute/servers.py
+++ b/tempest/api_schema/compute/servers.py
@@ -165,3 +165,17 @@
         'required': ['output']
     }
 }
+
+common_instance_actions = {
+    'type': 'object',
+    'properties': {
+        'action': {'type': 'string'},
+        'request_id': {'type': 'string'},
+        'user_id': {'type': 'string'},
+        'project_id': {'type': 'string'},
+        'start_time': {'type': 'string'},
+        'message': {'type': ['string', 'null']}
+    },
+    'required': ['action', 'request_id', 'user_id', 'project_id',
+                 'start_time', 'message']
+}
diff --git a/tempest/api_schema/compute/v2/servers.py b/tempest/api_schema/compute/v2/servers.py
index f7ed94e..fe53abd 100644
--- a/tempest/api_schema/compute/v2/servers.py
+++ b/tempest/api_schema/compute/v2/servers.py
@@ -177,3 +177,22 @@
 delete_server_group = {
     'status_code': [204]
 }
+
+instance_actions_object = copy.deepcopy(servers.common_instance_actions)
+instance_actions_object[
+    'properties'].update({'instance_uuid': {'type': 'string'}})
+instance_actions_object['required'].extend(['instance_uuid'])
+
+list_instance_actions = {
+    'status_code': [200],
+    'response_body': {
+        'type': 'object',
+        'properties': {
+            'instanceActions': {
+                'type': 'array',
+                'items': instance_actions_object
+            }
+        },
+        'required': ['instanceActions']
+    }
+}
diff --git a/tempest/api_schema/compute/v3/servers.py b/tempest/api_schema/compute/v3/servers.py
index 6716249..4fb2d87 100644
--- a/tempest/api_schema/compute/v3/servers.py
+++ b/tempest/api_schema/compute/v3/servers.py
@@ -99,3 +99,21 @@
 update_server_metadata = copy.deepcopy(servers.update_server_metadata)
 # V3 API's response status_code is 201
 update_server_metadata['status_code'] = [201]
+
+server_actions_object = copy.deepcopy(servers.common_instance_actions)
+server_actions_object['properties'].update({'server_uuid': {'type': 'string'}})
+server_actions_object['required'].extend(['server_uuid'])
+
+list_server_actions = {
+    'status_code': [200],
+    'response_body': {
+        'type': 'object',
+        'properties': {
+            'server_actions': {
+                'type': 'array',
+                'items': server_actions_object
+            }
+        },
+        'required': ['server_actions']
+    }
+}
diff --git a/tempest/cli/simple_read_only/test_cinder.py b/tempest/cli/simple_read_only/test_cinder.py
index 9001302..e9a0cee 100644
--- a/tempest/cli/simple_read_only/test_cinder.py
+++ b/tempest/cli/simple_read_only/test_cinder.py
@@ -142,7 +142,7 @@
                                'quota-show', 'type-list', 'snapshot-list'))
         self.assertFalse(wanted_commands - commands)
 
-     # Optional arguments:
+    # Optional arguments:
 
     def test_cinder_version(self):
         self.cinder('', flags='--version')
diff --git a/tempest/cmd/run_stress.py b/tempest/cmd/run_stress.py
index f773996..07f3f66 100755
--- a/tempest/cmd/run_stress.py
+++ b/tempest/cmd/run_stress.py
@@ -51,7 +51,7 @@
         except Exception:
             next
         if 'stress' in attrs:
-            if filter_attr is not None and not filter_attr in attrs:
+            if filter_attr is not None and filter_attr not in attrs:
                 continue
             class_setup_per = getattr(test_func, "st_class_setup_per")
 
diff --git a/tempest/cmd/verify_tempest_config.py b/tempest/cmd/verify_tempest_config.py
index 3bf05e1..0834cff 100755
--- a/tempest/cmd/verify_tempest_config.py
+++ b/tempest/cmd/verify_tempest_config.py
@@ -21,7 +21,7 @@
 import urlparse
 
 import httplib2
-from six.moves import configparser
+from six import moves
 
 from tempest import clients
 from tempest import config
@@ -46,7 +46,7 @@
 
 
 def change_option(option, group, value):
-    config_parse = configparser.SafeConfigParser()
+    config_parse = moves.configparser.SafeConfigParser()
     config_parse.optionxform = str
     config_parse.readfp(CONF_FILE)
     if not config_parse.has_section(group):
diff --git a/tempest/config.py b/tempest/config.py
index 1366361..e3f0f2a 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -247,7 +247,11 @@
                     'for removing from a host.  -1 never offload, 0 offload '
                     'when shelved. This time should be the same as the time '
                     'of nova.conf, and some tests will run for as long as the '
-                    'time.')
+                    'time.'),
+    cfg.StrOpt('floating_ip_range',
+               default='10.0.0.0/29',
+               help='Unallocated floating IP range, which will be used to '
+                    'test the floating IP bulk feature for CRUD operation.')
 ]
 
 compute_features_group = cfg.OptGroup(name='compute-feature-enabled',
diff --git a/tempest/openstack/common/versionutils.py b/tempest/openstack/common/versionutils.py
new file mode 100644
index 0000000..131046e
--- /dev/null
+++ b/tempest/openstack/common/versionutils.py
@@ -0,0 +1,148 @@
+# Copyright (c) 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.
+
+"""
+Helpers for comparing version strings.
+"""
+
+import functools
+import pkg_resources
+
+from tempest.openstack.common.gettextutils import _
+from tempest.openstack.common import log as logging
+
+
+LOG = logging.getLogger(__name__)
+
+
+class deprecated(object):
+    """A decorator to mark callables as deprecated.
+
+    This decorator logs a deprecation message when the callable it decorates is
+    used. The message will include the release where the callable was
+    deprecated, the release where it may be removed and possibly an optional
+    replacement.
+
+    Examples:
+
+    1. Specifying the required deprecated release
+
+    >>> @deprecated(as_of=deprecated.ICEHOUSE)
+    ... def a(): pass
+
+    2. Specifying a replacement:
+
+    >>> @deprecated(as_of=deprecated.ICEHOUSE, in_favor_of='f()')
+    ... def b(): pass
+
+    3. Specifying the release where the functionality may be removed:
+
+    >>> @deprecated(as_of=deprecated.ICEHOUSE, remove_in=+1)
+    ... def c(): pass
+
+    """
+
+    FOLSOM = 'F'
+    GRIZZLY = 'G'
+    HAVANA = 'H'
+    ICEHOUSE = 'I'
+
+    _RELEASES = {
+        'F': 'Folsom',
+        'G': 'Grizzly',
+        'H': 'Havana',
+        'I': 'Icehouse',
+    }
+
+    _deprecated_msg_with_alternative = _(
+        '%(what)s is deprecated as of %(as_of)s in favor of '
+        '%(in_favor_of)s and may be removed in %(remove_in)s.')
+
+    _deprecated_msg_no_alternative = _(
+        '%(what)s is deprecated as of %(as_of)s and may be '
+        'removed in %(remove_in)s. It will not be superseded.')
+
+    def __init__(self, as_of, in_favor_of=None, remove_in=2, what=None):
+        """Initialize decorator
+
+        :param as_of: the release deprecating the callable. Constants
+            are define in this class for convenience.
+        :param in_favor_of: the replacement for the callable (optional)
+        :param remove_in: an integer specifying how many releases to wait
+            before removing (default: 2)
+        :param what: name of the thing being deprecated (default: the
+            callable's name)
+
+        """
+        self.as_of = as_of
+        self.in_favor_of = in_favor_of
+        self.remove_in = remove_in
+        self.what = what
+
+    def __call__(self, func):
+        if not self.what:
+            self.what = func.__name__ + '()'
+
+        @functools.wraps(func)
+        def wrapped(*args, **kwargs):
+            msg, details = self._build_message()
+            LOG.deprecated(msg, details)
+            return func(*args, **kwargs)
+        return wrapped
+
+    def _get_safe_to_remove_release(self, release):
+        # TODO(dstanek): this method will have to be reimplemented once
+        #    when we get to the X release because once we get to the Y
+        #    release, what is Y+2?
+        new_release = chr(ord(release) + self.remove_in)
+        if new_release in self._RELEASES:
+            return self._RELEASES[new_release]
+        else:
+            return new_release
+
+    def _build_message(self):
+        details = dict(what=self.what,
+                       as_of=self._RELEASES[self.as_of],
+                       remove_in=self._get_safe_to_remove_release(self.as_of))
+
+        if self.in_favor_of:
+            details['in_favor_of'] = self.in_favor_of
+            msg = self._deprecated_msg_with_alternative
+        else:
+            msg = self._deprecated_msg_no_alternative
+        return msg, details
+
+
+def is_compatible(requested_version, current_version, same_major=True):
+    """Determine whether `requested_version` is satisfied by
+    `current_version`; in other words, `current_version` is >=
+    `requested_version`.
+
+    :param requested_version: version to check for compatibility
+    :param current_version: version to check against
+    :param same_major: if True, the major version must be identical between
+        `requested_version` and `current_version`. This is used when a
+        major-version difference indicates incompatibility between the two
+        versions. Since this is the common-case in practice, the default is
+        True.
+    :returns: True if compatible, False if not
+    """
+    requested_parts = pkg_resources.parse_version(requested_version)
+    current_parts = pkg_resources.parse_version(current_version)
+
+    if same_major and (requested_parts[0] != current_parts[0]):
+        return False
+
+    return current_parts >= requested_parts
diff --git a/tempest/services/compute/json/floating_ips_client.py b/tempest/services/compute/json/floating_ips_client.py
index e2e12d5..6fc2bc2 100644
--- a/tempest/services/compute/json/floating_ips_client.py
+++ b/tempest/services/compute/json/floating_ips_client.py
@@ -112,3 +112,28 @@
         body = json.loads(body)
         self.validate_response(schema.floating_ip_pools, resp, body)
         return resp, body['floating_ip_pools']
+
+    def create_floating_ips_bulk(self, ip_range, pool, interface):
+        """Allocate floating IPs in bulk."""
+        post_body = {
+            'ip_range': ip_range,
+            'pool': pool,
+            'interface': interface
+        }
+        post_body = json.dumps({'floating_ips_bulk_create': post_body})
+        resp, body = self.post('os-floating-ips-bulk', post_body)
+        body = json.loads(body)
+        return resp, body['floating_ips_bulk_create']
+
+    def list_floating_ips_bulk(self):
+        """Returns a list of all floating IPs bulk."""
+        resp, body = self.get('os-floating-ips-bulk')
+        body = json.loads(body)
+        return resp, body['floating_ip_info']
+
+    def delete_floating_ips_bulk(self, ip_range):
+        """Deletes the provided floating IPs bulk."""
+        post_body = json.dumps({'ip_range': ip_range})
+        resp, body = self.put('os-floating-ips-bulk/delete', post_body)
+        body = json.loads(body)
+        return resp, body['floating_ips_bulk_delete']
diff --git a/tempest/services/compute/json/servers_client.py b/tempest/services/compute/json/servers_client.py
index 9d3b3b6..23c1e64 100644
--- a/tempest/services/compute/json/servers_client.py
+++ b/tempest/services/compute/json/servers_client.py
@@ -463,6 +463,7 @@
         resp, body = self.get("servers/%s/os-instance-actions" %
                               str(server_id))
         body = json.loads(body)
+        self.validate_response(schema.list_instance_actions, resp, body)
         return resp, body['instanceActions']
 
     def get_instance_action(self, server_id, request_id):
diff --git a/tempest/services/compute/v3/json/servers_client.py b/tempest/services/compute/v3/json/servers_client.py
index 0ccbe7f..11258a6 100644
--- a/tempest/services/compute/v3/json/servers_client.py
+++ b/tempest/services/compute/v3/json/servers_client.py
@@ -461,6 +461,7 @@
         resp, body = self.get("servers/%s/os-server-actions" %
                               str(server_id))
         body = json.loads(body)
+        self.validate_response(schema.list_server_actions, resp, body)
         return resp, body['server_actions']
 
     def get_server_action(self, server_id, request_id):
diff --git a/tempest/services/network/xml/network_client.py b/tempest/services/network/xml/network_client.py
index a7a6b2c..22cc948 100644
--- a/tempest/services/network/xml/network_client.py
+++ b/tempest/services/network/xml/network_client.py
@@ -38,7 +38,7 @@
         return _root_tag_fetcher_and_xml_to_json_parse(body)
 
     def serialize(self, body):
-        #TODO(enikanorov): implement better json to xml conversion
+        # TODO(enikanorov): implement better json to xml conversion
         # expecting the dict with single key
         root = body.keys()[0]
         post_body = common.Element(root)
diff --git a/tempest/services/volume/xml/backups_client.py b/tempest/services/volume/xml/backups_client.py
index 81caaee..a691a25 100644
--- a/tempest/services/volume/xml/backups_client.py
+++ b/tempest/services/volume/xml/backups_client.py
@@ -22,5 +22,5 @@
     """
     TYPE = "xml"
 
-    #TODO(gfidente): XML client isn't yet implemented because of bug 1270589
+    # TODO(gfidente): XML client isn't yet implemented because of bug 1270589
     pass
diff --git a/tempest/stress/actions/volume_attach_delete.py b/tempest/stress/actions/volume_attach_delete.py
index c2e6072..b438f52 100644
--- a/tempest/stress/actions/volume_attach_delete.py
+++ b/tempest/stress/actions/volume_attach_delete.py
@@ -28,9 +28,9 @@
         # Step 1: create volume
         name = data_utils.rand_name("volume")
         self.logger.info("creating volume: %s" % name)
-        resp, volume = self.manager.volumes_client.create_volume(size=1,
-                                                                 display_name=
-                                                                 name)
+        resp, volume = self.manager.volumes_client.create_volume(
+            size=1,
+            display_name=name)
         assert(resp.status == 200)
         self.manager.volumes_client.wait_for_volume_status(volume['id'],
                                                            'available')
diff --git a/tempest/stress/actions/volume_attach_verify.py b/tempest/stress/actions/volume_attach_verify.py
index 1bc3b06..a3ca0b7 100644
--- a/tempest/stress/actions/volume_attach_verify.py
+++ b/tempest/stress/actions/volume_attach_verify.py
@@ -81,9 +81,9 @@
         name = data_utils.rand_name("volume")
         self.logger.info("creating volume: %s" % name)
         volumes_client = self.manager.volumes_client
-        resp, self.volume = volumes_client.create_volume(size=1,
-                                                         display_name=
-                                                         name)
+        resp, self.volume = volumes_client.create_volume(
+            size=1,
+            display_name=name)
         assert(resp.status == 200)
         volumes_client.wait_for_volume_status(self.volume['id'],
                                               'available')
diff --git a/tempest/test.py b/tempest/test.py
index 748a98c..bc94bcd 100644
--- a/tempest/test.py
+++ b/tempest/test.py
@@ -396,7 +396,7 @@
 
         :param file: the file name
         """
-        #NOTE(mkoderer): must be extended for xml support
+        # NOTE(mkoderer): must be extended for xml support
         fn = os.path.join(
             os.path.abspath(os.path.dirname(os.path.dirname(__file__))),
             "etc", "schemas", file)
diff --git a/tempest/tests/fake_http.py b/tempest/tests/fake_http.py
index 7b878af..ce2b2c0 100644
--- a/tempest/tests/fake_http.py
+++ b/tempest/tests/fake_http.py
@@ -32,7 +32,6 @@
                 'headers': headers
             }
             return (fake_headers, return_obj)
-           # return (headers, return_obj)
         elif isinstance(self.return_type, int):
             body = "fake_body"
             header_info = {
diff --git a/tempest/thirdparty/boto/test.py b/tempest/thirdparty/boto/test.py
index 4c39f78..d3cbc4b 100644
--- a/tempest/thirdparty/boto/test.py
+++ b/tempest/thirdparty/boto/test.py
@@ -56,8 +56,8 @@
     A_I_IMAGES_READY = all_read(ami_path, aki_path, ari_path)
     boto_logger = logging.getLogger('boto')
     level = boto_logger.logger.level
-    boto_logger.logger.setLevel(orig_logging.CRITICAL)  # suppress logging
-                                                        # for these
+    # suppress logging for boto
+    boto_logger.logger.setLevel(orig_logging.CRITICAL)
 
     def _cred_sub_check(connection_data):
         if not id_matcher.match(connection_data["aws_access_key_id"]):
diff --git a/tempest/thirdparty/boto/test_ec2_instance_run.py b/tempest/thirdparty/boto/test_ec2_instance_run.py
index 5f78627..7713931 100644
--- a/tempest/thirdparty/boto/test_ec2_instance_run.py
+++ b/tempest/thirdparty/boto/test_ec2_instance_run.py
@@ -79,8 +79,8 @@
             if state != "available":
                 for _image in cls.images.itervalues():
                     cls.ec2_client.deregister_image(_image["image_id"])
-                raise exceptions.EC2RegisterImageException(image_id=
-                                                           image["image_id"])
+                raise exceptions.EC2RegisterImageException(
+                    image_id=image["image_id"])
 
     def test_run_idempotent_instances(self):
         # EC2 run instances idempotently
diff --git a/test-requirements.txt b/test-requirements.txt
index b9c75c8..f63d34e 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -1,4 +1,4 @@
-hacking>=0.8.0,<0.9
+hacking>=0.9.2,<0.10
 # needed for doc build
 docutils==0.9.1
 sphinx>=1.2.1,<1.3
diff --git a/tox.ini b/tox.ini
index 6b4acc6..c1acde9 100644
--- a/tox.ini
+++ b/tox.ini
@@ -99,6 +99,8 @@
 
 [flake8]
 # E125 is a won't fix until https://github.com/jcrocholl/pep8/issues/126 is resolved.  For further detail see https://review.openstack.org/#/c/36788/
-ignore = E125,H404
+# H402 skipped because some docstrings aren't sentences
+# Skipped because of new hacking 0.9: H407,H405,H904,H305,E123,H307,E122,E129,E128
+ignore = E125,H402,H404,H407,H405,H904,H305,E123,H307,E122,E129,E128
 show-source = True
 exclude = .git,.venv,.tox,dist,doc,openstack,*egg