Merge "Fix Neutron VPNaaS Test"
diff --git a/etc/tempest.conf.sample b/etc/tempest.conf.sample
index 6101466..1080ddf 100644
--- a/etc/tempest.conf.sample
+++ b/etc/tempest.conf.sample
@@ -524,6 +524,16 @@
 #disk_format=raw
 
 
+[debug]
+
+#
+# Options defined in tempest.config
+#
+
+# Enable diagnostic commands (boolean value)
+#enable=true
+
+
 [dashboard]
 
 #
@@ -610,14 +620,15 @@
 #operator_role=Member
 
 
-[debug]
+[network-feature-enabled]
 
 #
 # Options defined in tempest.config
 #
 
-# Enable diagnostic commands (boolean value)
-#enable=true
+# A list of enabled extensions with a special entry all which
+# indicates every extension is enabled (list value)
+#api_extensions=all
 
 
 [service_available]
@@ -671,8 +682,13 @@
 # If false, skip disk config tests (boolean value)
 #disk_config=true
 
-# If false, skip flavor extra data test (boolean value)
-#flavor_extra=true
+# A list of enabled extensions with a special entry all which
+# indicates every extension is enabled (list value)
+#api_extensions=all
+
+# A list of enabled v3 extensions with a special entry all
+# which indicates every extension is enabled (list value)
+#api_v3_extensions=all
 
 # Does the test environment support changing the admin
 # password? (boolean value)
@@ -730,4 +746,8 @@
 # (boolean value)
 #multi_backend=false
 
+# A list of enabled extensions with a special entry all which
+# indicates every extension is enabled (list value)
+#api_extensions=all
+
 
diff --git a/requirements.txt b/requirements.txt
index 4f6a1d3..cd11aa7 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -5,9 +5,9 @@
 jsonschema>=1.3.0,!=1.4.0
 testtools>=0.9.32
 lxml>=2.3
-boto>=2.4.0,!=2.13.0
+boto>=2.12.0,!=2.13.0
 paramiko>=1.8.0
-netaddr
+netaddr>=0.7.6
 python-glanceclient>=0.9.0
 python-keystoneclient>=0.4.1
 python-novaclient>=2.15.0
@@ -20,5 +20,5 @@
 oslo.config>=1.2.0
 eventlet>=0.13.0
 six>=1.4.1
-iso8601>=0.1.4
+iso8601>=0.1.8
 fixtures>=0.3.14
diff --git a/tempest/api/compute/__init__.py b/tempest/api/compute/__init__.py
index a528754..dd92ee9 100644
--- a/tempest/api/compute/__init__.py
+++ b/tempest/api/compute/__init__.py
@@ -26,7 +26,6 @@
 RESIZE_AVAILABLE = CONFIG.compute_feature_enabled.resize
 CHANGE_PASSWORD_AVAILABLE = CONFIG.compute_feature_enabled.change_password
 DISK_CONFIG_ENABLED = CONFIG.compute_feature_enabled.disk_config
-FLAVOR_EXTRA_DATA_ENABLED = CONFIG.compute_feature_enabled.flavor_extra
 MULTI_USER = True
 
 
diff --git a/tempest/api/compute/admin/test_flavors.py b/tempest/api/compute/admin/test_flavors.py
index c5a8772..cf72e49 100644
--- a/tempest/api/compute/admin/test_flavors.py
+++ b/tempest/api/compute/admin/test_flavors.py
@@ -17,12 +17,10 @@
 
 import uuid
 
-from tempest.api import compute
 from tempest.api.compute import base
 from tempest.common.utils import data_utils
 from tempest import exceptions
-from tempest.test import attr
-from tempest.test import skip_because
+from tempest import test
 
 
 class FlavorsAdminTestJSON(base.BaseV2ComputeAdminTest):
@@ -36,7 +34,7 @@
     @classmethod
     def setUpClass(cls):
         super(FlavorsAdminTestJSON, cls).setUpClass()
-        if not compute.FLAVOR_EXTRA_DATA_ENABLED:
+        if not test.is_extension_enabled('FlavorExtraData', 'compute'):
             msg = "FlavorExtraData extension not enabled."
             raise cls.skipException(msg)
 
@@ -87,19 +85,19 @@
 
         return flavor['id']
 
-    @attr(type='gate')
+    @test.attr(type='gate')
     def test_create_flavor_with_int_id(self):
         flavor_id = data_utils.rand_int_id(start=1000)
         new_flavor_id = self._create_flavor(flavor_id)
         self.assertEqual(new_flavor_id, str(flavor_id))
 
-    @attr(type='gate')
+    @test.attr(type='gate')
     def test_create_flavor_with_uuid_id(self):
         flavor_id = str(uuid.uuid4())
         new_flavor_id = self._create_flavor(flavor_id)
         self.assertEqual(new_flavor_id, flavor_id)
 
-    @attr(type='gate')
+    @test.attr(type='gate')
     def test_create_flavor_with_none_id(self):
         # If nova receives a request with None as flavor_id,
         # nova generates flavor_id of uuid.
@@ -107,7 +105,7 @@
         new_flavor_id = self._create_flavor(flavor_id)
         self.assertEqual(new_flavor_id, str(uuid.UUID(new_flavor_id)))
 
-    @attr(type='gate')
+    @test.attr(type='gate')
     def test_create_flavor_verify_entry_in_list_details(self):
         # Create a flavor and ensure it's details are listed
         # This operation requires the user to have 'admin' role
@@ -132,7 +130,7 @@
                 flag = True
         self.assertTrue(flag)
 
-    @attr(type=['negative', 'gate'])
+    @test.attr(type=['negative', 'gate'])
     def test_get_flavor_details_for_deleted_flavor(self):
         # Delete a flavor and ensure it is not listed
         # Create a test flavor
@@ -166,7 +164,7 @@
                 flag = False
         self.assertTrue(flag)
 
-    @attr(type='gate')
+    @test.attr(type='gate')
     def test_create_list_flavor_without_extra_data(self):
         # Create a flavor and ensure it is listed
         # This operation requires the user to have 'admin' role
@@ -210,8 +208,8 @@
                 flag = True
         self.assertTrue(flag)
 
-    @skip_because(bug="1209101")
-    @attr(type='gate')
+    @test.skip_because(bug="1209101")
+    @test.attr(type='gate')
     def test_list_non_public_flavor(self):
         # Create a flavor with os-flavor-access:is_public false should
         # be present in list_details.
@@ -244,7 +242,7 @@
                 flag = True
         self.assertFalse(flag)
 
-    @attr(type='gate')
+    @test.attr(type='gate')
     def test_create_server_with_non_public_flavor(self):
         # Create a flavor with os-flavor-access:is_public false
         flavor_name = data_utils.rand_name(self.flavor_name_prefix)
@@ -264,7 +262,7 @@
                           self.os.servers_client.create_server,
                           'test', self.image_ref, flavor['id'])
 
-    @attr(type='gate')
+    @test.attr(type='gate')
     def test_list_public_flavor_with_other_user(self):
         # Create a Flavor with public access.
         # Try to List/Get flavor with another user
@@ -288,7 +286,7 @@
                 flag = True
         self.assertTrue(flag)
 
-    @attr(type='gate')
+    @test.attr(type='gate')
     def test_is_public_string_variations(self):
         flavor_id_not_public = data_utils.rand_int_id(start=1000)
         flavor_name_not_public = data_utils.rand_name(self.flavor_name_prefix)
@@ -331,7 +329,7 @@
         _test_string_variations(['t', 'true', 'yes', '1'],
                                 flavor_name_public)
 
-    @attr(type='gate')
+    @test.attr(type='gate')
     def test_create_flavor_using_string_ram(self):
         flavor_name = data_utils.rand_name(self.flavor_name_prefix)
         new_flavor_id = data_utils.rand_int_id(start=1000)
@@ -349,13 +347,13 @@
         self.assertEqual(flavor['ram'], int(ram))
         self.assertEqual(int(flavor['id']), new_flavor_id)
 
-    @attr(type=['negative', 'gate'])
+    @test.attr(type=['negative', 'gate'])
     def test_invalid_is_public_string(self):
         self.assertRaises(exceptions.BadRequest,
                           self.client.list_flavors_with_detail,
                           {'is_public': 'invalid'})
 
-    @attr(type=['negative', 'gate'])
+    @test.attr(type=['negative', 'gate'])
     def test_create_flavor_as_user(self):
         flavor_name = data_utils.rand_name(self.flavor_name_prefix)
         new_flavor_id = data_utils.rand_int_id(start=1000)
@@ -366,13 +364,13 @@
                           new_flavor_id, ephemeral=self.ephemeral,
                           swap=self.swap, rxtx=self.rxtx)
 
-    @attr(type=['negative', 'gate'])
+    @test.attr(type=['negative', 'gate'])
     def test_delete_flavor_as_user(self):
         self.assertRaises(exceptions.Unauthorized,
                           self.user_client.delete_flavor,
                           self.flavor_ref_alt)
 
-    @attr(type=['negative', 'gate'])
+    @test.attr(type=['negative', 'gate'])
     def test_create_flavor_using_invalid_ram(self):
         flavor_name = data_utils.rand_name(self.flavor_name_prefix)
         new_flavor_id = data_utils.rand_int_id(start=1000)
@@ -382,7 +380,7 @@
                           flavor_name, -1, self.vcpus,
                           self.disk, new_flavor_id)
 
-    @attr(type=['negative', 'gate'])
+    @test.attr(type=['negative', 'gate'])
     def test_create_flavor_using_invalid_vcpus(self):
         flavor_name = data_utils.rand_name(self.flavor_name_prefix)
         new_flavor_id = data_utils.rand_int_id(start=1000)
diff --git a/tempest/api/compute/admin/test_flavors_access.py b/tempest/api/compute/admin/test_flavors_access.py
index b866db1..048312b 100644
--- a/tempest/api/compute/admin/test_flavors_access.py
+++ b/tempest/api/compute/admin/test_flavors_access.py
@@ -15,10 +15,9 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from tempest.api import compute
 from tempest.api.compute import base
 from tempest.common.utils import data_utils
-from tempest.test import attr
+from tempest import test
 
 
 class FlavorsAccessTestJSON(base.BaseV2ComputeAdminTest):
@@ -33,7 +32,7 @@
     @classmethod
     def setUpClass(cls):
         super(FlavorsAccessTestJSON, cls).setUpClass()
-        if not compute.FLAVOR_EXTRA_DATA_ENABLED:
+        if not test.is_extension_enabled('FlavorExtraData', 'compute'):
             msg = "FlavorExtraData extension not enabled."
             raise cls.skipException(msg)
 
@@ -51,7 +50,7 @@
         cls.vcpus = 1
         cls.disk = 10
 
-    @attr(type='gate')
+    @test.attr(type='gate')
     def test_flavor_access_list_with_private_flavor(self):
         # Test to list flavor access successfully by querying private flavor
         flavor_name = data_utils.rand_name(self.flavor_name_prefix)
@@ -70,7 +69,7 @@
         self.assertEqual(str(new_flavor_id), str(first_flavor['flavor_id']))
         self.assertEqual(self.adm_tenant_id, first_flavor['tenant_id'])
 
-    @attr(type='gate')
+    @test.attr(type='gate')
     def test_flavor_access_add_remove(self):
         # Test to add and remove flavor access to a given tenant.
         flavor_name = data_utils.rand_name(self.flavor_name_prefix)
diff --git a/tempest/api/compute/admin/test_flavors_access_negative.py b/tempest/api/compute/admin/test_flavors_access_negative.py
index 340c1c7..976124e 100644
--- a/tempest/api/compute/admin/test_flavors_access_negative.py
+++ b/tempest/api/compute/admin/test_flavors_access_negative.py
@@ -17,11 +17,10 @@
 
 import uuid
 
-from tempest.api import compute
 from tempest.api.compute import base
 from tempest.common.utils import data_utils
 from tempest import exceptions
-from tempest.test import attr
+from tempest import test
 
 
 class FlavorsAccessNegativeTestJSON(base.BaseV2ComputeAdminTest):
@@ -36,7 +35,7 @@
     @classmethod
     def setUpClass(cls):
         super(FlavorsAccessNegativeTestJSON, cls).setUpClass()
-        if not compute.FLAVOR_EXTRA_DATA_ENABLED:
+        if not test.is_extension_enabled('FlavorExtraData', 'compute'):
             msg = "FlavorExtraData extension not enabled."
             raise cls.skipException(msg)
 
@@ -54,7 +53,7 @@
         cls.vcpus = 1
         cls.disk = 10
 
-    @attr(type=['negative', 'gate'])
+    @test.attr(type=['negative', 'gate'])
     def test_flavor_access_list_with_public_flavor(self):
         # Test to list flavor access with exceptions by querying public flavor
         flavor_name = data_utils.rand_name(self.flavor_name_prefix)
@@ -70,7 +69,7 @@
                           self.client.list_flavor_access,
                           new_flavor_id)
 
-    @attr(type=['negative', 'gate'])
+    @test.attr(type=['negative', 'gate'])
     def test_flavor_non_admin_add(self):
         # Test to add flavor access as a user without admin privileges.
         flavor_name = data_utils.rand_name(self.flavor_name_prefix)
@@ -86,7 +85,7 @@
                           new_flavor['id'],
                           self.tenant_id)
 
-    @attr(type=['negative', 'gate'])
+    @test.attr(type=['negative', 'gate'])
     def test_flavor_non_admin_remove(self):
         # Test to remove flavor access as a user without admin privileges.
         flavor_name = data_utils.rand_name(self.flavor_name_prefix)
@@ -106,7 +105,7 @@
                           new_flavor['id'],
                           self.tenant_id)
 
-    @attr(type=['negative', 'gate'])
+    @test.attr(type=['negative', 'gate'])
     def test_add_flavor_access_duplicate(self):
         # Create a new flavor.
         flavor_name = data_utils.rand_name(self.flavor_name_prefix)
@@ -130,7 +129,7 @@
                           new_flavor['id'],
                           self.tenant_id)
 
-    @attr(type=['negative', 'gate'])
+    @test.attr(type=['negative', 'gate'])
     def test_remove_flavor_access_not_found(self):
         # Create a new flavor.
         flavor_name = data_utils.rand_name(self.flavor_name_prefix)
diff --git a/tempest/api/compute/admin/test_flavors_extra_specs.py b/tempest/api/compute/admin/test_flavors_extra_specs.py
index 49b0429..875f742 100644
--- a/tempest/api/compute/admin/test_flavors_extra_specs.py
+++ b/tempest/api/compute/admin/test_flavors_extra_specs.py
@@ -15,10 +15,9 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from tempest.api import compute
 from tempest.api.compute import base
 from tempest.common.utils import data_utils
-from tempest.test import attr
+from tempest import test
 
 
 class FlavorsExtraSpecsTestJSON(base.BaseV2ComputeAdminTest):
@@ -34,7 +33,7 @@
     @classmethod
     def setUpClass(cls):
         super(FlavorsExtraSpecsTestJSON, cls).setUpClass()
-        if not compute.FLAVOR_EXTRA_DATA_ENABLED:
+        if not test.is_extension_enabled('FlavorExtraData', 'compute'):
             msg = "FlavorExtraData extension not enabled."
             raise cls.skipException(msg)
 
@@ -61,7 +60,7 @@
         cls.client.wait_for_resource_deletion(cls.flavor['id'])
         super(FlavorsExtraSpecsTestJSON, cls).tearDownClass()
 
-    @attr(type='gate')
+    @test.attr(type='gate')
     def test_flavor_set_get_update_show_unset_keys(self):
         # Test to SET, GET, UPDATE, SHOW, UNSET flavor extra
         # spec as a user with admin privileges.
@@ -101,7 +100,7 @@
             self.client.unset_flavor_extra_spec(self.flavor['id'], "key2")
         self.assertEqual(unset_resp.status, 200)
 
-    @attr(type='gate')
+    @test.attr(type='gate')
     def test_flavor_non_admin_get_all_keys(self):
         specs = {"key1": "value1", "key2": "value2"}
         set_resp, set_body = self.client.set_flavor_extra_spec(
@@ -113,7 +112,7 @@
         for key in specs:
             self.assertEqual(body[key], specs[key])
 
-    @attr(type='gate')
+    @test.attr(type='gate')
     def test_flavor_non_admin_get_specific_key(self):
         specs = {"key1": "value1", "key2": "value2"}
         resp, body = self.client.set_flavor_extra_spec(
diff --git a/tempest/api/compute/admin/test_flavors_extra_specs_negative.py b/tempest/api/compute/admin/test_flavors_extra_specs_negative.py
index d7e1f9f..fb09a63 100644
--- a/tempest/api/compute/admin/test_flavors_extra_specs_negative.py
+++ b/tempest/api/compute/admin/test_flavors_extra_specs_negative.py
@@ -16,11 +16,10 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from tempest.api import compute
 from tempest.api.compute import base
 from tempest.common.utils import data_utils
 from tempest import exceptions
-from tempest.test import attr
+from tempest import test
 
 
 class FlavorsExtraSpecsNegativeTestJSON(base.BaseV2ComputeAdminTest):
@@ -35,7 +34,7 @@
     @classmethod
     def setUpClass(cls):
         super(FlavorsExtraSpecsNegativeTestJSON, cls).setUpClass()
-        if not compute.FLAVOR_EXTRA_DATA_ENABLED:
+        if not test.is_extension_enabled('FlavorExtraData', 'compute'):
             msg = "FlavorExtraData extension not enabled."
             raise cls.skipException(msg)
 
@@ -62,7 +61,7 @@
         cls.client.wait_for_resource_deletion(cls.flavor['id'])
         super(FlavorsExtraSpecsNegativeTestJSON, cls).tearDownClass()
 
-    @attr(type=['negative', 'gate'])
+    @test.attr(type=['negative', 'gate'])
     def test_flavor_non_admin_set_keys(self):
         # Test to SET flavor extra spec as a user without admin privileges.
         specs = {"key1": "value1", "key2": "value2"}
@@ -71,7 +70,7 @@
                           self.flavor['id'],
                           specs)
 
-    @attr(type=['negative', 'gate'])
+    @test.attr(type=['negative', 'gate'])
     def test_flavor_non_admin_update_specific_key(self):
         # non admin user is not allowed to update flavor extra spec
         specs = {"key1": "value1", "key2": "value2"}
@@ -86,7 +85,7 @@
                           'key1',
                           key1='value1_new')
 
-    @attr(type=['negative', 'gate'])
+    @test.attr(type=['negative', 'gate'])
     def test_flavor_non_admin_unset_keys(self):
         specs = {"key1": "value1", "key2": "value2"}
         set_resp, set_body = self.client.set_flavor_extra_spec(
@@ -97,7 +96,7 @@
                           self.flavor['id'],
                           'key1')
 
-    @attr(type=['negative', 'gate'])
+    @test.attr(type=['negative', 'gate'])
     def test_flavor_unset_nonexistent_key(self):
         nonexistent_key = data_utils.rand_name('flavor_key')
         self.assertRaises(exceptions.NotFound,
@@ -105,14 +104,14 @@
                           self.flavor['id'],
                           nonexistent_key)
 
-    @attr(type=['negative', 'gate'])
+    @test.attr(type=['negative', 'gate'])
     def test_flavor_get_nonexistent_key(self):
         self.assertRaises(exceptions.NotFound,
                           self.flavors_client.get_flavor_extra_spec_with_key,
                           self.flavor['id'],
                           "nonexistent_key")
 
-    @attr(type=['negative', 'gate'])
+    @test.attr(type=['negative', 'gate'])
     def test_flavor_update_mismatch_key(self):
         # the key will be updated should be match the key in the body
         self.assertRaises(exceptions.BadRequest,
@@ -121,7 +120,7 @@
                           "key2",
                           key1="value")
 
-    @attr(type=['negative', 'gate'])
+    @test.attr(type=['negative', 'gate'])
     def test_flavor_update_more_key(self):
         # there should be just one item in the request body
         self.assertRaises(exceptions.BadRequest,
diff --git a/tempest/api/compute/admin/test_servers.py b/tempest/api/compute/admin/test_servers.py
index 5028cad..6fe3186 100644
--- a/tempest/api/compute/admin/test_servers.py
+++ b/tempest/api/compute/admin/test_servers.py
@@ -33,11 +33,8 @@
     def setUpClass(cls):
         super(ServersAdminTestJSON, cls).setUpClass()
         cls.client = cls.os_adm.servers_client
+        cls.non_admin_client = cls.servers_client
         cls.flavors_client = cls.os_adm.flavors_client
-        cls.identity_client = cls._get_identity_admin_client()
-        tenant = cls.identity_client.get_tenant_by_name(
-            cls.client.tenant_name)
-        cls.tenant_id = tenant['id']
 
         cls.s1_name = data_utils.rand_name('server')
         resp, server = cls.create_test_server(name=cls.s1_name,
@@ -116,6 +113,34 @@
         for key in basic_attrs:
             self.assertIn(key, str(diagnostic.keys()))
 
+    @attr(type='gate')
+    def test_rebuild_server_in_error_state(self):
+        # The server in error state should be rebuilt using the provided
+        # image and changed to ACTIVE state
+
+        # resetting vm state require admin priviledge
+        resp, server = self.client.reset_state(self.s1_id, state='error')
+        self.assertEqual(202, resp.status)
+        resp, rebuilt_server = self.non_admin_client.rebuild(
+            self.s1_id, self.image_ref_alt)
+        self.addCleanup(self.non_admin_client.wait_for_server_status,
+                        self.s1_id, 'ACTIVE')
+        self.addCleanup(self.non_admin_client.rebuild, self.s1_id,
+                        self.image_ref)
+
+        # Verify the properties in the initial response are correct
+        self.assertEqual(self.s1_id, rebuilt_server['id'])
+        rebuilt_image_id = rebuilt_server['image']['id']
+        self.assertEqual(self.image_ref_alt, rebuilt_image_id)
+        self.assertEqual(self.flavor_ref, rebuilt_server['flavor']['id'])
+        self.non_admin_client.wait_for_server_status(rebuilt_server['id'],
+                                                     'ACTIVE',
+                                                     raise_on_error=False)
+        # Verify the server properties after rebuilding
+        resp, server = self.non_admin_client.get_server(rebuilt_server['id'])
+        rebuilt_image_id = server['image']['id']
+        self.assertEqual(self.image_ref_alt, rebuilt_image_id)
+
 
 class ServersAdminTestXML(ServersAdminTestJSON):
     _interface = 'xml'
diff --git a/tempest/api/compute/images/test_images_oneserver.py b/tempest/api/compute/images/test_images_oneserver.py
index 18e32d8..19a308a 100644
--- a/tempest/api/compute/images/test_images_oneserver.py
+++ b/tempest/api/compute/images/test_images_oneserver.py
@@ -49,7 +49,7 @@
             LOG.exception(exc)
             # Rebuild server if cannot reach the ACTIVE state
             # Usually it means the server had a serius accident
-            self.server_id = self.rebuild_server(self.server_id)
+            self.__class__.server_id = self.rebuild_server(self.server_id)
 
     @classmethod
     def setUpClass(cls):
diff --git a/tempest/api/compute/images/test_images_oneserver_negative.py b/tempest/api/compute/images/test_images_oneserver_negative.py
index 4cd41ee..6757e8a 100644
--- a/tempest/api/compute/images/test_images_oneserver_negative.py
+++ b/tempest/api/compute/images/test_images_oneserver_negative.py
@@ -50,7 +50,7 @@
             LOG.exception(exc)
             # Rebuild server if cannot reach the ACTIVE state
             # Usually it means the server had a serius accident
-            self.server_id = self.rebuild_server(self.server_id)
+            self.__class__.server_id = self.rebuild_server(self.server_id)
 
     @classmethod
     def setUpClass(cls):
diff --git a/tempest/api/compute/servers/test_server_actions.py b/tempest/api/compute/servers/test_server_actions.py
index 5fa4c35..f244155 100644
--- a/tempest/api/compute/servers/test_server_actions.py
+++ b/tempest/api/compute/servers/test_server_actions.py
@@ -45,7 +45,7 @@
             self.client.wait_for_server_status(self.server_id, 'ACTIVE')
         except Exception:
             # Rebuild server if something happened to it during a test
-            self.server_id = self.rebuild_server(self.server_id)
+            self.__class__.server_id = self.rebuild_server(self.server_id)
 
     @classmethod
     def setUpClass(cls):
@@ -123,6 +123,7 @@
                                                    metadata=meta,
                                                    personality=personality,
                                                    adminPass=password)
+        self.addCleanup(self.client.rebuild, self.server_id, self.image_ref)
 
         # Verify the properties in the initial response are correct
         self.assertEqual(self.server_id, rebuilt_server['id'])
@@ -133,15 +134,44 @@
         # Verify the server properties after the rebuild completes
         self.client.wait_for_server_status(rebuilt_server['id'], 'ACTIVE')
         resp, server = self.client.get_server(rebuilt_server['id'])
-        rebuilt_image_id = rebuilt_server['image']['id']
+        rebuilt_image_id = server['image']['id']
         self.assertTrue(self.image_ref_alt.endswith(rebuilt_image_id))
-        self.assertEqual(new_name, rebuilt_server['name'])
+        self.assertEqual(new_name, server['name'])
 
         if self.run_ssh:
             # Verify that the user can authenticate with the provided password
             linux_client = RemoteClient(server, self.ssh_user, password)
             self.assertTrue(linux_client.can_authenticate())
 
+    @attr(type='gate')
+    def test_rebuild_server_in_stop_state(self):
+        # The server in stop state  should be rebuilt using the provided
+        # image and remain in SHUTOFF state
+        resp, server = self.client.get_server(self.server_id)
+        old_image = server['image']['id']
+        new_image = self.image_ref_alt \
+            if old_image == self.image_ref else self.image_ref
+        resp, server = self.client.stop(self.server_id)
+        self.assertEqual(202, resp.status)
+        self.client.wait_for_server_status(self.server_id, 'SHUTOFF')
+        self.addCleanup(self.client.start, self.server_id)
+        resp, rebuilt_server = self.client.rebuild(self.server_id, new_image)
+        self.addCleanup(self.client.wait_for_server_status, self.server_id,
+                        'SHUTOFF')
+        self.addCleanup(self.client.rebuild, self.server_id, old_image)
+
+        # Verify the properties in the initial response are correct
+        self.assertEqual(self.server_id, rebuilt_server['id'])
+        rebuilt_image_id = rebuilt_server['image']['id']
+        self.assertEqual(new_image, rebuilt_image_id)
+        self.assertEqual(self.flavor_ref, rebuilt_server['flavor']['id'])
+
+        # Verify the server properties after the rebuild completes
+        self.client.wait_for_server_status(rebuilt_server['id'], 'SHUTOFF')
+        resp, server = self.client.get_server(rebuilt_server['id'])
+        rebuilt_image_id = server['image']['id']
+        self.assertEqual(new_image, rebuilt_image_id)
+
     def _detect_server_image_flavor(self, server_id):
         # Detects the current server image flavor ref.
         resp, server = self.client.get_server(self.server_id)
diff --git a/tempest/api/compute/servers/test_servers_negative.py b/tempest/api/compute/servers/test_servers_negative.py
index 7b86d2d..8142250 100644
--- a/tempest/api/compute/servers/test_servers_negative.py
+++ b/tempest/api/compute/servers/test_servers_negative.py
@@ -34,7 +34,7 @@
         try:
             self.client.wait_for_server_status(self.server_id, 'ACTIVE')
         except Exception:
-            self.server_id = self.rebuild_server(self.server_id)
+            self.__class__.server_id = self.rebuild_server(self.server_id)
 
     @classmethod
     def setUpClass(cls):
diff --git a/tempest/api/compute/v3/admin/test_flavors_access.py b/tempest/api/compute/v3/admin/test_flavors_access.py
index b866db1..048312b 100644
--- a/tempest/api/compute/v3/admin/test_flavors_access.py
+++ b/tempest/api/compute/v3/admin/test_flavors_access.py
@@ -15,10 +15,9 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from tempest.api import compute
 from tempest.api.compute import base
 from tempest.common.utils import data_utils
-from tempest.test import attr
+from tempest import test
 
 
 class FlavorsAccessTestJSON(base.BaseV2ComputeAdminTest):
@@ -33,7 +32,7 @@
     @classmethod
     def setUpClass(cls):
         super(FlavorsAccessTestJSON, cls).setUpClass()
-        if not compute.FLAVOR_EXTRA_DATA_ENABLED:
+        if not test.is_extension_enabled('FlavorExtraData', 'compute'):
             msg = "FlavorExtraData extension not enabled."
             raise cls.skipException(msg)
 
@@ -51,7 +50,7 @@
         cls.vcpus = 1
         cls.disk = 10
 
-    @attr(type='gate')
+    @test.attr(type='gate')
     def test_flavor_access_list_with_private_flavor(self):
         # Test to list flavor access successfully by querying private flavor
         flavor_name = data_utils.rand_name(self.flavor_name_prefix)
@@ -70,7 +69,7 @@
         self.assertEqual(str(new_flavor_id), str(first_flavor['flavor_id']))
         self.assertEqual(self.adm_tenant_id, first_flavor['tenant_id'])
 
-    @attr(type='gate')
+    @test.attr(type='gate')
     def test_flavor_access_add_remove(self):
         # Test to add and remove flavor access to a given tenant.
         flavor_name = data_utils.rand_name(self.flavor_name_prefix)
diff --git a/tempest/api/compute/v3/admin/test_flavors_access_negative.py b/tempest/api/compute/v3/admin/test_flavors_access_negative.py
index 340c1c7..976124e 100644
--- a/tempest/api/compute/v3/admin/test_flavors_access_negative.py
+++ b/tempest/api/compute/v3/admin/test_flavors_access_negative.py
@@ -17,11 +17,10 @@
 
 import uuid
 
-from tempest.api import compute
 from tempest.api.compute import base
 from tempest.common.utils import data_utils
 from tempest import exceptions
-from tempest.test import attr
+from tempest import test
 
 
 class FlavorsAccessNegativeTestJSON(base.BaseV2ComputeAdminTest):
@@ -36,7 +35,7 @@
     @classmethod
     def setUpClass(cls):
         super(FlavorsAccessNegativeTestJSON, cls).setUpClass()
-        if not compute.FLAVOR_EXTRA_DATA_ENABLED:
+        if not test.is_extension_enabled('FlavorExtraData', 'compute'):
             msg = "FlavorExtraData extension not enabled."
             raise cls.skipException(msg)
 
@@ -54,7 +53,7 @@
         cls.vcpus = 1
         cls.disk = 10
 
-    @attr(type=['negative', 'gate'])
+    @test.attr(type=['negative', 'gate'])
     def test_flavor_access_list_with_public_flavor(self):
         # Test to list flavor access with exceptions by querying public flavor
         flavor_name = data_utils.rand_name(self.flavor_name_prefix)
@@ -70,7 +69,7 @@
                           self.client.list_flavor_access,
                           new_flavor_id)
 
-    @attr(type=['negative', 'gate'])
+    @test.attr(type=['negative', 'gate'])
     def test_flavor_non_admin_add(self):
         # Test to add flavor access as a user without admin privileges.
         flavor_name = data_utils.rand_name(self.flavor_name_prefix)
@@ -86,7 +85,7 @@
                           new_flavor['id'],
                           self.tenant_id)
 
-    @attr(type=['negative', 'gate'])
+    @test.attr(type=['negative', 'gate'])
     def test_flavor_non_admin_remove(self):
         # Test to remove flavor access as a user without admin privileges.
         flavor_name = data_utils.rand_name(self.flavor_name_prefix)
@@ -106,7 +105,7 @@
                           new_flavor['id'],
                           self.tenant_id)
 
-    @attr(type=['negative', 'gate'])
+    @test.attr(type=['negative', 'gate'])
     def test_add_flavor_access_duplicate(self):
         # Create a new flavor.
         flavor_name = data_utils.rand_name(self.flavor_name_prefix)
@@ -130,7 +129,7 @@
                           new_flavor['id'],
                           self.tenant_id)
 
-    @attr(type=['negative', 'gate'])
+    @test.attr(type=['negative', 'gate'])
     def test_remove_flavor_access_not_found(self):
         # Create a new flavor.
         flavor_name = data_utils.rand_name(self.flavor_name_prefix)
diff --git a/tempest/api/compute/v3/admin/test_flavors_extra_specs.py b/tempest/api/compute/v3/admin/test_flavors_extra_specs.py
index 49b0429..875f742 100644
--- a/tempest/api/compute/v3/admin/test_flavors_extra_specs.py
+++ b/tempest/api/compute/v3/admin/test_flavors_extra_specs.py
@@ -15,10 +15,9 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from tempest.api import compute
 from tempest.api.compute import base
 from tempest.common.utils import data_utils
-from tempest.test import attr
+from tempest import test
 
 
 class FlavorsExtraSpecsTestJSON(base.BaseV2ComputeAdminTest):
@@ -34,7 +33,7 @@
     @classmethod
     def setUpClass(cls):
         super(FlavorsExtraSpecsTestJSON, cls).setUpClass()
-        if not compute.FLAVOR_EXTRA_DATA_ENABLED:
+        if not test.is_extension_enabled('FlavorExtraData', 'compute'):
             msg = "FlavorExtraData extension not enabled."
             raise cls.skipException(msg)
 
@@ -61,7 +60,7 @@
         cls.client.wait_for_resource_deletion(cls.flavor['id'])
         super(FlavorsExtraSpecsTestJSON, cls).tearDownClass()
 
-    @attr(type='gate')
+    @test.attr(type='gate')
     def test_flavor_set_get_update_show_unset_keys(self):
         # Test to SET, GET, UPDATE, SHOW, UNSET flavor extra
         # spec as a user with admin privileges.
@@ -101,7 +100,7 @@
             self.client.unset_flavor_extra_spec(self.flavor['id'], "key2")
         self.assertEqual(unset_resp.status, 200)
 
-    @attr(type='gate')
+    @test.attr(type='gate')
     def test_flavor_non_admin_get_all_keys(self):
         specs = {"key1": "value1", "key2": "value2"}
         set_resp, set_body = self.client.set_flavor_extra_spec(
@@ -113,7 +112,7 @@
         for key in specs:
             self.assertEqual(body[key], specs[key])
 
-    @attr(type='gate')
+    @test.attr(type='gate')
     def test_flavor_non_admin_get_specific_key(self):
         specs = {"key1": "value1", "key2": "value2"}
         resp, body = self.client.set_flavor_extra_spec(
diff --git a/tempest/api/compute/v3/admin/test_flavors_extra_specs_negative.py b/tempest/api/compute/v3/admin/test_flavors_extra_specs_negative.py
index d7e1f9f..fb09a63 100644
--- a/tempest/api/compute/v3/admin/test_flavors_extra_specs_negative.py
+++ b/tempest/api/compute/v3/admin/test_flavors_extra_specs_negative.py
@@ -16,11 +16,10 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from tempest.api import compute
 from tempest.api.compute import base
 from tempest.common.utils import data_utils
 from tempest import exceptions
-from tempest.test import attr
+from tempest import test
 
 
 class FlavorsExtraSpecsNegativeTestJSON(base.BaseV2ComputeAdminTest):
@@ -35,7 +34,7 @@
     @classmethod
     def setUpClass(cls):
         super(FlavorsExtraSpecsNegativeTestJSON, cls).setUpClass()
-        if not compute.FLAVOR_EXTRA_DATA_ENABLED:
+        if not test.is_extension_enabled('FlavorExtraData', 'compute'):
             msg = "FlavorExtraData extension not enabled."
             raise cls.skipException(msg)
 
@@ -62,7 +61,7 @@
         cls.client.wait_for_resource_deletion(cls.flavor['id'])
         super(FlavorsExtraSpecsNegativeTestJSON, cls).tearDownClass()
 
-    @attr(type=['negative', 'gate'])
+    @test.attr(type=['negative', 'gate'])
     def test_flavor_non_admin_set_keys(self):
         # Test to SET flavor extra spec as a user without admin privileges.
         specs = {"key1": "value1", "key2": "value2"}
@@ -71,7 +70,7 @@
                           self.flavor['id'],
                           specs)
 
-    @attr(type=['negative', 'gate'])
+    @test.attr(type=['negative', 'gate'])
     def test_flavor_non_admin_update_specific_key(self):
         # non admin user is not allowed to update flavor extra spec
         specs = {"key1": "value1", "key2": "value2"}
@@ -86,7 +85,7 @@
                           'key1',
                           key1='value1_new')
 
-    @attr(type=['negative', 'gate'])
+    @test.attr(type=['negative', 'gate'])
     def test_flavor_non_admin_unset_keys(self):
         specs = {"key1": "value1", "key2": "value2"}
         set_resp, set_body = self.client.set_flavor_extra_spec(
@@ -97,7 +96,7 @@
                           self.flavor['id'],
                           'key1')
 
-    @attr(type=['negative', 'gate'])
+    @test.attr(type=['negative', 'gate'])
     def test_flavor_unset_nonexistent_key(self):
         nonexistent_key = data_utils.rand_name('flavor_key')
         self.assertRaises(exceptions.NotFound,
@@ -105,14 +104,14 @@
                           self.flavor['id'],
                           nonexistent_key)
 
-    @attr(type=['negative', 'gate'])
+    @test.attr(type=['negative', 'gate'])
     def test_flavor_get_nonexistent_key(self):
         self.assertRaises(exceptions.NotFound,
                           self.flavors_client.get_flavor_extra_spec_with_key,
                           self.flavor['id'],
                           "nonexistent_key")
 
-    @attr(type=['negative', 'gate'])
+    @test.attr(type=['negative', 'gate'])
     def test_flavor_update_mismatch_key(self):
         # the key will be updated should be match the key in the body
         self.assertRaises(exceptions.BadRequest,
@@ -121,7 +120,7 @@
                           "key2",
                           key1="value")
 
-    @attr(type=['negative', 'gate'])
+    @test.attr(type=['negative', 'gate'])
     def test_flavor_update_more_key(self):
         # there should be just one item in the request body
         self.assertRaises(exceptions.BadRequest,
diff --git a/tempest/api/compute/v3/admin/test_services.py b/tempest/api/compute/v3/admin/test_services.py
index 67f9947..64135ed 100644
--- a/tempest/api/compute/v3/admin/test_services.py
+++ b/tempest/api/compute/v3/admin/test_services.py
@@ -113,23 +113,6 @@
         self.assertEqual(200, resp.status)
         self.assertEqual(0, len(services))
 
-    @attr(type='gate')
-    def test_service_enable_disable(self):
-        resp, services = self.client.list_services()
-        host_name = services[0]['host']
-        binary_name = services[0]['binary']
-
-        resp, service = self.client.disable_service(host_name, binary_name)
-        self.assertEqual(200, resp.status)
-        params = {'host': host_name, 'binary': binary_name}
-        resp, services = self.client.list_services(params)
-        self.assertEqual('disabled', services[0]['status'])
-
-        resp, service = self.client.enable_service(host_name, binary_name)
-        self.assertEqual(200, resp.status)
-        resp, services = self.client.list_services(params)
-        self.assertEqual('enabled', services[0]['status'])
-
 
 class ServicesAdminV3TestXML(ServicesAdminV3TestJSON):
     _interface = 'xml'
diff --git a/tempest/api/compute/v3/servers/test_server_actions.py b/tempest/api/compute/v3/servers/test_server_actions.py
index dc78a47..090f4dd 100644
--- a/tempest/api/compute/v3/servers/test_server_actions.py
+++ b/tempest/api/compute/v3/servers/test_server_actions.py
@@ -45,7 +45,7 @@
             self.client.wait_for_server_status(self.server_id, 'ACTIVE')
         except Exception:
             # Rebuild server if something happened to it during a test
-            self.server_id = self.rebuild_server(self.server_id)
+            self.__class__.server_id = self.rebuild_server(self.server_id)
 
     @classmethod
     def setUpClass(cls):
diff --git a/tempest/api/compute/v3/servers/test_servers_negative.py b/tempest/api/compute/v3/servers/test_servers_negative.py
index 7b86d2d..8142250 100644
--- a/tempest/api/compute/v3/servers/test_servers_negative.py
+++ b/tempest/api/compute/v3/servers/test_servers_negative.py
@@ -34,7 +34,7 @@
         try:
             self.client.wait_for_server_status(self.server_id, 'ACTIVE')
         except Exception:
-            self.server_id = self.rebuild_server(self.server_id)
+            self.__class__.server_id = self.rebuild_server(self.server_id)
 
     @classmethod
     def setUpClass(cls):
diff --git a/tempest/api/compute/volumes/test_volumes_list.py b/tempest/api/compute/volumes/test_volumes_list.py
index f214641..f54e9b3 100644
--- a/tempest/api/compute/volumes/test_volumes_list.py
+++ b/tempest/api/compute/volumes/test_volumes_list.py
@@ -43,9 +43,8 @@
         cls.volume_list = []
         cls.volume_id_list = []
         for i in range(3):
-            v_name = data_utils.rand_name('volume-%s')
+            v_name = data_utils.rand_name('volume-%s' % cls._interface)
             metadata = {'Type': 'work'}
-            v_name += cls._interface
             try:
                 resp, volume = cls.client.create_volume(size=1,
                                                         display_name=v_name,
diff --git a/tempest/common/rest_client.py b/tempest/common/rest_client.py
index 9322f1b..9aca2ff 100644
--- a/tempest/common/rest_client.py
+++ b/tempest/common/rest_client.py
@@ -506,18 +506,26 @@
         if resp.status in (500, 501):
             message = resp_body
             if parse_resp:
-                resp_body = self._parse_resp(resp_body)
-                # I'm seeing both computeFault and cloudServersFault come back.
-                # Will file a bug to fix, but leave as is for now.
-                if 'cloudServersFault' in resp_body:
-                    message = resp_body['cloudServersFault']['message']
-                elif 'computeFault' in resp_body:
-                    message = resp_body['computeFault']['message']
-                elif 'error' in resp_body:  # Keystone errors
-                    message = resp_body['error']['message']
-                    raise exceptions.IdentityError(message)
-                elif 'message' in resp_body:
-                    message = resp_body['message']
+                try:
+                    resp_body = self._parse_resp(resp_body)
+                except ValueError:
+                    # If response body is a non-json string message.
+                    # Use resp_body as is and raise InvalidResponseBody
+                    # exception.
+                    raise exceptions.InvalidHTTPResponseBody(message)
+                else:
+                    # I'm seeing both computeFault
+                    # and cloudServersFault come back.
+                    # Will file a bug to fix, but leave as is for now.
+                    if 'cloudServersFault' in resp_body:
+                        message = resp_body['cloudServersFault']['message']
+                    elif 'computeFault' in resp_body:
+                        message = resp_body['computeFault']['message']
+                    elif 'error' in resp_body:  # Keystone errors
+                        message = resp_body['error']['message']
+                        raise exceptions.IdentityError(message)
+                    elif 'message' in resp_body:
+                        message = resp_body['message']
 
             raise exceptions.ServerFault(message)
 
diff --git a/tempest/common/waiters.py b/tempest/common/waiters.py
index bea2cdc..497a297 100644
--- a/tempest/common/waiters.py
+++ b/tempest/common/waiters.py
@@ -25,7 +25,7 @@
 
 # NOTE(afazekas): This function needs to know a token and a subject.
 def wait_for_server_status(client, server_id, status, ready_wait=True,
-                           extra_timeout=0):
+                           extra_timeout=0, raise_on_error=True):
     """Waits for a server to reach a given status."""
 
     def _get_task_state(body):
@@ -69,7 +69,7 @@
                      '/'.join((old_status, str(old_task_state))),
                      '/'.join((server_status, str(task_state))),
                      time.time() - start_time)
-        if server_status == 'ERROR':
+        if (server_status == 'ERROR') and raise_on_error:
             raise exceptions.BuildErrorException(server_id=server_id)
 
         timed_out = int(time.time()) - start_time >= timeout
diff --git a/tempest/config.py b/tempest/config.py
index 220fd04..3d9eba9 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -208,9 +208,14 @@
     cfg.BoolOpt('disk_config',
                 default=True,
                 help="If false, skip disk config tests"),
-    cfg.BoolOpt('flavor_extra',
-                default=True,
-                help="If false, skip flavor extra data test"),
+    cfg.ListOpt('api_extensions',
+                default=['all'],
+                help='A list of enabled extensions with a special entry all '
+                     'which indicates every extension is enabled'),
+    cfg.ListOpt('api_v3_extensions',
+                default=['all'],
+                help='A list of enabled v3 extensions with a special entry all'
+                     ' which indicates every extension is enabled'),
     cfg.BoolOpt('change_password',
                 default=False,
                 help="Does the test environment support changing the admin "
@@ -317,6 +322,16 @@
                     "connectivity"),
 ]
 
+network_feature_group = cfg.OptGroup(name='network-feature-enabled',
+                                     title='Enabled network service features')
+
+NetworkFeaturesGroup = [
+    cfg.ListOpt('api_extensions',
+                default=['all'],
+                help='A list of enabled extensions with a special entry all '
+                     'which indicates every extension is enabled'),
+]
+
 volume_group = cfg.OptGroup(name='volume',
                             title='Block Storage Options')
 
@@ -360,7 +375,11 @@
 VolumeFeaturesGroup = [
     cfg.BoolOpt('multi_backend',
                 default=False,
-                help="Runs Cinder multi-backend test (requires 2 backends)")
+                help="Runs Cinder multi-backend test (requires 2 backends)"),
+    cfg.ListOpt('api_extensions',
+                default=['all'],
+                help='A list of enabled extensions with a special entry all '
+                     'which indicates every extension is enabled'),
 ]
 
 
@@ -659,6 +678,8 @@
         register_opt_group(cfg.CONF, image_group, ImageGroup)
         register_opt_group(cfg.CONF, image_feature_group, ImageFeaturesGroup)
         register_opt_group(cfg.CONF, network_group, NetworkGroup)
+        register_opt_group(cfg.CONF, network_feature_group,
+                           NetworkFeaturesGroup)
         register_opt_group(cfg.CONF, volume_group, VolumeGroup)
         register_opt_group(cfg.CONF, volume_feature_group,
                            VolumeFeaturesGroup)
@@ -680,6 +701,7 @@
         self.images = cfg.CONF.image
         self.image_feature_enabled = cfg.CONF['image-feature-enabled']
         self.network = cfg.CONF.network
+        self.network_feature_enabled = cfg.CONF['network-feature-enabled']
         self.volume = cfg.CONF.volume
         self.volume_feature_enabled = cfg.CONF['volume-feature-enabled']
         self.object_storage = cfg.CONF['object-storage']
diff --git a/tempest/exceptions.py b/tempest/exceptions.py
index 02fc231..0bab9c7 100644
--- a/tempest/exceptions.py
+++ b/tempest/exceptions.py
@@ -184,3 +184,7 @@
 class ResponseWithEntity(RFCViolation):
     message = ("RFC Violation! Response with 205 HTTP Status Code "
                "MUST NOT have an entity")
+
+
+class InvalidHTTPResponseBody(RestClientException):
+    message = "HTTP response body is invalid json or xml"
diff --git a/tempest/scenario/orchestration/test_autoscaling.py b/tempest/scenario/orchestration/test_autoscaling.py
index e843793..90ef3a0 100644
--- a/tempest/scenario/orchestration/test_autoscaling.py
+++ b/tempest/scenario/orchestration/test_autoscaling.py
@@ -19,6 +19,7 @@
 from tempest.test import attr
 from tempest.test import call_until_true
 from tempest.test import services
+from tempest.test import skip_because
 
 
 class AutoScalingTest(manager.OrchestrationScenarioTest):
@@ -62,6 +63,7 @@
         if not self.config.orchestration.keypair_name:
             self.set_resource('stack', self.stack)
 
+    @skip_because(bug="1257575")
     @attr(type='slow')
     @services('orchestration', 'compute')
     def test_scale_up_then_down(self):
diff --git a/tempest/scenario/orchestration/test_autoscaling.yaml b/tempest/scenario/orchestration/test_autoscaling.yaml
index 745eb05..4651284 100644
--- a/tempest/scenario/orchestration/test_autoscaling.yaml
+++ b/tempest/scenario/orchestration/test_autoscaling.yaml
@@ -23,11 +23,11 @@
     Default: '420'
   ScaleUpThreshold:
     Description: Memory percentage threshold to scale up on
-    Type: Number
+    Type: String
     Default: '70'
   ScaleDownThreshold:
     Description: Memory percentage threshold to scale down on
-    Type: Number
+    Type: String
     Default: '60'
   ConsumeMemoryLimit:
     Description: Memory percentage threshold to consume
@@ -182,4 +182,4 @@
             # wait ConsumeStartSeconds then ramp up memory consumption
             # until it is over ConsumeMemoryLimit%
             # then exits ConsumeStopSeconds seconds after stack launch
-            /root/consume_memory > /root/consume_memory.log &
\ No newline at end of file
+            /root/consume_memory > /root/consume_memory.log &
diff --git a/tempest/services/compute/json/servers_client.py b/tempest/services/compute/json/servers_client.py
index 3c6a40f..eb1a0c3 100644
--- a/tempest/services/compute/json/servers_client.py
+++ b/tempest/services/compute/json/servers_client.py
@@ -154,10 +154,12 @@
         body = json.loads(body)
         return resp, body
 
-    def wait_for_server_status(self, server_id, status, extra_timeout=0):
+    def wait_for_server_status(self, server_id, status, extra_timeout=0,
+                               raise_on_error=True):
         """Waits for a server to reach a given status."""
         return waiters.wait_for_server_status(self, server_id, status,
-                                              extra_timeout=extra_timeout)
+                                              extra_timeout=extra_timeout,
+                                              raise_on_error=raise_on_error)
 
     def wait_for_server_termination(self, server_id, ignore_error=False):
         """Waits for server to reach termination."""
diff --git a/tempest/services/compute/xml/servers_client.py b/tempest/services/compute/xml/servers_client.py
index 7d40d0e..68f6cf0 100644
--- a/tempest/services/compute/xml/servers_client.py
+++ b/tempest/services/compute/xml/servers_client.py
@@ -363,10 +363,12 @@
         server = self._parse_server(etree.fromstring(body))
         return resp, server
 
-    def wait_for_server_status(self, server_id, status, extra_timeout=0):
+    def wait_for_server_status(self, server_id, status, extra_timeout=0,
+                               raise_on_error=True):
         """Waits for a server to reach a given status."""
         return waiters.wait_for_server_status(self, server_id, status,
-                                              extra_timeout=extra_timeout)
+                                              extra_timeout=extra_timeout,
+                                              raise_on_error=raise_on_error)
 
     def wait_for_server_termination(self, server_id, ignore_error=False):
         """Waits for server to reach termination."""
diff --git a/tempest/test.py b/tempest/test.py
index 6ae7925..ceb2c80 100644
--- a/tempest/test.py
+++ b/tempest/test.py
@@ -123,6 +123,42 @@
     return decorator
 
 
+def requires_ext(*args, **kwargs):
+    """A decorator to skip tests if an extension is not enabled
+
+    @param extension
+    @param service
+    """
+    def decorator(func):
+        @functools.wraps(func)
+        def wrapper(*func_args, **func_kwargs):
+            if not is_extension_enabled(kwargs['extension'],
+                                        kwargs['service']):
+                msg = "Skipped because %s extension: %s is not enabled" % (
+                    kwargs['service'], kwargs['extension'])
+                raise testtools.TestCase.skipException(msg)
+            return func(*func_args, **func_kwargs)
+        return wrapper
+    return decorator
+
+
+def is_extension_enabled(extension_name, service):
+    """A function that will check the list of enabled extensions from config
+
+    """
+    configs = config.TempestConfig()
+    config_dict = {
+        'compute': configs.compute_feature_enabled.api_extensions,
+        'compute_v3': configs.compute_feature_enabled.api_v3_extensions,
+        'volume': configs.volume_feature_enabled.api_extensions,
+        'network': configs.network_feature_enabled.api_extensions,
+    }
+    if config_dict[service][0] == 'all':
+        return True
+    if extension_name in config_dict[service]:
+        return True
+    return False
+
 # there is a mis-match between nose and testtools for older pythons.
 # testtools will set skipException to be either
 # unittest.case.SkipTest, unittest2.case.SkipTest or an internal skip
diff --git a/test-requirements.txt b/test-requirements.txt
index 5cdb819..9486244 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -1,7 +1,7 @@
 hacking>=0.8.0,<0.9
 # needed for doc build
 docutils==0.9.1
-sphinx>=1.1.2
+sphinx>=1.1.2,<1.2
 python-subunit
 oslo.sphinx
 mox>=0.5.3
diff --git a/tox.ini b/tox.ini
index e5698d2..9389cf4 100644
--- a/tox.ini
+++ b/tox.ini
@@ -9,6 +9,7 @@
          LANGUAGE=en_US:en
          LC_ALL=C
 usedevelop = True
+install_command = pip install -U {opts} {packages}
 
 [testenv:py26]
 commands = python setup.py test --slowest --testr-arg='tempest\.tests {posargs}'