Merge "Add testcases for volume"
diff --git a/.testr.conf b/.testr.conf
index 05b12c4..c25ebec 100644
--- a/.testr.conf
+++ b/.testr.conf
@@ -2,7 +2,8 @@
test_command=OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} \
OS_STDERR_CAPTURE=${OS_STDERR_CAPTURE:-1} \
OS_TEST_TIMEOUT=${OS_TEST_TIMEOUT:-500} \
- ${PYTHON:-python} -m subunit.run discover -t ./ ./tempest $LISTOPT $IDOPTION
+ OS_TEST_PATH=${OS_TEST_PATH:-./tempest} \
+ ${PYTHON:-python} -m subunit.run discover -t ./ $OS_TEST_PATH $LISTOPT $IDOPTION
test_id_option=--load-list $IDFILE
test_list_option=--list
group_regex=([^\.]*\.)*
diff --git a/etc/tempest.conf.sample b/etc/tempest.conf.sample
index 937bbd3..1080ddf 100644
--- a/etc/tempest.conf.sample
+++ b/etc/tempest.conf.sample
@@ -43,7 +43,7 @@
#logging_exception_prefix=%(asctime)s.%(msecs)03d %(process)d TRACE %(name)s %(instance)s
# list of logger=LEVEL pairs (list value)
-#default_log_levels=amqplib=WARN,sqlalchemy=WARN,boto=WARN,suds=INFO,keystone=INFO
+#default_log_levels=amqplib=WARN,sqlalchemy=WARN,boto=WARN,suds=INFO,keystone=INFO,paramiko=INFO
# publish error events (boolean value)
#publish_errors=false
@@ -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/etc/whitelist.yaml b/etc/whitelist.yaml
index e5a0d4d..a8c5276 100644
--- a/etc/whitelist.yaml
+++ b/etc/whitelist.yaml
@@ -160,14 +160,6 @@
- module: "cinder.brick.local_dev.lvm"
message: "Can't remove open logical volume"
-q-dhpc:
- - module: "neutron.common.legacy"
- message: "Skipping unknown group key: firewall_driver"
- - module: "neutron.agent.dhcp_agent"
- message: "Unable to enable dhcp"
- - module: "neutron.agent.dhcp_agent"
- message: " Network .* RPC info call failed"
-
ceilometer-collector:
- module: "stevedore.extension"
message: ".*"
@@ -226,3 +218,6 @@
- module: ".*"
message: ".*"
+s-proxy:
+ - module: "proxy-server"
+ message: "Timeout talking to memcached"
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..d20068e 100644
--- a/tempest/api/compute/__init__.py
+++ b/tempest/api/compute/__init__.py
@@ -16,7 +16,6 @@
# under the License.
from tempest import config
-from tempest.exceptions import InvalidConfiguration
from tempest.openstack.common import log as logging
LOG = logging.getLogger(__name__)
@@ -26,30 +25,3 @@
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
-
-
-# All compute tests -- single setup function
-def generic_setup_package():
- LOG.debug("Entering tempest.api.compute.setup_package")
-
- global MULTI_USER
-
- # Determine if there are two regular users that can be
- # used in testing. If the test cases are allowed to create
- # users (config.compute.allow_tenant_isolation is true,
- # then we allow multi-user.
- if not CONFIG.compute.allow_tenant_isolation:
- user1 = CONFIG.identity.username
- user2 = CONFIG.identity.alt_username
- if not user2 or user1 == user2:
- MULTI_USER = False
- else:
- user2_password = CONFIG.identity.alt_password
- user2_tenant_name = CONFIG.identity.alt_tenant_name
- if not user2_password or not user2_tenant_name:
- msg = ("Alternate user specified but not alternate "
- "tenant or password: alt_tenant_name=%s alt_password=%s"
- % (user2_tenant_name, user2_password))
- raise InvalidConfiguration(msg)
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/admin/test_services_negative.py b/tempest/api/compute/admin/test_services_negative.py
index f4449d3..da1482e 100644
--- a/tempest/api/compute/admin/test_services_negative.py
+++ b/tempest/api/compute/admin/test_services_negative.py
@@ -19,7 +19,7 @@
from tempest.test import attr
-class ServicesAdminTestJSON(base.BaseV2ComputeAdminTest):
+class ServicesAdminNegativeTestJSON(base.BaseV2ComputeAdminTest):
"""
Tests Services API. List and Enable/Disable require admin privileges.
@@ -29,7 +29,7 @@
@classmethod
def setUpClass(cls):
- super(ServicesAdminTestJSON, cls).setUpClass()
+ super(ServicesAdminNegativeTestJSON, cls).setUpClass()
cls.client = cls.os_adm.services_client
cls.non_admin_client = cls.services_client
@@ -66,5 +66,5 @@
self.assertEqual(0, len(services))
-class ServicesAdminTestXML(ServicesAdminTestJSON):
+class ServicesAdminNegativeTestXML(ServicesAdminNegativeTestJSON):
_interface = 'xml'
diff --git a/tempest/api/compute/base.py b/tempest/api/compute/base.py
index e72f3fc..d18b749 100644
--- a/tempest/api/compute/base.py
+++ b/tempest/api/compute/base.py
@@ -17,7 +17,6 @@
import time
-from tempest.api import compute
from tempest import clients
from tempest.common.utils import data_utils
from tempest import exceptions
@@ -31,7 +30,6 @@
class BaseComputeTest(tempest.test.BaseTestCase):
"""Base test case class for all Compute API tests."""
- conclusion = compute.generic_setup_package()
force_tenant_isolation = False
@classmethod
@@ -55,6 +53,30 @@
cls.image_ssh_password = cls.config.compute.image_ssh_password
cls.servers = []
cls.images = []
+ cls.multi_user = cls.get_multi_user()
+
+ @classmethod
+ def get_multi_user(cls):
+ multi_user = True
+ # Determine if there are two regular users that can be
+ # used in testing. If the test cases are allowed to create
+ # users (config.compute.allow_tenant_isolation is true,
+ # then we allow multi-user.
+ if not cls.config.compute.allow_tenant_isolation:
+ user1 = cls.config.identity.username
+ user2 = cls.config.identity.alt_username
+ if not user2 or user1 == user2:
+ multi_user = False
+ else:
+ user2_password = cls.config.identity.alt_password
+ user2_tenant_name = cls.config.identity.alt_tenant_name
+ if not user2_password or not user2_tenant_name:
+ msg = ("Alternate user specified but not alternate "
+ "tenant or password: alt_tenant_name=%s "
+ "alt_password=%s"
+ % (user2_tenant_name, user2_password))
+ raise exceptions.InvalidConfiguration(msg)
+ return multi_user
@classmethod
def clear_servers(cls):
@@ -178,6 +200,11 @@
kwargs['wait_until'])
resp, image = cls.images_client.get_image(image_id)
+ if kwargs['wait_until'] == 'ACTIVE':
+ if kwargs.get('wait_for_server', True):
+ cls.servers_client.wait_for_server_status(server_id,
+ 'ACTIVE')
+
return resp, image
@classmethod
diff --git a/tempest/api/compute/flavors/test_flavors.py b/tempest/api/compute/flavors/test_flavors.py
index fa99422..092fd65 100644
--- a/tempest/api/compute/flavors/test_flavors.py
+++ b/tempest/api/compute/flavors/test_flavors.py
@@ -16,7 +16,6 @@
# under the License.
from tempest.api.compute import base
-from tempest import exceptions
from tempest.test import attr
@@ -50,12 +49,6 @@
resp, flavor = self.client.get_flavor_details(self.flavor_ref)
self.assertEqual(self.flavor_ref, flavor['id'])
- @attr(type=['negative', 'gate'])
- def test_get_non_existant_flavor(self):
- # flavor details are not returned for non-existent flavors
- self.assertRaises(exceptions.NotFound, self.client.get_flavor_details,
- 999)
-
@attr(type='gate')
def test_list_flavors_limit_results(self):
# Only the expected number of flavors should be returned
@@ -136,24 +129,6 @@
resp, flavors = self.client.list_flavors(params)
self.assertFalse(any([i for i in flavors if i['id'] == flavor_id]))
- @attr(type=['negative', 'gate'])
- def test_invalid_minRam_filter(self):
- self.assertRaises(exceptions.BadRequest,
- self.client.list_flavors_with_detail,
- {'minRam': 'invalid'})
-
- @attr(type=['negative', 'gate'])
- def test_invalid_minDisk_filter(self):
- self.assertRaises(exceptions.BadRequest,
- self.client.list_flavors_with_detail,
- {'minDisk': 'invalid'})
-
- @attr(type=['negative', 'gate'])
- def test_get_flavor_details_for_invalid_flavor_id(self):
- # Ensure 404 returned for non-existent flavor ID
- self.assertRaises(exceptions.NotFound, self.client.get_flavor_details,
- 9999)
-
class FlavorsTestXML(FlavorsTestJSON):
_interface = 'xml'
diff --git a/tempest/api/compute/flavors/test_flavors_negative.py b/tempest/api/compute/flavors/test_flavors_negative.py
new file mode 100644
index 0000000..81e4f87
--- /dev/null
+++ b/tempest/api/compute/flavors/test_flavors_negative.py
@@ -0,0 +1,69 @@
+# 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 uuid
+
+from tempest.api.compute import base
+from tempest.common.utils import data_utils
+from tempest import exceptions
+from tempest.test import attr
+
+
+class FlavorsNegativeTestJSON(base.BaseV2ComputeTest):
+ _interface = 'json'
+
+ @classmethod
+ def setUpClass(cls):
+ super(FlavorsNegativeTestJSON, cls).setUpClass()
+ cls.client = cls.flavors_client
+
+ # Generating a nonexistent flavor id
+ resp, flavors = cls.client.list_flavors()
+ flavor_ids = [flavor['id'] for flavor in flavors]
+ while True:
+ cls.nonexistent_flavor_id = data_utils.rand_int_id(start=999)
+ if cls.nonexistent_flavor_id not in flavor_ids:
+ break
+
+ @attr(type=['negative', 'gate'])
+ def test_invalid_minRam_filter(self):
+ self.assertRaises(exceptions.BadRequest,
+ self.client.list_flavors_with_detail,
+ {'minRam': 'invalid'})
+
+ @attr(type=['negative', 'gate'])
+ def test_invalid_minDisk_filter(self):
+ self.assertRaises(exceptions.BadRequest,
+ self.client.list_flavors_with_detail,
+ {'minDisk': 'invalid'})
+
+ @attr(type=['negative', 'gate'])
+ def test_get_flavor_details_for_invalid_flavor_id(self):
+ # Ensure 404 returned for invalid flavor ID
+ invalid_flavor_id = str(uuid.uuid4())
+ self.assertRaises(exceptions.NotFound, self.client.get_flavor_details,
+ invalid_flavor_id)
+
+ @attr(type=['negative', 'gate'])
+ def test_non_existent_flavor_id(self):
+ # flavor details are not returned for non-existent flavors
+ self.assertRaises(exceptions.NotFound, self.client.get_flavor_details,
+ self.nonexistent_flavor_id)
+
+
+class FlavorsNegativeTestXML(FlavorsNegativeTestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/compute/images/test_images.py b/tempest/api/compute/images/test_images.py
index 4539981..f7db89b 100644
--- a/tempest/api/compute/images/test_images.py
+++ b/tempest/api/compute/images/test_images.py
@@ -14,7 +14,6 @@
# 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 import clients
from tempest.common.utils import data_utils
@@ -36,7 +35,7 @@
cls.image_ids = []
- if compute.MULTI_USER:
+ if cls.multi_user:
if cls.config.compute.allow_tenant_isolation:
creds = cls.isolated_creds.get_alt_creds()
username, tenant_name, password = creds
@@ -98,7 +97,8 @@
snapshot_name = data_utils.rand_name('test-snap-')
resp, image = self.create_image_from_server(server['id'],
name=snapshot_name,
- wait_until='ACTIVE')
+ wait_until='ACTIVE',
+ wait_for_server=False)
self.addCleanup(self.client.delete_image, image['id'])
self.assertEqual(snapshot_name, image['name'])
diff --git a/tempest/api/compute/images/test_images_oneserver.py b/tempest/api/compute/images/test_images_oneserver.py
index 18e32d8..b0ff7ab 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):
@@ -68,7 +68,7 @@
cls.image_ids = []
- if compute.MULTI_USER:
+ if cls.multi_user:
if cls.config.compute.allow_tenant_isolation:
creds = cls.isolated_creds.get_alt_creds()
username, tenant_name, password = creds
diff --git a/tempest/api/compute/images/test_images_oneserver_negative.py b/tempest/api/compute/images/test_images_oneserver_negative.py
index 4cd41ee..2d27b81 100644
--- a/tempest/api/compute/images/test_images_oneserver_negative.py
+++ b/tempest/api/compute/images/test_images_oneserver_negative.py
@@ -16,7 +16,6 @@
# 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 import clients
from tempest.common.utils import data_utils
@@ -50,7 +49,10 @@
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._reset_server()
+
+ def _reset_server(self):
+ self.__class__.server_id = self.rebuild_server(self.server_id)
@classmethod
def setUpClass(cls):
@@ -69,7 +71,7 @@
cls.image_ids = []
- if compute.MULTI_USER:
+ if cls.multi_user:
if cls.config.compute.allow_tenant_isolation:
creds = cls.isolated_creds.get_alt_creds()
username, tenant_name, password = creds
@@ -117,12 +119,12 @@
self.assertEqual(202, resp.status)
image_id = data_utils.parse_image_id(resp['location'])
self.image_ids.append(image_id)
+ self.addCleanup(self._reset_server)
# Create second snapshot
alt_snapshot_name = data_utils.rand_name('test-snap-')
self.assertRaises(exceptions.Conflict, self.client.create_image,
self.server_id, alt_snapshot_name)
- self.client.wait_for_image_status(image_id, 'ACTIVE')
@attr(type=['negative', 'gate'])
def test_create_image_specify_name_over_256_chars(self):
@@ -141,6 +143,7 @@
self.assertEqual(202, resp.status)
image_id = data_utils.parse_image_id(resp['location'])
self.image_ids.append(image_id)
+ self.addCleanup(self._reset_server)
# Do not wait, attempt to delete the image, ensure it's successful
resp, body = self.client.delete_image(image_id)
diff --git a/tempest/api/compute/images/test_list_image_filters.py b/tempest/api/compute/images/test_list_image_filters.py
index 1401654..ac2ecba 100644
--- a/tempest/api/compute/images/test_list_image_filters.py
+++ b/tempest/api/compute/images/test_list_image_filters.py
@@ -16,7 +16,6 @@
# under the License.
from tempest.api.compute import base
-from tempest.common.utils import data_utils
from tempest import exceptions
from tempest.openstack.common import log as logging
from tempest.test import attr
@@ -45,24 +44,21 @@
'ACTIVE')
# Create images to be used in the filter tests
- resp, body = cls.create_image_from_server(cls.server1['id'])
- cls.image1_id = data_utils.parse_image_id(resp['location'])
- cls.client.wait_for_image_status(cls.image1_id, 'ACTIVE')
- resp, cls.image1 = cls.client.get_image(cls.image1_id)
+ resp, cls.image1 = cls.create_image_from_server(
+ cls.server1['id'], wait_until='ACTIVE')
+ cls.image1_id = cls.image1['id']
# Servers have a hidden property for when they are being imaged
# Performing back-to-back create image calls on a single
# server will sometimes cause failures
- resp, body = cls.create_image_from_server(cls.server2['id'])
- cls.image3_id = data_utils.parse_image_id(resp['location'])
- cls.client.wait_for_image_status(cls.image3_id, 'ACTIVE')
- resp, cls.image3 = cls.client.get_image(cls.image3_id)
+ resp, cls.image3 = cls.create_image_from_server(
+ cls.server2['id'], wait_until='ACTIVE')
+ cls.image3_id = cls.image3['id']
- resp, body = cls.create_image_from_server(cls.server1['id'])
- cls.image2_id = data_utils.parse_image_id(resp['location'])
-
- cls.client.wait_for_image_status(cls.image2_id, 'ACTIVE')
- resp, cls.image2 = cls.client.get_image(cls.image2_id)
+ # Wait for the server to be active after the image upload
+ resp, cls.image2 = cls.create_image_from_server(
+ cls.server1['id'], wait_until='ACTIVE')
+ cls.image2_id = cls.image2['id']
except Exception as exc:
LOG.exception(exc)
cls.tearDownClass()
diff --git a/tempest/api/compute/servers/test_server_actions.py b/tempest/api/compute/servers/test_server_actions.py
index 5fa4c35..5552d0b 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)
@@ -217,6 +247,7 @@
image1_id = data_utils.parse_image_id(resp['location'])
self.addCleanup(_clean_oldest_backup, image1_id)
self.assertEqual(202, resp.status)
+ self.os.image_client.wait_for_image_status(image1_id, 'active')
backup2 = data_utils.rand_name('backup')
self.servers_client.wait_for_server_status(self.server_id, 'ACTIVE')
@@ -227,6 +258,7 @@
image2_id = data_utils.parse_image_id(resp['location'])
self.addCleanup(self.os.image_client.delete_image, image2_id)
self.assertEqual(202, resp.status)
+ self.os.image_client.wait_for_image_status(image2_id, 'active')
# verify they have been created
properties = {
@@ -266,6 +298,14 @@
self.assertEqual((backup2, backup3),
(image_list[0]['name'], image_list[1]['name']))
+ def _get_output(self):
+ resp, output = self.servers_client.get_console_output(
+ self.server_id, 10)
+ self.assertEqual(200, resp.status)
+ self.assertTrue(output, "Console output was empty.")
+ lines = len(output.split('\n'))
+ self.assertEqual(lines, 10)
+
@attr(type='gate')
def test_get_console_output(self):
# Positive test:Should be able to GET the console output
@@ -280,29 +320,24 @@
self.assertEqual(202, resp.status)
self.servers_client.wait_for_server_status(self.server_id, 'ACTIVE')
- def get_output():
- resp, output = self.servers_client.get_console_output(
- self.server_id, 10)
- self.assertEqual(200, resp.status)
- self.assertTrue(output, "Console output was empty.")
- lines = len(output.split('\n'))
- self.assertEqual(lines, 10)
- self.wait_for(get_output)
+ self.wait_for(self._get_output)
- @skip_because(bug="1014683")
@attr(type='gate')
- def test_get_console_output_server_id_in_reboot_status(self):
+ def test_get_console_output_server_id_in_shutoff_status(self):
# Positive test:Should be able to GET the console output
- # for a given server_id in reboot status
- resp, output = self.servers_client.reboot(self.server_id, 'SOFT')
- self.servers_client.wait_for_server_status(self.server_id,
- 'REBOOT')
- resp, output = self.servers_client.get_console_output(self.server_id,
- 10)
- self.assertEqual(200, resp.status)
- self.assertIsNotNone(output)
- lines = len(output.split('\n'))
- self.assertEqual(lines, 10)
+ # for a given server_id in SHUTOFF status
+
+ # NOTE: SHUTOFF is irregular status. To avoid test instability,
+ # one server is created only for this test without using
+ # the server that was created in setupClass.
+ resp, server = self.create_test_server(wait_until='ACTIVE')
+ temp_server_id = server['id']
+
+ resp, server = self.servers_client.stop(temp_server_id)
+ self.assertEqual(202, resp.status)
+ self.servers_client.wait_for_server_status(temp_server_id, 'SHUTOFF')
+
+ self.wait_for(self._get_output)
@attr(type='gate')
def test_pause_unpause_server(self):
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/test_authorization.py b/tempest/api/compute/test_authorization.py
index 49c4f32..327c7d1 100644
--- a/tempest/api/compute/test_authorization.py
+++ b/tempest/api/compute/test_authorization.py
@@ -15,7 +15,6 @@
# 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 import clients
from tempest.common.utils import data_utils
@@ -31,12 +30,10 @@
@classmethod
def setUpClass(cls):
- if not compute.MULTI_USER:
+ super(AuthorizationTestJSON, cls).setUpClass()
+ if not cls.multi_user:
msg = "Need >1 user"
raise cls.skipException(msg)
-
- super(AuthorizationTestJSON, cls).setUpClass()
-
cls.client = cls.os.servers_client
cls.images_client = cls.os.images_client
cls.keypairs_client = cls.os.keypairs_client
@@ -85,7 +82,7 @@
@classmethod
def tearDownClass(cls):
- if compute.MULTI_USER:
+ if cls.multi_user:
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'])
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/images/test_images.py b/tempest/api/compute/v3/images/test_images.py
index 3aacafb..a179d65 100644
--- a/tempest/api/compute/v3/images/test_images.py
+++ b/tempest/api/compute/v3/images/test_images.py
@@ -14,7 +14,6 @@
# 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 import clients
from tempest.common.utils import data_utils
@@ -34,7 +33,7 @@
cls.client = cls.images_client
cls.servers_client = cls.servers_client
- if compute.MULTI_USER:
+ if cls.multi_user:
if cls.config.compute.allow_tenant_isolation:
creds = cls.isolated_creds.get_alt_creds()
username, tenant_name, password = creds
diff --git a/tempest/api/compute/v3/servers/test_list_servers_negative.py b/tempest/api/compute/v3/servers/test_list_servers_negative.py
index 6225345..3f7f885 100644
--- a/tempest/api/compute/v3/servers/test_list_servers_negative.py
+++ b/tempest/api/compute/v3/servers/test_list_servers_negative.py
@@ -17,7 +17,6 @@
import datetime
-from tempest.api import compute
from tempest.api.compute import base
from tempest import clients
from tempest import exceptions
@@ -53,7 +52,7 @@
cls.client = cls.servers_client
cls.servers = []
- if compute.MULTI_USER:
+ if cls.multi_user:
if cls.config.compute.allow_tenant_isolation:
creds = cls.isolated_creds.get_alt_creds()
username, tenant_name, password = creds
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 d0455b2..b57dcfe 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/api/network/admin/test_agent_management.py b/tempest/api/network/admin/test_agent_management.py
new file mode 100644
index 0000000..94659b2
--- /dev/null
+++ b/tempest/api/network/admin/test_agent_management.py
@@ -0,0 +1,82 @@
+# 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.
+
+from tempest.api.network import base
+from tempest.common import tempest_fixtures as fixtures
+from tempest.test import attr
+
+
+class AgentManagementTestJSON(base.BaseAdminNetworkTest):
+ _interface = 'json'
+
+ @classmethod
+ def setUpClass(cls):
+ super(AgentManagementTestJSON, cls).setUpClass()
+ resp, body = cls.admin_client.list_agents()
+ agents = body['agents']
+ cls.agent = agents[0]
+
+ @attr(type='smoke')
+ def test_list_agent(self):
+ resp, body = self.admin_client.list_agents()
+ self.assertEqual('200', resp['status'])
+ agents = body['agents']
+ self.assertIn(self.agent, agents)
+
+ @attr(type='smoke')
+ def test_show_agent(self):
+ resp, body = self.admin_client.show_agent(self.agent['id'])
+ agent = body['agent']
+ self.assertEqual('200', resp['status'])
+ self.assertEqual(agent['id'], self.agent['id'])
+
+ @attr(type='smoke')
+ def test_update_agent_status(self):
+ origin_status = self.agent['admin_state_up']
+ # Try to update the 'admin_state_up' to the original
+ # one to avoid the negative effect.
+ agent_status = {'admin_state_up': origin_status}
+ resp, body = self.admin_client.update_agent(agent_id=self.agent['id'],
+ agent_info=agent_status)
+ updated_status = body['agent']['admin_state_up']
+ self.assertEqual('200', resp['status'])
+ self.assertEqual(origin_status, updated_status)
+
+ @attr(type='smoke')
+ def test_update_agent_description(self):
+ self.useFixture(fixtures.LockFixture('agent_description'))
+ description = 'description for update agent.'
+ agent_description = {'description': description}
+ resp, body = self.admin_client.update_agent(
+ agent_id=self.agent['id'],
+ agent_info=agent_description)
+ self.assertEqual('200', resp['status'])
+ self.addCleanup(self._restore_agent)
+ updated_description = body['agent']['description']
+ self.assertEqual(updated_description, description)
+
+ def _restore_agent(self):
+ """
+ Restore the agent description after update test.
+ """
+ description = self.agent['description'] or ''
+ origin_agent = {'description': description}
+ self.admin_client.update_agent(agent_id=self.agent['id'],
+ agent_info=origin_agent)
+
+
+class AgentManagementTestXML(AgentManagementTestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/network/common.py b/tempest/api/network/common.py
index ab19fa8..528a204 100644
--- a/tempest/api/network/common.py
+++ b/tempest/api/network/common.py
@@ -47,6 +47,9 @@
def delete(self):
raise NotImplemented()
+ def __hash__(self):
+ return id(self)
+
class DeletableNetwork(DeletableResource):
@@ -86,6 +89,23 @@
class DeletableFloatingIp(DeletableResource):
+ def update(self, *args, **kwargs):
+ result = self.client.update_floatingip(floatingip=self.id,
+ body=dict(
+ floatingip=dict(*args,
+ **kwargs)
+ ))
+ super(DeletableFloatingIp, self).update(**result['floatingip'])
+
+ def __repr__(self):
+ return '<%s addr="%s">' % (self.__class__.__name__,
+ self.floating_ip_address)
+
+ def __str__(self):
+ return '<"FloatingIP" addr="%s" id="%s">' % (self.__class__.__name__,
+ self.floating_ip_address,
+ self.id)
+
def delete(self):
self.client.delete_floatingip(self.id)
diff --git a/tempest/api/network/test_routers.py b/tempest/api/network/test_routers.py
index 3cbe23f..06e07bb 100644
--- a/tempest/api/network/test_routers.py
+++ b/tempest/api/network/test_routers.py
@@ -15,6 +15,8 @@
# License for the specific language governing permissions and limitations
# under the License.
+import netaddr
+
from tempest.api.network import base
from tempest.common.utils import data_utils
from tempest.test import attr
@@ -229,3 +231,22 @@
{'network_id': self.network_cfg.public_network_id,
'enable_snat': False})
self._verify_gateway_port(router['id'])
+
+ @attr(type='smoke')
+ def test_update_extra_route(self):
+ self.network = self.create_network()
+ self.name = self.network['name']
+ self.subnet = self.create_subnet(self.network)
+ # Add router interface with subnet id
+ self.router = self.create_router(data_utils.rand_name('router-'), True)
+ self.create_router_interface(self.router['id'], self.subnet['id'])
+ self.addCleanup(
+ self._delete_extra_routes,
+ self.router['id'])
+ # Update router extra route
+ cidr = netaddr.IPNetwork(self.subnet['cidr'])
+ resp, extra_route = self.client.update_extra_routes(
+ self.router['id'], str(cidr[0]), str(self.subnet['cidr']))
+
+ def _delete_extra_routes(self, router_id):
+ resp, _ = self.client.delete_extra_routes(router_id)
diff --git a/tempest/api/network/test_vpnaas_extensions.py b/tempest/api/network/test_vpnaas_extensions.py
index 7905b97..63a8e24 100644
--- a/tempest/api/network/test_vpnaas_extensions.py
+++ b/tempest/api/network/test_vpnaas_extensions.py
@@ -40,7 +40,9 @@
super(VPNaaSJSON, cls).setUpClass()
cls.network = cls.create_network()
cls.subnet = cls.create_subnet(cls.network)
- cls.router = cls.create_router(data_utils.rand_name("router-"))
+ cls.router = cls.create_router(
+ data_utils.rand_name("router-"),
+ external_network_id=cls.network_cfg.public_network_id)
cls.create_router_interface(cls.router['id'], cls.subnet['id'])
cls.vpnservice = cls.create_vpnservice(cls.subnet['id'],
cls.router['id'])
diff --git a/tempest/cli/simple_read_only/test_neutron.py b/tempest/cli/simple_read_only/test_neutron.py
index 047b17d..80376ab 100644
--- a/tempest/cli/simple_read_only/test_neutron.py
+++ b/tempest/cli/simple_read_only/test_neutron.py
@@ -44,35 +44,43 @@
raise cls.skipException(msg)
super(SimpleReadOnlyNeutronClientTest, cls).setUpClass()
+ @test.attr(type='smoke')
def test_neutron_fake_action(self):
self.assertRaises(subprocess.CalledProcessError,
self.neutron,
'this-does-not-exist')
+ @test.attr(type='smoke')
def test_neutron_net_list(self):
self.neutron('net-list')
+ @test.attr(type='smoke')
def test_neutron_ext_list(self):
ext = self.parser.listing(self.neutron('ext-list'))
self.assertTableStruct(ext, ['alias', 'name'])
+ @test.attr(type='smoke')
def test_neutron_dhcp_agent_list_hosting_net(self):
self.neutron('dhcp-agent-list-hosting-net',
params=CONF.compute.fixed_network_name)
+ @test.attr(type='smoke')
def test_neutron_agent_list(self):
agents = self.parser.listing(self.neutron('agent-list'))
field_names = ['id', 'agent_type', 'host', 'alive', 'admin_state_up']
self.assertTableStruct(agents, field_names)
+ @test.attr(type='smoke')
def test_neutron_floatingip_list(self):
self.neutron('floatingip-list')
@test.skip_because(bug="1240694")
+ @test.attr(type='smoke')
def test_neutron_meter_label_list(self):
self.neutron('meter-label-list')
@test.skip_because(bug="1240694")
+ @test.attr(type='smoke')
def test_neutron_meter_label_rule_list(self):
self.neutron('meter-label-rule-list')
@@ -83,40 +91,52 @@
if '404 Not Found' not in e.stderr:
self.fail('%s: Unexpected failure.' % command)
+ @test.attr(type='smoke')
def test_neutron_lb_healthmonitor_list(self):
self._test_neutron_lbaas_command('lb-healthmonitor-list')
+ @test.attr(type='smoke')
def test_neutron_lb_member_list(self):
self._test_neutron_lbaas_command('lb-member-list')
+ @test.attr(type='smoke')
def test_neutron_lb_pool_list(self):
self._test_neutron_lbaas_command('lb-pool-list')
+ @test.attr(type='smoke')
def test_neutron_lb_vip_list(self):
self._test_neutron_lbaas_command('lb-vip-list')
+ @test.attr(type='smoke')
def test_neutron_net_external_list(self):
self.neutron('net-external-list')
+ @test.attr(type='smoke')
def test_neutron_port_list(self):
self.neutron('port-list')
+ @test.attr(type='smoke')
def test_neutron_quota_list(self):
self.neutron('quota-list')
+ @test.attr(type='smoke')
def test_neutron_router_list(self):
self.neutron('router-list')
+ @test.attr(type='smoke')
def test_neutron_security_group_list(self):
security_grp = self.parser.listing(self.neutron('security-group-list'))
self.assertTableStruct(security_grp, ['id', 'name', 'description'])
+ @test.attr(type='smoke')
def test_neutron_security_group_rule_list(self):
self.neutron('security-group-rule-list')
+ @test.attr(type='smoke')
def test_neutron_subnet_list(self):
self.neutron('subnet-list')
+ @test.attr(type='smoke')
def test_neutron_help(self):
help_text = self.neutron('help')
lines = help_text.split('\n')
@@ -136,11 +156,14 @@
# Optional arguments:
+ @test.attr(type='smoke')
def test_neutron_version(self):
self.neutron('', flags='--version')
+ @test.attr(type='smoke')
def test_neutron_debug_net_list(self):
self.neutron('net-list', flags='--debug')
+ @test.attr(type='smoke')
def test_neutron_quiet_net_list(self):
self.neutron('net-list', flags='--quiet')
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..44198f0 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,16 +69,22 @@
'/'.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
if timed_out:
- message = ('Server %s failed to reach %s status within the '
- 'required time (%s s).' %
- (server_id, status, timeout))
+ expected_task_state = 'None' if ready_wait else 'n/a'
+ message = ('Server %(server_id)s failed to reach %(status)s '
+ 'status and task state "%(expected_task_state)s" '
+ 'within the required time (%(timeout)s s).' %
+ {'server_id': server_id,
+ 'status': status,
+ 'expected_task_state': expected_task_state,
+ 'timeout': timeout})
message += ' Current status: %s.' % server_status
+ message += ' Current task state: %s.' % task_state
raise exceptions.TimeoutException(message)
old_status = server_status
old_task_state = task_state
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/openstack/common/log.py b/tempest/openstack/common/log.py
index 5cf8ed6..abb44ef 100644
--- a/tempest/openstack/common/log.py
+++ b/tempest/openstack/common/log.py
@@ -132,6 +132,7 @@
'boto=WARN',
'suds=INFO',
'keystone=INFO',
+ 'paramiko=INFO'
],
help='list of logger=LEVEL pairs'),
cfg.BoolOpt('publish_errors',
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
index 06841e1..e839d20 100644
--- a/tempest/scenario/manager.py
+++ b/tempest/scenario/manager.py
@@ -343,7 +343,11 @@
raise
new_status = thing.status
- if new_status == error_status:
+
+ # Some components are reporting error status in lower case
+ # so case sensitive comparisons can really mess things
+ # up.
+ if new_status.lower() == error_status.lower():
message = "%s failed to get to expected status. \
In %s state." % (thing, new_status)
raise exceptions.BuildErrorException(message)
@@ -537,6 +541,27 @@
routers = self.network_client.list_routers()
return routers['routers']
+ def _list_ports(self):
+ ports = self.network_client.list_ports()
+ return ports['ports']
+
+ def _get_tenant_own_network_num(self, tenant_id):
+ nets = self._list_networks()
+ ownnets = [value for value in nets if tenant_id == value['tenant_id']]
+ return len(ownnets)
+
+ def _get_tenant_own_subnet_num(self, tenant_id):
+ subnets = self._list_subnets()
+ ownsubnets = ([value for value in subnets
+ if tenant_id == value['tenant_id']])
+ return len(ownsubnets)
+
+ def _get_tenant_own_port_num(self, tenant_id):
+ ports = self._list_ports()
+ ownports = ([value for value in ports
+ if tenant_id == value['tenant_id']])
+ return len(ownports)
+
def _create_subnet(self, network, namestart='subnet-smoke-'):
"""
Create a subnet for the given network within the cidr block
@@ -603,7 +628,15 @@
self.set_resource(data_utils.rand_name('floatingip-'), floating_ip)
return floating_ip
- def _ping_ip_address(self, ip_address):
+ def _disassociate_floating_ip(self, floating_ip):
+ """
+ :param floating_ip: type DeletableFloatingIp
+ """
+ floating_ip.update(port_id=None)
+ self.assertEqual(None, floating_ip.port_id)
+ return floating_ip
+
+ def _ping_ip_address(self, ip_address, should_succeed=True):
cmd = ['ping', '-c1', '-w1', ip_address]
def ping():
@@ -611,8 +644,7 @@
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
proc.wait()
- if proc.returncode == 0:
- return True
+ return (proc.returncode == 0) == should_succeed
return tempest.test.call_until_true(
ping, self.config.compute.ping_timeout, 1)
@@ -624,17 +656,37 @@
timeout=timeout)
return ssh_client.test_connection_auth()
- def _check_vm_connectivity(self, ip_address, username, private_key):
- self.assertTrue(self._ping_ip_address(ip_address),
- "Timed out waiting for %s to become "
- "reachable" % ip_address)
- self.assertTrue(self._is_reachable_via_ssh(
- ip_address,
- username,
- private_key,
- timeout=self.config.compute.ssh_timeout),
- 'Auth failure in connecting to %s@%s via ssh' %
- (username, ip_address))
+ def _check_vm_connectivity(self, ip_address,
+ username=None,
+ private_key=None,
+ should_connect=True):
+ """
+ :param ip_address: server to test against
+ :param username: server's ssh username
+ :param private_key: server's ssh private key to be used
+ :param should_connect: True/False indicates positive/negative test
+ positive - attempt ping and ssh
+ negative - attempt ping and fail if succeed
+
+ :raises: AssertError if the result of the connectivity check does
+ not match the value of the should_connect param
+ """
+ if should_connect:
+ msg = "Timed out waiting for %s to become reachable" % ip_address
+ else:
+ msg = "ip address %s is reachable" % ip_address
+ self.assertTrue(self._ping_ip_address(ip_address,
+ should_succeed=should_connect),
+ msg=msg)
+ if should_connect:
+ # no need to check ssh for negative connectivity
+ self.assertTrue(self._is_reachable_via_ssh(
+ ip_address,
+ username,
+ private_key,
+ timeout=self.config.compute.ssh_timeout),
+ 'Auth failure in connecting to %s@%s via ssh' %
+ (username, ip_address))
def _create_security_group_nova(self, client=None,
namestart='secgroup-smoke-',
@@ -801,6 +853,18 @@
return rules
+ def _show_quota_network(self, tenant_id):
+ quota = self.network_client.show_quota(tenant_id)
+ return quota['quota']['network']
+
+ def _show_quota_subnet(self, tenant_id):
+ quota = self.network_client.show_quota(tenant_id)
+ return quota['quota']['subnet']
+
+ def _show_quota_port(self, tenant_id):
+ quota = self.network_client.show_quota(tenant_id)
+ return quota['quota']['port']
+
class OrchestrationScenarioTest(OfficialClientTest):
"""
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/scenario/test_network_basic_ops.py b/tempest/scenario/test_network_basic_ops.py
index bfded53..d605dff 100644
--- a/tempest/scenario/test_network_basic_ops.py
+++ b/tempest/scenario/test_network_basic_ops.py
@@ -20,14 +20,56 @@
from tempest.common import debug
from tempest.common.utils import data_utils
from tempest import config
+from tempest.openstack.common import jsonutils
from tempest.openstack.common import log as logging
from tempest.scenario import manager
+
+import tempest.test
from tempest.test import attr
from tempest.test import services
LOG = logging.getLogger(__name__)
+class FloatingIPCheckTracker(object):
+ """
+ Checking VM connectivity through floating IP addresses is bound to fail
+ if the floating IP has not actually been associated with the VM yet.
+ This helper class facilitates checking for floating IP assignments on
+ VMs. It only checks for a given IP address once.
+ """
+
+ def __init__(self, compute_client, floating_ip_map):
+ self.compute_client = compute_client
+ self.unchecked = floating_ip_map.copy()
+
+ def run_checks(self):
+ """Check for any remaining unverified floating IPs
+
+ Gets VM details from nova and checks for floating IPs
+ within the returned information. Returns true when all
+ checks are complete and is suitable for use with
+ tempest.test.call_until_true()
+ """
+ to_delete = []
+ loggable_map = {}
+ for check_addr, server in self.unchecked.iteritems():
+ serverdata = self.compute_client.servers.get(server.id)
+ ip_addr = [addr for sublist in serverdata.networks.values() for
+ addr in sublist]
+ if check_addr.floating_ip_address in ip_addr:
+ to_delete.append(check_addr)
+ else:
+ loggable_map[server.id] = check_addr
+
+ for to_del in to_delete:
+ del self.unchecked[to_del]
+
+ LOG.debug('Unchecked floating IPs: %s',
+ jsonutils.dumps(loggable_map))
+ return len(self.unchecked) == 0
+
+
class TestNetworkBasicOps(manager.NetworkScenarioTest):
"""
@@ -46,6 +88,9 @@
ssh server hosted at the IP address. This check guarantees
that the IP address is associated with the target VM.
+ - detach the floating-ip from the VM and verify that it becomes
+ unreachable
+
# TODO(mnewby) - Need to implement the following:
- the Tempest host can ssh into the VM via the IP address and
successfully execute the following:
@@ -228,36 +273,56 @@
# key-based authentication by cloud-init.
ssh_login = self.config.compute.image_ssh_user
private_key = self.keypairs[self.tenant_id].private_key
- for server in self.servers:
- for net_name, ip_addresses in server.networks.iteritems():
- for ip_address in ip_addresses:
- self._check_vm_connectivity(ip_address, ssh_login,
- private_key)
+ try:
+ for server in self.servers:
+ for net_name, ip_addresses in server.networks.iteritems():
+ for ip_address in ip_addresses:
+ self._check_vm_connectivity(ip_address, ssh_login,
+ private_key)
+ except Exception as exc:
+ LOG.exception(exc)
+ debug.log_ip_ns()
+ raise exc
- def _assign_floating_ips(self):
+ def _wait_for_floating_ip_association(self):
+ ip_tracker = FloatingIPCheckTracker(self.compute_client,
+ self.floating_ips)
+
+ self.assertTrue(
+ tempest.test.call_until_true(
+ ip_tracker.run_checks, self.config.compute.build_timeout,
+ self.config.compute.build_interval),
+ "Timed out while waiting for the floating IP assignments "
+ "to propagate")
+
+ def _create_and_associate_floating_ips(self):
public_network_id = self.config.network.public_network_id
for server in self.servers:
floating_ip = self._create_floating_ip(server, public_network_id)
- self.floating_ips.setdefault(server, [])
- self.floating_ips[server].append(floating_ip)
+ self.floating_ips[floating_ip] = server
- def _check_public_network_connectivity(self):
+ def _check_public_network_connectivity(self, should_connect=True):
# The target login is assumed to have been configured for
# key-based authentication by cloud-init.
ssh_login = self.config.compute.image_ssh_user
private_key = self.keypairs[self.tenant_id].private_key
try:
- for server, floating_ips in self.floating_ips.iteritems():
- for floating_ip in floating_ips:
- ip_address = floating_ip.floating_ip_address
- self._check_vm_connectivity(ip_address,
- ssh_login,
- private_key)
+ for floating_ip, server in self.floating_ips.iteritems():
+ ip_address = floating_ip.floating_ip_address
+ self._check_vm_connectivity(ip_address,
+ ssh_login,
+ private_key,
+ should_connect=should_connect)
except Exception as exc:
LOG.exception(exc)
debug.log_ip_ns()
raise exc
+ def _disassociate_floating_ips(self):
+ for floating_ip, server in self.floating_ips.iteritems():
+ self._disassociate_floating_ip(floating_ip)
+ self.floating_ips[floating_ip] = None
+
@attr(type='smoke')
@services('compute', 'network')
def test_network_basic_ops(self):
@@ -266,6 +331,9 @@
self._create_networks()
self._check_networks()
self._create_servers()
- self._assign_floating_ips()
- self._check_public_network_connectivity()
+ self._create_and_associate_floating_ips()
+ self._wait_for_floating_ip_association()
self._check_tenant_network_connectivity()
+ self._check_public_network_connectivity(should_connect=True)
+ self._disassociate_floating_ips()
+ self._check_public_network_connectivity(should_connect=False)
diff --git a/tempest/scenario/test_network_quotas.py b/tempest/scenario/test_network_quotas.py
index 3268066..cb7aa0b 100644
--- a/tempest/scenario/test_network_quotas.py
+++ b/tempest/scenario/test_network_quotas.py
@@ -20,8 +20,6 @@
from tempest.scenario.manager import NetworkScenarioTest
from tempest.test import services
-MAX_REASONABLE_ITERATIONS = 51 # more than enough. Default for port is 50.
-
class TestNetworkQuotaBasic(NetworkScenarioTest):
"""
@@ -46,7 +44,9 @@
@services('network')
def test_create_network_until_quota_hit(self):
hit_limit = False
- for n in xrange(MAX_REASONABLE_ITERATIONS):
+ networknum = self._get_tenant_own_network_num(self.tenant_id)
+ max = self._show_quota_network(self.tenant_id) - networknum
+ for n in xrange(max):
try:
self.networks.append(
self._create_network(self.tenant_id,
@@ -56,6 +56,16 @@
raise
hit_limit = True
break
+ self.assertFalse(hit_limit, "Failed: Hit quota limit !")
+
+ try:
+ self.networks.append(
+ self._create_network(self.tenant_id,
+ namestart='network-quotatest-'))
+ except exc.NeutronClientException as e:
+ if (e.status_code != 409):
+ raise
+ hit_limit = True
self.assertTrue(hit_limit, "Failed: Did not hit quota limit !")
@services('network')
@@ -65,7 +75,9 @@
self._create_network(self.tenant_id,
namestart='network-quotatest-'))
hit_limit = False
- for n in xrange(MAX_REASONABLE_ITERATIONS):
+ subnetnum = self._get_tenant_own_subnet_num(self.tenant_id)
+ max = self._show_quota_subnet(self.tenant_id) - subnetnum
+ for n in xrange(max):
try:
self.subnets.append(
self._create_subnet(self.networks[0],
@@ -75,6 +87,16 @@
raise
hit_limit = True
break
+ self.assertFalse(hit_limit, "Failed: Hit quota limit !")
+
+ try:
+ self.subnets.append(
+ self._create_subnet(self.networks[0],
+ namestart='subnet-quotatest-'))
+ except exc.NeutronClientException as e:
+ if (e.status_code != 409):
+ raise
+ hit_limit = True
self.assertTrue(hit_limit, "Failed: Did not hit quota limit !")
@services('network')
@@ -84,7 +106,9 @@
self._create_network(self.tenant_id,
namestart='network-quotatest-'))
hit_limit = False
- for n in xrange(MAX_REASONABLE_ITERATIONS):
+ portnum = self._get_tenant_own_port_num(self.tenant_id)
+ max = self._show_quota_port(self.tenant_id) - portnum
+ for n in xrange(max):
try:
self.ports.append(
self._create_port(self.networks[0],
@@ -94,4 +118,14 @@
raise
hit_limit = True
break
+ self.assertFalse(hit_limit, "Failed: Hit quota limit !")
+
+ try:
+ self.ports.append(
+ self._create_port(self.networks[0],
+ namestart='port-quotatest-'))
+ except exc.NeutronClientException as e:
+ if (e.status_code != 409):
+ raise
+ hit_limit = True
self.assertTrue(hit_limit, "Failed: Did not hit quota limit !")
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/services/network/json/network_client.py b/tempest/services/network/json/network_client.py
index c6bd423..3199537 100644
--- a/tempest/services/network/json/network_client.py
+++ b/tempest/services/network/json/network_client.py
@@ -680,6 +680,24 @@
body = json.loads(body)
return resp, body
+ def show_agent(self, agent_id):
+ uri = '%s/agents/%s' % (self.uri_prefix, agent_id)
+ resp, body = self.get(uri, self.headers)
+ body = json.loads(body)
+ return resp, body
+
+ def update_agent(self, agent_id, agent_info):
+ """
+ :param agent_info: Agent update information.
+ E.g {"admin_state_up": True}
+ """
+ uri = '%s/agents/%s' % (self.uri_prefix, agent_id)
+ agent = {"agent": agent_info}
+ body = json.dumps(agent)
+ resp, body = self.put(uri, body=body, headers=self.headers)
+ body = json.loads(body)
+ return resp, body
+
def list_routers_on_l3_agent(self, agent_id):
uri = '%s/agents/%s/l3-routers' % (self.uri_prefix, agent_id)
resp, body = self.get(uri, self.headers)
@@ -754,3 +772,29 @@
resp, body = self.put(uri, body=body, headers=self.headers)
body = json.loads(body)
return resp, body
+
+ def update_extra_routes(self, router_id, nexthop, destination):
+ uri = '%s/routers/%s' % (self.uri_prefix, router_id)
+ put_body = {
+ 'router': {
+ 'routes': [{'nexthop': nexthop,
+ "destination": destination}]
+ }
+ }
+ body = json.dumps(put_body)
+ resp, body = self.put(uri, body=body, headers=self.headers)
+ body = json.loads(body)
+ return resp, body
+
+ def delete_extra_routes(self, router_id):
+ uri = '%s/routers/%s' % (self.uri_prefix, router_id)
+ null_routes = None
+ put_body = {
+ 'router': {
+ 'routes': null_routes
+ }
+ }
+ body = json.dumps(put_body)
+ resp, body = self.put(uri, body=body, headers=self.headers)
+ body = json.loads(body)
+ return resp, body
diff --git a/tempest/services/network/xml/network_client.py b/tempest/services/network/xml/network_client.py
index e11d4c1..c46a523 100755
--- a/tempest/services/network/xml/network_client.py
+++ b/tempest/services/network/xml/network_client.py
@@ -561,6 +561,23 @@
agents = {'agents': agents}
return resp, agents
+ def show_agent(self, agent_id):
+ uri = '%s/agents/%s' % (self.uri_prefix, agent_id)
+ resp, body = self.get(uri, self.headers)
+ body = _root_tag_fetcher_and_xml_to_json_parse(body)
+ return resp, body
+
+ def update_agent(self, agent_id, agent_info):
+ uri = '%s/agents/%s' % (self.uri_prefix, agent_id)
+ agent = Element('agent')
+ for (key, value) in agent_info.items():
+ p = Element(key, value)
+ agent.append(p)
+ resp, body = self.put(uri, body=str(Document(agent)),
+ headers=self.headers)
+ body = _root_tag_fetcher_and_xml_to_json_parse(body)
+ return resp, body
+
def list_routers_on_l3_agent(self, agent_id):
uri = '%s/agents/%s/l3-routers' % (self.uri_prefix, agent_id)
resp, body = self.get(uri, self.headers)
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/tempest/tests/test_wrappers.py b/tempest/tests/test_wrappers.py
index dbf1809..88bef9b 100644
--- a/tempest/tests/test_wrappers.py
+++ b/tempest/tests/test_wrappers.py
@@ -56,7 +56,7 @@
# version or an sdist to work. so make the test directory a git repo
# too.
subprocess.call(['git', 'init'])
- exit_code = subprocess.call('sh pretty_tox.sh tests.passing',
+ exit_code = subprocess.call('bash pretty_tox.sh tests.passing',
shell=True, stdout=DEVNULL, stderr=DEVNULL)
self.assertEqual(exit_code, 0)
@@ -71,7 +71,7 @@
# version or an sdist to work. so make the test directory a git repo
# too.
subprocess.call(['git', 'init'])
- exit_code = subprocess.call('sh pretty_tox.sh', shell=True,
+ exit_code = subprocess.call('bash pretty_tox.sh', shell=True,
stdout=DEVNULL, stderr=DEVNULL)
self.assertEqual(exit_code, 1)
@@ -82,7 +82,7 @@
# Change directory, run wrapper and check result
self.addCleanup(os.chdir, os.path.abspath(os.curdir))
os.chdir(self.directory)
- exit_code = subprocess.call('sh pretty_tox_serial.sh tests.passing',
+ exit_code = subprocess.call('bash pretty_tox_serial.sh tests.passing',
shell=True, stdout=DEVNULL, stderr=DEVNULL)
self.assertEqual(exit_code, 0)
@@ -93,6 +93,6 @@
# Change directory, run wrapper and check result
self.addCleanup(os.chdir, os.path.abspath(os.curdir))
os.chdir(self.directory)
- exit_code = subprocess.call('sh pretty_tox_serial.sh', shell=True,
+ exit_code = subprocess.call('bash pretty_tox_serial.sh', shell=True,
stdout=DEVNULL, stderr=DEVNULL)
self.assertEqual(exit_code, 1)
diff --git a/test-requirements.txt b/test-requirements.txt
index 41a784e..9486244 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -1,7 +1,8 @@
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
+mock>=1.0
diff --git a/tools/check_logs.py b/tools/check_logs.py
index 6d4436e..963709b 100755
--- a/tools/check_logs.py
+++ b/tools/check_logs.py
@@ -27,11 +27,13 @@
is_neutron = os.environ.get('DEVSTACK_GATE_NEUTRON', "0") == "1"
+is_grenade = (os.environ.get('DEVSTACK_GATE_GRENADE', "0") == "1" or
+ os.environ.get('DEVSTACK_GATE_GRENADE_FORWARD', "0") == "1")
dump_all_errors = is_neutron
def process_files(file_specs, url_specs, whitelists):
- regexp = re.compile(r"^.*(ERROR|CRITICAL).*\[.*\-.*\]")
+ regexp = re.compile(r"^.* (ERROR|CRITICAL) .*\[.*\-.*\]")
had_errors = False
for (name, filename) in file_specs:
whitelist = whitelists.get(name, [])
@@ -125,6 +127,9 @@
if is_neutron:
print("Currently not failing neutron builds with errors")
return 0
+ if is_grenade:
+ print("Currently not failing grenade runs with errors")
+ return 0
print("FAILED")
return 1
else:
diff --git a/tools/pretty_tox.sh b/tools/pretty_tox.sh
index a5a6076..07c35a0 100755
--- a/tools/pretty_tox.sh
+++ b/tools/pretty_tox.sh
@@ -1,4 +1,6 @@
-#!/bin/sh
+#!/usr/bin/env bash
+
+set -o pipefail
TESTRARGS=$1
python setup.py testr --slowest --testr-args="--subunit $TESTRARGS" | subunit2pyunit
diff --git a/tools/pretty_tox_serial.sh b/tools/pretty_tox_serial.sh
index 45f05bd..42ce760 100755
--- a/tools/pretty_tox_serial.sh
+++ b/tools/pretty_tox_serial.sh
@@ -1,4 +1,6 @@
-#!/bin/sh
+#!/usr/bin/env bash
+
+set -o pipefail
TESTRARGS=$@
diff --git a/tox.ini b/tox.ini
index e5698d2..c7f92ae 100644
--- a/tox.ini
+++ b/tox.ini
@@ -9,14 +9,18 @@
LANGUAGE=en_US:en
LC_ALL=C
usedevelop = True
+install_command = pip install -U {opts} {packages}
[testenv:py26]
+setenv = OS_TEST_PATH=./tempest/tests
commands = python setup.py test --slowest --testr-arg='tempest\.tests {posargs}'
[testenv:py33]
+setenv = OS_TEST_PATH=./tempest/tests
commands = python setup.py test --slowest --testr-arg='tempest\.tests {posargs}'
[testenv:py27]
+setenv = OS_TEST_PATH=./tempest/tests
commands = python setup.py test --slowest --testr-arg='tempest\.tests {posargs}'
[testenv:all]
@@ -30,19 +34,19 @@
# The regex below is used to select which tests to run and exclude the slow tag:
# See the testrepostiory bug: https://bugs.launchpad.net/testrepository/+bug/1208610
commands =
- sh tools/pretty_tox.sh '(?!.*\[.*\bslow\b.*\])(^tempest\.(api|scenario|thirdparty|cli)) {posargs}'
+ bash tools/pretty_tox.sh '(?!.*\[.*\bslow\b.*\])(^tempest\.(api|scenario|thirdparty|cli)) {posargs}'
[testenv:testr-full]
sitepackages = True
commands =
- sh tools/pretty_tox.sh '(?!.*\[.*\bslow\b.*\])(^tempest\.(api|scenario|thirdparty|cli)) {posargs}'
+ bash tools/pretty_tox.sh '(?!.*\[.*\bslow\b.*\])(^tempest\.(api|scenario|thirdparty|cli)) {posargs}'
[testenv:heat-slow]
sitepackages = True
setenv = OS_TEST_TIMEOUT=1200
# The regex below is used to select heat api/scenario tests tagged as slow.
commands =
- sh tools/pretty_tox_serial.sh '(?=.*\[.*\bslow\b.*\])(^tempest\.(api|scenario)\.orchestration) {posargs}'
+ bash tools/pretty_tox_serial.sh '(?=.*\[.*\bslow\b.*\])(^tempest\.(api|scenario)\.orchestration) {posargs}'
[testenv:large-ops]
sitepackages = True
@@ -78,7 +82,7 @@
[testenv:smoke]
sitepackages = True
commands =
- sh tools/pretty_tox.sh '(?!.*\[.*\bslow\b.*\])((smoke)|(^tempest\.scenario)) {posargs}'
+ bash tools/pretty_tox.sh '(?!.*\[.*\bslow\b.*\])((smoke)|(^tempest\.scenario)) {posargs}'
[testenv:smoke-serial]
sitepackages = True
@@ -86,7 +90,7 @@
# https://bugs.launchpad.net/tempest/+bug/1216076 so the neutron smoke
# job would fail if we moved it to parallel.
commands =
- sh tools/pretty_tox_serial.sh '(?!.*\[.*\bslow\b.*\])((smoke)|(^tempest\.scenario)) {posargs}'
+ bash tools/pretty_tox_serial.sh '(?!.*\[.*\bslow\b.*\])((smoke)|(^tempest\.scenario)) {posargs}'
[testenv:stress]
sitepackages = True