Merge "Revert "Update a test to work with Quantum SecGroup""
diff --git a/HACKING.rst b/HACKING.rst
index eafa81b..1db1e26 100644
--- a/HACKING.rst
+++ b/HACKING.rst
@@ -1,3 +1,7 @@
+Tempest Coding Guide
+====================
+
+
Test Data/Configuration
-----------------------
- Assume nothing about existing test data
diff --git a/doc/source/HACKING.rst b/doc/source/HACKING.rst
new file mode 120000
index 0000000..a2f06b7
--- /dev/null
+++ b/doc/source/HACKING.rst
@@ -0,0 +1 @@
+../../HACKING.rst
\ No newline at end of file
diff --git a/doc/source/conf.py b/doc/source/conf.py
index a55df76..fd8cbb5 100644
--- a/doc/source/conf.py
+++ b/doc/source/conf.py
@@ -91,7 +91,7 @@
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
-html_theme = 'default'
+html_theme = 'nature'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
diff --git a/doc/source/index.rst b/doc/source/index.rst
index e37e250..e8fdf2c 100644
--- a/doc/source/index.rst
+++ b/doc/source/index.rst
@@ -3,8 +3,9 @@
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
+=======================
Tempest Testing Project
-===================================
+=======================
Contents:
@@ -12,12 +13,17 @@
:maxdepth: 2
overview
+ HACKING
-
-Field Guide:
+------------
+Field Guides
+------------
+Tempest contains tests of many different types, the field guides
+attempt to explain these in a way that makes it easy to understand
+where your test contributions should go.
.. toctree::
- :maxdepth: 2
+ :maxdepth: 1
field_guide/index
field_guide/api
@@ -28,6 +34,7 @@
field_guide/whitebox
+==================
Indices and tables
==================
diff --git a/etc/logging.conf.sample b/etc/logging.conf.sample
index 5c1ea5f..685dd36 100644
--- a/etc/logging.conf.sample
+++ b/etc/logging.conf.sample
@@ -1,30 +1,41 @@
[loggers]
-keys=root
-
-[formatters]
-keys=normal,debug
+keys=root,tempest
[handlers]
-keys=file,devel
+keys=file,syslog,devel
+
+[formatters]
+keys=default,tests
[logger_root]
level=NOTSET
+handlers=syslog
+
+[logger_tempest]
+level=DEBUG
handlers=file
+qualname=tempest
[handler_file]
class=FileHandler
level=DEBUG
-formatter=normal
+formatter=tests
args=('tempest.log', 'w')
+[handler_syslog]
+class=handlers.SysLogHandler
+level=ERROR
+formatter = default
+args = ('/dev/log', handlers.SysLogHandler.LOG_USER)
+
[handler_devel]
class=StreamHandler
level=DEBUG
-formatter=debug
+formatter=default
args=(sys.stdout,)
-[formatter_normal]
-format=%(asctime)s %(levelname)s %(message)s
+[formatter_default]
+format=%(name)s: %(levelname)s: %(message)s
-[formatter_debug]
-format=%(asctime)s %(levelname)s %(module)s %(funcName)s %(message)s
+[formatter_tests]
+class = tempest.common.log.TestsFormatter
diff --git a/etc/tempest.conf.sample b/etc/tempest.conf.sample
index 3147859..8e0061f 100644
--- a/etc/tempest.conf.sample
+++ b/etc/tempest.conf.sample
@@ -12,9 +12,8 @@
disable_ssl_certificate_validation = False
# URL for where to find the OpenStack Identity API endpoint (Keystone)
uri = http://127.0.0.1:5000/v2.0/
-# Should typically be left as keystone unless you have a non-Keystone
-# authentication API service
-strategy = keystone
+# URL for where to find the OpenStack V3 Identity API endpoint (Keystone)
+uri_v3 = http://127.0.0.1:5000/v3/
# The identity region
region = RegionOne
@@ -63,6 +62,10 @@
flavor_ref = 1
flavor_ref_alt = 2
+# User names used to authenticate to an instance for a given image.
+image_ssh_user = root
+image_alt_ssh_user = root
+
# Number of seconds to wait while looping to check the status of an
# instance that is building.
build_interval = 10
@@ -121,11 +124,11 @@
# relax-xsm-sr-check
block_migrate_supports_cinder_iscsi = false
-# By default, rely on the status of the diskConfig extension to
-# decide if to execute disk config tests. When set to false, tests
-# are forced to skip, regardless of the extension status
-disk_config_enabled_override = true
+# When set to false, disk config tests are forced to skip
+disk_config_enabled = true
+# When set to false, flavor extra data tests are forced to skip
+flavor_extra_enabled = true
[whitebox]
# Whitebox options for compute. Whitebox options enable the
@@ -220,12 +223,16 @@
# Number of seconds to time out on waiting for a volume
# to be available or reach an expected status
build_timeout = 300
-# Runs Cinder multi-backend tests (requires 2 backend declared in cinder.conf)
+# Runs Cinder multi-backend tests (requires 2 backends declared in cinder.conf)
# They must have different volume_backend_name (backend1_name and backend2_name
# have to be different)
multi_backend_enabled = false
-backend1_name = LVM_iSCSI
-backend2_name = LVM_iSCSI_1
+backend1_name = BACKEND_1
+backend2_name = BACKEND_2
+# Protocol and vendor of volume backend to target when testing volume-types.
+# You should update to reflect those exported by configured backend driver.
+storage_protocol = iSCSI
+vendor_name = Open Source
[object-storage]
# This section contains configuration options used when executing tests
@@ -303,7 +310,7 @@
# Instance type for tests. Needs to be big enough for a
# full OS plus the test workload
-instance_type = m1.tiny
+instance_type = m1.micro
# Name of heat-cfntools enabled image to use when launching test instances
# If not specified, tests that spawn instances will not run
@@ -312,3 +319,25 @@
# Name of existing keypair to launch servers with. The default is not to specify
# any key, which will generate a keypair for each test class
#keypair_name = heat_key
+
+[scenario]
+# Directory containing image files
+img_dir = /opt/stack/new/devstack/files/images/cirros-0.3.1-x86_64-uec
+
+# AMI image file name
+ami_img_file = cirros-0.3.1-x86_64-blank.img
+
+# ARI image file name
+ari_img_file = cirros-0.3.1-x86_64-initrd
+
+# AKI image file name
+aki_img_file = cirros-0.3.1-x86_64-vmlinuz
+
+# ssh username for the image file
+ssh_user = cirros
+
+[CLI]
+# Enable cli tests
+enabled = True
+# directory where python client binaries are located
+cli_dir = /usr/local/bin/
diff --git a/tools/pip-requires b/requirements.txt
similarity index 85%
rename from tools/pip-requires
rename to requirements.txt
index 4873d75..606d7ae 100644
--- a/tools/pip-requires
+++ b/requirements.txt
@@ -3,7 +3,7 @@
anyjson
nose
httplib2>=0.7.0
-testtools>=0.9.29
+testtools>=0.9.32
lxml
boto>=2.2.1
paramiko
@@ -12,10 +12,10 @@
python-keystoneclient>=0.2.0
python-novaclient>=2.10.0
python-quantumclient>=2.1
+python-cinderclient>=1.0.4,<2
testresources
keyring
testrepository
oslo.config>=1.1.0
# Needed for whitebox testing
sqlalchemy
-MySQL-python
diff --git a/run_tests.sh b/run_tests.sh
index 56a6e6e..366564e 100755
--- a/run_tests.sh
+++ b/run_tests.sh
@@ -75,6 +75,16 @@
export TEMPEST_CONFIG=`basename "$config_file"`
fi
+if [ $logging -eq 1 ]; then
+ if [ ! -f "$logging_config" ]; then
+ echo "No such logging config file: $logging_config"
+ exit 1
+ fi
+ logging_config=`readlink -f "$logging_config"`
+ export TEMPEST_LOG_CONFIG_DIR=`dirname "$logging_config"`
+ export TEMPEST_LOG_CONFIG=`basename "$logging_config"`
+fi
+
cd `dirname "$0"`
export NOSE_WITH_OPENSTACK=1
@@ -84,14 +94,6 @@
export NOSE_OPENSTACK_SHOW_ELAPSED=1
export NOSE_OPENSTACK_STDOUT=1
-if [ $logging -eq 1 ]; then
- if [ ! -f "$logging_config" ]; then
- echo "No such logging config file: $logging_config"
- exit
- fi
- noseargs="$noseargs --logging-config=$logging_config"
-fi
-
if [ $no_site_packages -eq 1 ]; then
installvenvopts="--no-site-packages"
fi
@@ -164,11 +166,14 @@
fi
run_tests
+retval=$?
if [ $nova_coverage -eq 1 ]; then
run_coverage_report
fi
if [ -z "$noseargs" ]; then
- run_pep8
+ run_pep8
fi
+
+exit $retval
diff --git a/tempest/README.rst b/tempest/README.rst
index 63f3ad0..8f07a07 100644
--- a/tempest/README.rst
+++ b/tempest/README.rst
@@ -1,5 +1,6 @@
-Tempest Field Guide
-========
+============
+Tempest Field Guide Overview
+============
Tempest is designed to be useful for a large number of different
environments. This includes being useful for gating commits to
@@ -11,13 +12,13 @@
and guidelines. Below is the proposed Havana restructuring for Tempest
to make this clear.
-tempest/
- api/ - API tests
- cli/ - CLI tests
- scenario/ - complex scenario tests
- stress/ - stress tests
- thirdparty/ - 3rd party api tests
- whitebox/ - white box testing
+| tempest/
+| api/ - API tests
+| cli/ - CLI tests
+| scenario/ - complex scenario tests
+| stress/ - stress tests
+| thirdparty/ - 3rd party api tests
+| whitebox/ - white box testing
Each of these directories contains different types of tests. What
belongs in each directory, the rules and examples for good tests, are
@@ -38,8 +39,6 @@
projects themselves, possibly as functional tests in their unit test
frameworks.
-TODO: The bulk of tempest/tests should move to tempest/api
-
cli
------------
@@ -50,8 +49,6 @@
instantiate. Tempest seems like a logical place for this, as it
prereqs having a running OpenStack cloud.
-TODO: the top level cli directory moves to tempest/cli
-
scenario
------------
@@ -62,9 +59,6 @@
Scenario tests can and should use the OpenStack python clients.
-TODO: tests/network/test_network_basic_ops.py,
-tests/compute/servers/*_ops.py should move to tempest/scenario (others)
-
stress
-----------
@@ -81,18 +75,14 @@
------------
Many openstack components include 3rdparty API support. It is
-completely legitmate for Tempest to include tests of 3rdparty APIs,
-but those should be kept seperate from the normal OpenStack
+completely legitimate for Tempest to include tests of 3rdparty APIs,
+but those should be kept separate from the normal OpenStack
validation.
-TODO: tempest/tests/boto should become tempest/3rdparty/boto
-
whitebox
----------
Whitebox tests are tests which require access to the database of the
-target OpenStack machine to verify internal state after opperations
+target OpenStack machine to verify internal state after operations
are made. White box tests are allowed to use the python clients.
-
-TODO: collect out whitebox tests to this location.
diff --git a/tempest/api/README.rst b/tempest/api/README.rst
index cf0aac7..617fda4 100644
--- a/tempest/api/README.rst
+++ b/tempest/api/README.rst
@@ -1,9 +1,9 @@
Tempest Guide to API tests
-========
+==========================
What are these tests?
---------
+---------------------
One of Tempest's prime function is to ensure that your OpenStack cloud
works with the OpenStack API as documented. The current largest
@@ -21,7 +21,7 @@
Why are these tests in tempest?
---------
+-------------------------------
This is one of the core missions for the Tempest project, and where it
started. Many people use this bit of function in Tempest to ensure
@@ -34,7 +34,7 @@
Scope of these tests
---------
+--------------------
API tests should always use the Tempest implementation of the
OpenStack API, as we want to ensure that bugs aren't hidden by the
diff --git a/tempest/api/compute/__init__.py b/tempest/api/compute/__init__.py
index 6cd6f69..fb96b4a 100644
--- a/tempest/api/compute/__init__.py
+++ b/tempest/api/compute/__init__.py
@@ -15,10 +15,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-import logging
-
-
-from tempest import clients
+from tempest.common import log as logging
from tempest import config
from tempest.exceptions import InvalidConfiguration
@@ -28,9 +25,8 @@
CREATE_IMAGE_ENABLED = CONFIG.compute.create_image_enabled
RESIZE_AVAILABLE = CONFIG.compute.resize_available
CHANGE_PASSWORD_AVAILABLE = CONFIG.compute.change_password_available
-DISK_CONFIG_ENABLED = True
-DISK_CONFIG_ENABLED_OVERRIDE = CONFIG.compute.disk_config_enabled_override
-FLAVOR_EXTRA_DATA_ENABLED = True
+DISK_CONFIG_ENABLED = CONFIG.compute.disk_config_enabled
+FLAVOR_EXTRA_DATA_ENABLED = CONFIG.compute.flavor_extra_enabled
MULTI_USER = True
@@ -38,27 +34,7 @@
def generic_setup_package():
LOG.debug("Entering tempest.api.compute.setup_package")
- global MULTI_USER, DISK_CONFIG_ENABLED, FLAVOR_EXTRA_DATA_ENABLED
- os = clients.Manager()
- images_client = os.images_client
- flavors_client = os.flavors_client
- extensions_client = os.extensions_client
- DISK_CONFIG_ENABLED = (DISK_CONFIG_ENABLED_OVERRIDE and
- extensions_client.is_enabled('DiskConfig'))
- FLAVOR_EXTRA_DATA_ENABLED = extensions_client.is_enabled('FlavorExtraData')
-
- # Validate reference data exists
- # If not, we raise the exception here and prevent
- # going forward...
- image_ref = CONFIG.compute.image_ref
- image_ref_alt = CONFIG.compute.image_ref_alt
- images_client.get_image(image_ref)
- images_client.get_image(image_ref_alt)
-
- flavor_ref = CONFIG.compute.flavor_ref
- flavor_ref_alt = CONFIG.compute.flavor_ref_alt
- flavors_client.get_flavor_details(flavor_ref)
- flavors_client.get_flavor_details(flavor_ref_alt)
+ global MULTI_USER
# Determine if there are two regular users that can be
# used in testing. If the test cases are allowed to create
diff --git a/tempest/api/compute/admin/test_aggregates.py b/tempest/api/compute/admin/test_aggregates.py
index 1eff4dd..b66bd7e 100644
--- a/tempest/api/compute/admin/test_aggregates.py
+++ b/tempest/api/compute/admin/test_aggregates.py
@@ -43,7 +43,7 @@
filter(lambda y: y['service'] == 'compute', hosts_all))
cls.host = hosts[0]
- @attr(type='positive')
+ @attr(type='gate')
def test_aggregate_create_delete(self):
# Create and delete an aggregate.
aggregate_name = rand_name(self.aggregate_name_prefix)
@@ -56,7 +56,7 @@
self.assertEquals(200, resp.status)
self.client.wait_for_resource_deletion(aggregate['id'])
- @attr(type='positive')
+ @attr(type='gate')
def test_aggregate_create_delete_with_az(self):
# Create and delete an aggregate.
aggregate_name = rand_name(self.aggregate_name_prefix)
@@ -70,7 +70,7 @@
self.assertEquals(200, resp.status)
self.client.wait_for_resource_deletion(aggregate['id'])
- @attr(type='positive')
+ @attr(type='gate')
def test_aggregate_create_verify_entry_in_list(self):
# Create an aggregate and ensure it is listed.
aggregate_name = rand_name(self.aggregate_name_prefix)
@@ -83,7 +83,7 @@
map(lambda x: (x['id'], x['availability_zone']),
aggregates))
- @attr(type='positive')
+ @attr(type='gate')
def test_aggregate_create_get_details(self):
# Create an aggregate and ensure its details are returned.
aggregate_name = rand_name(self.aggregate_name_prefix)
@@ -96,7 +96,7 @@
self.assertEquals(aggregate['availability_zone'],
body['availability_zone'])
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_aggregate_create_as_user(self):
# Regular user is not allowed to create an aggregate.
aggregate_name = rand_name(self.aggregate_name_prefix)
@@ -104,7 +104,7 @@
self.user_client.create_aggregate,
aggregate_name)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_aggregate_delete_as_user(self):
# Regular user is not allowed to delete an aggregate.
aggregate_name = rand_name(self.aggregate_name_prefix)
@@ -115,13 +115,13 @@
self.user_client.delete_aggregate,
aggregate['id'])
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_aggregate_list_as_user(self):
# Regular user is not allowed to list aggregates.
self.assertRaises(exceptions.Unauthorized,
self.user_client.list_aggregates)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_aggregate_get_details_as_user(self):
# Regular user is not allowed to get aggregate details.
aggregate_name = rand_name(self.aggregate_name_prefix)
@@ -132,19 +132,19 @@
self.user_client.get_aggregate,
aggregate['id'])
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_aggregate_delete_with_invalid_id(self):
# Delete an aggregate with invalid id should raise exceptions.
self.assertRaises(exceptions.NotFound,
self.client.delete_aggregate, -1)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_aggregate_get_details_with_invalid_id(self):
# Get aggregate details with invalid id should raise exceptions.
self.assertRaises(exceptions.NotFound,
self.client.get_aggregate, -1)
- @attr(type='positive')
+ @attr(type='gate')
def test_aggregate_add_remove_host(self):
# Add an host to the given aggregate and remove.
aggregate_name = rand_name(self.aggregate_name_prefix)
@@ -165,7 +165,7 @@
body['availability_zone'])
self.assertNotIn(self.host, body['hosts'])
- @attr(type='positive')
+ @attr(type='gate')
def test_aggregate_add_host_list(self):
# Add an host to the given aggregate and list.
aggregate_name = rand_name(self.aggregate_name_prefix)
@@ -182,7 +182,7 @@
self.assertEquals(None, agg['availability_zone'])
self.assertIn(self.host, agg['hosts'])
- @attr(type='positive')
+ @attr(type='gate')
def test_aggregate_add_host_get_details(self):
# Add an host to the given aggregate and get details.
aggregate_name = rand_name(self.aggregate_name_prefix)
@@ -196,7 +196,7 @@
self.assertEquals(None, body['availability_zone'])
self.assertIn(self.host, body['hosts'])
- @attr(type='positive')
+ @attr(type='gate')
def test_aggregate_add_host_create_server_with_az(self):
# Add an host to the given aggregate and create a server.
aggregate_name = rand_name(self.aggregate_name_prefix)
@@ -215,7 +215,7 @@
resp, body = admin_servers_client.get_server(server['id'])
self.assertEqual(self.host, body[self._host_key])
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_aggregate_add_non_exist_host(self):
# Adding a non-exist host to an aggregate should raise exceptions.
resp, hosts_all = self.os_adm.hosts_client.list_hosts()
@@ -232,7 +232,7 @@
self.assertRaises(exceptions.NotFound, self.client.add_host,
aggregate['id'], non_exist_host)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_aggregate_add_host_as_user(self):
# Regular user is not allowed to add a host to an aggregate.
aggregate_name = rand_name(self.aggregate_name_prefix)
@@ -243,7 +243,7 @@
self.user_client.add_host,
aggregate['id'], self.host)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_aggregate_remove_host_as_user(self):
# Regular user is not allowed to remove a host from an aggregate.
aggregate_name = rand_name(self.aggregate_name_prefix)
diff --git a/tempest/api/compute/admin/test_availability_zone.py b/tempest/api/compute/admin/test_availability_zone.py
index db54011..8a56b89 100644
--- a/tempest/api/compute/admin/test_availability_zone.py
+++ b/tempest/api/compute/admin/test_availability_zone.py
@@ -34,14 +34,14 @@
cls.client = cls.os_adm.availability_zone_client
cls.non_adm_client = cls.availability_zone_client
- @attr('positive')
+ @attr(type='gate')
def test_get_availability_zone_list(self):
# List of availability zone
resp, availability_zone = self.client.get_availability_zone_list()
self.assertEqual(200, resp.status)
self.assertTrue(len(availability_zone) > 0)
- @attr('positive')
+ @attr(type='gate')
def test_get_availability_zone_list_detail(self):
# List of availability zones and available services
resp, availability_zone = \
@@ -49,7 +49,7 @@
self.assertEqual(200, resp.status)
self.assertTrue(len(availability_zone) > 0)
- @attr('positive')
+ @attr(type='gate')
def test_get_availability_zone_list_with_non_admin_user(self):
# List of availability zone with non admin user
resp, availability_zone = \
@@ -57,7 +57,7 @@
self.assertEqual(200, resp.status)
self.assertTrue(len(availability_zone) > 0)
- @attr('negative')
+ @attr(type=['negative', 'gate'])
def test_get_availability_zone_list_detail_with_non_admin_user(self):
# List of availability zones and available services with non admin user
self.assertRaises(
diff --git a/tempest/api/compute/admin/test_fixed_ips.py b/tempest/api/compute/admin/test_fixed_ips.py
index 4194b7e..f201cf7 100644
--- a/tempest/api/compute/admin/test_fixed_ips.py
+++ b/tempest/api/compute/admin/test_fixed_ips.py
@@ -51,43 +51,43 @@
class FixedIPsTestJson(FixedIPsBase):
_interface = 'json'
- @attr(type='positive')
+ @attr(type='gate')
def test_list_fixed_ip_details(self):
resp, fixed_ip = self.client.get_fixed_ip_details(self.ip)
self.assertEqual(fixed_ip['address'], self.ip)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_list_fixed_ip_details_with_non_admin_user(self):
self.assertRaises(exceptions.Unauthorized,
self.non_admin_client.get_fixed_ip_details, self.ip)
- @attr(type='positive')
+ @attr(type='gate')
def test_set_reserve(self):
body = {"reserve": "None"}
resp, body = self.client.reserve_fixed_ip(self.ip, body)
self.assertEqual(resp.status, 202)
- @attr(type='positive')
+ @attr(type='gate')
def test_set_unreserve(self):
body = {"unreserve": "None"}
resp, body = self.client.reserve_fixed_ip(self.ip, body)
self.assertEqual(resp.status, 202)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_set_reserve_with_non_admin_user(self):
body = {"reserve": "None"}
self.assertRaises(exceptions.Unauthorized,
self.non_admin_client.reserve_fixed_ip,
self.ip, body)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_set_unreserve_with_non_admin_user(self):
body = {"unreserve": "None"}
self.assertRaises(exceptions.Unauthorized,
self.non_admin_client.reserve_fixed_ip,
self.ip, body)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_set_reserve_with_invalid_ip(self):
# NOTE(maurosr): since this exercises the same code snippet, we do it
# only for reserve action
@@ -96,7 +96,7 @@
self.client.reserve_fixed_ip,
"my.invalid.ip", body)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_fixed_ip_with_invalid_action(self):
body = {"invalid_action": "None"}
self.assertRaises(exceptions.BadRequest,
diff --git a/tempest/api/compute/admin/test_flavors.py b/tempest/api/compute/admin/test_flavors.py
index 95457b9..6db20f9 100644
--- a/tempest/api/compute/admin/test_flavors.py
+++ b/tempest/api/compute/admin/test_flavors.py
@@ -53,7 +53,7 @@
self.assertEqual(resp.status, 202)
self.client.wait_for_resource_deletion(flavor_id)
- @attr(type='positive')
+ @attr(type='gate')
def test_create_flavor(self):
# Create a flavor and ensure it is listed
# This operation requires the user to have 'admin' role
@@ -92,7 +92,7 @@
self.assertEqual(resp.status, 200)
self.assertEqual(flavor['name'], flavor_name)
- @attr(type='positive')
+ @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
@@ -117,7 +117,7 @@
flag = True
self.assertTrue(flag)
- @attr(type='negative')
+ @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
@@ -151,6 +151,7 @@
flag = False
self.assertTrue(flag)
+ @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
@@ -192,7 +193,7 @@
flag = True
self.assertTrue(flag)
- @attr(type='positive')
+ @attr(type='gate')
def test_flavor_not_public_verify_entry_not_in_list_details(self):
#Create a flavor with os-flavor-access:is_public false should not
#be present in list_details.
@@ -216,6 +217,7 @@
flag = True
self.assertFalse(flag)
+ @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
@@ -239,7 +241,7 @@
flag = True
self.assertTrue(flag)
- @attr(type='positive')
+ @attr(type='gate')
def test_is_public_string_variations(self):
flavor_id_not_public = rand_int_id(start=1000)
flavor_name_not_public = rand_name(self.flavor_name_prefix)
@@ -282,13 +284,13 @@
_test_string_variations(['t', 'true', 'yes', '1'],
flavor_name_public)
- @attr(type='negative')
+ @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')
+ @attr(type=['negative', 'gate'])
def test_create_flavor_as_user(self):
flavor_name = rand_name(self.flavor_name_prefix)
new_flavor_id = rand_int_id(start=1000)
@@ -299,7 +301,7 @@
new_flavor_id, ephemeral=self.ephemeral,
swap=self.swap, rxtx=self.rxtx)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_delete_flavor_as_user(self):
self.assertRaises(exceptions.Unauthorized,
self.user_client.delete_flavor,
diff --git a/tempest/api/compute/admin/test_flavors_access.py b/tempest/api/compute/admin/test_flavors_access.py
index 85648a9..63d5025 100644
--- a/tempest/api/compute/admin/test_flavors_access.py
+++ b/tempest/api/compute/admin/test_flavors_access.py
@@ -50,7 +50,7 @@
cls.vcpus = 1
cls.disk = 10
- @attr('positive')
+ @attr(type='gate')
def test_flavor_access_add_remove(self):
#Test to add and remove flavor access to a given tenant.
flavor_name = rand_name(self.flavor_name_prefix)
@@ -87,7 +87,7 @@
self.assertEqual(resp.status, 200)
self.assertNotIn(new_flavor['id'], map(lambda x: x['id'], flavors))
- @attr('negative')
+ @attr(type=['negative', 'gate'])
def test_flavor_non_admin_add(self):
#Test to add flavor access as a user without admin privileges.
flavor_name = rand_name(self.flavor_name_prefix)
@@ -103,7 +103,7 @@
new_flavor['id'],
self.tenant_id)
- @attr('negative')
+ @attr(type=['negative', 'gate'])
def test_flavor_non_admin_remove(self):
#Test to remove flavor access as a user without admin privileges.
flavor_name = 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 357a787..db376b5 100644
--- a/tempest/api/compute/admin/test_flavors_extra_specs.py
+++ b/tempest/api/compute/admin/test_flavors_extra_specs.py
@@ -60,6 +60,7 @@
resp, body = cls.client.delete_flavor(cls.flavor['id'])
super(FlavorsExtraSpecsTestJSON, cls).tearDownClass()
+ @attr(type='gate')
def test_flavor_set_get_unset_keys(self):
#Test to SET, GET UNSET flavor extra spec as a user
#with admin privileges.
@@ -80,7 +81,7 @@
self.client.unset_flavor_extra_spec(self.flavor['id'], "key1")
self.assertEqual(unset_resp.status, 200)
- @attr('negative')
+ @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"}
@@ -89,6 +90,7 @@
self.flavor['id'],
specs)
+ @attr(type='gate')
def test_flavor_non_admin_get_keys(self):
specs = {"key1": "value1", "key2": "value2"}
set_resp, set_body = self.client.set_flavor_extra_spec(
@@ -99,7 +101,7 @@
for key in specs:
self.assertEquals(body[key], specs[key])
- @attr('negative')
+ @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(
diff --git a/tempest/api/compute/admin/test_hypervisor.py b/tempest/api/compute/admin/test_hypervisor.py
new file mode 100644
index 0000000..00a5955
--- /dev/null
+++ b/tempest/api/compute/admin/test_hypervisor.py
@@ -0,0 +1,112 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 IBM Corporation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.api.compute import base
+from tempest import exceptions
+from tempest.test import attr
+
+
+class HypervisorAdminTestJSON(base.BaseComputeAdminTest):
+
+ """
+ Tests Hypervisors API that require admin privileges
+ """
+
+ _interface = 'json'
+
+ @classmethod
+ def setUpClass(cls):
+ super(HypervisorAdminTestJSON, cls).setUpClass()
+ cls.client = cls.os_adm.hypervisor_client
+ cls.non_adm_client = cls.hypervisor_client
+
+ def _list_hypervisors(self):
+ # List of hypervisors
+ resp, hypers = self.client.get_hypervisor_list()
+ self.assertEqual(200, resp.status)
+ return hypers
+
+ @attr(type=['positive', 'gate'])
+ def test_get_hypervisor_list(self):
+ # List of hypervisor and available hypervisors hostname
+ hypers = self._list_hypervisors()
+ self.assertTrue(len(hypers) > 0)
+
+ @attr(type=['positive', 'gate'])
+ def test_get_hypervisor_list_details(self):
+ # Display the details of the all hypervisor
+ resp, hypers = self.client.get_hypervisor_list_details()
+ self.assertEqual(200, resp.status)
+ self.assertTrue(len(hypers) > 0)
+
+ @attr(type=['positive', 'gate'])
+ def test_get_hypervisor_show_details(self):
+ # Display the details of the specified hypervisor
+ hypers = self._list_hypervisors()
+ self.assertTrue(len(hypers) > 0)
+
+ resp, details = (self.client.
+ get_hypervisor_show_details(hypers[0]['id']))
+ self.assertEqual(200, resp.status)
+ self.assertTrue(len(details) > 0)
+ self.assertEqual(details['hypervisor_hostname'],
+ hypers[0]['hypervisor_hostname'])
+
+ @attr(type=['positive', 'gate'])
+ def test_get_hypervisor_show_servers(self):
+ # Show instances about the specific hypervisors
+ hypers = self._list_hypervisors()
+ self.assertTrue(len(hypers) > 0)
+
+ hostname = hypers[0]['hypervisor_hostname']
+ resp, hypervisors = self.client.get_hypervisor_servers(hostname)
+ self.assertEqual(200, resp.status)
+ self.assertTrue(len(hypervisors) > 0)
+
+ @attr(type=['positive', 'gate'])
+ def test_get_hypervisor_stats(self):
+ # Verify the stats of the all hypervisor
+ resp, stats = self.client.get_hypervisor_stats()
+ self.assertEqual(200, resp.status)
+ self.assertTrue(len(stats) > 0)
+
+ @attr(type=['positive', 'gate'])
+ def test_get_hypervisor_uptime(self):
+ # Verify that GET shows the specified hypervisor uptime
+ hypers = self._list_hypervisors()
+
+ resp, uptime = self.client.get_hypervisor_uptime(hypers[0]['id'])
+ self.assertEqual(200, resp.status)
+ self.assertTrue(len(uptime) > 0)
+
+ @attr(type=['negative', 'gate'])
+ def test_get_hypervisor_list_with_non_admin_user(self):
+ # List of hypervisor and available services with non admin user
+ self.assertRaises(
+ exceptions.Unauthorized,
+ self.non_adm_client.get_hypervisor_list)
+
+ @attr(type=['negative', 'gate'])
+ def test_get_hypervisor_list_details_with_non_admin_user(self):
+ # List of hypervisor details and available services with non admin user
+ self.assertRaises(
+ exceptions.Unauthorized,
+ self.non_adm_client.get_hypervisor_list_details)
+
+
+class HypervisorAdminTestXML(HypervisorAdminTestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/compute/admin/test_quotas.py b/tempest/api/compute/admin/test_quotas.py
index ba21dc5..a6b4e31 100644
--- a/tempest/api/compute/admin/test_quotas.py
+++ b/tempest/api/compute/admin/test_quotas.py
@@ -19,6 +19,7 @@
from tempest.common.utils.data_utils import rand_name
from tempest import exceptions
from tempest.test import attr
+import testtools
class QuotasAdminTestJSON(base.BaseComputeAdminTest):
@@ -70,11 +71,9 @@
self.assertEqual(200, resp.status)
self.assertEqual(expected_quota_set, quota_set)
+ @testtools.skip("Skipped until the Bug #1160749 is resolved")
+ @attr(type='gate')
def test_update_all_quota_resources_for_tenant(self):
- self.skipTest("This test require the change in nova component "
- "https://review.openstack.org/#/c/25887/, the related "
- "bug is https://bugs.launchpad.net/nova/+bug/1160749 "
- "once the change is merged I will enable this testcase")
# Admin can update all the resource quota limits for a tenant
new_quota_set = {'force': True,
'injected_file_content_bytes': 20480,
@@ -103,6 +102,7 @@
"defaults")
#TODO(afazekas): merge these test cases
+ @attr(type='gate')
def test_get_updated_quotas(self):
# Verify that GET shows the updated quota set
self.adm_client.update_quota_set(self.demo_tenant_id,
@@ -123,11 +123,9 @@
self.assertEqual(200, resp.status, "Failed to reset quota "
"defaults")
+ @testtools.skip("Skipped until the Bug #1160749 is resolved")
+ @attr(type='gate')
def test_create_server_when_cpu_quota_is_full(self):
- self.skipTest("This test require the change in nova component "
- "https://review.openstack.org/#/c/25887/, the related "
- "bug is https://bugs.launchpad.net/nova/+bug/1160749 "
- "once the change is merged I will enable this testcase")
# Disallow server creation when tenant's vcpu quota is full
resp, quota_set = self.client.get_quota_set(self.demo_tenant_id)
default_vcpu_quota = quota_set['cores']
@@ -141,11 +139,9 @@
cores=default_vcpu_quota)
self.assertRaises(exceptions.OverLimit, self.create_server)
+ @testtools.skip("Skipped until the Bug #1160749 is resolved")
+ @attr(type='gate')
def test_create_server_when_memory_quota_is_full(self):
- self.skipTest("This test require the change in nova component "
- "https://review.openstack.org/#/c/25887/, the related "
- "bug is https://bugs.launchpad.net/nova/+bug/1160749 "
- "once the change is merged I will enable this testcase")
# Disallow server creation when tenant's memory quota is full
resp, quota_set = self.client.get_quota_set(self.demo_tenant_id)
default_mem_quota = quota_set['ram']
@@ -161,12 +157,9 @@
#TODO(afazekas): Add test that tried to update the quota_set as a regular user
- @attr(type='negative')
+ @testtools.skip("Skipped until the Bug #1160749 is resolved")
+ @attr(type=['negative', 'gate'])
def test_create_server_when_instances_quota_is_full(self):
- self.skipTest("This test require the change in nova component "
- "https://review.openstack.org/#/c/25887/, the related "
- "bug is https://bugs.launchpad.net/nova/+bug/1160749 "
- "once the change is merged I will enable this testcase")
#Once instances quota limit is reached, disallow server creation
resp, quota_set = self.client.get_quota_set(self.demo_tenant_id)
default_instances_quota = quota_set['instances']
@@ -179,7 +172,8 @@
instances=default_instances_quota)
self.assertRaises(exceptions.OverLimit, self.create_server)
- @attr(type='negative')
+ @testtools.skip("Skipped until the Bug #1160749 is resolved")
+ @attr(type=['negative', 'gate'])
def test_security_groups_exceed_limit(self):
# Negative test: Creation Security Groups over limit should FAIL
@@ -200,7 +194,8 @@
self.sg_client.create_security_group,
"sg-overlimit", "sg-desc")
- @attr(type='negative')
+ @testtools.skip("Skipped until the Bug #1160749 is resolved")
+ @attr(type=['negative', 'gate'])
def test_security_groups_rules_exceed_limit(self):
# Negative test: Creation of Security Group Rules should FAIL
# when we reach limit maxSecurityGroupRules
diff --git a/tempest/api/compute/admin/test_services.py b/tempest/api/compute/admin/test_services.py
index aacfe8a..78dac21 100644
--- a/tempest/api/compute/admin/test_services.py
+++ b/tempest/api/compute/admin/test_services.py
@@ -34,14 +34,14 @@
cls.client = cls.os_adm.services_client
cls.non_admin_client = cls.services_client
- @attr(type='positive')
+ @attr(type='gate')
def test_list_services(self):
# List Compute services
resp, services = self.client.list_services()
self.assertEqual(200, resp.status)
self.assertTrue(len(services) >= 2)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_list_services_with_non_admin_user(self):
# List Compute service with non admin user
self.assertRaises(exceptions.Unauthorized,
diff --git a/tempest/api/compute/admin/test_simple_tenant_usage.py b/tempest/api/compute/admin/test_simple_tenant_usage.py
new file mode 100644
index 0000000..ce05899
--- /dev/null
+++ b/tempest/api/compute/admin/test_simple_tenant_usage.py
@@ -0,0 +1,115 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 NEC Corporation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import datetime
+
+from tempest.api.compute import base
+from tempest import exceptions
+from tempest.test import attr
+import time
+
+
+class TenantUsagesTestJSON(base.BaseComputeAdminTest):
+
+ _interface = 'json'
+
+ @classmethod
+ def setUpClass(cls):
+ super(TenantUsagesTestJSON, cls).setUpClass()
+ cls.adm_client = cls.os_adm.tenant_usages_client
+ cls.client = cls.os.tenant_usages_client
+ cls.identity_client = cls._get_identity_admin_client()
+
+ resp, tenants = cls.identity_client.list_tenants()
+ cls.tenant_id = [tnt['id'] for tnt in tenants if tnt['name'] ==
+ cls.client.tenant_name][0]
+
+ # Create a server in the demo tenant
+ resp, server = cls.create_server(wait_until='ACTIVE')
+ time.sleep(2)
+
+ now = datetime.datetime.now()
+ cls.start = cls._parse_strtime(now - datetime.timedelta(days=1))
+ cls.end = cls._parse_strtime(now + datetime.timedelta(days=1))
+
+ @classmethod
+ def _parse_strtime(cls, at):
+ # Returns formatted datetime
+ return at.strftime('%Y-%m-%dT%H:%M:%S.%f')
+
+ @attr(type='gate')
+ def test_list_usage_all_tenants(self):
+ # Get usage for all tenants
+ params = {'start': self.start,
+ 'end': self.end,
+ 'detailed': int(bool(True))}
+ resp, tenant_usage = self.adm_client.list_tenant_usages(params)
+ self.assertEqual(200, resp.status)
+ self.assertEqual(len(tenant_usage), 8)
+
+ @attr(type='gate')
+ def test_get_usage_tenant(self):
+ # Get usage for a specific tenant
+ params = {'start': self.start,
+ 'end': self.end}
+ resp, tenant_usage = self.adm_client.get_tenant_usage(
+ self.tenant_id, params)
+
+ self.assertEqual(200, resp.status)
+ self.assertEqual(len(tenant_usage), 8)
+
+ @attr(type='gate')
+ def test_get_usage_tenant_with_non_admin_user(self):
+ # Get usage for a specific tenant with non admin user
+ params = {'start': self.start,
+ 'end': self.end}
+ resp, tenant_usage = self.client.get_tenant_usage(
+ self.tenant_id, params)
+
+ self.assertEqual(200, resp.status)
+ self.assertEqual(len(tenant_usage), 8)
+
+ @attr(type=['negative', 'gate'])
+ def test_get_usage_tenant_with_empty_tenant_id(self):
+ # Get usage for a specific tenant empty
+ params = {'start': self.start,
+ 'end': self.end}
+ self.assertRaises(exceptions.NotFound,
+ self.adm_client.get_tenant_usage,
+ '', params)
+
+ @attr(type=['negative', 'gate'])
+ def test_get_usage_tenant_with_invalid_date(self):
+ # Get usage for tenant with invalid date
+ params = {'start': self.end,
+ 'end': self.start}
+ self.assertRaises(exceptions.BadRequest,
+ self.adm_client.get_tenant_usage,
+ self.tenant_id, params)
+
+ @attr(type=['negative', 'gate'])
+ def test_list_usage_all_tenants_with_non_admin_user(self):
+ # Get usage for all tenants with non admin user
+ params = {'start': self.start,
+ 'end': self.end,
+ 'detailed': int(bool(True))}
+ self.assertRaises(exceptions.Unauthorized,
+ self.client.list_tenant_usages, params)
+
+
+class TenantUsagesTestXML(TenantUsagesTestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/compute/base.py b/tempest/api/compute/base.py
index 5f0df3c..abc5899 100644
--- a/tempest/api/compute/base.py
+++ b/tempest/api/compute/base.py
@@ -15,11 +15,12 @@
# License for the specific language governing permissions and limitations
# under the License.
-import logging
import time
from tempest.api import compute
from tempest import clients
+from tempest.common import log as logging
+from tempest.common.utils.data_utils import parse_image_id
from tempest.common.utils.data_utils import rand_name
from tempest import exceptions
import tempest.test
@@ -64,6 +65,7 @@
cls.availability_zone_client = os.availability_zone_client
cls.aggregates_client = os.aggregates_client
cls.services_client = os.services_client
+ cls.hypervisor_client = os.hypervisor_client
cls.build_interval = cls.config.compute.build_interval
cls.build_timeout = cls.config.compute.build_timeout
cls.ssh_user = cls.config.compute.ssh_user
@@ -72,6 +74,9 @@
cls.flavor_ref = cls.config.compute.flavor_ref
cls.flavor_ref_alt = cls.config.compute.flavor_ref_alt
cls.servers = []
+ cls.images = []
+
+ cls.servers_client_v3_auth = os.servers_client_v3_auth
@classmethod
def _get_identity_admin_client(cls):
@@ -171,7 +176,18 @@
pass
@classmethod
+ def clear_images(cls):
+ for image_id in cls.images:
+ try:
+ cls.images_client.delete_image(image_id)
+ except Exception as exc:
+ LOG.info('Exception raised deleting image %s', image_id)
+ LOG.exception(exc)
+ pass
+
+ @classmethod
def tearDownClass(cls):
+ cls.clear_images()
cls.clear_servers()
cls.clear_isolated_creds()
@@ -184,15 +200,43 @@
flavor = kwargs.get('flavor', cls.flavor_ref)
image_id = kwargs.get('image_id', cls.image_ref)
- resp, server = cls.servers_client.create_server(
+ resp, body = cls.servers_client.create_server(
name, image_id, flavor, **kwargs)
- cls.servers.append(server)
+
+ # handle the case of multiple servers
+ servers = [body]
+ if 'min_count' in kwargs or 'max_count' in kwargs:
+ # Get servers created which name match with name param.
+ r, b = cls.servers_client.list_servers()
+ servers = [s for s in b['servers'] if s['name'].startswith(name)]
+
+ cls.servers.extend(servers)
if 'wait_until' in kwargs:
- cls.servers_client.wait_for_server_status(
- server['id'], kwargs['wait_until'])
+ for server in servers:
+ cls.servers_client.wait_for_server_status(
+ server['id'], kwargs['wait_until'])
- return resp, server
+ return resp, body
+
+ @classmethod
+ def create_image_from_server(cls, server_id, **kwargs):
+ """Wrapper utility that returns a test server."""
+ name = rand_name(cls.__name__ + "-image")
+ if 'name' in kwargs:
+ name = kwargs.pop('name')
+
+ resp, image = cls.images_client.create_image(
+ server_id, name)
+ image_id = parse_image_id(resp['location'])
+ cls.images.append(image_id)
+
+ if 'wait_until' in kwargs:
+ cls.images_client.wait_for_image_status(image_id,
+ kwargs['wait_until'])
+ resp, image = cls.images_client.get_image(image_id)
+
+ return resp, image
def wait_for(self, condition):
"""Repeatedly calls condition() until a timeout."""
diff --git a/tempest/api/compute/flavors/test_flavors.py b/tempest/api/compute/flavors/test_flavors.py
index d51d9d8..27526eb 100644
--- a/tempest/api/compute/flavors/test_flavors.py
+++ b/tempest/api/compute/flavors/test_flavors.py
@@ -50,27 +50,27 @@
resp, flavor = self.client.get_flavor_details(self.flavor_ref)
self.assertEqual(self.flavor_ref, int(flavor['id']))
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_get_non_existant_flavor(self):
# flavor details are not returned for non existant flavors
self.assertRaises(exceptions.NotFound, self.client.get_flavor_details,
999)
- @attr(type='positive')
+ @attr(type='gate')
def test_list_flavors_limit_results(self):
# Only the expected number of flavors should be returned
params = {'limit': 1}
resp, flavors = self.client.list_flavors(params)
self.assertEqual(1, len(flavors))
- @attr(type='positive')
+ @attr(type='gate')
def test_list_flavors_detailed_limit_results(self):
# Only the expected number of flavors (detailed) should be returned
params = {'limit': 1}
resp, flavors = self.client.list_flavors_with_detail(params)
self.assertEqual(1, len(flavors))
- @attr(type='positive')
+ @attr(type='gate')
def test_list_flavors_using_marker(self):
# The list of flavors should start from the provided marker
resp, flavors = self.client.list_flavors()
@@ -81,7 +81,7 @@
self.assertFalse(any([i for i in flavors if i['id'] == flavor_id]),
'The list of flavors did not start after the marker.')
- @attr(type='positive')
+ @attr(type='gate')
def test_list_flavors_detailed_using_marker(self):
# The list of flavors should start from the provided marker
resp, flavors = self.client.list_flavors_with_detail()
@@ -92,7 +92,7 @@
self.assertFalse(any([i for i in flavors if i['id'] == flavor_id]),
'The list of flavors did not start after the marker.')
- @attr(type='positive')
+ @attr(type='gate')
def test_list_flavors_detailed_filter_by_min_disk(self):
# The detailed list of flavors should be filtered by disk space
resp, flavors = self.client.list_flavors_with_detail()
@@ -103,7 +103,7 @@
resp, flavors = self.client.list_flavors_with_detail(params)
self.assertFalse(any([i for i in flavors if i['id'] == flavor_id]))
- @attr(type='positive')
+ @attr(type='gate')
def test_list_flavors_detailed_filter_by_min_ram(self):
# The detailed list of flavors should be filtered by RAM
resp, flavors = self.client.list_flavors_with_detail()
@@ -114,7 +114,7 @@
resp, flavors = self.client.list_flavors_with_detail(params)
self.assertFalse(any([i for i in flavors if i['id'] == flavor_id]))
- @attr(type='positive')
+ @attr(type='gate')
def test_list_flavors_filter_by_min_disk(self):
# The list of flavors should be filtered by disk space
resp, flavors = self.client.list_flavors_with_detail()
@@ -125,7 +125,7 @@
resp, flavors = self.client.list_flavors(params)
self.assertFalse(any([i for i in flavors if i['id'] == flavor_id]))
- @attr(type='positive')
+ @attr(type='gate')
def test_list_flavors_filter_by_min_ram(self):
# The list of flavors should be filtered by RAM
resp, flavors = self.client.list_flavors_with_detail()
@@ -136,19 +136,19 @@
resp, flavors = self.client.list_flavors(params)
self.assertFalse(any([i for i in flavors if i['id'] == flavor_id]))
- @attr(type='negative')
+ @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')
+ @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')
+ @attr(type=['negative', 'gate'])
def test_get_flavor_details_for_invalid_flavor_id(self):
# Ensure 404 returned for non-existant flavor ID
self.assertRaises(exceptions.NotFound, self.client.get_flavor_details,
diff --git a/tempest/api/compute/floating_ips/test_floating_ips_actions.py b/tempest/api/compute/floating_ips/test_floating_ips_actions.py
index 8263dc2..0d7f26d 100644
--- a/tempest/api/compute/floating_ips/test_floating_ips_actions.py
+++ b/tempest/api/compute/floating_ips/test_floating_ips_actions.py
@@ -56,7 +56,7 @@
resp, body = cls.client.delete_floating_ip(cls.floating_ip_id)
super(FloatingIPsTestJSON, cls).tearDownClass()
- @attr(type='positive')
+ @attr(type='gate')
def test_allocate_floating_ip(self):
# Positive test:Allocation of a new floating IP to a project
# should be successful
@@ -73,7 +73,7 @@
#Deleting the floating IP which is created in this method
self.client.delete_floating_ip(floating_ip_id_allocated)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_allocate_floating_ip_from_nonexistent_pool(self):
# Positive test:Allocation of a new floating IP from a nonexistent_pool
#to a project should fail
@@ -81,7 +81,7 @@
self.client.create_floating_ip,
"non_exist_pool")
- @attr(type='positive')
+ @attr(type='gate')
def test_delete_floating_ip(self):
# Positive test:Deletion of valid floating IP from project
# should be successful
@@ -96,7 +96,7 @@
# Check it was really deleted.
self.client.wait_for_resource_deletion(floating_ip_body['id'])
- @attr(type='positive')
+ @attr(type='gate')
def test_associate_disassociate_floating_ip(self):
# Positive test:Associate and disassociate the provided floating IP
# to a specific server should be successful
@@ -112,7 +112,7 @@
self.server_id)
self.assertEqual(202, resp.status)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_delete_nonexistant_floating_ip(self):
# Negative test:Deletion of a nonexistent floating IP
# from project should fail
@@ -121,7 +121,7 @@
self.assertRaises(exceptions.NotFound, self.client.delete_floating_ip,
self.non_exist_id)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_associate_nonexistant_floating_ip(self):
# Negative test:Association of a non existent floating IP
# to specific server should fail
@@ -130,7 +130,7 @@
self.client.associate_floating_ip_to_server,
"0.0.0.0", self.server_id)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_dissociate_nonexistant_floating_ip(self):
# Negative test:Dissociation of a non existent floating IP should fail
# Dissociating non existent floating IP
@@ -138,7 +138,7 @@
self.client.disassociate_floating_ip_from_server,
"0.0.0.0", self.server_id)
- @attr(type='positive')
+ @attr(type='gate')
def test_associate_already_associated_floating_ip(self):
# positive test:Association of an already associated floating IP
# to specific server should change the association of the Floating IP
@@ -170,7 +170,7 @@
self.client.disassociate_floating_ip_from_server,
self.floating_ip, self.server_id)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_associate_ip_to_server_without_passing_floating_ip(self):
# Negative test:Association of empty floating IP to specific server
# should raise NotFound exception
diff --git a/tempest/api/compute/floating_ips/test_list_floating_ips.py b/tempest/api/compute/floating_ips/test_list_floating_ips.py
index 017659e..3e1aa82 100644
--- a/tempest/api/compute/floating_ips/test_list_floating_ips.py
+++ b/tempest/api/compute/floating_ips/test_list_floating_ips.py
@@ -42,7 +42,7 @@
cls.client.delete_floating_ip(cls.floating_ip_id[i])
super(FloatingIPDetailsTestJSON, cls).tearDownClass()
- @attr(type='positive')
+ @attr(type='gate')
def test_list_floating_ips(self):
# Positive test:Should return the list of floating IPs
resp, body = self.client.list_floating_ips()
@@ -53,7 +53,7 @@
for i in range(3):
self.assertTrue(self.floating_ip[i] in floating_ips)
- @attr(type='positive')
+ @attr(type='gate')
def test_get_floating_ip_details(self):
# Positive test:Should be able to GET the details of floatingIP
#Creating a floating IP for which details are to be checked
@@ -77,7 +77,7 @@
finally:
self.client.delete_floating_ip(floating_ip_id)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_get_nonexistant_floating_ip_details(self):
# Negative test:Should not be able to GET the details
# of nonexistant floating IP
diff --git a/tempest/api/compute/images/test_image_metadata.py b/tempest/api/compute/images/test_image_metadata.py
index 6b167a8..7b8e1cc 100644
--- a/tempest/api/compute/images/test_image_metadata.py
+++ b/tempest/api/compute/images/test_image_metadata.py
@@ -52,12 +52,14 @@
resp, _ = self.client.set_image_metadata(self.image_id, meta)
self.assertEqual(resp.status, 200)
+ @attr(type='gate')
def test_list_image_metadata(self):
# All metadata key/value pairs for an image should be returned
resp, resp_metadata = self.client.list_image_metadata(self.image_id)
expected = {'key1': 'value1', 'key2': 'value2'}
self.assertEqual(expected, resp_metadata)
+ @attr(type='gate')
def test_set_image_metadata(self):
# The metadata for the image should match the new values
req_metadata = {'meta2': 'value2', 'meta3': 'value3'}
@@ -67,6 +69,7 @@
resp, resp_metadata = self.client.list_image_metadata(self.image_id)
self.assertEqual(req_metadata, resp_metadata)
+ @attr(type='gate')
def test_update_image_metadata(self):
# The metadata for the image should match the updated values
req_metadata = {'key1': 'alt1', 'key3': 'value3'}
@@ -77,12 +80,14 @@
expected = {'key1': 'alt1', 'key2': 'value2', 'key3': 'value3'}
self.assertEqual(expected, resp_metadata)
+ @attr(type='gate')
def test_get_image_metadata_item(self):
# The value for a specific metadata key should be returned
resp, meta = self.client.get_image_metadata_item(self.image_id,
'key2')
self.assertTrue('value2', meta['key2'])
+ @attr(type='gate')
def test_set_image_metadata_item(self):
# The value provided for the given meta item should be set for
# the image
@@ -93,6 +98,7 @@
expected = {'key1': 'alt', 'key2': 'value2'}
self.assertEqual(expected, resp_metadata)
+ @attr(type='gate')
def test_delete_image_metadata_item(self):
# The metadata value/key pair should be deleted from the image
resp, body = self.client.delete_image_metadata_item(self.image_id,
@@ -101,34 +107,34 @@
expected = {'key2': 'value2'}
self.assertEqual(expected, resp_metadata)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_list_nonexistant_image_metadata(self):
# Negative test: List on nonexistant image
# metadata should not happen
self.assertRaises(exceptions.NotFound, self.client.list_image_metadata,
999)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_update_nonexistant_image_metadata(self):
# Negative test:An update should not happen for a nonexistant image
meta = {'key1': 'alt1', 'key2': 'alt2'}
self.assertRaises(exceptions.NotFound,
self.client.update_image_metadata, 999, meta)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_get_nonexistant_image_metadata_item(self):
# Negative test: Get on nonexistant image should not happen
self.assertRaises(exceptions.NotFound,
self.client.get_image_metadata_item, 999, 'key2')
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_set_nonexistant_image_metadata(self):
# Negative test: Metadata should not be set to a nonexistant image
meta = {'key1': 'alt1', 'key2': 'alt2'}
self.assertRaises(exceptions.NotFound, self.client.set_image_metadata,
999, meta)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_set_nonexistant_image_metadata_item(self):
# Negative test: Metadata item should not be set to a
# nonexistant image
@@ -137,7 +143,7 @@
self.client.set_image_metadata_item, 999, 'key1',
meta)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_delete_nonexistant_image_metadata_item(self):
# Negative test: Shouldnt be able to delete metadata
# item from nonexistant image
diff --git a/tempest/api/compute/images/test_images.py b/tempest/api/compute/images/test_images.py
index 0647d06..a74bb68 100644
--- a/tempest/api/compute/images/test_images.py
+++ b/tempest/api/compute/images/test_images.py
@@ -15,8 +15,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-import testtools
-
from tempest.api import compute
from tempest.api.compute import base
from tempest import clients
@@ -64,7 +62,7 @@
self.image_ids.append(image_id)
return resp, body
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_create_image_from_deleted_server(self):
# An image should not be created if the server instance is removed
resp, server = self.create_server(wait_until='ACTIVE')
@@ -79,7 +77,7 @@
self.__create_image__,
server['id'], name, meta)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_create_image_from_invalid_server(self):
# An image should not be created with invalid server id
# Create a new image with invalid server id
@@ -90,7 +88,7 @@
self.assertRaises(exceptions.NotFound, self.__create_image__,
'!@#$%^&*()', name, meta)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_create_image_when_server_is_terminating(self):
# Return an error when creating image of server that is terminating
resp, server = self.create_server(wait_until='ACTIVE')
@@ -100,18 +98,17 @@
self.assertRaises(exceptions.Duplicate, self.client.create_image,
server['id'], snapshot_name)
- @testtools.skip("Until Bug #1039739 is fixed")
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_create_image_when_server_is_rebooting(self):
# Return error when creating an image of server that is rebooting
- resp, server = self.create_server()
+ resp, server = self.create_server(wait_until='ACTIVE')
self.servers_client.reboot(server['id'], 'HARD')
snapshot_name = rand_name('test-snap-')
self.assertRaises(exceptions.Duplicate, self.client.create_image,
server['id'], snapshot_name)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_create_image_specify_uuid_35_characters_or_less(self):
# Return an error if Image ID passed is 35 characters or less
snapshot_name = rand_name('test-snap-')
@@ -119,7 +116,7 @@
self.assertRaises(exceptions.NotFound, self.client.create_image,
test_uuid, snapshot_name)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_create_image_specify_uuid_37_characters_or_more(self):
# Return an error if Image ID passed is 37 characters or more
snapshot_name = rand_name('test-snap-')
@@ -127,13 +124,13 @@
self.assertRaises(exceptions.NotFound, self.client.create_image,
test_uuid, snapshot_name)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_delete_image_with_invalid_image_id(self):
# An image should not be deleted with invalid image id
self.assertRaises(exceptions.NotFound, self.client.delete_image,
'!@$%^&*()')
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_delete_non_existent_image(self):
# Return an error while trying to delete a non-existent image
@@ -141,24 +138,24 @@
self.assertRaises(exceptions.NotFound, self.client.delete_image,
non_existent_image_id)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_delete_image_blank_id(self):
# Return an error while trying to delete an image with blank Id
self.assertRaises(exceptions.NotFound, self.client.delete_image, '')
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_delete_image_non_hex_string_id(self):
# Return an error while trying to delete an image with non hex id
image_id = '11a22b9-120q-5555-cc11-00ab112223gj'
self.assertRaises(exceptions.NotFound, self.client.delete_image,
image_id)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_delete_image_negative_image_id(self):
# Return an error while trying to delete an image with negative id
self.assertRaises(exceptions.NotFound, self.client.delete_image, -1)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_delete_image_id_is_over_35_character_limit(self):
# Return an error while trying to delete image with id over limit
self.assertRaises(exceptions.NotFound, self.client.delete_image,
diff --git a/tempest/api/compute/images/test_images_oneserver.py b/tempest/api/compute/images/test_images_oneserver.py
index e5e02a7..4163245 100644
--- a/tempest/api/compute/images/test_images_oneserver.py
+++ b/tempest/api/compute/images/test_images_oneserver.py
@@ -62,8 +62,8 @@
cls.alt_manager = clients.AltManager()
cls.alt_client = cls.alt_manager.images_client
- @attr(type='negative')
@testtools.skip("Until Bug #1006725 is fixed")
+ @attr(type=['negative', 'gate'])
def test_create_image_specify_multibyte_character_image_name(self):
# Return an error if the image name has multi-byte characters
snapshot_name = rand_name('\xef\xbb\xbf')
@@ -71,7 +71,7 @@
self.client.create_image, self.server['id'],
snapshot_name)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_create_image_specify_invalid_metadata(self):
# Return an error when creating image with invalid metadata
snapshot_name = rand_name('test-snap-')
@@ -79,7 +79,7 @@
self.assertRaises(exceptions.BadRequest, self.client.create_image,
self.server['id'], snapshot_name, meta)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_create_image_specify_metadata_over_limits(self):
# Return an error when creating image with meta data over 256 chars
snapshot_name = rand_name('test-snap-')
@@ -87,9 +87,9 @@
self.assertRaises(exceptions.BadRequest, self.client.create_image,
self.server['id'], snapshot_name, meta)
- @attr(type='negative')
@testtools.skipUnless(compute.MULTI_USER,
'Need multiple users for this test.')
+ @attr(type=['negative', 'gate'])
def test_delete_image_of_another_tenant(self):
# Return an error while trying to delete another tenant's image
self.servers_client.wait_for_server_status(self.server['id'], 'ACTIVE')
@@ -104,9 +104,9 @@
self.assertRaises(exceptions.NotFound,
self.alt_client.delete_image, image_id)
- @attr(type='smoke')
@testtools.skipUnless(compute.CREATE_IMAGE_ENABLED,
'Environment unable to create images.')
+ @attr(type='smoke')
def test_create_delete_image(self):
# Create a new image
@@ -131,11 +131,11 @@
# Verify the image was deleted correctly
resp, body = self.client.delete_image(image_id)
self.assertEqual('204', resp['status'])
- self.assertRaises(exceptions.NotFound, self.client.get_image, image_id)
+ self.client.wait_for_resource_deletion(image_id)
- @attr(type='negative')
@testtools.skipUnless(compute.MULTI_USER,
'Need multiple users for this test.')
+ @attr(type=['negative', 'gate'])
def test_create_image_for_server_in_another_tenant(self):
# Creating image of another tenant's server should be return error
@@ -143,7 +143,7 @@
self.assertRaises(exceptions.NotFound, self.alt_client.create_image,
self.server['id'], snapshot_name)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_create_second_image_when_first_image_is_being_saved(self):
# Disallow creating another image when first image is being saved
@@ -161,7 +161,7 @@
self.server['id'], alt_snapshot_name)
self.client.wait_for_image_status(image_id, 'ACTIVE')
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_create_image_specify_name_over_256_chars(self):
# Return an error if snapshot name over 256 characters is passed
@@ -169,7 +169,7 @@
self.assertRaises(exceptions.BadRequest, self.client.create_image,
self.server['id'], snapshot_name)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_delete_image_that_is_not_yet_active(self):
# Return an error while trying to delete an image what is creating
diff --git a/tempest/api/compute/images/test_list_image_filters.py b/tempest/api/compute/images/test_list_image_filters.py
index 4ba8e99..5c6b630 100644
--- a/tempest/api/compute/images/test_list_image_filters.py
+++ b/tempest/api/compute/images/test_list_image_filters.py
@@ -16,12 +16,15 @@
# under the License.
from tempest.api.compute import base
+from tempest.common import log as logging
from tempest.common.utils.data_utils import parse_image_id
-from tempest.common.utils.data_utils import rand_name
from tempest import exceptions
from tempest.test import attr
+LOG = logging.getLogger(__name__)
+
+
class ListImageFiltersTestJSON(base.BaseComputeTest):
_interface = 'json'
@@ -29,6 +32,7 @@
def setUpClass(cls):
super(ListImageFiltersTestJSON, cls).setUpClass()
cls.client = cls.images_client
+ cls.image_ids = []
try:
resp, cls.server1 = cls.create_server()
@@ -38,9 +42,7 @@
'ACTIVE')
# Create images to be used in the filter tests
- image1_name = rand_name('image')
- resp, body = cls.client.create_image(cls.server1['id'],
- image1_name)
+ resp, body = cls.create_image_from_server(cls.server1['id'])
cls.image1_id = parse_image_id(resp['location'])
cls.client.wait_for_image_resp_code(cls.image1_id, 200)
cls.client.wait_for_image_status(cls.image1_id, 'ACTIVE')
@@ -49,42 +51,30 @@
# 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
- image3_name = rand_name('image')
- resp, body = cls.client.create_image(cls.server2['id'],
- image3_name)
+ resp, body = cls.create_image_from_server(cls.server2['id'])
cls.image3_id = parse_image_id(resp['location'])
cls.client.wait_for_image_resp_code(cls.image3_id, 200)
cls.client.wait_for_image_status(cls.image3_id, 'ACTIVE')
resp, cls.image3 = cls.client.get_image(cls.image3_id)
- image2_name = rand_name('image')
- resp, body = cls.client.create_image(cls.server1['id'],
- image2_name)
+ resp, body = cls.create_image_from_server(cls.server1['id'])
cls.image2_id = parse_image_id(resp['location'])
cls.client.wait_for_image_resp_code(cls.image2_id, 200)
+
cls.client.wait_for_image_status(cls.image2_id, 'ACTIVE')
resp, cls.image2 = cls.client.get_image(cls.image2_id)
- except Exception:
- cls.clear_servers()
- cls.client.delete_image(cls.image1_id)
- cls.client.delete_image(cls.image2_id)
- cls.client.delete_image(cls.image3_id)
+ except Exception as exc:
+ LOG.exception(exc)
+ cls.tearDownClass()
raise
- @classmethod
- def tearDownClass(cls):
- cls.client.delete_image(cls.image1_id)
- cls.client.delete_image(cls.image2_id)
- cls.client.delete_image(cls.image3_id)
- super(ListImageFiltersTestJSON, cls).tearDownClass()
-
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_get_image_not_existing(self):
# Check raises a NotFound
self.assertRaises(exceptions.NotFound, self.client.get_image,
"nonexistingimageid")
- @attr(type='positive')
+ @attr(type='gate')
def test_list_images_filter_by_status(self):
# The list of images should contain only images with the
# provided status
@@ -95,7 +85,7 @@
self.assertTrue(any([i for i in images if i['id'] == self.image2_id]))
self.assertTrue(any([i for i in images if i['id'] == self.image3_id]))
- @attr(type='positive')
+ @attr(type='gate')
def test_list_images_filter_by_name(self):
# List of all images should contain the expected images filtered
# by name
@@ -106,7 +96,7 @@
self.assertFalse(any([i for i in images if i['id'] == self.image2_id]))
self.assertFalse(any([i for i in images if i['id'] == self.image3_id]))
- @attr(type='positive')
+ @attr(type='gate')
def test_list_images_filter_by_server_id(self):
# The images should contain images filtered by server id
params = {'server': self.server1['id']}
@@ -118,7 +108,7 @@
self.assertTrue(any([i for i in images if i['id'] == self.image2_id]))
self.assertFalse(any([i for i in images if i['id'] == self.image3_id]))
- @attr(type='positive')
+ @attr(type='gate')
def test_list_images_filter_by_server_ref(self):
# The list of servers should be filtered by server ref
server_links = self.server2['links']
@@ -135,7 +125,7 @@
self.assertTrue(any([i for i in images
if i['id'] == self.image3_id]))
- @attr(type='positive')
+ @attr(type='gate')
def test_list_images_filter_by_type(self):
# The list of servers should be filtered by image type
params = {'type': 'snapshot'}
@@ -146,7 +136,7 @@
self.assertTrue(any([i for i in images if i['id'] == self.image3_id]))
self.assertFalse(any([i for i in images if i['id'] == self.image_ref]))
- @attr(type='positive')
+ @attr(type='gate')
def test_list_images_limit_results(self):
# Verify only the expected number of results are returned
params = {'limit': '1'}
@@ -155,7 +145,7 @@
#ref: Question #224349
self.assertEqual(1, len([x for x in images if 'id' in x]))
- @attr(type='positive')
+ @attr(type='gate')
def test_list_images_filter_by_changes_since(self):
# Verify only updated images are returned in the detailed list
@@ -166,7 +156,7 @@
found = any([i for i in images if i['id'] == self.image3_id])
self.assertTrue(found)
- @attr(type='positive')
+ @attr(type='gate')
def test_list_images_with_detail_filter_by_status(self):
# Detailed list of all images should only contain images
# with the provided status
@@ -177,7 +167,7 @@
self.assertTrue(any([i for i in images if i['id'] == self.image2_id]))
self.assertTrue(any([i for i in images if i['id'] == self.image3_id]))
- @attr(type='positive')
+ @attr(type='gate')
def test_list_images_with_detail_filter_by_name(self):
# Detailed list of all images should contain the expected
# images filtered by name
@@ -188,7 +178,7 @@
self.assertFalse(any([i for i in images if i['id'] == self.image2_id]))
self.assertFalse(any([i for i in images if i['id'] == self.image3_id]))
- @attr(type='positive')
+ @attr(type='gate')
def test_list_images_with_detail_limit_results(self):
# Verify only the expected number of results (with full details)
# are returned
@@ -196,7 +186,7 @@
resp, images = self.client.list_images_with_detail(params)
self.assertEqual(1, len(images))
- @attr(type='positive')
+ @attr(type='gate')
def test_list_images_with_detail_filter_by_server_ref(self):
# Detailed list of servers should be filtered by server ref
server_links = self.server2['links']
@@ -213,7 +203,7 @@
self.assertTrue(any([i for i in images
if i['id'] == self.image3_id]))
- @attr(type='positive')
+ @attr(type='gate')
def test_list_images_with_detail_filter_by_type(self):
# The detailed list of servers should be filtered by image type
params = {'type': 'snapshot'}
@@ -225,7 +215,7 @@
self.assertTrue(any([i for i in images if i['id'] == self.image3_id]))
self.assertFalse(any([i for i in images if i['id'] == self.image_ref]))
- @attr(type='positive')
+ @attr(type='gate')
def test_list_images_with_detail_filter_by_changes_since(self):
# Verify an update image is returned
@@ -235,7 +225,7 @@
resp, images = self.client.list_images_with_detail(params)
self.assertTrue(any([i for i in images if i['id'] == self.image1_id]))
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_get_nonexistant_image(self):
# Negative test: GET on non existant image should fail
self.assertRaises(exceptions.NotFound, self.client.get_image, 999)
diff --git a/tempest/api/compute/keypairs/test_keypairs.py b/tempest/api/compute/keypairs/test_keypairs.py
index 15a23e5..6abca3f 100644
--- a/tempest/api/compute/keypairs/test_keypairs.py
+++ b/tempest/api/compute/keypairs/test_keypairs.py
@@ -29,7 +29,7 @@
super(KeyPairsTestJSON, cls).setUpClass()
cls.client = cls.keypairs_client
- @attr(type='positive')
+ @attr(type='gate')
def test_keypairs_create_list_delete(self):
# Keypairs created should be available in the response list
#Create 3 keypairs
@@ -63,7 +63,7 @@
resp, _ = self.client.delete_keypair(keypair['name'])
self.assertEqual(202, resp.status)
- @attr(type='positive')
+ @attr(type='gate')
def test_keypair_create_delete(self):
# Keypair should be created, verified and deleted
k_name = rand_name('keypair-')
@@ -79,7 +79,7 @@
resp, _ = self.client.delete_keypair(k_name)
self.assertEqual(202, resp.status)
- @attr(type='positive')
+ @attr(type='gate')
def test_get_keypair_detail(self):
# Keypair should be created, Got details by name and deleted
k_name = rand_name('keypair-')
@@ -102,7 +102,7 @@
resp, _ = self.client.delete_keypair(k_name)
self.assertEqual(202, resp.status)
- @attr(type='positive')
+ @attr(type='gate')
def test_keypair_create_with_pub_key(self):
# Keypair should be created with a given public key
k_name = rand_name('keypair-')
@@ -126,7 +126,7 @@
resp, _ = self.client.delete_keypair(k_name)
self.assertEqual(202, resp.status)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_keypair_create_with_invalid_pub_key(self):
# Keypair should not be created with a non RSA public key
k_name = rand_name('keypair-')
@@ -134,14 +134,14 @@
self.assertRaises(exceptions.BadRequest,
self.client.create_keypair, k_name, pub_key)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_keypair_delete_nonexistant_key(self):
# Non-existant key deletion should throw a proper error
k_name = rand_name("keypair-non-existant-")
self.assertRaises(exceptions.NotFound, self.client.delete_keypair,
k_name)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_create_keypair_with_empty_public_key(self):
# Keypair should not be created with an empty public key
k_name = rand_name("keypair-")
@@ -149,7 +149,7 @@
self.assertRaises(exceptions.BadRequest, self.client.create_keypair,
k_name, pub_key)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_create_keypair_when_public_key_bits_exceeds_maximum(self):
# Keypair should not be created when public key bits are too long
k_name = rand_name("keypair-")
@@ -157,7 +157,7 @@
self.assertRaises(exceptions.BadRequest, self.client.create_keypair,
k_name, pub_key)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_create_keypair_with_duplicate_name(self):
# Keypairs with duplicate names should not be created
k_name = rand_name('keypair-')
@@ -169,20 +169,20 @@
resp, _ = self.client.delete_keypair(k_name)
self.assertEqual(202, resp.status)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_create_keypair_with_empty_name_string(self):
# Keypairs with name being an empty string should not be created
self.assertRaises(exceptions.BadRequest, self.client.create_keypair,
'')
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_create_keypair_with_long_keynames(self):
# Keypairs with name longer than 255 chars should not be created
k_name = 'keypair-'.ljust(260, '0')
self.assertRaises(exceptions.BadRequest, self.client.create_keypair,
k_name)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_create_keypair_invalid_name(self):
# Keypairs with name being an invalid name should not be created
k_name = 'key_/.\@:'
diff --git a/tempest/api/compute/limits/test_absolute_limits.py b/tempest/api/compute/limits/test_absolute_limits.py
index b2c496b..beae122 100644
--- a/tempest/api/compute/limits/test_absolute_limits.py
+++ b/tempest/api/compute/limits/test_absolute_limits.py
@@ -29,6 +29,7 @@
cls.client = cls.limits_client
cls.server_client = cls.servers_client
+ @attr(type='gate')
def test_absLimits_get(self):
# To check if all limits are present in the response
resp, absolute_limits = self.client.get_absolute_limits()
@@ -48,7 +49,7 @@
"Failed to find element %s in absolute limits list"
% ', '.join(ele for ele in missing_elements))
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_max_image_meta_exceed_limit(self):
#We should not create vm with image meta over maxImageMeta limit
# Get max limit value
@@ -63,8 +64,9 @@
self.assertRaises(exceptions.OverLimit,
self.server_client.create_server,
- name='test', meta=meta_data, flavor_ref='84',
- image_ref='9e6a2e3b-1601-42a5-985f-c3a2f93a5ec3')
+ name='test', meta=meta_data,
+ flavor_ref=self.flavor_ref,
+ image_ref=self.image_ref)
class AbsoluteLimitsTestXML(AbsoluteLimitsTestJSON):
diff --git a/tempest/api/compute/security_groups/test_security_group_rules.py b/tempest/api/compute/security_groups/test_security_group_rules.py
index ad311d9..6a32b64 100644
--- a/tempest/api/compute/security_groups/test_security_group_rules.py
+++ b/tempest/api/compute/security_groups/test_security_group_rules.py
@@ -29,7 +29,7 @@
super(SecurityGroupRulesTestJSON, cls).setUpClass()
cls.client = cls.security_groups_client
- @attr(type='positive')
+ @attr(type='gate')
def test_security_group_rules_create(self):
# Positive test: Creation of Security Group rule
# should be successfull
@@ -52,7 +52,7 @@
self.addCleanup(self.client.delete_security_group_rule, rule['id'])
self.assertEqual(200, resp.status)
- @attr(type='positive')
+ @attr(type='gate')
def test_security_group_rules_create_with_optional_arguments(self):
# Positive test: Creation of Security Group rule
# with optional arguments
@@ -91,7 +91,7 @@
self.addCleanup(self.client.delete_security_group_rule, rule['id'])
self.assertEqual(200, resp.status)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_security_group_rules_create_with_invalid_id(self):
# Negative test: Creation of Security Group rule should FAIL
# with invalid Parent group id
@@ -104,7 +104,7 @@
self.client.create_security_group_rule,
parent_group_id, ip_protocol, from_port, to_port)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_security_group_rules_create_with_invalid_ip_protocol(self):
# Negative test: Creation of Security Group rule should FAIL
# with invalid ip_protocol
@@ -124,7 +124,7 @@
self.client.create_security_group_rule,
parent_group_id, ip_protocol, from_port, to_port)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_security_group_rules_create_with_invalid_from_port(self):
# Negative test: Creation of Security Group rule should FAIL
# with invalid from_port
@@ -143,7 +143,7 @@
self.client.create_security_group_rule,
parent_group_id, ip_protocol, from_port, to_port)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_security_group_rules_create_with_invalid_to_port(self):
# Negative test: Creation of Security Group rule should FAIL
# with invalid from_port
@@ -162,7 +162,7 @@
self.client.create_security_group_rule,
parent_group_id, ip_protocol, from_port, to_port)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_security_group_rules_create_with_invalid_port_range(self):
# Negative test: Creation of Security Group rule should FAIL
# with invalid port range.
@@ -181,7 +181,7 @@
self.client.create_security_group_rule,
secgroup_id, ip_protocol, from_port, to_port)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_security_group_rules_delete_with_invalid_id(self):
# Negative test: Deletion of Security Group rule should be FAIL
# with invalid rule id
@@ -189,7 +189,7 @@
self.client.delete_security_group_rule,
rand_name('999'))
- @attr(type='positive')
+ @attr(type='gate')
def test_security_group_rules_list(self):
# Positive test: Created Security Group rules should be
# in the list of all rules
diff --git a/tempest/api/compute/security_groups/test_security_groups.py b/tempest/api/compute/security_groups/test_security_groups.py
index 75afe0d..f960ca4 100644
--- a/tempest/api/compute/security_groups/test_security_groups.py
+++ b/tempest/api/compute/security_groups/test_security_groups.py
@@ -33,7 +33,7 @@
resp, _ = self.client.delete_security_group(securitygroup_id)
self.assertEqual(202, resp.status)
- @attr(type='positive')
+ @attr(type='gate')
def test_security_groups_create_list_delete(self):
# Positive test:Should return the list of Security Groups
#Create 3 Security Groups
@@ -61,7 +61,7 @@
#TODO(afazekas): scheduled for delete,
#test_security_group_create_get_delete covers it
- @attr(type='positive')
+ @attr(type='gate')
def test_security_group_create_delete(self):
# Security Group should be created, verified and deleted
s_name = rand_name('securitygroup-')
@@ -80,7 +80,7 @@
"The created Security Group name is "
"not equal to the requested name")
- @attr(type='positive')
+ @attr(type='gate')
def test_security_group_create_get_delete(self):
# Security Group should be created, fetched and deleted
s_name = rand_name('securitygroup-')
@@ -104,7 +104,7 @@
"The fetched Security Group is different "
"from the created Group")
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_security_group_get_nonexistant_group(self):
# Negative test:Should not be able to GET the details
# of nonexistant Security Group
@@ -120,7 +120,7 @@
self.assertRaises(exceptions.NotFound, self.client.get_security_group,
non_exist_id)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_security_group_create_with_invalid_group_name(self):
# Negative test: Security Group should not be created with group name
# as an empty string/with white spaces/chars more than 255
@@ -138,7 +138,7 @@
self.client.create_security_group, s_name,
s_description)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_security_group_create_with_invalid_group_description(self):
# Negative test:Security Group should not be created with description
# as an empty string/with white spaces/chars more than 255
@@ -155,7 +155,7 @@
self.client.create_security_group, s_name,
s_description)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_security_group_create_with_duplicate_name(self):
# Negative test:Security Group with duplicate name should not
# be created
@@ -172,7 +172,7 @@
self.client.create_security_group, s_name,
s_description)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_delete_the_default_security_group(self):
# Negative test:Deletion of the "default" Security Group should Fail
default_security_group_id = None
@@ -186,7 +186,7 @@
self.client.delete_security_group,
default_security_group_id)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_delete_nonexistant_security_group(self):
# Negative test:Deletion of a nonexistant Security Group should Fail
security_group_id = []
@@ -201,13 +201,14 @@
self.assertRaises(exceptions.NotFound,
self.client.delete_security_group, non_exist_id)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_delete_security_group_without_passing_id(self):
# Negative test:Deletion of a Security Group with out passing ID
# should Fail
self.assertRaises(exceptions.NotFound,
self.client.delete_security_group, '')
+ @attr(type='gate')
def test_server_security_groups(self):
# Checks that security groups may be added and linked to a server
# and not deleted if the server is active.
diff --git a/tempest/api/compute/servers/test_attach_interfaces.py b/tempest/api/compute/servers/test_attach_interfaces.py
index 8977cad..113ac78 100644
--- a/tempest/api/compute/servers/test_attach_interfaces.py
+++ b/tempest/api/compute/servers/test_attach_interfaces.py
@@ -14,6 +14,7 @@
# under the License.
from tempest.api.compute import base
+from tempest.test import attr
import time
@@ -92,6 +93,7 @@
self.assertEqual(sorted(list1), sorted(list2))
+ @attr(type='gate')
def test_create_list_show_delete_interfaces(self):
server, ifs = self._create_server_get_interfaces()
interface_count = len(ifs)
diff --git a/tempest/api/compute/servers/test_create_server.py b/tempest/api/compute/servers/test_create_server.py
index f06a0cc..e5fee4d 100644
--- a/tempest/api/compute/servers/test_create_server.py
+++ b/tempest/api/compute/servers/test_create_server.py
@@ -28,7 +28,6 @@
from tempest.test import attr
-@attr(type='smoke')
class ServersTestJSON(base.BaseComputeTest):
_interface = 'json'
run_ssh = tempest.config.TempestConfig().compute.run_ssh
@@ -92,15 +91,15 @@
found = any([i for i in servers if i['id'] == self.server['id']])
self.assertTrue(found)
- @attr(type='positive')
@testtools.skipIf(not run_ssh, 'Instance validation tests are disabled.')
+ @attr(type='gate')
def test_can_log_into_created_server(self):
# Check that the user can authenticate with the generated password
linux_client = RemoteClient(self.server, self.ssh_user, self.password)
self.assertTrue(linux_client.can_authenticate())
- @attr(type='positive')
@testtools.skipIf(not run_ssh, 'Instance validation tests are disabled.')
+ @attr(type='gate')
def test_verify_created_server_vcpus(self):
# Verify that the number of vcpus reported by the instance matches
# the amount stated by the flavor
@@ -108,15 +107,14 @@
linux_client = RemoteClient(self.server, self.ssh_user, self.password)
self.assertEqual(flavor['vcpus'], linux_client.get_number_of_vcpus())
- @attr(type='positive')
@testtools.skipIf(not run_ssh, 'Instance validation tests are disabled.')
+ @attr(type='gate')
def test_host_name_is_same_as_server_name(self):
# Verify the instance host name is the same as the server name
linux_client = RemoteClient(self.server, self.ssh_user, self.password)
self.assertTrue(linux_client.hostname_equals_servername(self.name))
-@attr(type='positive')
class ServersTestManualDisk(ServersTestJSON):
disk_config = 'MANUAL'
diff --git a/tempest/api/compute/servers/test_disk_config.py b/tempest/api/compute/servers/test_disk_config.py
index 64fb7d3..e9385b5 100644
--- a/tempest/api/compute/servers/test_disk_config.py
+++ b/tempest/api/compute/servers/test_disk_config.py
@@ -33,7 +33,7 @@
super(ServerDiskConfigTestJSON, cls).setUpClass()
cls.client = cls.os.servers_client
- @attr(type='positive')
+ @attr(type='gate')
def test_rebuild_server_with_manual_disk_config(self):
# A server should be rebuilt using the manual disk config option
resp, server = self.create_server(disk_config='AUTO',
@@ -57,7 +57,7 @@
#Delete the server
resp, body = self.client.delete_server(server['id'])
- @attr(type='positive')
+ @attr(type='gate')
def test_rebuild_server_with_auto_disk_config(self):
# A server should be rebuilt using the auto disk config option
resp, server = self.create_server(disk_config='MANUAL',
@@ -81,8 +81,8 @@
#Delete the server
resp, body = self.client.delete_server(server['id'])
- @attr(type='positive')
@testtools.skipUnless(compute.RESIZE_AVAILABLE, 'Resize not available.')
+ @attr(type='gate')
def test_resize_server_from_manual_to_auto(self):
# A server should be resized from manual to auto disk config
resp, server = self.create_server(disk_config='MANUAL',
@@ -101,8 +101,8 @@
#Delete the server
resp, body = self.client.delete_server(server['id'])
- @attr(type='positive')
@testtools.skipUnless(compute.RESIZE_AVAILABLE, 'Resize not available.')
+ @attr(type='gate')
def test_resize_server_from_auto_to_manual(self):
# A server should be resized from auto to manual disk config
resp, server = self.create_server(disk_config='AUTO',
diff --git a/tempest/api/compute/servers/test_instance_actions.py b/tempest/api/compute/servers/test_instance_actions.py
index 81fd26c..f13e51e 100644
--- a/tempest/api/compute/servers/test_instance_actions.py
+++ b/tempest/api/compute/servers/test_instance_actions.py
@@ -31,7 +31,7 @@
cls.request_id = resp['x-compute-request-id']
cls.server_id = server['id']
- @attr(type='positive')
+ @attr(type='gate')
def test_list_instance_actions(self):
# List actions of the provided server
resp, body = self.client.reboot(self.server_id, 'HARD')
@@ -43,7 +43,7 @@
self.assertTrue(any([i for i in body if i['action'] == 'create']))
self.assertTrue(any([i for i in body if i['action'] == 'reboot']))
- @attr(type='positive')
+ @attr(type='gate')
def test_get_instance_action(self):
# Get the action details of the provided server
resp, body = self.client.get_instance_action(self.server_id,
@@ -52,13 +52,13 @@
self.assertEqual(self.server_id, body['instance_uuid'])
self.assertEqual('create', body['action'])
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_list_instance_actions_invalid_server(self):
# List actions of the invalid server id
self.assertRaises(exceptions.NotFound,
self.client.list_instance_actions, 'server-999')
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_get_instance_action_invalid_request(self):
# Get the action details of the provided server with invalid request
self.assertRaises(exceptions.NotFound, self.client.get_instance_action,
diff --git a/tempest/api/compute/servers/test_list_server_filters.py b/tempest/api/compute/servers/test_list_server_filters.py
index 637a86f..31b44f7 100644
--- a/tempest/api/compute/servers/test_list_server_filters.py
+++ b/tempest/api/compute/servers/test_list_server_filters.py
@@ -84,7 +84,7 @@
super(ListServerFiltersTestJSON, cls).tearDownClass()
@utils.skip_unless_attr('multiple_images', 'Only one image found')
- @attr(type='positive')
+ @attr(type='gate')
def test_list_servers_filter_by_image(self):
# Filter the list of servers by image
params = {'image': self.image_ref}
@@ -95,7 +95,7 @@
self.assertNotIn(self.s2['id'], map(lambda x: x['id'], servers))
self.assertIn(self.s3['id'], map(lambda x: x['id'], servers))
- @attr(type='positive')
+ @attr(type='gate')
def test_list_servers_filter_by_flavor(self):
# Filter the list of servers by flavor
params = {'flavor': self.flavor_ref_alt}
@@ -106,7 +106,7 @@
self.assertNotIn(self.s2['id'], map(lambda x: x['id'], servers))
self.assertIn(self.s3['id'], map(lambda x: x['id'], servers))
- @attr(type='positive')
+ @attr(type='gate')
def test_list_servers_filter_by_server_name(self):
# Filter the list of servers by server name
params = {'name': self.s1_name}
@@ -117,7 +117,7 @@
self.assertNotIn(self.s2_name, map(lambda x: x['name'], servers))
self.assertNotIn(self.s3_name, map(lambda x: x['name'], servers))
- @attr(type='positive')
+ @attr(type='gate')
def test_list_servers_filter_by_server_status(self):
# Filter the list of servers by server status
params = {'status': 'active'}
@@ -128,7 +128,7 @@
self.assertIn(self.s2['id'], map(lambda x: x['id'], servers))
self.assertIn(self.s3['id'], map(lambda x: x['id'], servers))
- @attr(type='positive')
+ @attr(type='gate')
def test_list_servers_filter_by_limit(self):
# Verify only the expected number of servers are returned
params = {'limit': 1}
@@ -137,7 +137,7 @@
self.assertEqual(1, len([x for x in servers['servers'] if 'id' in x]))
@utils.skip_unless_attr('multiple_images', 'Only one image found')
- @attr(type='positive')
+ @attr(type='gate')
def test_list_servers_detailed_filter_by_image(self):
# Filter the detailed list of servers by image
params = {'image': self.image_ref}
@@ -148,7 +148,7 @@
self.assertNotIn(self.s2['id'], map(lambda x: x['id'], servers))
self.assertIn(self.s3['id'], map(lambda x: x['id'], servers))
- @attr(type='positive')
+ @attr(type='gate')
def test_list_servers_detailed_filter_by_flavor(self):
# Filter the detailed list of servers by flavor
params = {'flavor': self.flavor_ref_alt}
@@ -159,7 +159,7 @@
self.assertNotIn(self.s2['id'], map(lambda x: x['id'], servers))
self.assertIn(self.s3['id'], map(lambda x: x['id'], servers))
- @attr(type='positive')
+ @attr(type='gate')
def test_list_servers_detailed_filter_by_server_name(self):
# Filter the detailed list of servers by server name
params = {'name': self.s1_name}
@@ -170,7 +170,7 @@
self.assertNotIn(self.s2_name, map(lambda x: x['name'], servers))
self.assertNotIn(self.s3_name, map(lambda x: x['name'], servers))
- @attr(type='positive')
+ @attr(type='gate')
def test_list_servers_detailed_filter_by_server_status(self):
# Filter the detailed list of servers by server status
params = {'status': 'active'}
@@ -182,7 +182,7 @@
self.assertIn(self.s3['id'], map(lambda x: x['id'], servers))
self.assertEqual(['ACTIVE'] * 3, [x['status'] for x in servers])
- @attr(type='positive')
+ @attr(type='gate')
def test_list_servers_filtered_by_name_wildcard(self):
# List all servers that contains 'server' in name
params = {'name': 'server'}
@@ -205,7 +205,7 @@
self.assertNotIn(self.s3_name, map(lambda x: x['name'], servers))
@testtools.skip('Until Bug #1170718 is resolved.')
- @attr(type='positive')
+ @attr(type='gate')
def test_list_servers_filtered_by_ip(self):
# Filter servers by ip
# Here should be listed 1 server
@@ -218,7 +218,7 @@
self.assertNotIn(self.s2_name, map(lambda x: x['name'], servers))
self.assertNotIn(self.s3_name, map(lambda x: x['name'], servers))
- @attr(type='positive')
+ @attr(type='gate')
def test_list_servers_filtered_by_ip_regex(self):
# Filter servers by regex ip
# List all servers filtered by part of ip address.
@@ -232,7 +232,7 @@
self.assertIn(self.s2_name, map(lambda x: x['name'], servers))
self.assertIn(self.s3_name, map(lambda x: x['name'], servers))
- @attr(type='positive')
+ @attr(type='gate')
def test_list_servers_detailed_limit_results(self):
# Verify only the expected number of detailed results are returned
params = {'limit': 1}
diff --git a/tempest/api/compute/servers/test_list_servers_negative.py b/tempest/api/compute/servers/test_list_servers_negative.py
index 2c15c99..db9bdc1 100644
--- a/tempest/api/compute/servers/test_list_servers_negative.py
+++ b/tempest/api/compute/servers/test_list_servers_negative.py
@@ -15,11 +15,13 @@
# License for the specific language governing permissions and limitations
# under the License.
+import datetime
from tempest.api import compute
from tempest.api.compute import base
from tempest import clients
from tempest import exceptions
+from tempest.test import attr
class ListServersNegativeTestJSON(base.BaseComputeTest):
@@ -91,6 +93,7 @@
ignore_error=True)
cls.deleted_fixtures.append(srv)
+ @attr(type='gate')
def test_list_servers_with_a_deleted_server(self):
# Verify deleted servers do not show by default in list servers
# List servers and verify server not returned
@@ -102,6 +105,7 @@
self.assertEqual('200', resp['status'])
self.assertEqual([], actual)
+ @attr(type='gate')
def test_list_servers_by_non_existing_image(self):
# Listing servers for a non existing image returns empty list
non_existing_image = '1234abcd-zzz0-aaa9-ppp3-0987654abcde'
@@ -110,6 +114,7 @@
self.assertEqual('200', resp['status'])
self.assertEqual([], servers)
+ @attr(type='gate')
def test_list_servers_by_non_existing_flavor(self):
# Listing servers by non existing flavor returns empty list
non_existing_flavor = 1234
@@ -118,6 +123,7 @@
self.assertEqual('200', resp['status'])
self.assertEqual([], servers)
+ @attr(type='gate')
def test_list_servers_by_non_existing_server_name(self):
# Listing servers for a non existent server name returns empty list
non_existing_name = 'junk_server_1234'
@@ -126,6 +132,7 @@
self.assertEqual('200', resp['status'])
self.assertEqual([], servers)
+ @attr(type='gate')
def test_list_servers_status_non_existing(self):
# Return an empty list when invalid status is specified
non_existing_status = 'BALONEY'
@@ -134,6 +141,7 @@
self.assertEqual('200', resp['status'])
self.assertEqual([], servers)
+ @attr(type='gate')
def test_list_servers_by_limits(self):
# List servers by specifying limits
resp, body = self.client.list_servers({'limit': 1})
@@ -141,25 +149,30 @@
#when _interface='xml', one element for servers_links in servers
self.assertEqual(1, len([x for x in body['servers'] if 'id' in x]))
+ @attr(type='gate')
def test_list_servers_by_limits_greater_than_actual_count(self):
# List servers by specifying a greater value for limit
resp, body = self.client.list_servers({'limit': 100})
self.assertEqual('200', resp['status'])
self.assertEqual(len(self.existing_fixtures), len(body['servers']))
+ @attr(type='gate')
def test_list_servers_by_limits_pass_string(self):
# Return an error if a string value is passed for limit
self.assertRaises(exceptions.BadRequest, self.client.list_servers,
{'limit': 'testing'})
+ @attr(type='gate')
def test_list_servers_by_limits_pass_negative_value(self):
# Return an error if a negative value for limit is passed
self.assertRaises(exceptions.BadRequest, self.client.list_servers,
{'limit': -1})
+ @attr(type='gate')
def test_list_servers_by_changes_since(self):
# Servers are listed by specifying changes-since date
- changes_since = {'changes-since': '2011-01-01T12:34:00Z'}
+ since = datetime.datetime.utcnow() - datetime.timedelta(minutes=2)
+ changes_since = {'changes-since': since.isoformat()}
resp, body = self.client.list_servers(changes_since)
self.assertEqual('200', resp['status'])
# changes-since returns all instances, including deleted.
@@ -167,11 +180,13 @@
len(self.deleted_fixtures))
self.assertEqual(num_expected, len(body['servers']))
+ @attr(type='gate')
def test_list_servers_by_changes_since_invalid_date(self):
# Return an error when invalid date format is passed
self.assertRaises(exceptions.BadRequest, self.client.list_servers,
{'changes-since': '2011/01/01'})
+ @attr(type='gate')
def test_list_servers_by_changes_since_future_date(self):
# Return an empty list when a date in the future is passed
changes_since = {'changes-since': '2051-01-01T12:34:00Z'}
@@ -179,6 +194,7 @@
self.assertEqual('200', resp['status'])
self.assertEqual(0, len(body['servers']))
+ @attr(type='gate')
def test_list_servers_detail_server_is_deleted(self):
# Server details are not listed for a deleted server
deleted_ids = [s['id'] for s in self.deleted_fixtures]
diff --git a/tempest/api/compute/servers/test_multiple_create.py b/tempest/api/compute/servers/test_multiple_create.py
index 476a767..9fde618 100644
--- a/tempest/api/compute/servers/test_multiple_create.py
+++ b/tempest/api/compute/servers/test_multiple_create.py
@@ -25,16 +25,6 @@
_interface = 'json'
_name = 'multiple-create-test'
- def _get_created_servers(self, name):
- """Get servers created which name match with name param."""
- resp, body = self.servers_client.list_servers()
- servers = body['servers']
- servers_created = []
- for server in servers:
- if server['name'].startswith(name):
- servers_created.append(server)
- return servers_created
-
def _generate_name(self):
return rand_name(self._name)
@@ -45,22 +35,10 @@
"""
kwargs['name'] = kwargs.get('name', self._generate_name())
resp, body = self.create_server(**kwargs)
- created_servers = self._get_created_servers(kwargs['name'])
- # NOTE(maurosr): append it to cls.servers list from base.BaseCompute
- # class.
- self.servers.extend(created_servers)
- # NOTE(maurosr): get a server list, check status of the ones with names
- # that match and wait for them become active. At a first look, since
- # they are building in parallel, wait inside the for doesn't seem be
- # harmful to the performance
- if wait_until is not None:
- for server in created_servers:
- self.servers_client.wait_for_server_status(server['id'],
- wait_until)
return resp, body
- @attr(type='positive')
+ @attr(type='gate')
def test_multiple_create(self):
resp, body = self._create_multiple_servers(wait_until='ACTIVE',
min_count=1,
@@ -71,31 +49,31 @@
self.assertEqual('202', resp['status'])
self.assertFalse('reservation_id' in body)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_min_count_less_than_one(self):
invalid_min_count = 0
self.assertRaises(exceptions.BadRequest, self._create_multiple_servers,
min_count=invalid_min_count)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_min_count_non_integer(self):
invalid_min_count = 2.5
self.assertRaises(exceptions.BadRequest, self._create_multiple_servers,
min_count=invalid_min_count)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_max_count_less_than_one(self):
invalid_max_count = 0
self.assertRaises(exceptions.BadRequest, self._create_multiple_servers,
max_count=invalid_max_count)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_max_count_non_integer(self):
invalid_max_count = 2.5
self.assertRaises(exceptions.BadRequest, self._create_multiple_servers,
max_count=invalid_max_count)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_max_count_less_than_min_count(self):
min_count = 3
max_count = 2
@@ -103,7 +81,7 @@
min_count=min_count,
max_count=max_count)
- @attr(type='positive')
+ @attr(type='gate')
def test_multiple_create_with_reservation_return(self):
resp, body = self._create_multiple_servers(wait_until='ACTIVE',
min_count=1,
diff --git a/tempest/api/compute/servers/test_server_actions.py b/tempest/api/compute/servers/test_server_actions.py
index e8f41ec..9f97f4f 100644
--- a/tempest/api/compute/servers/test_server_actions.py
+++ b/tempest/api/compute/servers/test_server_actions.py
@@ -51,9 +51,9 @@
cls.client = cls.servers_client
cls.rebuild_servers()
- @attr(type='smoke')
@testtools.skipUnless(compute.CHANGE_PASSWORD_AVAILABLE,
'Change password not available.')
+ @attr(type='gate')
def test_change_server_password(self):
# The server's password should be set to the provided password
new_password = 'Newpass1234'
@@ -86,8 +86,8 @@
new_boot_time = linux_client.get_boot_time()
self.assertGreater(new_boot_time, boot_time)
- @attr(type='smoke')
@testtools.skip('Until Bug #1014647 is dealt with.')
+ @attr(type='smoke')
def test_reboot_server_soft(self):
# The server should be signaled to reboot gracefully
if self.run_ssh:
@@ -147,8 +147,8 @@
if int(current_flavor) == self.flavor_ref else self.flavor_ref
return int(current_flavor), int(new_flavor_ref)
- @attr(type='smoke')
@testtools.skipIf(not resize_available, 'Resize not available.')
+ @attr(type='smoke')
def test_resize_server_confirm(self):
# The server's RAM and disk space should be modified to that of
# the provided flavor
@@ -166,8 +166,8 @@
resp, server = self.client.get_server(self.server_id)
self.assertEqual(new_flavor_ref, int(server['flavor']['id']))
- @attr(type='positive')
@testtools.skipIf(not resize_available, 'Resize not available.')
+ @attr(type='gate')
def test_resize_server_revert(self):
# The server's RAM and disk space should return to its original
# values after a resize is reverted
@@ -195,13 +195,13 @@
required time (%s s).' % (self.server_id, self.build_timeout)
raise exceptions.TimeoutException(message)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_reboot_nonexistent_server_soft(self):
# Negative Test: The server reboot on non existent server should return
# an error
self.assertRaises(exceptions.NotFound, self.client.reboot, 999, 'SOFT')
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_rebuild_nonexistent_server(self):
# Negative test: The server rebuild for a non existing server
# should not be allowed
@@ -217,7 +217,7 @@
personality=personality,
adminPass='rebuild')
- @attr(type='positive')
+ @attr(type='gate')
def test_get_console_output(self):
# Positive test:Should be able to GET the console output
# for a given server_id and number of lines
@@ -230,7 +230,7 @@
self.assertEqual(lines, 10)
self.wait_for(get_output)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_get_console_output_invalid_server_id(self):
# Negative test: Should not be able to get the console output
# for an invalid server_id
@@ -238,8 +238,8 @@
self.servers_client.get_console_output,
'!@#$%^&*()', 10)
- @attr(type='positive')
@testtools.skip('Until tempest Bug #1014683 is fixed.')
+ @attr(type='gate')
def test_get_console_output_server_id_in_reboot_status(self):
# Positive test:Should be able to GET the console output
# for a given server_id in reboot status
diff --git a/tempest/api/compute/servers/test_server_addresses.py b/tempest/api/compute/servers/test_server_addresses.py
index 7cb78e9..3cd0a91 100644
--- a/tempest/api/compute/servers/test_server_addresses.py
+++ b/tempest/api/compute/servers/test_server_addresses.py
@@ -30,13 +30,13 @@
resp, cls.server = cls.create_server(wait_until='ACTIVE')
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_list_server_addresses_invalid_server_id(self):
# List addresses request should fail if server id not in system
self.assertRaises(exceptions.NotFound, self.client.list_addresses,
'999')
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_list_server_addresses_by_network_neg(self):
# List addresses by network should fail if network name not valid
self.assertRaises(exceptions.NotFound,
diff --git a/tempest/api/compute/servers/test_server_metadata.py b/tempest/api/compute/servers/test_server_metadata.py
index 664a0c0..442d30c 100644
--- a/tempest/api/compute/servers/test_server_metadata.py
+++ b/tempest/api/compute/servers/test_server_metadata.py
@@ -42,6 +42,7 @@
resp, _ = self.client.set_server_metadata(self.server_id, meta)
self.assertEqual(resp.status, 200)
+ @attr(type='gate')
def test_list_server_metadata(self):
# All metadata key/value pairs for a server should be returned
resp, resp_metadata = self.client.list_server_metadata(self.server_id)
@@ -51,6 +52,7 @@
expected = {'key1': 'value1', 'key2': 'value2'}
self.assertEqual(expected, resp_metadata)
+ @attr(type='gate')
def test_set_server_metadata(self):
# The server's metadata should be replaced with the provided values
#Create a new set of metadata for the server
@@ -64,6 +66,7 @@
resp, resp_metadata = self.client.list_server_metadata(self.server_id)
self.assertEqual(resp_metadata, req_metadata)
+ @attr(type='gate')
def test_server_create_metadata_key_too_long(self):
# Attempt to start a server with a meta-data key that is > 255
# characters
@@ -78,7 +81,7 @@
# no teardown - all creates should fail
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_create_metadata_key_error(self):
# Blank key should trigger an error.
meta = {'': 'data1'}
@@ -86,6 +89,7 @@
self.create_server,
meta=meta)
+ @attr(type='gate')
def test_update_server_metadata(self):
# The server's metadata values should be updated to the
# provided values
@@ -99,6 +103,7 @@
expected = {'key1': 'alt1', 'key2': 'value2', 'key3': 'value3'}
self.assertEqual(expected, resp_metadata)
+ @attr(type='gate')
def test_update_metadata_empty_body(self):
# The original metadata should not be lost if empty metadata body is
# passed
@@ -108,12 +113,14 @@
expected = {'key1': 'value1', 'key2': 'value2'}
self.assertEqual(expected, resp_metadata)
+ @attr(type='gate')
def test_get_server_metadata_item(self):
# The value for a specic metadata key should be returned
resp, meta = self.client.get_server_metadata_item(self.server_id,
'key2')
self.assertTrue('value2', meta['key2'])
+ @attr(type='gate')
def test_set_server_metadata_item(self):
# The item's value should be updated to the provided value
#Update the metadata value
@@ -127,6 +134,7 @@
expected = {'key1': 'value1', 'key2': 'value2', 'nova': 'alt'}
self.assertEqual(expected, resp_metadata)
+ @attr(type='gate')
def test_delete_server_metadata_item(self):
# The metadata value/key pair should be deleted from the server
resp, meta = self.client.delete_server_metadata_item(self.server_id,
@@ -138,20 +146,20 @@
expected = {'key2': 'value2'}
self.assertEqual(expected, resp_metadata)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_get_nonexistant_server_metadata_item(self):
# Negative test: GET on nonexistant server should not succeed
self.assertRaises(exceptions.NotFound,
self.client.get_server_metadata_item, 999, 'test2')
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_list_nonexistant_server_metadata(self):
# Negative test:List metadata on a non existant server should
# not succeed
self.assertRaises(exceptions.NotFound,
self.client.list_server_metadata, 999)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_set_server_metadata_item_incorrect_uri_key(self):
# Raise BadRequest if key in uri does not match
# the key passed in body.
@@ -161,7 +169,7 @@
self.client.set_server_metadata_item,
self.server_id, 'key', meta)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_set_nonexistant_server_metadata(self):
# Negative test: Set metadata on a non existant server should not
# succeed
@@ -169,14 +177,14 @@
self.assertRaises(exceptions.NotFound,
self.client.set_server_metadata, 999, meta)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_update_nonexistant_server_metadata(self):
# Negative test: An update should not happen for a nonexistant image
meta = {'key1': 'value1', 'key2': 'value2'}
self.assertRaises(exceptions.NotFound,
self.client.update_server_metadata, 999, meta)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_update_metadata_key_error(self):
# Blank key should trigger an error.
meta = {'': 'data1'}
@@ -184,7 +192,7 @@
self.client.update_server_metadata,
self.server_id, meta=meta)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_delete_nonexistant_server_metadata_item(self):
# Negative test: Should not be able to delete metadata item from a
# nonexistant server
@@ -193,7 +201,7 @@
self.assertRaises(exceptions.NotFound,
self.client.delete_server_metadata_item, 999, 'd')
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_set_server_metadata_too_long(self):
# Raise a 413 OverLimit exception while exceeding metadata items limit
# for tenant.
@@ -206,7 +214,7 @@
self.client.set_server_metadata,
self.server_id, req_metadata)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_update_server_metadata_too_long(self):
# Raise a 413 OverLimit exception while exceeding metadata items limit
# for tenant.
@@ -219,7 +227,7 @@
self.client.update_server_metadata,
self.server_id, req_metadata)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_update_all_metadata_field_error(self):
# Raise a bad request error for blank key.
# set_server_metadata will replace all metadata with new value
diff --git a/tempest/api/compute/servers/test_server_personality.py b/tempest/api/compute/servers/test_server_personality.py
index b0ee9e7..a3ec423 100644
--- a/tempest/api/compute/servers/test_server_personality.py
+++ b/tempest/api/compute/servers/test_server_personality.py
@@ -31,6 +31,7 @@
cls.client = cls.servers_client
cls.user_client = cls.limits_client
+ @attr(type='gate')
def test_personality_files_exceed_limit(self):
# Server creation should fail if greater than the maximum allowed
# number of files are injected into the server.
@@ -45,7 +46,7 @@
self.assertRaises(exceptions.OverLimit, self.create_server,
personality=personality)
- @attr(type='positive')
+ @attr(type='gate')
def test_can_create_server_with_max_number_personality_files(self):
# Server should be created successfully if maximum allowed number of
# files is injected into the server during creation.
diff --git a/tempest/api/compute/servers/test_server_rescue.py b/tempest/api/compute/servers/test_server_rescue.py
index a7410ec..8225a4c 100644
--- a/tempest/api/compute/servers/test_server_rescue.py
+++ b/tempest/api/compute/servers/test_server_rescue.py
@@ -120,19 +120,19 @@
self.assertEqual(202, resp.status)
self.servers_client.wait_for_server_status(self.server_id, 'ACTIVE')
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_rescued_vm_reboot(self):
self.assertRaises(exceptions.Duplicate, self.servers_client.reboot,
self.rescue_id, 'HARD')
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_rescued_vm_rebuild(self):
self.assertRaises(exceptions.Duplicate,
self.servers_client.rebuild,
self.rescue_id,
self.image_ref_alt)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_rescued_vm_attach_volume(self):
# Rescue the server
self.servers_client.rescue_server(self.server_id, self.password)
@@ -146,7 +146,7 @@
self.volume_to_attach['id'],
device='/dev/%s' % self.device)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_rescued_vm_detach_volume(self):
# Attach the volume to the server
self.servers_client.attach_volume(self.server_id,
@@ -169,7 +169,7 @@
self.server_id,
self.volume_to_detach['id'])
- @attr(type='positive')
+ @attr(type='gate')
def test_rescued_vm_associate_dissociate_floating_ip(self):
# Rescue the server
self.servers_client.rescue_server(
@@ -189,7 +189,7 @@
self.server_id)
self.assertEqual(202, resp.status)
- @attr(type='positive')
+ @attr(type='gate')
def test_rescued_vm_add_remove_security_group(self):
# Rescue the server
self.servers_client.rescue_server(
diff --git a/tempest/api/compute/servers/test_servers.py b/tempest/api/compute/servers/test_servers.py
index f86ac07..3ff2538 100644
--- a/tempest/api/compute/servers/test_servers.py
+++ b/tempest/api/compute/servers/test_servers.py
@@ -32,7 +32,7 @@
self.clear_servers()
super(ServersTestJSON, self).tearDown()
- @attr(type='positive')
+ @attr(type='gate')
def test_create_server_with_admin_password(self):
# If an admin password is provided on server creation, the server's
# root password should be set to that password.
@@ -41,6 +41,7 @@
# Verify the password is set correctly in the response
self.assertEqual('testpassword', server['adminPass'])
+ @attr(type='gate')
def test_create_with_existing_server_name(self):
# Creating a server with a name that already exists is allowed
@@ -59,7 +60,7 @@
name2 = server['name']
self.assertEqual(name1, name2)
- @attr(type='positive')
+ @attr(type='gate')
def test_create_specify_keypair(self):
# Specify a keypair while creating a server
@@ -72,7 +73,7 @@
resp, server = self.client.get_server(server['id'])
self.assertEqual(key_name, server['key_name'])
- @attr(type='positive')
+ @attr(type='gate')
def test_update_server_name(self):
# The server name should be changed to the the provided value
resp, server = self.create_server(wait_until='ACTIVE')
@@ -87,7 +88,7 @@
resp, server = self.client.get_server(server['id'])
self.assertEqual('newname', server['name'])
- @attr(type='positive')
+ @attr(type='gate')
def test_update_access_server_address(self):
# The server's access addresses should reflect the provided values
resp, server = self.create_server(wait_until='ACTIVE')
@@ -104,6 +105,7 @@
self.assertEqual('1.1.1.1', server['accessIPv4'])
self.assertEqual('::babe:202:202', server['accessIPv6'])
+ @attr(type='gate')
def test_delete_server_while_in_building_state(self):
# Delete a server while it's VM state is Building
resp, server = self.create_server(wait_until='BUILD')
diff --git a/tempest/api/compute/servers/test_servers_negative.py b/tempest/api/compute/servers/test_servers_negative.py
index b369179..5f53080 100644
--- a/tempest/api/compute/servers/test_servers_negative.py
+++ b/tempest/api/compute/servers/test_servers_negative.py
@@ -35,7 +35,7 @@
cls.alt_os = clients.AltManager()
cls.alt_client = cls.alt_os.servers_client
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_server_name_blank(self):
# Create a server with name parameter empty
@@ -43,7 +43,7 @@
self.create_server,
name='')
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_personality_file_contents_not_encoded(self):
# Use an unencoded file when creating a server with personality
@@ -55,7 +55,7 @@
self.create_server,
personality=person)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_create_with_invalid_image(self):
# Create a server with an unknown image
@@ -63,7 +63,7 @@
self.create_server,
image_id=-1)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_create_with_invalid_flavor(self):
# Create a server with an unknown flavor
@@ -71,7 +71,7 @@
self.create_server,
flavor=-1,)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_invalid_access_ip_v4_address(self):
# An access IPv4 address must match a valid address pattern
@@ -79,7 +79,7 @@
self.assertRaises(exceptions.BadRequest,
self.create_server, accessIPv4=IPv4)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_invalid_ip_v6_address(self):
# An access IPv6 address must match a valid address pattern
@@ -88,7 +88,7 @@
self.assertRaises(exceptions.BadRequest,
self.create_server, accessIPv6=IPv6)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_reboot_deleted_server(self):
# Reboot a deleted server
resp, server = self.create_server()
@@ -98,7 +98,7 @@
self.assertRaises(exceptions.NotFound, self.client.reboot,
self.server_id, 'SOFT')
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_rebuild_deleted_server(self):
# Rebuild a deleted server
@@ -111,7 +111,7 @@
self.client.rebuild,
self.server_id, self.image_ref_alt)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_create_numeric_server_name(self):
# Create a server with a numeric name
if self.__class__._interface == "xml":
@@ -122,7 +122,7 @@
self.create_server,
name=server_name)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_create_server_name_length_exceeds_256(self):
# Create a server with name length exceeding 256 characters
@@ -131,7 +131,7 @@
self.create_server,
name=server_name)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_create_with_invalid_network_uuid(self):
# Pass invalid network uuid while creating a server
@@ -141,7 +141,7 @@
self.create_server,
networks=networks)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_create_with_non_existant_keypair(self):
# Pass a non existant keypair while creating a server
@@ -150,7 +150,7 @@
self.create_server,
key_name=key_name)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_create_server_metadata_exceeds_length_limit(self):
# Pass really long metadata while creating a server
@@ -159,7 +159,7 @@
self.create_server,
meta=metadata)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_update_name_of_non_existent_server(self):
# Update name of a non-existent server
@@ -169,7 +169,7 @@
self.assertRaises(exceptions.NotFound, self.client.update_server,
server_name, name=new_name)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_update_server_set_empty_name(self):
# Update name of the server to an empty string
@@ -179,7 +179,7 @@
self.assertRaises(exceptions.BadRequest, self.client.update_server,
server_name, name=new_name)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_update_server_of_another_tenant(self):
# Update name of a server that belongs to another tenant
@@ -189,7 +189,7 @@
self.alt_client.update_server, server['id'],
name=new_name)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_update_server_name_length_exceeds_256(self):
# Update name of server exceed the name length limit
@@ -200,14 +200,14 @@
server['id'],
name=new_name)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_delete_non_existent_server(self):
# Delete a non existent server
self.assertRaises(exceptions.NotFound, self.client.delete_server,
'999erra43')
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_delete_a_server_of_another_tenant(self):
# Delete a server that belongs to another tenant
try:
@@ -218,20 +218,20 @@
finally:
self.client.delete_server(server['id'])
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_delete_server_pass_negative_id(self):
# Pass an invalid string parameter to delete server
self.assertRaises(exceptions.NotFound, self.client.delete_server, -1)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_delete_server_pass_id_exceeding_length_limit(self):
# Pass a server ID that exceeds length limit to delete server
self.assertRaises(exceptions.NotFound, self.client.delete_server,
sys.maxint + 1)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_create_with_nonexistent_security_group(self):
# Create a server with a nonexistent security group
@@ -240,7 +240,7 @@
self.create_server,
security_groups=security_groups)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_get_non_existent_server(self):
# Get a non existent server details
diff --git a/tempest/api/compute/servers/test_virtual_interfaces.py b/tempest/api/compute/servers/test_virtual_interfaces.py
index bdec1cb..3119643 100644
--- a/tempest/api/compute/servers/test_virtual_interfaces.py
+++ b/tempest/api/compute/servers/test_virtual_interfaces.py
@@ -23,7 +23,6 @@
from tempest.test import attr
-@attr(type='smoke')
class VirtualInterfacesTestJSON(base.BaseComputeTest):
_interface = 'json'
@@ -34,7 +33,7 @@
resp, server = cls.create_server(wait_until='ACTIVE')
cls.server_id = server['id']
- @attr(type='positive')
+ @attr(type='gate')
def test_list_virtual_interfaces(self):
# Positive test:Should be able to GET the virtual interfaces list
# for a given server_id
@@ -49,7 +48,7 @@
self.assertTrue(netaddr.valid_mac(mac_address),
"Invalid mac address detected.")
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_list_virtual_interfaces_invalid_server_id(self):
# Negative test: Should not be able to GET virtual interfaces
# for an invalid server_id
@@ -59,6 +58,5 @@
invalid_server_id)
-@attr(type='smoke')
class VirtualInterfacesTestXML(VirtualInterfacesTestJSON):
_interface = 'xml'
diff --git a/tempest/api/compute/test_auth_token.py b/tempest/api/compute/test_auth_token.py
new file mode 100644
index 0000000..bbe92ef
--- /dev/null
+++ b/tempest/api/compute/test_auth_token.py
@@ -0,0 +1,52 @@
+# Copyright 2013 IBM Corp
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+
+import testtools
+
+from tempest.api.compute import base
+import tempest.config as config
+
+
+class AuthTokenTestJSON(base.BaseComputeTest):
+ _interface = 'json'
+
+ @classmethod
+ def setUpClass(cls):
+ super(AuthTokenTestJSON, cls).setUpClass()
+
+ cls.servers_v2 = cls.os.servers_client
+ cls.servers_v3 = cls.os.servers_client_v3_auth
+
+ def test_v2_token(self):
+ # Can get a token using v2 of the identity API and use that to perform
+ # an operation on the compute service.
+
+ # Doesn't matter which compute API is used,
+ # picking list_servers because it's easy.
+ self.servers_v2.list_servers()
+
+ @testtools.skipIf(not config.TempestConfig().identity.uri_v3,
+ 'v3 auth client not configured')
+ def test_v3_token(self):
+ # Can get a token using v3 of the identity API and use that to perform
+ # an operation on the compute service.
+
+ # Doesn't matter which compute API is used,
+ # picking list_servers because it's easy.
+ self.servers_v3.list_servers()
+
+
+class AuthTokenTestXML(AuthTokenTestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/compute/test_authorization.py b/tempest/api/compute/test_authorization.py
index b0d6a0b..1a65a20 100644
--- a/tempest/api/compute/test_authorization.py
+++ b/tempest/api/compute/test_authorization.py
@@ -21,6 +21,7 @@
from tempest.common.utils.data_utils import parse_image_id
from tempest.common.utils.data_utils import rand_name
from tempest import exceptions
+from tempest.test import attr
class AuthorizationTestJSON(base.BaseComputeTest):
@@ -89,26 +90,31 @@
cls.security_client.delete_security_group(cls.security_group['id'])
super(AuthorizationTestJSON, cls).tearDownClass()
+ @attr(type='gate')
def test_get_server_for_alt_account_fails(self):
# A GET request for a server on another user's account should fail
self.assertRaises(exceptions.NotFound, self.alt_client.get_server,
self.server['id'])
+ @attr(type='gate')
def test_delete_server_for_alt_account_fails(self):
# A DELETE request for another user's server should fail
self.assertRaises(exceptions.NotFound, self.alt_client.delete_server,
self.server['id'])
+ @attr(type='gate')
def test_update_server_for_alt_account_fails(self):
# An update server request for another user's server should fail
self.assertRaises(exceptions.NotFound, self.alt_client.update_server,
self.server['id'], name='test')
+ @attr(type='gate')
def test_list_server_addresses_for_alt_account_fails(self):
# A list addresses request for another user's server should fail
self.assertRaises(exceptions.NotFound, self.alt_client.list_addresses,
self.server['id'])
+ @attr(type='gate')
def test_list_server_addresses_by_network_for_alt_account_fails(self):
# A list address/network request for another user's server should fail
server_id = self.server['id']
@@ -116,6 +122,7 @@
self.alt_client.list_addresses_by_network, server_id,
'public')
+ @attr(type='gate')
def test_list_servers_with_alternate_tenant(self):
# A list on servers from one tenant should not
# show on alternate tenant
@@ -125,37 +132,44 @@
alt_server_ids = [s['id'] for s in body['servers']]
self.assertNotIn(self.server['id'], alt_server_ids)
+ @attr(type='gate')
def test_change_password_for_alt_account_fails(self):
# A change password request for another user's server should fail
self.assertRaises(exceptions.NotFound, self.alt_client.change_password,
self.server['id'], 'newpass')
+ @attr(type='gate')
def test_reboot_server_for_alt_account_fails(self):
# A reboot request for another user's server should fail
self.assertRaises(exceptions.NotFound, self.alt_client.reboot,
self.server['id'], 'HARD')
+ @attr(type='gate')
def test_rebuild_server_for_alt_account_fails(self):
# A rebuild request for another user's server should fail
self.assertRaises(exceptions.NotFound, self.alt_client.rebuild,
self.server['id'], self.image_ref_alt)
+ @attr(type='gate')
def test_resize_server_for_alt_account_fails(self):
# A resize request for another user's server should fail
self.assertRaises(exceptions.NotFound, self.alt_client.resize,
self.server['id'], self.flavor_ref_alt)
+ @attr(type='gate')
def test_create_image_for_alt_account_fails(self):
# A create image request for another user's server should fail
self.assertRaises(exceptions.NotFound,
self.alt_images_client.create_image,
self.server['id'], 'testImage')
+ @attr(type='gate')
def test_create_server_with_unauthorized_image(self):
# Server creation with another user's image should fail
self.assertRaises(exceptions.BadRequest, self.alt_client.create_server,
'test', self.image['id'], self.flavor_ref)
+ @attr(type='gate')
def test_create_server_fails_when_tenant_incorrect(self):
# A create server request should fail if the tenant id does not match
# the current user
@@ -170,6 +184,7 @@
# Reset the base_url...
self.alt_client.base_url = saved_base_url
+ @attr(type='gate')
def test_create_keypair_in_analt_user_tenant(self):
# A create keypair request should fail if the tenant id does not match
# the current user
@@ -192,29 +207,34 @@
self.fail("Create keypair request should not happen "
"if the tenant id does not match the current user")
+ @attr(type='gate')
def test_get_keypair_of_alt_account_fails(self):
# A GET request for another user's keypair should fail
self.assertRaises(exceptions.NotFound,
self.alt_keypairs_client.get_keypair,
self.keypairname)
+ @attr(type='gate')
def test_delete_keypair_of_alt_account_fails(self):
# A DELETE request for another user's keypair should fail
self.assertRaises(exceptions.NotFound,
self.alt_keypairs_client.delete_keypair,
self.keypairname)
+ @attr(type='gate')
def test_get_image_for_alt_account_fails(self):
# A GET request for an image on another user's account should fail
self.assertRaises(exceptions.NotFound,
self.alt_images_client.get_image, self.image['id'])
+ @attr(type='gate')
def test_delete_image_for_alt_account_fails(self):
# A DELETE request for another user's image should fail
self.assertRaises(exceptions.NotFound,
self.alt_images_client.delete_image,
self.image['id'])
+ @attr(type='gate')
def test_create_security_group_in_analt_user_tenant(self):
# A create security group request should fail if the tenant id does not
# match the current user
@@ -238,18 +258,21 @@
self.fail("Create Security Group request should not happen if"
"the tenant id does not match the current user")
+ @attr(type='gate')
def test_get_security_group_of_alt_account_fails(self):
# A GET request for another user's security group should fail
self.assertRaises(exceptions.NotFound,
self.alt_security_client.get_security_group,
self.security_group['id'])
+ @attr(type='gate')
def test_delete_security_group_of_alt_account_fails(self):
# A DELETE request for another user's security group should fail
self.assertRaises(exceptions.NotFound,
self.alt_security_client.delete_security_group,
self.security_group['id'])
+ @attr(type='gate')
def test_create_security_group_rule_in_analt_user_tenant(self):
# A create security group rule request should fail if the tenant id
# does not match the current user
@@ -278,6 +301,7 @@
"happen if the tenant id does not match the"
" current user")
+ @attr(type='gate')
def test_delete_security_group_rule_of_alt_account_fails(self):
# A DELETE request for another user's security group rule
# should fail
@@ -285,6 +309,7 @@
self.alt_security_client.delete_security_group_rule,
self.rule['id'])
+ @attr(type='gate')
def test_set_metadata_of_alt_account_server_fails(self):
# A set metadata for another user's server should fail
req_metadata = {'meta1': 'data1', 'meta2': 'data2'}
@@ -293,6 +318,7 @@
self.server['id'],
req_metadata)
+ @attr(type='gate')
def test_set_metadata_of_alt_account_image_fails(self):
# A set metadata for another user's image should fail
req_metadata = {'meta1': 'value1', 'meta2': 'value2'}
@@ -300,6 +326,7 @@
self.alt_images_client.set_image_metadata,
self.image['id'], req_metadata)
+ @attr(type='gate')
def test_get_metadata_of_alt_account_server_fails(self):
# A get metadata for another user's server should fail
req_metadata = {'meta1': 'data1'}
@@ -310,6 +337,7 @@
self.alt_client.get_server_metadata_item,
self.server['id'], 'meta1')
+ @attr(type='gate')
def test_get_metadata_of_alt_account_image_fails(self):
# A get metadata for another user's image should fail
req_metadata = {'meta1': 'value1'}
@@ -321,6 +349,7 @@
self.alt_images_client.get_image_metadata_item,
self.image['id'], 'meta1')
+ @attr(type='gate')
def test_delete_metadata_of_alt_account_server_fails(self):
# A delete metadata for another user's server should fail
req_metadata = {'meta1': 'data1'}
@@ -331,6 +360,7 @@
self.alt_client.delete_server_metadata_item,
self.server['id'], 'meta1')
+ @attr(type='gate')
def test_delete_metadata_of_alt_account_image_fails(self):
# A delete metadata for another user's image should fail
req_metadata = {'meta1': 'data1'}
@@ -342,6 +372,7 @@
self.alt_images_client.delete_image_metadata_item,
self.image['id'], 'meta1')
+ @attr(type='gate')
def test_get_console_output_of_alt_account_server_fails(self):
# A Get Console Output for another user's server should fail
self.assertRaises(exceptions.NotFound,
diff --git a/tempest/api/compute/test_extensions.py b/tempest/api/compute/test_extensions.py
index 5bc4a30..291c8e4 100644
--- a/tempest/api/compute/test_extensions.py
+++ b/tempest/api/compute/test_extensions.py
@@ -23,7 +23,7 @@
class ExtensionsTestJSON(base.BaseComputeTest):
_interface = 'json'
- @attr(type='positive')
+ @attr(type='gate')
def test_list_extensions(self):
# List of all extensions
resp, extensions = self.extensions_client.list_extensions()
diff --git a/tempest/api/compute/test_live_block_migration.py b/tempest/api/compute/test_live_block_migration.py
index 15ca129..84fd653 100644
--- a/tempest/api/compute/test_live_block_migration.py
+++ b/tempest/api/compute/test_live_block_migration.py
@@ -97,9 +97,9 @@
self.volumes_client.wait_for_volume_status(volume_id, 'available')
self.volumes_client.delete_volume(volume_id)
- @attr(type='positive')
@testtools.skipIf(not CONF.compute.live_migration_available,
'Live migration not available')
+ @attr(type='gate')
def test_live_block_migration(self):
# Live block migrate an instance to another host
if len(self._get_compute_hostnames()) < 2:
@@ -114,6 +114,7 @@
@testtools.skipIf(not CONF.compute.live_migration_available,
'Live migration not available')
+ @attr(type='gate')
def test_invalid_host_for_migration(self):
# Migrating to an invalid host should not change the status
server_id = self._get_an_active_server()
@@ -123,12 +124,12 @@
server_id, target_host)
self.assertEquals('ACTIVE', self._get_server_status(server_id))
- @attr(type='positive')
@testtools.skipIf(not CONF.compute.live_migration_available or
not CONF.compute.use_block_migration_for_live_migration,
'Block Live migration not available')
@testtools.skipIf(not CONF.compute.block_migrate_supports_cinder_iscsi,
'Block Live migration not configured for iSCSI')
+ @attr(type='gate')
def test_iscsi_volume(self):
# Live block migrate an instance to another host
if len(self._get_compute_hostnames()) < 2:
diff --git a/tempest/api/compute/volumes/test_attach_volume.py b/tempest/api/compute/volumes/test_attach_volume.py
index a086c17..b507e03 100644
--- a/tempest/api/compute/volumes/test_attach_volume.py
+++ b/tempest/api/compute/volumes/test_attach_volume.py
@@ -69,8 +69,8 @@
self.attached = True
- @attr(type='positive')
@testtools.skipIf(not run_ssh, 'SSH required for this test')
+ @attr(type='gate')
def test_attach_detach_volume(self):
# Stop and Start a server with an attached volume, ensuring that
# the volume remains attached.
diff --git a/tempest/api/compute/volumes/test_volumes_get.py b/tempest/api/compute/volumes/test_volumes_get.py
index 4646ae2..1acc57d 100644
--- a/tempest/api/compute/volumes/test_volumes_get.py
+++ b/tempest/api/compute/volumes/test_volumes_get.py
@@ -33,72 +33,68 @@
def test_volume_create_get_delete(self):
# CREATE, GET, DELETE Volume
volume = None
- try:
- v_name = rand_name('Volume-%s-') % self._interface
- metadata = {'Type': 'work'}
- #Create volume
- resp, volume = self.client.create_volume(size=1,
- display_name=v_name,
- metadata=metadata)
- self.assertEqual(200, resp.status)
- self.assertTrue('id' in volume)
- self.assertTrue('displayName' in volume)
- self.assertEqual(volume['displayName'], v_name,
- "The created volume name is not equal "
- "to the requested name")
- self.assertTrue(volume['id'] is not None,
- "Field volume id is empty or not found.")
- #Wait for Volume status to become ACTIVE
- self.client.wait_for_volume_status(volume['id'], 'available')
- #GET Volume
- resp, fetched_volume = self.client.get_volume(volume['id'])
- self.assertEqual(200, resp.status)
- #Verfication of details of fetched Volume
- self.assertEqual(v_name,
- fetched_volume['displayName'],
- 'The fetched Volume is different '
- 'from the created Volume')
- self.assertEqual(volume['id'],
- fetched_volume['id'],
- 'The fetched Volume is different '
- 'from the created Volume')
- self.assertEqual(metadata,
- fetched_volume['metadata'],
- 'The fetched Volume is different '
- 'from the created Volume')
+ v_name = rand_name('Volume-%s-') % self._interface
+ metadata = {'Type': 'work'}
+ #Create volume
+ resp, volume = self.client.create_volume(size=1,
+ display_name=v_name,
+ metadata=metadata)
+ self.addCleanup(self._delete_volume, volume)
+ self.assertEqual(200, resp.status)
+ self.assertTrue('id' in volume)
+ self.assertTrue('displayName' in volume)
+ self.assertEqual(volume['displayName'], v_name,
+ "The created volume name is not equal "
+ "to the requested name")
+ self.assertTrue(volume['id'] is not None,
+ "Field volume id is empty or not found.")
+ #Wait for Volume status to become ACTIVE
+ self.client.wait_for_volume_status(volume['id'], 'available')
+ #GET Volume
+ resp, fetched_volume = self.client.get_volume(volume['id'])
+ self.assertEqual(200, resp.status)
+ #Verfication of details of fetched Volume
+ self.assertEqual(v_name,
+ fetched_volume['displayName'],
+ 'The fetched Volume is different '
+ 'from the created Volume')
+ self.assertEqual(volume['id'],
+ fetched_volume['id'],
+ 'The fetched Volume is different '
+ 'from the created Volume')
+ self.assertEqual(metadata,
+ fetched_volume['metadata'],
+ 'The fetched Volume is different '
+ 'from the created Volume')
- finally:
- if volume:
- #Delete the Volume created in this method
- resp, _ = self.client.delete_volume(volume['id'])
- self.assertEqual(202, resp.status)
- #Checking if the deleted Volume still exists
- self.client.wait_for_resource_deletion(volume['id'])
-
- @attr(type='positive')
+ @attr(type='gate')
def test_volume_get_metadata_none(self):
# CREATE, GET empty metadata dict
+ v_name = rand_name('Volume-')
+ #Create volume
+ resp, volume = self.client.create_volume(size=1,
+ display_name=v_name,
+ metadata={})
+ self.addCleanup(self._delete_volume, volume)
+ self.assertEqual(200, resp.status)
+ self.assertTrue('id' in volume)
+ self.assertTrue('displayName' in volume)
+ #Wait for Volume status to become ACTIVE
+ self.client.wait_for_volume_status(volume['id'], 'available')
+ #GET Volume
+ resp, fetched_volume = self.client.get_volume(volume['id'])
+ self.assertEqual(200, resp.status)
+ self.assertEqual(fetched_volume['metadata'], {})
+
+ def _delete_volume(self, volume):
+ #Delete the Volume created in this method
try:
- v_name = rand_name('Volume-')
- #Create volume
- resp, volume = self.client.create_volume(size=1,
- display_name=v_name,
- metadata={})
- self.assertEqual(200, resp.status)
- self.assertTrue('id' in volume)
- self.assertTrue('displayName' in volume)
- #Wait for Volume status to become ACTIVE
- self.client.wait_for_volume_status(volume['id'], 'available')
- #GET Volume
- resp, fetched_volume = self.client.get_volume(volume['id'])
- self.assertEqual(200, resp.status)
- self.assertEqual(fetched_volume['metadata'], {})
- finally:
- #Delete the Volume created in this method
resp, _ = self.client.delete_volume(volume['id'])
self.assertEqual(202, resp.status)
#Checking if the deleted Volume still exists
self.client.wait_for_resource_deletion(volume['id'])
+ except KeyError:
+ return
class VolumesGetTestXML(VolumesGetTestJSON):
diff --git a/tempest/api/compute/volumes/test_volumes_list.py b/tempest/api/compute/volumes/test_volumes_list.py
index b1ef2fd..d52349e 100644
--- a/tempest/api/compute/volumes/test_volumes_list.py
+++ b/tempest/api/compute/volumes/test_volumes_list.py
@@ -17,6 +17,7 @@
from tempest.api.compute import base
from tempest.common.utils.data_utils import rand_name
+from tempest.test import attr
class VolumesTestJSON(base.BaseComputeTest):
@@ -74,6 +75,7 @@
cls.client.wait_for_resource_deletion(volume['id'])
super(VolumesTestJSON, cls).tearDownClass()
+ @attr(type='gate')
def test_volume_list(self):
# Should return the list of Volumes
# Fetch all Volumes
@@ -89,6 +91,7 @@
', '.join(m_vol['displayName']
for m_vol in missing_volumes))
+ @attr(type='gate')
def test_volume_list_with_details(self):
# Should return the list of Volumes with details
#Fetch all Volumes
diff --git a/tempest/api/compute/volumes/test_volumes_negative.py b/tempest/api/compute/volumes/test_volumes_negative.py
index a4ecd0d..de214fc 100644
--- a/tempest/api/compute/volumes/test_volumes_negative.py
+++ b/tempest/api/compute/volumes/test_volumes_negative.py
@@ -18,6 +18,7 @@
from tempest.api.compute import base
from tempest.common.utils.data_utils import rand_name
from tempest import exceptions
+from tempest.test import attr
class VolumesNegativeTest(base.BaseComputeTest):
@@ -28,6 +29,7 @@
super(VolumesNegativeTest, cls).setUpClass()
cls.client = cls.volumes_extensions_client
+ @attr(type='gate')
def test_volume_get_nonexistant_volume_id(self):
# Negative: Should not be able to get details of nonexistant volume
#Creating a nonexistant volume id
@@ -43,6 +45,7 @@
self.assertRaises(exceptions.NotFound, self.client.get_volume,
non_exist_id)
+ @attr(type='gate')
def test_volume_delete_nonexistant_volume_id(self):
# Negative: Should not be able to delete nonexistant Volume
# Creating nonexistant volume id
@@ -58,6 +61,7 @@
self.assertRaises(exceptions.NotFound, self.client.delete_volume,
non_exist_id)
+ @attr(type='gate')
def test_create_volume_with_invalid_size(self):
# Negative: Should not be able to create volume with invalid size
# in request
@@ -66,6 +70,7 @@
self.assertRaises(exceptions.BadRequest, self.client.create_volume,
size='#$%', display_name=v_name, metadata=metadata)
+ @attr(type='gate')
def test_create_volume_with_out_passing_size(self):
# Negative: Should not be able to create volume without passing size
# in request
@@ -74,6 +79,7 @@
self.assertRaises(exceptions.BadRequest, self.client.create_volume,
size='', display_name=v_name, metadata=metadata)
+ @attr(type='gate')
def test_create_volume_with_size_zero(self):
# Negative: Should not be able to create volume with size zero
v_name = rand_name('Volume-')
@@ -81,21 +87,25 @@
self.assertRaises(exceptions.BadRequest, self.client.create_volume,
size='0', display_name=v_name, metadata=metadata)
+ @attr(type='gate')
def test_get_invalid_volume_id(self):
# Negative: Should not be able to get volume with invalid id
self.assertRaises(exceptions.NotFound,
self.client.get_volume, '#$%%&^&^')
+ @attr(type='gate')
def test_get_volume_without_passing_volume_id(self):
# Negative: Should not be able to get volume when empty ID is passed
self.assertRaises(exceptions.NotFound, self.client.get_volume, '')
+ @attr(type='gate')
def test_delete_invalid_volume_id(self):
# Negative: Should not be able to delete volume when invalid ID is
# passed
self.assertRaises(exceptions.NotFound,
self.client.delete_volume, '!@#$%^&*()')
+ @attr(type='gate')
def test_delete_volume_without_passing_volume_id(self):
# Negative: Should not be able to delete volume when empty ID is passed
self.assertRaises(exceptions.NotFound, self.client.delete_volume, '')
diff --git a/tempest/api/identity/__init__.py b/tempest/api/identity/__init__.py
index e5fdc1b..718aa15 100644
--- a/tempest/api/identity/__init__.py
+++ b/tempest/api/identity/__init__.py
@@ -15,7 +15,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-import logging
+from tempest.common import log as logging
LOG = logging.getLogger(__name__)
diff --git a/tempest/api/identity/admin/test_tenants.py b/tempest/api/identity/admin/test_tenants.py
index 815cfbe..e8625db 100644
--- a/tempest/api/identity/admin/test_tenants.py
+++ b/tempest/api/identity/admin/test_tenants.py
@@ -88,6 +88,7 @@
self.assertRaises(exceptions.NotFound, self.client.delete_tenant,
'junk_tenant_123456abc')
+ @attr(type='gate')
def test_tenant_create_with_description(self):
# Create tenant with a description
tenant_name = rand_name('tenant-')
@@ -109,6 +110,7 @@
self.client.delete_tenant(tenant_id)
self.data.tenants.remove(tenant)
+ @attr(type='gate')
def test_tenant_create_enabled(self):
# Create a tenant that is enabled
tenant_name = rand_name('tenant-')
@@ -126,6 +128,7 @@
self.client.delete_tenant(tenant_id)
self.data.tenants.remove(tenant)
+ @attr(type='gate')
def test_tenant_create_not_enabled(self):
# Create a tenant that is not enabled
tenant_name = rand_name('tenant-')
@@ -182,12 +185,14 @@
self.assertRaises(exceptions.BadRequest, self.client.create_tenant,
name='')
+ @attr(type='gate')
def test_create_tenants_name_length_over_64(self):
# Tenant name length should not be greater than 64 characters
tenant_name = 'a' * 65
self.assertRaises(exceptions.BadRequest, self.client.create_tenant,
tenant_name)
+ @attr(type='gate')
def test_tenant_update_name(self):
# Update name attribute of a tenant
t_name1 = rand_name('tenant-')
@@ -215,6 +220,7 @@
self.client.delete_tenant(t_id)
self.data.tenants.remove(tenant)
+ @attr(type='gate')
def test_tenant_update_desc(self):
# Update description attribute of a tenant
t_name = rand_name('tenant-')
@@ -243,6 +249,7 @@
self.client.delete_tenant(t_id)
self.data.tenants.remove(tenant)
+ @attr(type='gate')
def test_tenant_update_enable(self):
# Update the enabled attribute of a tenant
t_name = rand_name('tenant-')
diff --git a/tempest/api/identity/admin/test_users.py b/tempest/api/identity/admin/test_users.py
index abc311b..c029300 100644
--- a/tempest/api/identity/admin/test_users.py
+++ b/tempest/api/identity/admin/test_users.py
@@ -33,7 +33,7 @@
alt_tenant = rand_name('test_tenant_')
alt_description = rand_name('desc_')
- @attr(type=['smoke', 'gate'])
+ @attr(type='smoke')
def test_create_user(self):
# Create a user
self.data.setup_test_tenant()
@@ -44,7 +44,7 @@
self.assertEqual('200', resp['status'])
self.assertEqual(self.alt_user, user['name'])
- @attr(type='gate')
+ @attr(type=['negative', 'gate'])
def test_create_user_by_unauthorized_user(self):
# Non-admin should not be authorized to create a user
self.data.setup_test_tenant()
@@ -53,7 +53,7 @@
self.alt_password, self.data.tenant['id'],
self.alt_email)
- @attr(type='gate')
+ @attr(type=['negative', 'gate'])
def test_create_user_with_empty_name(self):
# User with an empty name should not be created
self.data.setup_test_tenant()
@@ -61,15 +61,15 @@
self.alt_password, self.data.tenant['id'],
self.alt_email)
- @attr(type='gate')
- def test_create_user_with_name_length_over_64(self):
- # Length of user name filed should be restricted to 64 characters
+ @attr(type=['negative', 'gate'])
+ def test_create_user_with_name_length_over_255(self):
+ # Length of user name filed should be restricted to 255 characters
self.data.setup_test_tenant()
self.assertRaises(exceptions.BadRequest, self.client.create_user,
- 'a' * 65, self.alt_password,
+ 'a' * 256, self.alt_password,
self.data.tenant['id'], self.alt_email)
- @attr(type='gate')
+ @attr(type=['negative', 'gate'])
def test_create_user_with_duplicate_name(self):
# Duplicate user should not be created
self.data.setup_test_user()
@@ -77,8 +77,8 @@
self.data.test_user, self.data.test_password,
self.data.tenant['id'], self.data.test_email)
- @attr(type='gate')
@testtools.skip("Until Bug #999084 is fixed")
+ @attr(type=['negative', 'gate'])
def test_create_user_with_empty_password(self):
# User with an empty password should not be created
self.data.setup_test_tenant()
@@ -86,8 +86,8 @@
self.alt_user, '', self.data.tenant['id'],
self.alt_email)
- @attr(type='gate')
@testtools.skip("Until Bug #999084 is fixed")
+ @attr(type=['negative', 'gate'])
def test_create_user_with_long_password(self):
# User having password exceeding max length should not be created
self.data.setup_test_tenant()
@@ -95,22 +95,22 @@
self.alt_user, 'a' * 65, self.data.tenant['id'],
self.alt_email)
- @attr(type='gate')
@testtools.skip("Until Bug #999084 is fixed")
+ @attr(type=['negative', 'gate'])
def test_create_user_with_invalid_email_format(self):
# Email format should be validated while creating a user
self.data.setup_test_tenant()
self.assertRaises(exceptions.BadRequest, self.client.create_user,
self.alt_user, '', self.data.tenant['id'], '12345')
- @attr(type='gate')
+ @attr(type=['negative', 'gate'])
def test_create_user_for_non_existant_tenant(self):
# Attempt to create a user in a non-existent tenant should fail
self.assertRaises(exceptions.NotFound, self.client.create_user,
self.alt_user, self.alt_password, '49ffgg99999',
self.alt_email)
- @attr(type='gate')
+ @attr(type=['negative', 'gate'])
def test_create_user_request_without_a_token(self):
# Request to create a user without a valid token should fail
self.data.setup_test_tenant()
@@ -125,7 +125,7 @@
# Unset the token to allow further tests to generate a new token
self.client.clear_auth()
- @attr(type=['smoke', 'gate'])
+ @attr(type='smoke')
def test_delete_user(self):
# Delete a user
self.data.setup_test_tenant()
@@ -136,7 +136,7 @@
resp, body = self.client.delete_user(user['id'])
self.assertEquals('204', resp['status'])
- @attr(type='gate')
+ @attr(type=['negative', 'gate'])
def test_delete_users_by_unauthorized_user(self):
# Non admin user should not be authorized to delete a user
self.data.setup_test_user()
@@ -144,13 +144,13 @@
self.non_admin_client.delete_user,
self.data.user['id'])
- @attr(type='gate')
+ @attr(type=['negative', 'gate'])
def test_delete_non_existant_user(self):
# Attempt to delete a non-existent user should fail
self.assertRaises(exceptions.NotFound, self.client.delete_user,
'junk12345123')
- @attr(type=['smoke', 'gate'])
+ @attr(type='smoke')
def test_user_authentication(self):
# Valid user's token is authenticated
self.data.setup_test_user()
@@ -163,7 +163,7 @@
self.data.test_tenant)
self.assertEqual('200', resp['status'])
- @attr(type='gate')
+ @attr(type=['negative', 'gate'])
def test_authentication_for_disabled_user(self):
# Disabled user's token should not get authenticated
self.data.setup_test_user()
@@ -173,7 +173,7 @@
self.data.test_password,
self.data.test_tenant)
- @attr(type='gate')
+ @attr(type=['negative', 'gate'])
def test_authentication_when_tenant_is_disabled(self):
# User's token for a disabled tenant should not be authenticated
self.data.setup_test_user()
@@ -183,7 +183,7 @@
self.data.test_password,
self.data.test_tenant)
- @attr(type='gate')
+ @attr(type=['negative', 'gate'])
def test_authentication_with_invalid_tenant(self):
# User's token for an invalid tenant should not be authenticated
self.data.setup_test_user()
@@ -192,7 +192,7 @@
self.data.test_password,
'junktenant1234')
- @attr(type='gate')
+ @attr(type=['negative', 'gate'])
def test_authentication_with_invalid_username(self):
# Non-existent user's token should not get authenticated
self.data.setup_test_user()
@@ -200,7 +200,7 @@
'junkuser123', self.data.test_password,
self.data.test_tenant)
- @attr(type='gate')
+ @attr(type=['negative', 'gate'])
def test_authentication_with_invalid_password(self):
# User's token with invalid password should not be authenticated
self.data.setup_test_user()
@@ -225,7 +225,7 @@
self.assertEqual('200', resp['status'])
self.client.clear_auth()
- @attr(type=['smoke', 'gate'])
+ @attr(type='smoke')
def test_get_users(self):
# Get a list of users and find the test user
self.data.setup_test_user()
@@ -234,14 +234,14 @@
Contains(self.data.test_user),
"Could not find %s" % self.data.test_user)
- @attr(type='gate')
+ @attr(type=['negative', 'gate'])
def test_get_users_by_unauthorized_user(self):
# Non admin user should not be authorized to get user list
self.data.setup_test_user()
self.assertRaises(exceptions.Unauthorized,
self.non_admin_client.get_users)
- @attr(type='gate')
+ @attr(type=['negative', 'gate'])
def test_get_users_request_without_token(self):
# Request to get list of users without a valid token should fail
token = self.client.get_auth()
@@ -316,7 +316,7 @@
"Failed to find user %s in fetched list" %
', '.join(m_user for m_user in missing_users))
- @attr(type='gate')
+ @attr(type=['negative', 'gate'])
def test_list_users_with_invalid_tenant(self):
# Should not be able to return a list of all
# users for a nonexistant tenant
diff --git a/tempest/api/identity/admin/v3/test_domains.py b/tempest/api/identity/admin/v3/test_domains.py
new file mode 100644
index 0000000..8d019fe
--- /dev/null
+++ b/tempest/api/identity/admin/v3/test_domains.py
@@ -0,0 +1,54 @@
+# 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.
+
+from tempest.api.identity import base
+from tempest.common.utils.data_utils import rand_name
+from tempest.test import attr
+
+
+class DomainsTestJSON(base.BaseIdentityAdminTest):
+ _interface = 'json'
+
+ def _delete_domain(self, domain_id):
+ # It is necessary to disable the domian before deleting,
+ # or else it would result in unauthorized error
+ _, body = self.v3_client.update_domain(domain_id, enabled=False)
+ resp, _ = self.v3_client.delete_domain(domain_id)
+ self.assertEqual(204, resp.status)
+
+ @attr(type='smoke')
+ def test_list_domains(self):
+ #Test to list domains
+ domain_ids = list()
+ fetched_ids = list()
+ for _ in range(3):
+ _, domain = self.v3_client.create_domain(
+ rand_name('domain-'), description=rand_name('domain-desc-'))
+ # Delete the domian at the end of this method
+ self.addCleanup(self._delete_domain, domain['id'])
+ domain_ids.append(domain['id'])
+ # List and Verify Domains
+ resp, body = self.v3_client.list_domains()
+ self.assertEqual(resp['status'], '200')
+ for d in body:
+ fetched_ids.append(d['id'])
+ missing_doms = [d for d in domain_ids if d not in fetched_ids]
+ self.assertEqual(0, len(missing_doms))
+
+
+class DomainsTestXML(DomainsTestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/identity/admin/v3/test_policies.py b/tempest/api/identity/admin/v3/test_policies.py
new file mode 100644
index 0000000..799b081
--- /dev/null
+++ b/tempest/api/identity/admin/v3/test_policies.py
@@ -0,0 +1,82 @@
+# 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.
+
+from tempest.api.identity import base
+from tempest.common.utils.data_utils import rand_name
+from tempest.test import attr
+
+
+class PoliciesTestJSON(base.BaseIdentityAdminTest):
+ _interface = 'json'
+
+ def _delete_policy(self, policy_id):
+ resp, _ = self.policy_client.delete_policy(policy_id)
+ self.assertEqual(204, resp.status)
+
+ @attr(type='smoke')
+ def test_list_policies(self):
+ #Test to list policies
+ policy_ids = list()
+ fetched_ids = list()
+ for _ in range(3):
+ blob = rand_name('BlobName-')
+ policy_type = rand_name('PolicyType-')
+ resp, policy = self.policy_client.create_policy(blob,
+ policy_type)
+ # Delete the Policy at the end of this method
+ self.addCleanup(self._delete_policy, policy['id'])
+ policy_ids.append(policy['id'])
+ # List and Verify Policies
+ resp, body = self.policy_client.list_policies()
+ self.assertEqual(resp['status'], '200')
+ for p in body:
+ fetched_ids.append(p['id'])
+ missing_pols = [p for p in policy_ids if p not in fetched_ids]
+ self.assertEqual(0, len(missing_pols))
+
+ @attr(type='smoke')
+ def test_create_update_delete_policy(self):
+ #Test to update policy
+ blob = rand_name('BlobName-')
+ policy_type = rand_name('PolicyType-')
+ resp, policy = self.policy_client.create_policy(blob, policy_type)
+ self.addCleanup(self._delete_policy, policy['id'])
+ self.assertIn('id', policy)
+ self.assertIn('type', policy)
+ self.assertIn('blob', policy)
+ self.assertIsNotNone(policy['id'])
+ self.assertEqual(blob, policy['blob'])
+ self.assertEqual(policy_type, policy['type'])
+ resp, fetched_policy = self.policy_client.get_policy(policy['id'])
+ self.assertEqual(resp['status'], '200')
+ #Update policy
+ update_type = rand_name('UpdatedPolicyType-')
+ resp, data = self.policy_client.update_policy(
+ policy['id'], type=update_type)
+ self.assertTrue('type' in data)
+ #Assertion for updated value with fetched value
+ resp, fetched_policy = self.policy_client.get_policy(policy['id'])
+ self.assertIn('id', fetched_policy)
+ self.assertIn('blob', fetched_policy)
+ self.assertIn('type', fetched_policy)
+ self.assertEqual(fetched_policy['id'], policy['id'])
+ self.assertEqual(fetched_policy['blob'], policy['blob'])
+ self.assertEqual(update_type, fetched_policy['type'])
+
+
+class PoliciesTestXML(PoliciesTestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/identity/base.py b/tempest/api/identity/base.py
index 6980425..db55509 100644
--- a/tempest/api/identity/base.py
+++ b/tempest/api/identity/base.py
@@ -31,6 +31,7 @@
cls.endpoints_client = os.endpoints_client
cls.v3_client = os.identity_v3_client
cls.service_client = os.service_client
+ cls.policy_client = os.policy_client
if not cls.client.has_admin_extensions():
raise cls.skipException("Admin extensions disabled")
diff --git a/tempest/api/image/base.py b/tempest/api/image/base.py
index f12e957..e62d84b 100644
--- a/tempest/api/image/base.py
+++ b/tempest/api/image/base.py
@@ -14,9 +14,8 @@
# License for the specific language governing permissions and limitations
# under the License.
-import logging
-
from tempest import clients
+from tempest.common import log as logging
from tempest.common.utils.data_utils import rand_name
from tempest import exceptions
import tempest.test
diff --git a/tempest/api/image/v1/test_images.py b/tempest/api/image/v1/test_images.py
index c5d3f93..640daa5 100644
--- a/tempest/api/image/v1/test_images.py
+++ b/tempest/api/image/v1/test_images.py
@@ -47,7 +47,6 @@
properties=properties)
self.assertTrue('id' in body)
image_id = body.get('id')
- self.created_images.append(image_id)
self.assertEqual('New Name', body.get('name'))
self.assertTrue(body.get('is_public'))
self.assertEqual('queued', body.get('status'))
@@ -71,8 +70,6 @@
properties={'key1': 'value1',
'key2': 'value2'})
self.assertTrue('id' in body)
- image_id = body.get('id')
- self.created_images.append(image_id)
self.assertEqual('New Remote Image', body.get('name'))
self.assertTrue(body.get('is_public'))
self.assertEqual('active', body.get('status'))
@@ -88,7 +85,6 @@
copy_from=self.config.images.http_image)
self.assertTrue('id' in body)
image_id = body.get('id')
- self.created_images.append(image_id)
self.assertEqual('New Http Image', body.get('name'))
self.assertTrue(body.get('is_public'))
self.client.wait_for_image_status(image_id, 'active')
@@ -106,8 +102,6 @@
min_ram=40,
properties=properties)
self.assertTrue('id' in body)
- image_id = body.get('id')
- self.created_images.append(image_id)
self.assertEqual('New_image_with_min_ram', body.get('name'))
self.assertTrue(body.get('is_public'))
self.assertEqual('queued', body.get('status'))
diff --git a/tempest/api/image/v2/test_images.py b/tempest/api/image/v2/test_images.py
index 966adc3..34db6e3 100644
--- a/tempest/api/image/v2/test_images.py
+++ b/tempest/api/image/v2/test_images.py
@@ -50,7 +50,6 @@
visibility='public')
self.assertTrue('id' in body)
image_id = body.get('id')
- self.created_images.append(image_id)
self.assertTrue('name' in body)
self.assertEqual('New Name', body.get('name'))
self.assertTrue('visibility' in body)
@@ -79,7 +78,7 @@
# We add a few images here to test the listing functionality of
# the images API
for x in xrange(0, 10):
- cls.created_images.append(cls._create_standard_image(x))
+ cls._create_standard_image(x)
@classmethod
def _create_standard_image(cls, number):
diff --git a/tempest/api/network/base.py b/tempest/api/network/base.py
index e0e40cb..03e73df 100644
--- a/tempest/api/network/base.py
+++ b/tempest/api/network/base.py
@@ -73,6 +73,8 @@
cidr = netaddr.IPNetwork(cls.network_cfg.tenant_network_cidr)
mask_bits = cls.network_cfg.tenant_network_mask_bits
# Find a cidr that is not in use yet and create a subnet with it
+ body = None
+ failure = None
for subnet_cidr in cidr.subnet(mask_bits):
try:
resp, body = cls.client.create_subnet(network['id'],
@@ -82,6 +84,12 @@
is_overlapping_cidr = 'overlaps with another subnet' in str(e)
if not is_overlapping_cidr:
raise
+ # save the failure in case all of the CIDRs are overlapping
+ failure = e
+
+ if not body and failure:
+ raise failure
+
subnet = body['subnet']
cls.subnets.append(subnet)
return subnet
diff --git a/tempest/api/network/test_networks.py b/tempest/api/network/test_networks.py
index d4e0645..1f45f92 100644
--- a/tempest/api/network/test_networks.py
+++ b/tempest/api/network/test_networks.py
@@ -54,7 +54,7 @@
cls.subnet = cls.create_subnet(cls.network)
cls.cidr = cls.subnet['cidr']
- @attr(type='positive')
+ @attr(type='gate')
def test_create_delete_network_subnet(self):
# Creates a network
name = rand_name('network-')
@@ -83,7 +83,7 @@
resp, body = self.client.delete_network(network['id'])
self.assertEqual('204', resp['status'])
- @attr(type='positive')
+ @attr(type='gate')
def test_show_network(self):
# Verifies the details of a network
resp, body = self.client.show_network(self.network['id'])
@@ -92,7 +92,7 @@
self.assertEqual(self.network['id'], network['id'])
self.assertEqual(self.name, network['name'])
- @attr(type='positive')
+ @attr(type='gate')
def test_list_networks(self):
# Verify the network exists in the list of all networks
resp, body = self.client.list_networks()
@@ -100,7 +100,7 @@
found = any(n for n in networks if n['id'] == self.network['id'])
self.assertTrue(found)
- @attr(type='positive')
+ @attr(type='gate')
def test_show_subnet(self):
# Verifies the details of a subnet
resp, body = self.client.show_subnet(self.subnet['id'])
@@ -109,7 +109,7 @@
self.assertEqual(self.subnet['id'], subnet['id'])
self.assertEqual(self.cidr, subnet['cidr'])
- @attr(type='positive')
+ @attr(type='gate')
def test_list_subnets(self):
# Verify the subnet exists in the list of all subnets
resp, body = self.client.list_subnets()
diff --git a/tempest/api/object_storage/base.py b/tempest/api/object_storage/base.py
index 745de22..bf013ec 100644
--- a/tempest/api/object_storage/base.py
+++ b/tempest/api/object_storage/base.py
@@ -47,3 +47,31 @@
except exceptions.EndpointNotFound:
skip_msg = "No OpenStack Object Storage API endpoint"
raise cls.skipException(skip_msg)
+
+ @classmethod
+ def delete_containers(cls, containers, container_client=None,
+ object_client=None):
+ """Remove given containers and all objects in them.
+
+ The containers should be visible from the container_client given.
+ Will not throw any error if the containers don't exist.
+
+ :param containers: list of container names to remove
+ :param container_client: if None, use cls.container_client, this means
+ that the default testing user will be used (see 'username' in
+ 'etc/tempest.conf')
+ :param object_client: if None, use cls.object_client
+ """
+ if container_client is None:
+ container_client = cls.container_client
+ if object_client is None:
+ object_client = cls.object_client
+ for cont in containers:
+ try:
+ objlist = container_client.list_all_container_objects(cont)
+ # delete every object in the container
+ for obj in objlist:
+ object_client.delete_object(cont, obj['name'])
+ container_client.delete_container(cont)
+ except exceptions.NotFound:
+ pass
diff --git a/tempest/api/object_storage/test_account_services.py b/tempest/api/object_storage/test_account_services.py
index ddedfc6..029f2d5 100644
--- a/tempest/api/object_storage/test_account_services.py
+++ b/tempest/api/object_storage/test_account_services.py
@@ -19,6 +19,7 @@
from tempest.common.utils.data_utils import rand_name
from tempest import exceptions
from tempest.test import attr
+from tempest.test import HTTP_SUCCESS
class AccountTest(base.BaseObjectTest):
@@ -47,35 +48,33 @@
def test_list_account_metadata(self):
# list all account metadata
resp, metadata = self.account_client.list_account_metadata()
- self.assertEqual(resp['status'], '204')
+ self.assertIn(int(resp['status']), HTTP_SUCCESS)
self.assertIn('x-account-object-count', resp)
self.assertIn('x-account-container-count', resp)
self.assertIn('x-account-bytes-used', resp)
@attr(type='smoke')
- def test_create_account_metadata(self):
+ def test_create_and_delete_account_metadata(self):
+ header = 'test-account-meta'
+ data = 'Meta!'
# add metadata to account
- metadata = {'test-account-meta': 'Meta!'}
- resp, _ = \
- self.account_client.create_account_metadata(metadata=metadata)
- self.assertEqual(resp['status'], '204')
+ resp, _ = self.account_client.create_account_metadata(
+ metadata={header: data})
+ self.assertIn(int(resp['status']), HTTP_SUCCESS)
- resp, metadata = self.account_client.list_account_metadata()
- self.assertIn('x-account-meta-test-account-meta', resp)
- self.assertEqual(resp['x-account-meta-test-account-meta'], 'Meta!')
+ resp, _ = self.account_client.list_account_metadata()
+ self.assertIn('x-account-meta-' + header, resp)
+ self.assertEqual(resp['x-account-meta-' + header], data)
- @attr(type='smoke')
- def test_delete_account_metadata(self):
# delete metadata from account
- metadata = ['test-account-meta']
resp, _ = \
- self.account_client.delete_account_metadata(metadata=metadata)
- self.assertEqual(resp['status'], '204')
+ self.account_client.delete_account_metadata(metadata=[header])
+ self.assertIn(int(resp['status']), HTTP_SUCCESS)
- resp, metadata = self.account_client.list_account_metadata()
- self.assertNotIn('x-account-meta-test-account-meta', resp)
+ resp, _ = self.account_client.list_account_metadata()
+ self.assertNotIn('x-account-meta-' + header, resp)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_list_containers_with_non_authorized_user(self):
# list containers using non-authorized user
diff --git a/tempest/api/object_storage/test_container_services.py b/tempest/api/object_storage/test_container_services.py
index 0b4b57b..5cb6341 100644
--- a/tempest/api/object_storage/test_container_services.py
+++ b/tempest/api/object_storage/test_container_services.py
@@ -19,6 +19,7 @@
from tempest.common.utils.data_utils import arbitrary_string
from tempest.common.utils.data_utils import rand_name
from tempest.test import attr
+from tempest.test import HTTP_SUCCESS
class ContainerTest(base.BaseObjectTest):
@@ -29,15 +30,7 @@
@classmethod
def tearDownClass(cls):
- for container in cls.containers:
- objlist = \
- cls.container_client.list_all_container_objects(container)
- # delete every object in the container
- for obj in objlist:
- resp, _ = \
- cls.object_client.delete_object(container, obj['name'])
- # delete the container
- resp, _ = cls.container_client.delete_container(container)
+ cls.delete_containers(cls.containers)
@attr(type='smoke')
def test_create_container(self):
@@ -54,7 +47,7 @@
self.containers.append(container_name)
# delete container
resp, _ = self.container_client.delete_container(container_name)
- self.assertEqual(resp['status'], '204')
+ self.assertIn(int(resp['status']), HTTP_SUCCESS)
self.containers.remove(container_name)
@attr(type='smoke')
@@ -82,7 +75,7 @@
resp, object_list = \
self.container_client.\
list_container_contents(container_name, params=params)
- self.assertEqual(resp['status'], '200')
+ self.assertIn(int(resp['status']), HTTP_SUCCESS)
self.assertIsNotNone(object_list)
object_names = [obj['name'] for obj in object_list]
@@ -103,12 +96,12 @@
resp, _ = \
self.container_client.update_container_metadata(container_name,
metadata=metadata)
- self.assertEqual(resp['status'], '204')
+ self.assertIn(int(resp['status']), HTTP_SUCCESS)
# list container metadata
resp, _ = self.container_client.list_container_metadata(
container_name)
- self.assertEqual(resp['status'], '204')
+ self.assertIn(int(resp['status']), HTTP_SUCCESS)
self.assertIn('x-container-meta-name', resp)
self.assertIn('x-container-meta-description', resp)
self.assertEqual(resp['x-container-meta-name'], 'Pictures')
@@ -118,15 +111,10 @@
resp, _ = self.container_client.delete_container_metadata(
container_name,
metadata=metadata.keys())
- self.assertEqual(resp['status'], '204')
+ self.assertIn(int(resp['status']), HTTP_SUCCESS)
# check if the metadata are no longer there
resp, _ = self.container_client.list_container_metadata(container_name)
- self.assertEqual(resp['status'], '204')
+ self.assertIn(int(resp['status']), HTTP_SUCCESS)
self.assertNotIn('x-container-meta-name', resp)
self.assertNotIn('x-container-meta-description', resp)
-
- # delete container
- resp, _ = self.container_client.delete_container(container_name)
- self.assertEqual(resp['status'], '204')
- self.containers.remove(container_name)
diff --git a/tempest/api/object_storage/test_container_sync.py b/tempest/api/object_storage/test_container_sync.py
index 3243990..ea8637c 100644
--- a/tempest/api/object_storage/test_container_sync.py
+++ b/tempest/api/object_storage/test_container_sync.py
@@ -48,17 +48,11 @@
@classmethod
def tearDownClass(cls):
- for cont_name, client in cls.clients.items():
- objlist = client[0].list_all_container_objects(cont_name)
- # delete every object in the container
- if objlist:
- for obj in objlist:
- resp, _ = client[1].delete_object(cont_name, obj['name'])
- # delete the container
- resp, _ = client[0].delete_container(cont_name)
+ for client in cls.clients.values():
+ cls.delete_containers(cls.containers, client[0], client[1])
@testtools.skip('Until Bug #1093743 is resolved.')
- @attr(type='positive')
+ @attr(type='gate')
def test_container_synchronization(self):
# container to container synchronization
# to allow/accept sync requests to/from other accounts
diff --git a/tempest/api/object_storage/test_object_expiry.py b/tempest/api/object_storage/test_object_expiry.py
index 1fe47ea..aaa2c64 100644
--- a/tempest/api/object_storage/test_object_expiry.py
+++ b/tempest/api/object_storage/test_object_expiry.py
@@ -23,6 +23,7 @@
from tempest.common.utils.data_utils import arbitrary_string
from tempest.common.utils.data_utils import rand_name
from tempest import exceptions
+from tempest.test import attr
class ObjectExpiryTest(base.BaseObjectTest):
@@ -39,17 +40,10 @@
But delete action for the expired object is raising
NotFound exception and also non empty container cannot be deleted.
"""
- objlist = \
- cls.container_client.list_all_container_objects(cls.container_name)
- # delete every object in the container
- if objlist:
- for obj in objlist:
- resp, _ = cls.object_client.delete_object(cls.container_name,
- obj['name'])
- # delete the container
- resp, _ = cls.container_client.delete_container(cls.container_name)
+ cls.delete_containers([cls.container_name])
@testtools.skip('Until Bug #1069849 is resolved.')
+ @attr(type='gate')
def test_get_object_after_expiry_time(self):
#TODO(harika-vakadi): similar test case has to be created for
# "X-Delete-At", after this test case works.
diff --git a/tempest/api/object_storage/test_object_services.py b/tempest/api/object_storage/test_object_services.py
index 4d7ee74..2f52a65 100644
--- a/tempest/api/object_storage/test_object_services.py
+++ b/tempest/api/object_storage/test_object_services.py
@@ -15,15 +15,15 @@
# License for the specific language governing permissions and limitations
# under the License.
+import hashlib
import time
-import testtools
-
from tempest.api.object_storage import base
from tempest.common.utils.data_utils import arbitrary_string
from tempest.common.utils.data_utils import rand_name
from tempest import exceptions
from tempest.test import attr
+from tempest.test import HTTP_SUCCESS
class ObjectTest(base.BaseObjectTest):
@@ -32,6 +32,7 @@
super(ObjectTest, cls).setUpClass()
cls.container_name = rand_name(name='TestContainer')
cls.container_client.create_container(cls.container_name)
+ cls.containers = [cls.container_name]
cls.data.setup_test_user()
resp, body = cls.token_client.auth(cls.data.test_user,
@@ -44,14 +45,7 @@
@classmethod
def tearDownClass(cls):
- objlist = cls.container_client.list_all_container_objects(
- cls.container_name)
- # delete every object in the container
- for obj in objlist:
- resp, _ = cls.object_client.delete_object(cls.container_name,
- obj['name'])
- # delete the container
- resp, _ = cls.container_client.delete_container(cls.container_name)
+ cls.delete_containers(cls.containers)
# delete the user setup created
cls.data.teardown_all()
@@ -79,7 +73,7 @@
# delete object
resp, _ = self.object_client.delete_object(self.container_name,
object_name)
- self.assertEqual(resp['status'], '204')
+ self.assertIn(int(resp['status']), HTTP_SUCCESS)
@attr(type='smoke')
def test_object_metadata(self):
@@ -96,12 +90,12 @@
orig_metadata = {meta_key: meta_value}
resp, _ = self.object_client.update_object_metadata(
self.container_name, object_name, orig_metadata)
- self.assertEqual(resp['status'], '202')
+ self.assertIn(int(resp['status']), HTTP_SUCCESS)
# get object metadata
resp, resp_metadata = self.object_client.list_object_metadata(
self.container_name, object_name)
- self.assertEqual(resp['status'], '200')
+ self.assertIn(int(resp['status']), HTTP_SUCCESS)
actual_meta_key = 'x-object-meta-' + meta_key
self.assertTrue(actual_meta_key in resp)
self.assertEqual(resp[actual_meta_key], meta_value)
@@ -118,7 +112,7 @@
# get object
resp, body = self.object_client.get_object(self.container_name,
object_name)
- self.assertEqual(resp['status'], '200')
+ self.assertIn(int(resp['status']), HTTP_SUCCESS)
self.assertEqual(body, data)
@attr(type='smoke')
@@ -198,9 +192,11 @@
# create a container to use as asource container
src_container_name = rand_name(name='TestSourceContainer')
self.container_client.create_container(src_container_name)
+ self.containers.append(src_container_name)
# create a container to use as a destination container
dst_container_name = rand_name(name='TestDestinationContainer')
self.container_client.create_container(dst_container_name)
+ self.containers.append(dst_container_name)
# create object in source container
object_name = rand_name(name='Object')
data = arbitrary_string(size=len(object_name) * 2,
@@ -214,7 +210,7 @@
resp, _ = self.object_client.update_object_metadata(src_container_name,
object_name,
orig_metadata)
- self.assertEqual(resp['status'], '202')
+ self.assertIn(int(resp['status']), HTTP_SUCCESS)
try:
# copy object from source container to destination container
resp, _ = self.object_client.copy_object_across_containers(
@@ -233,19 +229,8 @@
except Exception as e:
self.fail("Got exception :%s ; while copying"
" object across containers" % e)
- finally:
- # delete objects from respective containers
- resp, _ = self.object_client.delete_object(dst_container_name,
- object_name)
- resp, _ = self.object_client.delete_object(src_container_name,
- object_name)
- # delete containers created in this method
- resp, _ = self.container_client.delete_container(
- src_container_name)
- resp, _ = self.container_client.delete_container(
- dst_container_name)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_write_object_without_using_creds(self):
# trying to create object with empty headers
object_name = rand_name(name='Object')
@@ -258,7 +243,7 @@
self.container_name, object_name, data,
metadata=obj_headers)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_delete_object_without_using_creds(self):
# create object
object_name = rand_name(name='Object')
@@ -271,7 +256,7 @@
self.custom_object_client.delete_object,
self.container_name, object_name)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_write_object_with_non_authorized_user(self):
# attempt to upload another file using non-authorized user
object_name = rand_name(name='Object')
@@ -284,7 +269,7 @@
self.container_name, object_name, data,
metadata=self.custom_headers)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_read_object_with_non_authorized_user(self):
object_name = rand_name(name='Object')
data = arbitrary_string(size=len(object_name) * 5,
@@ -299,7 +284,7 @@
self.container_name, object_name,
metadata=self.custom_headers)
- @attr(type='negative')
+ @attr(type=['negative', 'gate'])
def test_delete_object_with_non_authorized_user(self):
object_name = rand_name(name='Object')
data = arbitrary_string(size=len(object_name) * 5,
@@ -313,8 +298,7 @@
self.container_name, object_name,
metadata=self.custom_headers)
- @testtools.skip('Until Bug #1097137 is resolved.')
- @attr(type='positive')
+ @attr(type='gate')
def test_get_object_using_temp_url(self):
# access object using temporary URL within expiration time
@@ -326,7 +310,7 @@
metadata = {'Temp-URL-Key': key}
resp, _ = self.account_client.create_account_metadata(
metadata=metadata)
- self.assertEqual(resp['status'], '204')
+ self.assertIn(int(resp['status']), HTTP_SUCCESS)
flag = True
resp, _ = self.account_client.list_account_metadata()
self.assertIn('x-account-meta-temp-url-key', resp)
@@ -352,7 +336,7 @@
resp, _ = self.account_client.list_account_metadata()
self.assertNotIn('x-account-meta-temp-url-key', resp)
- @attr(type='positive')
+ @attr(type='gate')
def test_object_upload_in_segments(self):
# create object
object_name = rand_name(name='LObject')
@@ -381,6 +365,30 @@
self.container_name, object_name)
self.assertEqual(data * segments, body)
+ @attr(type='gate')
+ def test_get_object_if_different(self):
+ # http://en.wikipedia.org/wiki/HTTP_ETag
+ # Make a conditional request for an object using the If-None-Match
+ # header, it should get downloaded only if the local file is different,
+ # otherwise the response code should be 304 Not Modified
+ object_name = rand_name(name='TestObject')
+ data = arbitrary_string()
+ self.object_client.create_object(self.container_name,
+ object_name, data)
+ # local copy is identical, no download
+ md5 = hashlib.md5(data).hexdigest()
+ headers = {'If-None-Match': md5}
+ url = "%s/%s" % (self.container_name, object_name)
+ resp, _ = self.object_client.get(url, headers=headers)
+ self.assertEqual(resp['status'], '304')
+
+ # local copy is different, download
+ local_data = "something different"
+ md5 = hashlib.md5(local_data).hexdigest()
+ headers = {'If-None-Match': md5}
+ resp, body = self.object_client.get(url, headers=headers)
+ self.assertIn(int(resp['status']), HTTP_SUCCESS)
+
class PublicObjectTest(base.BaseObjectTest):
def setUp(self):
@@ -389,14 +397,7 @@
self.container_client.create_container(self.container_name)
def tearDown(self):
- objlist = self.container_client.list_all_container_objects(
- self.container_name)
- # delete every object in the container
- for obj in objlist:
- resp, _ = self.object_client.delete_object(
- self.container_name, obj['name'])
- # delete the container
- resp, _ = self.container_client.delete_container(self.container_name)
+ self.delete_containers([self.container_name])
super(PublicObjectTest, self).tearDown()
@attr(type='smoke')
@@ -408,7 +409,7 @@
cont_headers = {'X-Container-Read': '.r:*,.rlistings'}
resp_meta, body = self.container_client.update_container_metadata(
self.container_name, metadata=cont_headers, metadata_prefix='')
- self.assertEqual(resp_meta['status'], '204')
+ self.assertIn(int(resp_meta['status']), HTTP_SUCCESS)
# create object
object_name = rand_name(name='Object')
data = arbitrary_string(size=len(object_name),
@@ -420,7 +421,7 @@
# list container metadata
resp_meta, _ = self.container_client.list_container_metadata(
self.container_name)
- self.assertEqual(resp_meta['status'], '204')
+ self.assertIn(int(resp['status']), HTTP_SUCCESS)
self.assertIn('x-container-read', resp_meta)
self.assertEqual(resp_meta['x-container-read'], '.r:*,.rlistings')
@@ -438,7 +439,7 @@
resp_meta, body = self.container_client.update_container_metadata(
self.container_name, metadata=cont_headers,
metadata_prefix='')
- self.assertEqual(resp_meta['status'], '204')
+ self.assertIn(int(resp_meta['status']), HTTP_SUCCESS)
# create object
object_name = rand_name(name='Object')
data = arbitrary_string(size=len(object_name) * 1,
@@ -450,7 +451,7 @@
# list container metadata
resp, _ = self.container_client.list_container_metadata(
self.container_name)
- self.assertEqual(resp['status'], '204')
+ self.assertIn(int(resp['status']), HTTP_SUCCESS)
self.assertIn('x-container-read', resp)
self.assertEqual(resp['x-container-read'], '.r:*,.rlistings')
@@ -466,71 +467,3 @@
except Exception as e:
self.fail("Failed to get public readable object with another"
" user creds raised exception is %s" % e)
-
- @testtools.skip('Until Bug #1020722 is resolved.')
- @attr(type='smoke')
- def test_write_public_object_without_using_creds(self):
- # make container public-writable, and create object anonymously, e.g.
- # without using credentials
- try:
- # update container metadata to make publicly writable
- cont_headers = {'X-Container-Write': '-*'}
- resp_meta, body = self.container_client.update_container_metadata(
- self.container_name, metadata=cont_headers, metadata_prefix='')
- self.assertEqual(resp_meta['status'], '204')
- # list container metadata
- resp, _ = self.container_client.list_container_metadata(
- self.container_name)
- self.assertEqual(resp['status'], '204')
- self.assertIn('x-container-write', resp)
- self.assertEqual(resp['x-container-write'], '-*')
-
- object_name = rand_name(name='Object')
- data = arbitrary_string(size=len(object_name),
- base_text=object_name)
- headers = {'Content-Type': 'application/json',
- 'Accept': 'application/json'}
- # create object as anonymous user
- resp, body = self.custom_object_client.create_object(
- self.container_name, object_name, data, metadata=headers)
- self.assertEqual(resp['status'], '201')
-
- except Exception as e:
- self.fail("Failed to create public writable object without using"
- " creds raised exception is %s" % e)
-
- @testtools.skip('Until Bug #1020722 is resolved.')
- @attr(type='smoke')
- def test_write_public_with_another_user_creds(self):
- # make container public-writable, and create object with another user's
- # credentials
- try:
- # update container metadata to make it publicly writable
- cont_headers = {'X-Container-Write': '-*'}
- resp_meta, body = self.container_client.update_container_metadata(
- self.container_name, metadata=cont_headers,
- metadata_prefix='')
- self.assertEqual(resp_meta['status'], '204')
- # list container metadata
- resp, _ = self.container_client.list_container_metadata(
- self.container_name)
- self.assertEqual(resp['status'], '204')
- self.assertIn('x-container-write', resp)
- self.assertEqual(resp['x-container-write'], '-*')
-
- # trying to get auth token of alternative user
- token = self.identity_client_alt.get_auth()
- headers = {'Content-Type': 'application/json',
- 'Accept': 'application/json',
- 'X-Auth-Token': token}
-
- # trying to create an object with another user's creds
- object_name = rand_name(name='Object')
- data = arbitrary_string(size=len(object_name),
- base_text=object_name)
- resp, body = self.custom_object_client.create_object(
- self.container_name, object_name, data, metadata=headers)
- self.assertEqual(resp['status'], '201')
- except Exception as e:
- self.fail("Failed to create public writable object with another"
- " user creds raised exception is %s" % e)
diff --git a/tempest/api/object_storage/test_object_version.py b/tempest/api/object_storage/test_object_version.py
index d449c0a..cda3e4f 100644
--- a/tempest/api/object_storage/test_object_version.py
+++ b/tempest/api/object_storage/test_object_version.py
@@ -28,15 +28,7 @@
@classmethod
def tearDownClass(cls):
- for container in cls.containers:
- objlist = \
- cls.container_client.list_all_container_objects(container)
- # delete every object in the container
- for obj in objlist:
- resp, _ = \
- cls.object_client.delete_object(container, obj['name'])
- # delete the container
- resp, _ = cls.container_client.delete_container(container)
+ cls.delete_containers(cls.containers)
def assertContainer(self, container, count, byte, versioned):
resp, _ = self.container_client.list_container_metadata(container)
@@ -94,10 +86,3 @@
vers_container_name)
self.assertContainer(vers_container_name, '0', '0',
'Missing Header')
- # delete containers
- resp, _ = self.container_client.delete_container(base_container_name)
- self.assertEqual(resp['status'], '204')
- self.containers.remove(base_container_name)
- resp, _ = self.container_client.delete_container(vers_container_name)
- self.assertEqual(resp['status'], '204')
- self.containers.remove(vers_container_name)
diff --git a/tools/__init__.py b/tempest/api/orchestration/__init__.py
similarity index 100%
copy from tools/__init__.py
copy to tempest/api/orchestration/__init__.py
diff --git a/tempest/api/orchestration/base.py b/tempest/api/orchestration/base.py
new file mode 100644
index 0000000..544558e
--- /dev/null
+++ b/tempest/api/orchestration/base.py
@@ -0,0 +1,110 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import logging
+import time
+
+from tempest import clients
+from tempest.common.utils.data_utils import rand_name
+import tempest.test
+
+
+LOG = logging.getLogger(__name__)
+
+
+class BaseOrchestrationTest(tempest.test.BaseTestCase):
+ """Base test case class for all Orchestration API tests."""
+
+ @classmethod
+ def setUpClass(cls):
+
+ os = clients.OrchestrationManager()
+ cls.orchestration_cfg = os.config.orchestration
+ if not cls.orchestration_cfg.heat_available:
+ raise cls.skipException("Heat support is required")
+
+ cls.os = os
+ cls.orchestration_client = os.orchestration_client
+ cls.keypairs_client = os.keypairs_client
+ cls.stacks = []
+
+ @classmethod
+ def _get_identity_admin_client(cls):
+ """
+ Returns an instance of the Identity Admin API client
+ """
+ os = clients.AdminManager(interface=cls._interface)
+ admin_client = os.identity_client
+ return admin_client
+
+ @classmethod
+ def _get_client_args(cls):
+
+ return (
+ cls.config,
+ cls.config.identity.admin_username,
+ cls.config.identity.admin_password,
+ cls.config.identity.uri
+ )
+
+ def create_stack(self, stack_name, template_data, parameters={}):
+ resp, body = self.client.create_stack(
+ stack_name,
+ template=template_data,
+ parameters=parameters)
+ self.assertEqual('201', resp['status'])
+ stack_id = resp['location'].split('/')[-1]
+ stack_identifier = '%s/%s' % (stack_name, stack_id)
+ self.stacks.append(stack_identifier)
+ return stack_identifier
+
+ @classmethod
+ def clear_stacks(cls):
+ for stack_identifier in cls.stacks:
+ try:
+ cls.orchestration_client.delete_stack(stack_identifier)
+ except Exception:
+ pass
+
+ for stack_identifier in cls.stacks:
+ try:
+ cls.orchestration_client.wait_for_stack_status(
+ stack_identifier, 'DELETE_COMPLETE')
+ except Exception:
+ pass
+
+ def _create_keypair(self, namestart='keypair-heat-'):
+ kp_name = rand_name(namestart)
+ resp, body = self.keypairs_client.create_keypair(kp_name)
+ self.assertEqual(body['name'], kp_name)
+ return body
+
+ @classmethod
+ def tearDownClass(cls):
+ cls.clear_stacks()
+
+ def wait_for(self, condition):
+ """Repeatedly calls condition() until a timeout."""
+ start_time = int(time.time())
+ while True:
+ try:
+ condition()
+ except Exception:
+ pass
+ else:
+ return
+ if int(time.time()) - start_time >= self.build_timeout:
+ condition()
+ return
+ time.sleep(self.build_interval)
diff --git a/tools/__init__.py b/tempest/api/orchestration/stacks/__init__.py
similarity index 100%
rename from tools/__init__.py
rename to tempest/api/orchestration/stacks/__init__.py
diff --git a/tempest/api/orchestration/stacks/test_instance_cfn_init.py b/tempest/api/orchestration/stacks/test_instance_cfn_init.py
new file mode 100644
index 0000000..2349830
--- /dev/null
+++ b/tempest/api/orchestration/stacks/test_instance_cfn_init.py
@@ -0,0 +1,152 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# 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 json
+import logging
+
+from tempest.api.orchestration import base
+from tempest.common.utils.data_utils import rand_name
+from tempest.test import attr
+
+
+LOG = logging.getLogger(__name__)
+
+
+class InstanceCfnInitTestJSON(base.BaseOrchestrationTest):
+ _interface = 'json'
+
+ template = """
+HeatTemplateFormatVersion: '2012-12-12'
+Description: |
+ Template which uses a wait condition to confirm that a minimal
+ cfn-init and cfn-signal has worked
+Parameters:
+ KeyName:
+ Type: String
+ InstanceType:
+ Type: String
+ ImageId:
+ Type: String
+Resources:
+ CfnUser:
+ Type: AWS::IAM::User
+ SmokeKeys:
+ Type: AWS::IAM::AccessKey
+ Properties:
+ UserName: {Ref: CfnUser}
+ SmokeServer:
+ Type: AWS::EC2::Instance
+ Metadata:
+ AWS::CloudFormation::Init:
+ config:
+ files:
+ /tmp/smoke-status:
+ content: smoke test complete
+ /etc/cfn/cfn-credentials:
+ content:
+ Fn::Join:
+ - ''
+ - - AWSAccessKeyId=
+ - {Ref: SmokeKeys}
+ - '
+
+ '
+ - AWSSecretKey=
+ - Fn::GetAtt: [SmokeKeys, SecretAccessKey]
+ - '
+
+ '
+ mode: '000400'
+ owner: root
+ group: root
+ Properties:
+ ImageId: {Ref: ImageId}
+ InstanceType: {Ref: InstanceType}
+ KeyName: {Ref: KeyName}
+ UserData:
+ Fn::Base64:
+ Fn::Join:
+ - ''
+ - - |-
+ #!/bin/bash -v
+ /opt/aws/bin/cfn-init
+ - |-
+ || error_exit ''Failed to run cfn-init''
+ /opt/aws/bin/cfn-signal -e 0 --data "`cat /tmp/smoke-status`" '
+ - {Ref: WaitHandle}
+ - '''
+
+ '
+ WaitHandle:
+ Type: AWS::CloudFormation::WaitConditionHandle
+ WaitCondition:
+ Type: AWS::CloudFormation::WaitCondition
+ DependsOn: SmokeServer
+ Properties:
+ Handle: {Ref: WaitHandle}
+ Timeout: '600'
+Outputs:
+ WaitConditionStatus:
+ Description: Contents of /tmp/smoke-status on SmokeServer
+ Value:
+ Fn::GetAtt: [WaitCondition, Data]
+"""
+
+ @classmethod
+ def setUpClass(cls):
+ super(InstanceCfnInitTestJSON, cls).setUpClass()
+ if not cls.orchestration_cfg.image_ref:
+ raise cls.skipException("No image available to test")
+ cls.client = cls.orchestration_client
+
+ def setUp(self):
+ super(InstanceCfnInitTestJSON, self).setUp()
+ stack_name = rand_name('heat')
+ keypair_name = (self.orchestration_cfg.keypair_name or
+ self._create_keypair()['name'])
+
+ # create the stack
+ self.stack_identifier = self.create_stack(
+ stack_name,
+ self.template,
+ parameters={
+ 'KeyName': keypair_name,
+ 'InstanceType': self.orchestration_cfg.instance_type,
+ 'ImageId': self.orchestration_cfg.image_ref
+ })
+
+ @attr(type='gate')
+ def test_stack_wait_condition_data(self):
+
+ sid = self.stack_identifier
+
+ # wait for create to complete.
+ self.client.wait_for_stack_status(sid, 'CREATE_COMPLETE')
+
+ # fetch the stack
+ resp, body = self.client.get_stack(sid)
+ self.assertEqual('CREATE_COMPLETE', body['stack_status'])
+
+ # fetch the stack
+ resp, body = self.client.get_stack(sid)
+ self.assertEqual('CREATE_COMPLETE', body['stack_status'])
+
+ # This is an assert of great significance, as it means the following
+ # has happened:
+ # - cfn-init read the provided metadata and wrote out a file
+ # - a user was created and credentials written to the instance
+ # - a cfn-signal was built which was signed with provided credentials
+ # - the wait condition was fulfilled and the stack has changed state
+ wait_status = json.loads(body['outputs'][0]['output_value'])
+ self.assertEqual('smoke test complete', wait_status['00000'])
diff --git a/tempest/api/orchestration/stacks/test_stacks.py b/tempest/api/orchestration/stacks/test_stacks.py
new file mode 100644
index 0000000..5fed581
--- /dev/null
+++ b/tempest/api/orchestration/stacks/test_stacks.py
@@ -0,0 +1,76 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import logging
+
+from tempest.api.orchestration import base
+from tempest.common.utils.data_utils import rand_name
+from tempest.test import attr
+
+
+LOG = logging.getLogger(__name__)
+
+
+class StacksTestJSON(base.BaseOrchestrationTest):
+ _interface = 'json'
+
+ empty_template = "HeatTemplateFormatVersion: '2012-12-12'\n"
+
+ @classmethod
+ def setUpClass(cls):
+ super(StacksTestJSON, cls).setUpClass()
+ cls.client = cls.orchestration_client
+
+ @attr(type='smoke')
+ def test_stack_list_responds(self):
+ resp, body = self.client.list_stacks()
+ stacks = body['stacks']
+ self.assertEqual('200', resp['status'])
+ self.assertIsInstance(stacks, list)
+
+ @attr(type='smoke')
+ def test_stack_crud_no_resources(self):
+ stack_name = rand_name('heat')
+
+ # count how many stacks to start with
+ resp, body = self.client.list_stacks()
+
+ # create the stack
+ stack_identifier = self.create_stack(
+ stack_name, self.empty_template)
+ stack_id = stack_identifier.split('/')[1]
+
+ # wait for create complete (with no resources it should be instant)
+ self.client.wait_for_stack_status(stack_identifier, 'CREATE_COMPLETE')
+
+ # check for stack in list
+ resp, body = self.client.list_stacks()
+ list_ids = list([stack['id'] for stack in body['stacks']])
+ self.assertIn(stack_id, list_ids)
+
+ # fetch the stack
+ resp, body = self.client.get_stack(stack_identifier)
+ self.assertEqual('CREATE_COMPLETE', body['stack_status'])
+
+ # fetch the stack by name
+ resp, body = self.client.get_stack(stack_name)
+ self.assertEqual('CREATE_COMPLETE', body['stack_status'])
+
+ # fetch the stack by id
+ resp, body = self.client.get_stack(stack_id)
+ self.assertEqual('CREATE_COMPLETE', body['stack_status'])
+
+ # delete the stack
+ resp = self.client.delete_stack(stack_identifier)
+ self.assertEqual('204', resp[0]['status'])
diff --git a/tempest/api/volume/admin/test_multi_backend.py b/tempest/api/volume/admin/test_multi_backend.py
index bbc1d97..086b981 100644
--- a/tempest/api/volume/admin/test_multi_backend.py
+++ b/tempest/api/volume/admin/test_multi_backend.py
@@ -1,8 +1,5 @@
# 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
@@ -15,12 +12,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-import logging
-import testtools
-
from tempest.api.volume import base
+from tempest.common import log as logging
from tempest.common.utils.data_utils import rand_name
-from tempest import config
from tempest.services.volume.json.admin import volume_types_client
from tempest.services.volume.json import volumes_client
from tempest.test import attr
@@ -31,66 +25,62 @@
class VolumeMultiBackendTest(base.BaseVolumeAdminTest):
_interface = "json"
- multi_backend_enabled = config.TempestConfig().volume.multi_backend_enabled
- backend1_name = config.TempestConfig().volume.backend1_name
- backend2_name = config.TempestConfig().volume.backend2_name
- backend_names_equal = False
- if (backend1_name == backend2_name):
- backend_names_equal = True
-
@classmethod
- @testtools.skipIf(not multi_backend_enabled,
- "Cinder multi-backend feature is not available")
def setUpClass(cls):
super(VolumeMultiBackendTest, cls).setUpClass()
+ if not cls.config.volume.multi_backend_enabled:
+ raise cls.skipException("Cinder multi-backend feature disabled")
+
+ cls.backend1_name = cls.config.volume.backend1_name
+ cls.backend2_name = cls.config.volume.backend2_name
adm_user = cls.config.identity.admin_username
adm_pass = cls.config.identity.admin_password
adm_tenant = cls.config.identity.admin_tenant_name
auth_url = cls.config.identity.uri
- cls.client = volumes_client.VolumesClientJSON(cls.config,
- adm_user,
- adm_pass,
- auth_url,
- adm_tenant)
- cls.client2 = volume_types_client.VolumeTypesClientJSON(cls.config,
- adm_user,
- adm_pass,
- auth_url,
- adm_tenant)
+ cls.volume_client = volumes_client.VolumesClientJSON(cls.config,
+ adm_user,
+ adm_pass,
+ auth_url,
+ adm_tenant)
+ cls.type_client = volume_types_client.VolumeTypesClientJSON(cls.config,
+ adm_user,
+ adm_pass,
+ auth_url,
+ adm_tenant)
- ## variables initialization
- type_name1 = rand_name('type-')
- type_name2 = rand_name('type-')
- cls.volume_type_list = []
-
- vol_name1 = rand_name('Volume-')
- vol_name2 = rand_name('Volume-')
+ cls.volume_type_id_list = []
cls.volume_id_list = []
-
try:
- ## Volume types creation
+ # Volume/Type creation (uses backend1_name)
+ type1_name = rand_name('Type-')
+ vol1_name = rand_name('Volume-')
extra_specs1 = {"volume_backend_name": cls.backend1_name}
- resp, cls.body1 = cls.client2.create_volume_type(
- type_name1, extra_specs=extra_specs1)
- cls.volume_type_list.append(cls.body1)
+ resp, cls.type1 = cls.type_client.create_volume_type(
+ type1_name, extra_specs=extra_specs1)
+ cls.volume_type_id_list.append(cls.type1['id'])
- extra_specs2 = {"volume_backend_name": cls.backend2_name}
- resp, cls.body2 = cls.client2.create_volume_type(
- type_name2, extra_specs=extra_specs2)
- cls.volume_type_list.append(cls.body2)
-
- ## Volumes creation
- resp, cls.volume1 = cls.client.create_volume(
- size=1, display_name=vol_name1, volume_type=type_name1)
- cls.client.wait_for_volume_status(cls.volume1['id'], 'available')
+ resp, cls.volume1 = cls.volume_client.create_volume(
+ size=1, display_name=vol1_name, volume_type=type1_name)
cls.volume_id_list.append(cls.volume1['id'])
+ cls.volume_client.wait_for_volume_status(cls.volume1['id'],
+ 'available')
- resp, cls.volume2 = cls.client.create_volume(
- size=1, display_name=vol_name2, volume_type=type_name2)
- cls.client.wait_for_volume_status(cls.volume2['id'], 'available')
- cls.volume_id_list.append(cls.volume2['id'])
+ if cls.backend1_name != cls.backend2_name:
+ # Volume/Type creation (uses backend2_name)
+ type2_name = rand_name('Type-')
+ vol2_name = rand_name('Volume-')
+ extra_specs2 = {"volume_backend_name": cls.backend2_name}
+ resp, cls.type2 = cls.type_client.create_volume_type(
+ type2_name, extra_specs=extra_specs2)
+ cls.volume_type_id_list.append(cls.type2['id'])
+
+ resp, cls.volume2 = cls.volume_client.create_volume(
+ size=1, display_name=vol2_name, volume_type=type2_name)
+ cls.volume_id_list.append(cls.volume2['id'])
+ cls.volume_client.wait_for_volume_status(cls.volume2['id'],
+ 'available')
except Exception:
LOG.exception("setup failed")
cls.tearDownClass()
@@ -100,60 +90,43 @@
def tearDownClass(cls):
## volumes deletion
for volume_id in cls.volume_id_list:
- cls.client.delete_volume(volume_id)
- cls.client.wait_for_resource_deletion(volume_id)
+ cls.volume_client.delete_volume(volume_id)
+ cls.volume_client.wait_for_resource_deletion(volume_id)
## volume types deletion
- for volume_type in cls.volume_type_list:
- cls.client2.delete_volume_type(volume_type)
+ for volume_type_id in cls.volume_type_id_list:
+ cls.type_client.delete_volume_type(volume_type_id)
super(VolumeMultiBackendTest, cls).tearDownClass()
- @attr(type=['smoke'])
- def test_multi_backend_enabled(self):
- # this test checks that multi backend is enabled for at least the
- # computes where the volumes created in setUp were made
+ @attr(type='smoke')
+ def test_backend_name_reporting(self):
+ # this test checks if os-vol-attr:host is populated correctly after
+ # the multi backend feature has been enabled
# if multi-backend is enabled: os-vol-attr:host should be like:
# host@backend_name
- # this test fails if:
- # - multi backend is not enabled
- resp, fetched_volume = self.client.get_volume(self.volume1['id'])
+ resp, volume = self.volume_client.get_volume(self.volume1['id'])
self.assertEqual(200, resp.status)
- volume_host1 = fetched_volume['os-vol-host-attr:host']
- msg = ("Multi-backend is not available for at least host "
- "%(volume_host1)s") % locals()
- self.assertTrue(len(volume_host1.split("@")) > 1, msg)
-
- resp, fetched_volume = self.client.get_volume(self.volume2['id'])
- self.assertEqual(200, resp.status)
-
- volume_host2 = fetched_volume['os-vol-host-attr:host']
- msg = ("Multi-backend is not available for at least host "
- "%(volume_host2)s") % locals()
- self.assertTrue(len(volume_host2.split("@")) > 1, msg)
+ volume1_host = volume['os-vol-host-attr:host']
+ msg = ("multi-backend reporting incorrect values for volume %s" %
+ self.volume1['id'])
+ self.assertTrue(len(volume1_host.split("@")) > 1, msg)
@attr(type='gate')
def test_backend_name_distinction(self):
- # this test checks that the two volumes created at setUp doesn't
- # belong to the same backend (if they are in the same backend, that
- # means, volume_backend_name distinction is not working properly)
- # this test fails if:
- # - tempest.conf is not well configured
- # - the two volumes belongs to the same backend
+ # this test checks that the two volumes created at setUp don't
+ # belong to the same backend (if they are, than the
+ # volume backend distinction is not working properly)
+ if self.backend1_name == self.backend2_name:
+ raise self.skipException("backends configured with same name")
- # checks tempest.conf
- msg = ("tempest.conf is not well configured, "
- "backend1_name and backend2_name are equal")
- self.assertEqual(self.backend_names_equal, False, msg)
+ resp, volume = self.volume_client.get_volume(self.volume1['id'])
+ volume1_host = volume['os-vol-host-attr:host']
- # checks the two volumes belongs to different backend
- resp, fetched_volume = self.client.get_volume(self.volume1['id'])
- volume_host1 = fetched_volume['os-vol-host-attr:host']
+ resp, volume = self.volume_client.get_volume(self.volume2['id'])
+ volume2_host = volume['os-vol-host-attr:host']
- resp, fetched_volume = self.client.get_volume(self.volume2['id'])
- volume_host2 = fetched_volume['os-vol-host-attr:host']
-
- msg = ("volume2 was created in the same backend as volume1: "
- "%(volume_host2)s.") % locals()
- self.assertNotEqual(volume_host2, volume_host1, msg)
+ msg = ("volumes %s and %s were created in the same backend" %
+ (self.volume1['id'], self.volume2['id']))
+ self.assertNotEqual(volume1_host, volume2_host, msg)
diff --git a/tempest/api/volume/admin/test_volume_types.py b/tempest/api/volume/admin/test_volume_types.py
index 74a62cb..3c4b5d8 100644
--- a/tempest/api/volume/admin/test_volume_types.py
+++ b/tempest/api/volume/admin/test_volume_types.py
@@ -38,7 +38,7 @@
auth_url,
adm_tenant)
- @attr(type=['smoke'])
+ @attr(type='smoke')
def test_volume_type_list(self):
# List Volume types.
try:
@@ -48,15 +48,17 @@
except Exception:
self.fail("Could not list volume types")
- @attr(type=['smoke'])
+ @attr(type='smoke')
def test_create_get_delete_volume_with_volume_type_and_extra_specs(self):
# Create/get/delete volume with volume_type and extra spec.
try:
volume = {}
vol_name = rand_name("volume-")
vol_type_name = rand_name("volume-type-")
- extra_specs = {"storage_protocol": "iSCSI",
- "vendor_name": "Open Source"}
+ proto = self.config.volume.storage_protocol
+ vendor = self.config.volume.vendor_name
+ extra_specs = {"storage_protocol": proto,
+ "vendor_name": vendor}
body = {}
resp, body = self.client.create_volume_type(
vol_type_name,
@@ -100,7 +102,7 @@
resp, _ = self.client.delete_volume_type(body['id'])
self.assertEqual(202, resp.status)
- @attr(type=['smoke'])
+ @attr(type='smoke')
def test_volume_type_create_delete(self):
# Create/Delete volume type.
try:
@@ -123,7 +125,7 @@
except Exception:
self.fail("Could not create a volume_type")
- @attr(type=['smoke'])
+ @attr(type='smoke')
def test_volume_type_create_get(self):
# Create/get volume type.
try:
diff --git a/tempest/api/volume/admin/test_volume_types_extra_specs.py b/tempest/api/volume/admin/test_volume_types_extra_specs.py
index 20c5cc4..417f296 100644
--- a/tempest/api/volume/admin/test_volume_types_extra_specs.py
+++ b/tempest/api/volume/admin/test_volume_types_extra_specs.py
@@ -34,7 +34,7 @@
cls.client.delete_volume_type(cls.volume_type['id'])
super(VolumeTypesExtraSpecsTest, cls).tearDownClass()
- @attr(type=['smoke'])
+ @attr(type='smoke')
def test_volume_type_extra_specs_list(self):
# List Volume types extra specs.
try:
@@ -53,7 +53,7 @@
except Exception:
self.fail("Could not list volume types extra specs")
- @attr(type=['gate'])
+ @attr(type='gate')
def test_volume_type_extra_specs_update(self):
# Update volume type extra specs
try:
@@ -77,7 +77,7 @@
except Exception:
self.fail("Couldnt update volume type extra spec")
- @attr(type=['smoke'])
+ @attr(type='smoke')
def test_volume_type_extra_spec_create_get_delete(self):
# Create/Get/Delete volume type extra spec.
try:
diff --git a/tempest/api/volume/base.py b/tempest/api/volume/base.py
index 978ec53..fc510cb 100644
--- a/tempest/api/volume/base.py
+++ b/tempest/api/volume/base.py
@@ -15,10 +15,10 @@
# License for the specific language governing permissions and limitations
# under the License.
-import logging
import time
from tempest import clients
+from tempest.common import log as logging
from tempest.common.utils.data_utils import rand_name
from tempest import exceptions
import tempest.test
@@ -128,9 +128,9 @@
resp, snapshot = cls.snapshots_client.create_snapshot(volume_id,
**kwargs)
assert 200 == resp.status
+ cls.snapshots.append(snapshot)
cls.snapshots_client.wait_for_snapshot_status(snapshot['id'],
'available')
- cls.snapshots.append(snapshot)
return snapshot
#NOTE(afazekas): these create_* and clean_* could be defined
@@ -141,8 +141,8 @@
"""Wrapper utility that returns a test volume."""
resp, volume = cls.volumes_client.create_volume(size, **kwargs)
assert 200 == resp.status
- cls.volumes_client.wait_for_volume_status(volume['id'], 'available')
cls.volumes.append(volume)
+ cls.volumes_client.wait_for_volume_status(volume['id'], 'available')
return volume
@classmethod
diff --git a/tempest/api/volume/test_volumes_actions.py b/tempest/api/volume/test_volumes_actions.py
index 41fd930..cd5ab34 100644
--- a/tempest/api/volume/test_volumes_actions.py
+++ b/tempest/api/volume/test_volumes_actions.py
@@ -52,7 +52,7 @@
super(VolumesActionsTest, cls).tearDownClass()
- @attr(type=['smoke'])
+ @attr(type='smoke')
def test_attach_detach_volume_to_instance(self):
# Volume is attached and detached successfully from an instance
try:
diff --git a/tempest/api/volume/test_volumes_get.py b/tempest/api/volume/test_volumes_get.py
index 0148183..eda7153 100644
--- a/tempest/api/volume/test_volumes_get.py
+++ b/tempest/api/volume/test_volumes_get.py
@@ -21,7 +21,6 @@
class VolumesGetTest(base.BaseVolumeTest):
-
_interface = "json"
@classmethod
@@ -29,22 +28,17 @@
super(VolumesGetTest, cls).setUpClass()
cls.client = cls.volumes_client
- def _volume_create_get_delete(self, image_ref=None):
+ def _volume_create_get_delete(self, **kwargs):
# Create a volume, Get it's details and Delete the volume
try:
volume = {}
- v_name = rand_name('Volume-')
- metadata = {'Type': 'work'}
+ v_name = rand_name('Volume')
+ metadata = {'Type': 'Test'}
#Create a volume
- if not image_ref:
- resp, volume = self.client.create_volume(size=1,
- display_name=v_name,
- metadata=metadata)
- else:
- resp, volume = self.client.create_volume(size=1,
- display_name=v_name,
- metadata=metadata,
- imageRef=image_ref)
+ resp, volume = self.client.create_volume(size=1,
+ display_name=v_name,
+ metadata=metadata,
+ **kwargs)
self.assertEqual(200, resp.status)
self.assertTrue('id' in volume)
self.assertTrue('display_name' in volume)
@@ -105,13 +99,19 @@
self.assertEqual(202, resp.status)
self.client.wait_for_resource_deletion(volume['id'])
- @attr(type=['smoke'])
+ @attr(type='smoke')
def test_volume_create_get_delete(self):
- self._volume_create_get_delete(image_ref=None)
+ self._volume_create_get_delete()
- @attr(type=['smoke'])
- def test_volume_from_image(self):
- self._volume_create_get_delete(image_ref=self.config.compute.image_ref)
+ @attr(type='smoke')
+ def test_volume_create_get_delete_from_image(self):
+ self._volume_create_get_delete(imageRef=self.config.compute.image_ref)
+
+ @attr(type='gate')
+ def test_volume_create_get_delete_as_clone(self):
+ origin = self.create_volume(size=1,
+ display_name="Volume Origin")
+ self._volume_create_get_delete(source_volid=origin['id'])
class VolumesGetTestXML(VolumesGetTest):
diff --git a/tempest/api/volume/test_volumes_list.py b/tempest/api/volume/test_volumes_list.py
index 3202662..5d5fd7e 100644
--- a/tempest/api/volume/test_volumes_list.py
+++ b/tempest/api/volume/test_volumes_list.py
@@ -76,7 +76,7 @@
cls.client.wait_for_resource_deletion(volid)
super(VolumesListTest, cls).tearDownClass()
- @attr(type=['smoke'])
+ @attr(type='smoke')
def test_volume_list(self):
# Get a list of Volumes
# Fetch all volumes
diff --git a/tempest/api/volume/test_volumes_snapshots.py b/tempest/api/volume/test_volumes_snapshots.py
index c05a6d1..602209a 100644
--- a/tempest/api/volume/test_volumes_snapshots.py
+++ b/tempest/api/volume/test_volumes_snapshots.py
@@ -12,9 +12,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-import logging
-
from tempest.api.volume import base
+from tempest.common import log as logging
+from tempest.common.utils.data_utils import rand_name
from tempest.test import attr
LOG = logging.getLogger(__name__)
@@ -37,27 +37,39 @@
def tearDownClass(cls):
super(VolumesSnapshotTest, cls).tearDownClass()
- @attr(type=['smoke'])
- def test_snapshot_create_get_delete(self):
- # Create a snapshot, get some of the details and then deletes it
- resp, snapshot = self.snapshots_client.create_snapshot(
- self.volume_origin['id'])
- self.assertEqual(200, resp.status)
- self.snapshots_client.wait_for_snapshot_status(snapshot['id'],
- 'available')
- errmsg = "Referred volume origin ID mismatch"
- self.assertEqual(self.volume_origin['id'],
- snapshot['volume_id'],
- errmsg)
- self.snapshots_client.delete_snapshot(snapshot['id'])
- self.snapshots_client.wait_for_resource_deletion(snapshot['id'])
+ @attr(type='gate')
+ def test_snapshot_create_get_list_delete(self):
+ # Create a snapshot
+ s_name = rand_name('snap')
+ snapshot = self.create_snapshot(self.volume_origin['id'],
+ display_name=s_name)
- @attr(type=['smoke'])
+ # Get the snap and check for some of its details
+ resp, snap_get = self.snapshots_client.get_snapshot(snapshot['id'])
+ self.assertEqual(200, resp.status)
+ self.assertEqual(self.volume_origin['id'],
+ snap_get['volume_id'],
+ "Referred volume origin mismatch")
+
+ # Compare also with the output from the list action
+ tracking_data = (snapshot['id'], snapshot['display_name'])
+ resp, snaps_list = self.snapshots_client.list_snapshots()
+ self.assertEqual(200, resp.status)
+ snaps_data = [(f['id'], f['display_name']) for f in snaps_list]
+ self.assertIn(tracking_data, snaps_data)
+
+ # Delete the snapshot
+ self.snapshots_client.delete_snapshot(snapshot['id'])
+ self.assertEqual(200, resp.status)
+ self.snapshots_client.wait_for_resource_deletion(snapshot['id'])
+ self.snapshots.remove(snapshot)
+
+ @attr(type='gate')
def test_volume_from_snapshot(self):
# Create a temporary snap using wrapper method from base, then
# create a snap based volume, check resp code and deletes it
snapshot = self.create_snapshot(self.volume_origin['id'])
- # NOTE: size is required also when passing snapshot_id
+ # NOTE(gfidente): size is required also when passing snapshot_id
resp, volume = self.volumes_client.create_volume(
size=1,
snapshot_id=snapshot['id'])
diff --git a/tempest/cli/README.rst b/tempest/cli/README.rst
index 4742d4a..76b05a3 100644
--- a/tempest/cli/README.rst
+++ b/tempest/cli/README.rst
@@ -1,16 +1,16 @@
Tempest Guide to CLI tests
-========
+==========================
What are these tests?
----------
+---------------------
The cli tests test the various OpenStack command line interface tools
to ensure that they minimally function. The current scope is read only
operations on a cloud that are hard to test via unit tests.
Why are these tests in tempest?
----------
+-------------------------------
These tests exist here because it is extremely difficult to build a
functional enough environment in the python-*client unit tests to
provide this kind of testing. Because we already put up a cloud in the
@@ -20,14 +20,14 @@
Scope of these tests
----------
+--------------------
This should stay limited to the scope of testing the cli. Functional
testing of the cloud should be elsewhere, this is about exercising the
cli code.
Example of a good test
----------
+----------------------
Tests should be isolated to a single command in one of the python
clients.
diff --git a/tempest/cli/__init__.py b/tempest/cli/__init__.py
index 413990d..353607b 100644
--- a/tempest/cli/__init__.py
+++ b/tempest/cli/__init__.py
@@ -109,7 +109,7 @@
else:
with open('/dev/null', 'w') as devnull:
result = self.check_output(cmd, stderr=devnull)
- except subprocess.CalledProcessError, e:
+ except subprocess.CalledProcessError as e:
LOG.error("command output:\n%s" % e.output)
raise
return result
diff --git a/tempest/cli/simple_read_only/test_keystone.py b/tempest/cli/simple_read_only/test_keystone.py
index 067f58c..45d519b 100644
--- a/tempest/cli/simple_read_only/test_keystone.py
+++ b/tempest/cli/simple_read_only/test_keystone.py
@@ -107,3 +107,14 @@
def test_admin_bashcompletion(self):
self.keystone('bash-completion')
+
+ # Optional arguments:
+
+ def test_admin_version(self):
+ self.keystone('', flags='--version')
+
+ def test_admin_debug_list(self):
+ self.keystone('catalog', flags='--debug')
+
+ def test_admin_timeout(self):
+ self.keystone('catalog', flags='--timeout 15')
diff --git a/tempest/clients.py b/tempest/clients.py
index 037a1c4..a5c7b4d 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -15,8 +15,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-import logging
-
+from tempest.common import log as logging
from tempest import config
from tempest import exceptions
from tempest.services import botoclients
@@ -31,6 +30,8 @@
from tempest.services.compute.json.floating_ips_client import \
FloatingIPsClientJSON
from tempest.services.compute.json.hosts_client import HostsClientJSON
+from tempest.services.compute.json.hypervisor_client import \
+ HypervisorClientJSON
from tempest.services.compute.json.images_client import ImagesClientJSON
from tempest.services.compute.json.interfaces_client import \
InterfacesClientJSON
@@ -41,6 +42,8 @@
SecurityGroupsClientJSON
from tempest.services.compute.json.servers_client import ServersClientJSON
from tempest.services.compute.json.services_client import ServicesClientJSON
+from tempest.services.compute.json.tenant_usages_client import \
+ TenantUsagesClientJSON
from tempest.services.compute.json.volumes_extensions_client import \
VolumesExtensionsClientJSON
from tempest.services.compute.xml.aggregates_client import AggregatesClientXML
@@ -51,6 +54,7 @@
from tempest.services.compute.xml.flavors_client import FlavorsClientXML
from tempest.services.compute.xml.floating_ips_client import \
FloatingIPsClientXML
+from tempest.services.compute.xml.hypervisor_client import HypervisorClientXML
from tempest.services.compute.xml.images_client import ImagesClientXML
from tempest.services.compute.xml.interfaces_client import \
InterfacesClientXML
@@ -61,6 +65,8 @@
import SecurityGroupsClientXML
from tempest.services.compute.xml.servers_client import ServersClientXML
from tempest.services.compute.xml.services_client import ServicesClientXML
+from tempest.services.compute.xml.tenant_usages_client import \
+ TenantUsagesClientXML
from tempest.services.compute.xml.volumes_extensions_client import \
VolumesExtensionsClientXML
from tempest.services.identity.json.identity_client import IdentityClientJSON
@@ -69,11 +75,13 @@
EndPointClientJSON
from tempest.services.identity.v3.json.identity_client import \
IdentityV3ClientJSON
+from tempest.services.identity.v3.json.policy_client import PolicyClientJSON
from tempest.services.identity.v3.json.service_client import \
ServiceClientJSON
from tempest.services.identity.v3.xml.endpoints_client import EndPointClientXML
from tempest.services.identity.v3.xml.identity_client import \
IdentityV3ClientXML
+from tempest.services.identity.v3.xml.policy_client import PolicyClientXML
from tempest.services.identity.v3.xml.service_client import \
ServiceClientXML
from tempest.services.identity.xml.identity_client import IdentityClientXML
@@ -216,6 +224,21 @@
"xml": ServicesClientXML,
}
+TENANT_USAGES_CLIENT = {
+ "json": TenantUsagesClientJSON,
+ "xml": TenantUsagesClientXML,
+}
+
+POLICY_CLIENT = {
+ "json": PolicyClientJSON,
+ "xml": PolicyClientXML,
+}
+
+HYPERVISOR_CLIENT = {
+ "json": HypervisorClientJSON,
+ "xml": HypervisorClientXML,
+}
+
class Manager(object):
@@ -249,13 +272,18 @@
raise exceptions.InvalidConfiguration(msg)
self.auth_url = self.config.identity.uri
+ self.auth_url_v3 = self.config.identity.uri_v3
- if self.config.identity.strategy == 'keystone':
- client_args = (self.config, self.username, self.password,
- self.auth_url, self.tenant_name)
+ client_args = (self.config, self.username, self.password,
+ self.auth_url, self.tenant_name)
+
+ if self.auth_url_v3:
+ auth_version = 'v3'
+ client_args_v3_auth = (self.config, self.username,
+ self.password, self.auth_url_v3,
+ self.tenant_name, auth_version)
else:
- client_args = (self.config, self.username, self.password,
- self.auth_url)
+ client_args_v3_auth = None
try:
self.servers_client = SERVERS_CLIENTS[interface](*client_args)
@@ -287,6 +315,17 @@
self.service_client = SERVICE_CLIENT[interface](*client_args)
self.aggregates_client = AGGREGATES_CLIENT[interface](*client_args)
self.services_client = SERVICES_CLIENT[interface](*client_args)
+ self.tenant_usages_client = \
+ TENANT_USAGES_CLIENT[interface](*client_args)
+ self.policy_client = POLICY_CLIENT[interface](*client_args)
+ self.hypervisor_client = HYPERVISOR_CLIENT[interface](*client_args)
+
+ if client_args_v3_auth:
+ self.servers_client_v3_auth = SERVERS_CLIENTS[interface](
+ *client_args_v3_auth)
+ else:
+ self.servers_client_v3_auth = None
+
except KeyError:
msg = "Unsupported interface type `%s'" % interface
raise exceptions.InvalidConfiguration(msg)
diff --git a/tempest/common/glance_http.py b/tempest/common/glance_http.py
index 4ddaf17..cd33a22 100644
--- a/tempest/common/glance_http.py
+++ b/tempest/common/glance_http.py
@@ -19,7 +19,6 @@
import hashlib
import httplib
import json
-import logging
import posixpath
import re
import socket
@@ -35,6 +34,7 @@
import OpenSSL
+from tempest.common import log as logging
from tempest import exceptions as exc
@@ -304,14 +304,14 @@
if self.cert_file:
try:
self.context.use_certificate_file(self.cert_file)
- except Exception, e:
+ except Exception as e:
msg = 'Unable to load cert from "%s" %s' % (self.cert_file, e)
raise exc.SSLConfigurationError(msg)
if self.key_file is None:
# We support having key and cert in same file
try:
self.context.use_privatekey_file(self.cert_file)
- except Exception, e:
+ except Exception as e:
msg = ('No key file specified and unable to load key '
'from "%s" %s' % (self.cert_file, e))
raise exc.SSLConfigurationError(msg)
@@ -319,14 +319,14 @@
if self.key_file:
try:
self.context.use_privatekey_file(self.key_file)
- except Exception, e:
+ except Exception as e:
msg = 'Unable to load key from "%s" %s' % (self.key_file, e)
raise exc.SSLConfigurationError(msg)
if self.cacert:
try:
self.context.load_verify_locations(self.cacert)
- except Exception, e:
+ except Exception as e:
msg = 'Unable to load CA from "%s"' % (self.cacert, e)
raise exc.SSLConfigurationError(msg)
else:
diff --git a/tempest/common/log.py b/tempest/common/log.py
new file mode 100644
index 0000000..2159bfe
--- /dev/null
+++ b/tempest/common/log.py
@@ -0,0 +1,116 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 NEC Corporation.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import ConfigParser
+import inspect
+import logging
+import logging.config
+import os
+import re
+
+from oslo.config import cfg
+
+
+_DEFAULT_LOG_FORMAT = "%(asctime)s %(levelname)8s [%(name)s] %(message)s"
+_DEFAULT_LOG_DATE_FORMAT = "%Y-%m-%d %H:%M:%S"
+
+_loggers = {}
+
+
+def getLogger(name='unknown'):
+ if len(_loggers) == 0:
+ loaded = _load_log_config()
+ getLogger.adapter = TestsAdapter if loaded else None
+
+ if name not in _loggers:
+ logger = logging.getLogger(name)
+ if getLogger.adapter:
+ _loggers[name] = getLogger.adapter(logger, name)
+ else:
+ _loggers[name] = logger
+
+ return _loggers[name]
+
+
+def _load_log_config():
+ conf_dir = os.environ.get('TEMPEST_LOG_CONFIG_DIR', None)
+ conf_file = os.environ.get('TEMPEST_LOG_CONFIG', None)
+ if not conf_dir or not conf_file:
+ return False
+
+ log_config = os.path.join(conf_dir, conf_file)
+ try:
+ logging.config.fileConfig(log_config)
+ except ConfigParser.Error as exc:
+ raise cfg.ConfigFileParseError(log_config, str(exc))
+ return True
+
+
+class TestsAdapter(logging.LoggerAdapter):
+
+ def __init__(self, logger, project_name):
+ self.logger = logger
+ self.project = project_name
+ self.regexp = re.compile(r"test_\w+\.py")
+
+ def __getattr__(self, key):
+ return getattr(self.logger, key)
+
+ def _get_test_name(self):
+ frames = inspect.stack()
+ for frame in frames:
+ binary_name = frame[1]
+ if self.regexp.search(binary_name) and 'self' in frame[0].f_locals:
+ return frame[0].f_locals.get('self').id()
+ elif frame[3] == '_run_cleanups':
+ #NOTE(myamazaki): method calling addCleanup
+ return frame[0].f_locals.get('self').case.id()
+ elif frame[3] in ['setUpClass', 'tearDownClass']:
+ #NOTE(myamazaki): setUpClass or tearDownClass
+ return "%s.%s.%s" % (frame[0].f_locals['cls'].__module__,
+ frame[0].f_locals['cls'].__name__,
+ frame[3])
+ return None
+
+ def process(self, msg, kwargs):
+ if 'extra' not in kwargs:
+ kwargs['extra'] = {}
+ extra = kwargs['extra']
+
+ test_name = self._get_test_name()
+ if test_name:
+ extra.update({'testname': test_name})
+ extra['extra'] = extra.copy()
+
+ return msg, kwargs
+
+
+class TestsFormatter(logging.Formatter):
+ def __init__(self, fmt=None, datefmt=None):
+ super(TestsFormatter, self).__init__()
+ self.default_format = _DEFAULT_LOG_FORMAT
+ self.testname_format =\
+ "%(asctime)s %(levelname)8s [%(testname)s] %(message)s"
+ self.datefmt = _DEFAULT_LOG_DATE_FORMAT
+
+ def format(self, record):
+ extra = record.__dict__.get('extra', None)
+ if extra and 'testname' in extra:
+ self._fmt = self.testname_format
+ else:
+ self._fmt = self.default_format
+ return logging.Formatter.format(self, record)
diff --git a/tempest/common/rest_client.py b/tempest/common/rest_client.py
index fba3b0f..e94455d 100644
--- a/tempest/common/rest_client.py
+++ b/tempest/common/rest_client.py
@@ -1,6 +1,7 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2012 OpenStack, LLC
+# Copyright 2013 IBM Corp.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -19,11 +20,11 @@
import hashlib
import httplib2
import json
-import logging
from lxml import etree
import re
import time
+from tempest.common import log as logging
from tempest import exceptions
from tempest.services.compute.xml.common import xml_to_json
@@ -36,19 +37,20 @@
TYPE = "json"
LOG = logging.getLogger(__name__)
- def __init__(self, config, user, password, auth_url, tenant_name=None):
+ def __init__(self, config, user, password, auth_url, tenant_name=None,
+ auth_version='v2'):
self.config = config
self.user = user
self.password = password
self.auth_url = auth_url
self.tenant_name = tenant_name
+ self.auth_version = auth_version
self.service = None
self.token = None
self.base_url = None
self.region = {'compute': self.config.identity.region}
self.endpoint_url = 'publicURL'
- self.strategy = self.config.identity.strategy
self.headers = {'Content-Type': 'application/%s' % self.TYPE,
'Accept': 'application/%s' % self.TYPE}
self.build_interval = config.compute.build_interval
@@ -69,16 +71,14 @@
Sets the token and base_url used in requests based on the strategy type
"""
- if self.strategy == 'keystone':
- self.token, self.base_url = self.keystone_auth(self.user,
- self.password,
- self.auth_url,
- self.service,
- self.tenant_name)
+ if self.auth_version == 'v3':
+ auth_func = self.identity_auth_v3
else:
- self.token, self.base_url = self.basic_auth(self.user,
- self.password,
- self.auth_url)
+ auth_func = self.keystone_auth
+
+ self.token, self.base_url = (
+ auth_func(self.user, self.password, self.auth_url,
+ self.service, self.tenant_name))
def clear_auth(self):
"""
@@ -116,7 +116,7 @@
def keystone_auth(self, user, password, auth_url, service, tenant_name):
"""
- Provides authentication via Keystone.
+ Provides authentication via Keystone using v2 identity API.
"""
# Normalize URI to ensure /tokens is in it.
@@ -144,8 +144,8 @@
try:
auth_data = json.loads(resp_body)['access']
token = auth_data['token']['id']
- except Exception, e:
- print "Failed to obtain token for user: %s" % e
+ except Exception as e:
+ print("Failed to obtain token for user: %s" % e)
raise
mgmt_url = None
@@ -170,6 +170,90 @@
raise exceptions.IdentityError('Unexpected status code {0}'.format(
resp.status))
+ def identity_auth_v3(self, user, password, auth_url, service,
+ project_name, domain_id='default'):
+ """Provides authentication using Identity API v3."""
+
+ req_url = auth_url.rstrip('/') + '/auth/tokens'
+
+ creds = {
+ "auth": {
+ "identity": {
+ "methods": ["password"],
+ "password": {
+ "user": {
+ "name": user, "password": password,
+ "domain": {"id": domain_id}
+ }
+ }
+ },
+ "scope": {
+ "project": {
+ "domain": {"id": domain_id},
+ "name": project_name
+ }
+ }
+ }
+ }
+
+ headers = {'Content-Type': 'application/json'}
+ body = json.dumps(creds)
+ resp, body = self.http_obj.request(req_url, 'POST',
+ headers=headers, body=body)
+
+ if resp.status == 201:
+ try:
+ token = resp['x-subject-token']
+ except Exception:
+ self.LOG.exception("Failed to obtain token using V3"
+ " authentication (auth URL is '%s')" %
+ req_url)
+ raise
+
+ catalog = json.loads(body)['token']['catalog']
+
+ mgmt_url = None
+ for service_info in catalog:
+ if service_info['type'] != service:
+ continue # this isn't the entry for us.
+
+ endpoints = service_info['endpoints']
+
+ # Look for an endpoint in the region if configured.
+ if service in self.region:
+ region = self.region[service]
+
+ for ep in endpoints:
+ if ep['region'] != region:
+ continue
+
+ mgmt_url = ep['url']
+ # FIXME(blk-u): this isn't handling endpoint type
+ # (public, internal, admin).
+ break
+
+ if not mgmt_url:
+ # Didn't find endpoint for region, use the first.
+
+ ep = endpoints[0]
+ mgmt_url = ep['url']
+ # FIXME(blk-u): this isn't handling endpoint type
+ # (public, internal, admin).
+
+ break
+
+ return token, mgmt_url
+
+ elif resp.status == 401:
+ raise exceptions.AuthenticationFailure(user=user,
+ password=password)
+ else:
+ self.LOG.error("Failed to obtain token using V3 authentication"
+ " (auth URL is '%s'), the response status is %s" %
+ (req_url, resp.status))
+ raise exceptions.AuthenticationFailure(user=user,
+ password=password)
+
def post(self, url, body, headers):
return self.request('POST', url, headers, body)
diff --git a/tempest/config.py b/tempest/config.py
index 6d6bc2b..8795b33 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -15,12 +15,12 @@
# License for the specific language governing permissions and limitations
# under the License.
-import logging
import os
import sys
from oslo.config import cfg
+from tempest.common import log as logging
from tempest.common.utils.misc import singleton
LOG = logging.getLogger(__name__)
@@ -37,11 +37,9 @@
help="Set to True if using self-signed SSL certificates."),
cfg.StrOpt('uri',
default=None,
- help="Full URI of the OpenStack Identity API (Keystone)"),
- cfg.StrOpt('strategy',
- default='keystone',
- help="Which auth method does the environment use? "
- "(basic|keystone)"),
+ help="Full URI of the OpenStack Identity API (Keystone), v2"),
+ cfg.StrOpt('uri_v3',
+ help='Full URI of the OpenStack Identity API (Keystone), v3'),
cfg.StrOpt('region',
default='RegionOne',
help="The identity region name to use."),
@@ -118,6 +116,13 @@
cfg.IntOpt('flavor_ref_alt',
default=2,
help='Valid secondary flavor to be used in tests.'),
+ cfg.StrOpt('image_ssh_user',
+ default="root",
+ help="User name used to authenticate to an instance."),
+ cfg.StrOpt('image_alt_ssh_user',
+ default="root",
+ help="User name used to authenticate to an instance using "
+ "the alternate image."),
cfg.BoolOpt('resize_available',
default=False,
help="Does the test environment support resizing?"),
@@ -176,10 +181,12 @@
default=None,
help="Path to a private key file for SSH access to remote "
"hosts"),
- cfg.BoolOpt('disk_config_enabled_override',
+ cfg.BoolOpt('disk_config_enabled',
default=True,
- help="If false, skip config tests regardless of the "
- "extension status"),
+ help="If false, skip disk config tests"),
+ cfg.BoolOpt('flavor_extra_enabled',
+ default=True,
+ help="If false, skip flavor extra data test"),
]
@@ -316,13 +323,19 @@
help="Catalog type of the Volume Service"),
cfg.BoolOpt('multi_backend_enabled',
default=False,
- help="Runs Cinder multi-backend test (requires 2 backend)"),
+ help="Runs Cinder multi-backend test (requires 2 backends)"),
cfg.StrOpt('backend1_name',
- default='LVM_iSCSI',
+ default='BACKEND_1',
help="Name of the backend1 (must be declared in cinder.conf)"),
cfg.StrOpt('backend2_name',
- default='LVM_iSCSI_1',
+ default='BACKEND_2',
help="Name of the backend2 (must be declared in cinder.conf)"),
+ cfg.StrOpt('storage_protocol',
+ default='iSCSI',
+ help='Backend protocol to target when creating volume types'),
+ cfg.StrOpt('vendor_name',
+ default='Open Source',
+ help='Backend vendor to target when creating volume types'),
]
@@ -379,7 +392,7 @@
default=False,
help="Whether or not Heat is expected to be available"),
cfg.StrOpt('instance_type',
- default='m1.tiny',
+ default='m1.micro',
help="Instance type for tests. Needs to be big enough for a "
"full OS plus the test workload"),
cfg.StrOpt('image_ref',
@@ -486,6 +499,34 @@
conf.register_opt(opt, group='stress')
+scenario_group = cfg.OptGroup(name='scenario', title='Scenario Test Options')
+
+ScenarioGroup = [
+ cfg.StrOpt('img_dir',
+ default='/opt/stack/new/devstack/files/images/'
+ 'cirros-0.3.1-x86_64-uec',
+ help='Directory containing image files'),
+ cfg.StrOpt('ami_img_file',
+ default='cirros-0.3.1-x86_64-blank.img',
+ help='AMI image file name'),
+ cfg.StrOpt('ari_img_file',
+ default='cirros-0.3.1-x86_64-initrd',
+ help='ARI image file name'),
+ cfg.StrOpt('aki_img_file',
+ default='cirros-0.3.1-x86_64-vmlinuz',
+ help='AKI image file name'),
+ cfg.StrOpt('ssh_user',
+ default='cirros',
+ help='ssh username for the image file')
+]
+
+
+def register_scenario_opts(conf):
+ conf.register_group(scenario_group)
+ for opt in ScenarioGroup:
+ conf.register_opt(opt, group='scenario')
+
+
@singleton
class TempestConfig:
"""Provides OpenStack configuration information."""
@@ -535,6 +576,7 @@
register_boto_opts(cfg.CONF)
register_compute_admin_opts(cfg.CONF)
register_stress_opts(cfg.CONF)
+ register_scenario_opts(cfg.CONF)
self.compute = cfg.CONF.compute
self.whitebox = cfg.CONF.whitebox
self.identity = cfg.CONF.identity
@@ -546,6 +588,7 @@
self.boto = cfg.CONF.boto
self.compute_admin = cfg.CONF['compute-admin']
self.stress = cfg.CONF.stress
+ self.scenario = cfg.CONF.scenario
if not self.compute_admin.username:
self.compute_admin.username = self.identity.admin_username
self.compute_admin.password = self.identity.admin_password
diff --git a/tools/hacking/__init__.py b/tempest/hacking/__init__.py
similarity index 100%
rename from tools/hacking/__init__.py
rename to tempest/hacking/__init__.py
diff --git a/tempest/hacking/checks.py b/tempest/hacking/checks.py
new file mode 100644
index 0000000..5e941da
--- /dev/null
+++ b/tempest/hacking/checks.py
@@ -0,0 +1,69 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 IBM Corp.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import re
+
+
+PYTHON_CLIENTS = ['cinder', 'glance', 'keystone', 'nova', 'swift', 'quantum']
+
+SKIP_DECORATOR_RE = re.compile(r'\s*@testtools.skip\((.*)\)')
+SKIP_STR_RE = re.compile(r'.*Bug #\d+.*')
+PYTHON_CLIENT_RE = re.compile('import (%s)client' % '|'.join(PYTHON_CLIENTS))
+
+
+def skip_bugs(physical_line):
+ """Check skip lines for proper bug entries
+
+ T101: skips must contain "Bug #<bug_number>"
+ """
+
+ res = SKIP_DECORATOR_RE.match(physical_line)
+ if res:
+ content = res.group(1)
+ res = SKIP_STR_RE.match(content)
+ if not res:
+ return (physical_line.find(content),
+ 'T101: skips must contain "Bug #<bug_number>"')
+
+
+def import_no_clients_in_api(physical_line, filename):
+ """Check for client imports from tempest/api tests
+
+ T102: Cannot import OpenStack python clients
+ """
+
+ if "tempest/api" in filename:
+ res = PYTHON_CLIENT_RE.match(physical_line)
+ if res:
+ return (physical_line.find(res.group(1)),
+ ("T102: python clients import not allowed"
+ " in tempest/api/* tests"))
+
+
+def import_no_files_in_tests(physical_line, filename):
+ """Check for merges that try to land into tempest/tests
+
+ T103: tempest/tests directory is deprecated
+ """
+
+ if "tempest/tests" in filename:
+ return (0, ("T103: tempest/tests is deprecated"))
+
+
+def factory(register):
+ register(skip_bugs)
+ register(import_no_clients_in_api)
+ register(import_no_files_in_tests)
diff --git a/tempest/manager.py b/tempest/manager.py
index 047ad41..4a447f3 100644
--- a/tempest/manager.py
+++ b/tempest/manager.py
@@ -15,14 +15,14 @@
# License for the specific language governing permissions and limitations
# under the License.
-import logging
-
+from tempest.common import log as logging
import tempest.config
from tempest import exceptions
# Tempest REST Fuzz testing client libs
from tempest.services.compute.json import extensions_client
from tempest.services.compute.json import flavors_client
from tempest.services.compute.json import floating_ips_client
+from tempest.services.compute.json import hypervisor_client
from tempest.services.compute.json import images_client
from tempest.services.compute.json import keypairs_client
from tempest.services.compute.json import limits_client
@@ -47,6 +47,7 @@
VolumesClient = volumes_client.VolumesClientJSON
SnapshotsClient = snapshots_client.SnapshotsClientJSON
QuotasClient = quotas_client.QuotasClientJSON
+HypervisorClient = hypervisor_client.HypervisorClientJSON
LOG = logging.getLogger(__name__)
@@ -114,11 +115,8 @@
if 'tokens' not in auth_url:
auth_url = auth_url.rstrip('/') + '/tokens'
- if self.config.identity.strategy == 'keystone':
- client_args = (self.config, username, password, auth_url,
- tenant_name)
- else:
- client_args = (self.config, username, password, auth_url)
+ client_args = (self.config, username, password, auth_url,
+ tenant_name)
self.servers_client = ServersClient(*client_args)
self.flavors_client = FlavorsClient(*client_args)
@@ -133,6 +131,7 @@
self.snapshots_client = SnapshotsClient(*client_args)
self.quotas_client = QuotasClient(*client_args)
self.network_client = NetworkClient(*client_args)
+ self.hypervisor_client = HypervisorClient(*client_args)
class ComputeFuzzClientAltManager(Manager):
diff --git a/tempest/scenario/README.rst b/tempest/scenario/README.rst
index c5fa0d3..98b74e4 100644
--- a/tempest/scenario/README.rst
+++ b/tempest/scenario/README.rst
@@ -1,9 +1,9 @@
Tempest Guide to Scenario tests
-========
+===============================
What are these tests?
---------
+---------------------
Scenario tests are "through path" tests of OpenStack
function. Complicated setups where one part might depend on completion
@@ -17,13 +17,13 @@
Why are these tests in tempest?
---------
+-------------------------------
This is one of tempests core purposes, testing the integration between
projects.
Scope of these tests
---------
+--------------------
Scenario tests should always test at least 2 services in
interaction. They should use the official python client libraries for
OpenStack, as they provide a more realistic approach in how people
@@ -34,7 +34,7 @@
Example of a good test
---------
+----------------------
While we are looking for interaction of 2 or more services, be
specific in your interactions. A giant "this is my data center" smoke
test is hard to debug when it goes wrong.
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
index a358f20..366ff43 100644
--- a/tempest/scenario/manager.py
+++ b/tempest/scenario/manager.py
@@ -20,19 +20,17 @@
import subprocess
# Default client libs
+import cinderclient.client
import glanceclient
import keystoneclient.v2_0.client
import netaddr
import novaclient.client
-try:
- # TODO(sdague): is there are reason this is still optional
- from quantumclient.common import exceptions as exc
- import quantumclient.v2_0.client
+from quantumclient.common import exceptions as exc
+import quantumclient.v2_0.client
-except ImportError:
- pass
from tempest.api.network import common as net_common
+from tempest.common import ssh
from tempest.common.utils.data_utils import rand_name
from tempest import exceptions
import tempest.manager
@@ -49,6 +47,7 @@
"""
NOVACLIENT_VERSION = '2'
+ CINDERCLIENT_VERSION = '1'
def __init__(self):
super(OfficialClientManager, self).__init__()
@@ -56,11 +55,13 @@
self.image_client = self._get_image_client()
self.identity_client = self._get_identity_client()
self.network_client = self._get_network_client()
+ self.volume_client = self._get_volume_client()
self.client_attr_names = [
'compute_client',
'image_client',
'identity_client',
'network_client',
+ 'volume_client'
]
def _get_compute_client(self, username=None, password=None,
@@ -103,6 +104,22 @@
return glanceclient.Client('1', endpoint=endpoint, token=token,
insecure=dscv)
+ def _get_volume_client(self, username=None, password=None,
+ tenant_name=None):
+ if not username:
+ username = self.config.identity.username
+ if not password:
+ password = self.config.identity.password
+ if not tenant_name:
+ tenant_name = self.config.identity.tenant_name
+
+ auth_url = self.config.identity.uri
+ return cinderclient.client.Client(self.CINDERCLIENT_VERSION,
+ username,
+ password,
+ tenant_name,
+ auth_url)
+
def _get_identity_client(self, username=None, password=None,
tenant_name=None):
# This identity client is not intended to check the security
@@ -263,6 +280,11 @@
self.fail("SecurityGroup object not successfully created.")
# Add rules to the security group
+
+ # These rules are intended to permit inbound ssh and icmp
+ # traffic from all sources, so no group_id is provided.
+ # Setting a group_id would only permit traffic from ports
+ # belonging to the same security group.
rulesets = [
{
# ssh
@@ -270,7 +292,6 @@
'from_port': 22,
'to_port': 22,
'cidr': '0.0.0.0/0',
- 'group_id': secgroup.id
},
{
# ping
@@ -278,7 +299,6 @@
'from_port': -1,
'to_port': -1,
'cidr': '0.0.0.0/0',
- 'group_id': secgroup.id
}
]
for ruleset in rulesets:
@@ -420,3 +440,22 @@
# TODO(mnewby) Allow configuration of execution and sleep duration.
return tempest.test.call_until_true(ping, 20, 1)
+
+ def _is_reachable_via_ssh(self, ip_address, username, private_key,
+ timeout=120):
+ ssh_client = ssh.Client(ip_address, username,
+ pkey=private_key,
+ timeout=timeout)
+ return ssh_client.test_connection_auth()
+
+ def _check_vm_connectivity(self, ip_address, username, private_key,
+ timeout=120):
+ 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=timeout),
+ 'Auth failure in connecting to %s@%s via ssh' %
+ (username, ip_address))
diff --git a/tempest/scenario/test_minimum_basic.py b/tempest/scenario/test_minimum_basic.py
new file mode 100644
index 0000000..a55bbb2
--- /dev/null
+++ b/tempest/scenario/test_minimum_basic.py
@@ -0,0 +1,208 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 NEC Corporation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import logging
+
+from tempest.common.utils.data_utils import rand_name
+from tempest.common.utils.linux.remote_client import RemoteClient
+from tempest.scenario import manager
+
+
+LOG = logging.getLogger(__name__)
+
+
+class TestMinimumBasicScenario(manager.OfficialClientTest):
+
+ """
+ This is a basic minimum scenario test.
+
+ This test below:
+ * across the multiple components
+ * as a regular user
+ * with and without optional parameters
+ * check command outputs
+
+ """
+
+ def _wait_for_server_status(self, status):
+ server_id = self.server.id
+ self.status_timeout(
+ self.compute_client.servers, server_id, status)
+
+ def _wait_for_volume_status(self, status):
+ volume_id = self.volume.id
+ self.status_timeout(
+ self.volume_client.volumes, volume_id, status)
+
+ def _image_create(self, name, fmt, path, properties={}):
+ name = rand_name('%s-' % name)
+ image_file = open(path, 'rb')
+ self.addCleanup(image_file.close)
+ params = {
+ 'name': name,
+ 'container_format': fmt,
+ 'disk_format': fmt,
+ 'is_public': 'True',
+ }
+ params.update(properties)
+ image = self.image_client.images.create(**params)
+ self.addCleanup(self.image_client.images.delete, image)
+ self.assertEqual("queued", image.status)
+ image.update(data=image_file)
+ return image.id
+
+ def glance_image_create(self):
+ aki_img_path = self.config.scenario.img_dir + "/" + \
+ self.config.scenario.aki_img_file
+ ari_img_path = self.config.scenario.img_dir + "/" + \
+ self.config.scenario.ari_img_file
+ ami_img_path = self.config.scenario.img_dir + "/" + \
+ self.config.scenario.ami_img_file
+ LOG.debug("paths: ami: %s, ari: %s, aki: %s"
+ % (ami_img_path, ari_img_path, aki_img_path))
+ kernel_id = self._image_create('scenario-aki', 'aki', aki_img_path)
+ ramdisk_id = self._image_create('scenario-ari', 'ari', ari_img_path)
+ properties = {
+ 'properties': {'kernel_id': kernel_id, 'ramdisk_id': ramdisk_id}
+ }
+ self.image = self._image_create('scenario-ami', 'ami',
+ path=ami_img_path,
+ properties=properties)
+
+ def nova_keypair_add(self):
+ name = rand_name('scenario-keypair-')
+
+ self.keypair = self.compute_client.keypairs.create(name=name)
+ self.addCleanup(self.compute_client.keypairs.delete, self.keypair)
+ self.assertEqual(name, self.keypair.name)
+
+ def nova_boot(self):
+ name = rand_name('scenario-server-')
+ client = self.compute_client
+ flavor_id = self.config.compute.flavor_ref
+ self.server = client.servers.create(name=name, image=self.image,
+ flavor=flavor_id,
+ key_name=self.keypair.name)
+ self.addCleanup(self.compute_client.servers.delete, self.server)
+ self.assertEqual(name, self.server.name)
+ self._wait_for_server_status('ACTIVE')
+
+ def nova_list(self):
+ servers = self.compute_client.servers.list()
+ LOG.debug("server_list:%s" % servers)
+ self.assertTrue(self.server in servers)
+
+ def nova_show(self):
+ got_server = self.compute_client.servers.get(self.server)
+ LOG.debug("got server:%s" % got_server)
+ self.assertEqual(self.server, got_server)
+
+ def cinder_create(self):
+ name = rand_name('scenario-volume-')
+ LOG.debug("volume display-name:%s" % name)
+ self.volume = self.volume_client.volumes.create(size=1,
+ display_name=name)
+ LOG.debug("volume created:%s" % self.volume.display_name)
+ self._wait_for_volume_status('available')
+
+ self.addCleanup(self.volume_client.volumes.delete, self.volume)
+ self.assertEqual(name, self.volume.display_name)
+
+ def cinder_list(self):
+ volumes = self.volume_client.volumes.list()
+ self.assertTrue(self.volume in volumes)
+
+ def cinder_show(self):
+ volume = self.volume_client.volumes.get(self.volume.id)
+ self.assertEqual(self.volume, volume)
+
+ def nova_volume_attach(self):
+ attach_volume_client = self.compute_client.volumes.create_server_volume
+ volume = attach_volume_client(self.server.id,
+ self.volume.id,
+ '/dev/vdb')
+ self.assertEqual(self.volume.id, volume.id)
+ self._wait_for_volume_status('in-use')
+
+ def nova_reboot(self):
+ self.server.reboot()
+ self._wait_for_server_status('ACTIVE')
+
+ def nova_floating_ip_create(self):
+ self.floating_ip = self.compute_client.floating_ips.create()
+ self.addCleanup(self.floating_ip.delete)
+
+ def nova_floating_ip_add(self):
+ self.server.add_floating_ip(self.floating_ip)
+
+ def nova_security_group_rule_create(self):
+ sgs = self.compute_client.security_groups.list()
+ for sg in sgs:
+ if sg.name == 'default':
+ secgroup = sg
+
+ ruleset = {
+ # ssh
+ 'ip_protocol': 'tcp',
+ 'from_port': 22,
+ 'to_port': 22,
+ 'cidr': '0.0.0.0/0',
+ 'group_id': None
+ }
+ sg_rule = self.compute_client.security_group_rules.create(secgroup.id,
+ **ruleset)
+ self.addCleanup(self.compute_client.security_group_rules.delete,
+ sg_rule.id)
+
+ def ssh_to_server(self):
+ username = self.config.scenario.ssh_user
+ self.linux_client = RemoteClient(self.floating_ip.ip,
+ username,
+ pkey=self.keypair.private_key)
+
+ def check_partitions(self):
+ partitions = self.linux_client.get_partitions()
+ self.assertEqual(1, partitions.count('vdb'))
+
+ def nova_volume_detach(self):
+ detach_volume_client = self.compute_client.volumes.delete_server_volume
+ detach_volume_client(self.server.id, self.volume.id)
+ self._wait_for_volume_status('available')
+
+ volume = self.volume_client.volumes.get(self.volume.id)
+ self.assertEqual('available', volume.status)
+
+ def test_minimum_basic_scenario(self):
+ self.glance_image_create()
+ self.nova_keypair_add()
+ self.nova_boot()
+ self.nova_list()
+ self.nova_show()
+ self.cinder_create()
+ self.cinder_list()
+ self.cinder_show()
+ self.nova_volume_attach()
+ self.cinder_show()
+ self.nova_reboot()
+
+ self.nova_floating_ip_create()
+ self.nova_floating_ip_add()
+ self.nova_security_group_rule_create()
+ self.ssh_to_server()
+ self.check_partitions()
+
+ self.nova_volume_detach()
diff --git a/tempest/scenario/test_network_basic_ops.py b/tempest/scenario/test_network_basic_ops.py
index 5ccfd52..b94caaa 100644
--- a/tempest/scenario/test_network_basic_ops.py
+++ b/tempest/scenario/test_network_basic_ops.py
@@ -31,10 +31,15 @@
* For a freshly-booted VM with an IP address ("port") on a given network:
- - the Tempest host can ping the IP address. This implies that
- the VM has been assigned the correct IP address and has
+ - the Tempest host can ping the IP address. This implies, but
+ does not guarantee (see the ssh check that follows), that the
+ VM has been assigned the correct IP address and has
connectivity to the Tempest host.
+ - the Tempest host can perform key-based authentication to an
+ ssh server hosted at the IP address. This check guarantees
+ that the IP address is associated with the target VM.
+
#TODO(mnewby) - Need to implement the following:
- the Tempest host can ssh into the VM via the IP address and
successfully execute the following:
@@ -214,12 +219,15 @@
raise self.skipTest(msg)
if not self.servers:
raise self.skipTest("No VM's have been created")
+ # 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
for server in self.servers:
for net_name, ip_addresses in server.networks.iteritems():
for ip_address in ip_addresses:
- self.assertTrue(self._ping_ip_address(ip_address),
- "Timed out waiting for %s's ip to become "
- "reachable" % server.name)
+ self._check_vm_connectivity(ip_address, ssh_login,
+ private_key)
@attr(type='smoke')
def test_007_assign_floating_ips(self):
@@ -237,9 +245,11 @@
def test_008_check_public_network_connectivity(self):
if not self.floating_ips:
raise self.skipTest('No floating ips have been allocated.')
+ # 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
for server, floating_ips in self.floating_ips.iteritems():
for floating_ip in floating_ips:
ip_address = floating_ip.floating_ip_address
- self.assertTrue(self._ping_ip_address(ip_address),
- "Timed out waiting for %s's ip to become "
- "reachable" % server.name)
+ self._check_vm_connectivity(ip_address, ssh_login, private_key)
diff --git a/tempest/scenario/test_server_advanced_ops.py b/tempest/scenario/test_server_advanced_ops.py
index e48157e..6202e91 100644
--- a/tempest/scenario/test_server_advanced_ops.py
+++ b/tempest/scenario/test_server_advanced_ops.py
@@ -15,9 +15,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-import logging
-
-
+from tempest.common import log as logging
from tempest.common.utils.data_utils import rand_name
from tempest.scenario import manager
@@ -30,6 +28,7 @@
This test case stresses some advanced server instance operations:
* Resizing an instance
+ * Sequence suspend resume
"""
@classmethod
@@ -46,11 +45,6 @@
msg = "Skipping test - flavor_ref and flavor_ref_alt are identical"
raise cls.skipException(msg)
- @classmethod
- def tearDownClass(cls):
- for thing in cls.resources:
- thing.delete()
-
def test_resize_server_confirm(self):
# We create an instance for use in this test
i_name = rand_name('instance')
@@ -58,12 +52,8 @@
base_image_id = self.config.compute.image_ref
self.instance = self.compute_client.servers.create(
i_name, base_image_id, flavor_id)
- try:
- self.assertEqual(self.instance.name, i_name)
- self.set_resource('instance', self.instance)
- except AttributeError:
- self.fail("Instance not successfully created.")
-
+ self.assertEqual(self.instance.name, i_name)
+ self.set_resource('instance', self.instance)
self.assertEqual(self.instance.status, 'BUILD')
instance_id = self.get_resource('instance').id
self.status_timeout(
@@ -79,5 +69,42 @@
LOG.debug("Confirming resize of instance %s", instance_id)
instance.confirm_resize()
+
self.status_timeout(
self.compute_client.servers, instance_id, 'ACTIVE')
+
+ def test_server_sequence_suspend_resume(self):
+ # We create an instance for use in this test
+ i_name = rand_name('instance')
+ flavor_id = self.config.compute.flavor_ref
+ base_image_id = self.config.compute.image_ref
+ self.instance = self.compute_client.servers.create(
+ i_name, base_image_id, flavor_id)
+ self.assertEqual(self.instance.name, i_name)
+ self.set_resource('instance', self.instance)
+ self.assertEqual(self.instance.status, 'BUILD')
+ instance_id = self.get_resource('instance').id
+ self.status_timeout(
+ self.compute_client.servers, instance_id, 'ACTIVE')
+ instance = self.get_resource('instance')
+ instance_id = instance.id
+ LOG.debug("Suspending instance %s. Current status: %s",
+ instance_id, instance.status)
+ instance.suspend()
+ self.status_timeout(self.compute_client.servers, instance_id,
+ 'SUSPENDED')
+ LOG.debug("Resuming instance %s. Current status: %s",
+ instance_id, instance.status)
+ instance.resume()
+ self.status_timeout(self.compute_client.servers, instance_id,
+ 'ACTIVE')
+ LOG.debug("Suspending instance %s. Current status: %s",
+ instance_id, instance.status)
+ instance.suspend()
+ self.status_timeout(self.compute_client.servers, instance_id,
+ 'SUSPENDED')
+ LOG.debug("Resuming instance %s. Current status: %s",
+ instance_id, instance.status)
+ instance.resume()
+ self.status_timeout(self.compute_client.servers, instance_id,
+ 'ACTIVE')
diff --git a/tempest/scenario/test_server_basic_ops.py b/tempest/scenario/test_server_basic_ops.py
index c5c6728..d318dd9 100644
--- a/tempest/scenario/test_server_basic_ops.py
+++ b/tempest/scenario/test_server_basic_ops.py
@@ -15,8 +15,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-import logging
-
+from tempest.common import log as logging
from tempest.common.utils.data_utils import rand_name
from tempest.scenario import manager
diff --git a/tempest/services/compute/json/hypervisor_client.py b/tempest/services/compute/json/hypervisor_client.py
new file mode 100644
index 0000000..e2e5c7b
--- /dev/null
+++ b/tempest/services/compute/json/hypervisor_client.py
@@ -0,0 +1,65 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 IBM Corporation.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import json
+
+from tempest.common.rest_client import RestClient
+
+
+class HypervisorClientJSON(RestClient):
+
+ def __init__(self, config, username, password, auth_url, tenant_name=None):
+ super(HypervisorClientJSON, self).__init__(config, username,
+ password, auth_url,
+ tenant_name)
+ self.service = self.config.compute.catalog_type
+
+ def get_hypervisor_list(self):
+ """List hypervisors information."""
+ resp, body = self.get('os-hypervisors')
+ body = json.loads(body)
+ return resp, body['hypervisors']
+
+ def get_hypervisor_list_details(self):
+ """Show detailed hypervisors information."""
+ resp, body = self.get('os-hypervisors/detail')
+ body = json.loads(body)
+ return resp, body['hypervisors']
+
+ def get_hypervisor_show_details(self, hyper_id):
+ """Display the details of the specified hypervisor."""
+ resp, body = self.get('os-hypervisors/%s' % hyper_id)
+ body = json.loads(body)
+ return resp, body['hypervisor']
+
+ def get_hypervisor_servers(self, hyper_name):
+ """List instances belonging to the specified hypervisor."""
+ resp, body = self.get('os-hypervisors/%s/servers' % hyper_name)
+ body = json.loads(body)
+ return resp, body['hypervisors']
+
+ def get_hypervisor_stats(self):
+ """Get hypervisor statistics over all compute nodes."""
+ resp, body = self.get('os-hypervisors/statistics')
+ body = json.loads(body)
+ return resp, body['hypervisor_statistics']
+
+ def get_hypervisor_uptime(self, hyper_id):
+ """Display the uptime of the specified hypervisor."""
+ resp, body = self.get('os-hypervisors/%s/uptime' % hyper_id)
+ body = json.loads(body)
+ return resp, body['hypervisor']
diff --git a/tempest/services/compute/json/images_client.py b/tempest/services/compute/json/images_client.py
index 376dafc..b13d0f1 100644
--- a/tempest/services/compute/json/images_client.py
+++ b/tempest/services/compute/json/images_client.py
@@ -150,3 +150,10 @@
resp, body = self.delete("images/%s/metadata/%s" %
(str(image_id), key))
return resp, body
+
+ def is_resource_deleted(self, id):
+ try:
+ self.get_image(id)
+ except exceptions.NotFound:
+ return True
+ return False
diff --git a/tempest/services/compute/json/servers_client.py b/tempest/services/compute/json/servers_client.py
index 3569b50..d4822da 100644
--- a/tempest/services/compute/json/servers_client.py
+++ b/tempest/services/compute/json/servers_client.py
@@ -26,9 +26,11 @@
class ServersClientJSON(RestClient):
- def __init__(self, config, username, password, auth_url, tenant_name=None):
+ def __init__(self, config, username, password, auth_url, tenant_name=None,
+ auth_version='v2'):
super(ServersClientJSON, self).__init__(config, username, password,
- auth_url, tenant_name)
+ auth_url, tenant_name,
+ auth_version=auth_version)
self.service = self.config.compute.catalog_type
def create_server(self, name, image_ref, flavor_ref, **kwargs):
diff --git a/tempest/services/compute/json/tenant_usages_client.py b/tempest/services/compute/json/tenant_usages_client.py
new file mode 100644
index 0000000..4dd6964
--- /dev/null
+++ b/tempest/services/compute/json/tenant_usages_client.py
@@ -0,0 +1,47 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 NEC Corporation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import json
+import urllib
+
+from tempest.common.rest_client import RestClient
+
+
+class TenantUsagesClientJSON(RestClient):
+
+ def __init__(self, config, username, password, auth_url, tenant_name=None):
+ super(TenantUsagesClientJSON, self).__init__(
+ config, username, password, auth_url, tenant_name)
+ self.service = self.config.compute.catalog_type
+
+ def list_tenant_usages(self, params=None):
+ url = 'os-simple-tenant-usage'
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+
+ resp, body = self.get(url)
+ body = json.loads(body)
+ return resp, body['tenant_usages'][0]
+
+ def get_tenant_usage(self, tenant_id, params=None):
+ url = 'os-simple-tenant-usage/%s' % tenant_id
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+
+ resp, body = self.get(url)
+ body = json.loads(body)
+ return resp, body['tenant_usage']
diff --git a/tempest/services/compute/xml/hypervisor_client.py b/tempest/services/compute/xml/hypervisor_client.py
new file mode 100644
index 0000000..3c4f2b8
--- /dev/null
+++ b/tempest/services/compute/xml/hypervisor_client.py
@@ -0,0 +1,72 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 IBM Corporation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from lxml import etree
+
+from tempest.common.rest_client import RestClientXML
+from tempest.services.compute.xml.common import xml_to_json
+
+
+class HypervisorClientXML(RestClientXML):
+
+ def __init__(self, config, username, password, auth_url, tenant_name=None):
+ super(HypervisorClientXML, self).__init__(config, username,
+ password, auth_url,
+ tenant_name)
+ self.service = self.config.compute.catalog_type
+
+ def _parse_array(self, node):
+ return [xml_to_json(x) for x in node]
+
+ def get_hypervisor_list(self):
+ """List hypervisors information."""
+ resp, body = self.get('os-hypervisors', self.headers)
+ hypervisors = self._parse_array(etree.fromstring(body))
+ return resp, hypervisors
+
+ def get_hypervisor_list_details(self):
+ """Show detailed hypervisors information."""
+ resp, body = self.get('os-hypervisors/detail', self.headers)
+ hypervisors = self._parse_array(etree.fromstring(body))
+ return resp, hypervisors
+
+ def get_hypervisor_show_details(self, hyper_id):
+ """Display the details of the specified hypervisor."""
+ resp, body = self.get('os-hypervisors/%s' % hyper_id,
+ self.headers)
+ hypervisor = xml_to_json(etree.fromstring(body))
+ return resp, hypervisor
+
+ def get_hypervisor_servers(self, hyper_name):
+ """List instances belonging to the specified hypervisor."""
+ resp, body = self.get('os-hypervisors/%s/servers' % hyper_name,
+ self.headers)
+ hypervisors = self._parse_array(etree.fromstring(body))
+ return resp, hypervisors
+
+ def get_hypervisor_stats(self):
+ """Get hypervisor statistics over all compute nodes."""
+ resp, body = self.get('os-hypervisors/statistics', self.headers)
+ stats = xml_to_json(etree.fromstring(body))
+ return resp, stats
+
+ def get_hypervisor_uptime(self, hyper_id):
+ """Display the uptime of the specified hypervisor."""
+ resp, body = self.get('os-hypervisors/%s/uptime' % hyper_id,
+ self.headers)
+ uptime = xml_to_json(etree.fromstring(body))
+ return resp, uptime
diff --git a/tempest/services/compute/xml/images_client.py b/tempest/services/compute/xml/images_client.py
index c7e337b..cc13aa1 100644
--- a/tempest/services/compute/xml/images_client.py
+++ b/tempest/services/compute/xml/images_client.py
@@ -226,3 +226,10 @@
"""Deletes a single image metadata key/value pair."""
return self.delete("images/%s/metadata/%s" % (str(image_id), key),
self.headers)
+
+ def is_resource_deleted(self, id):
+ try:
+ self.get_image(id)
+ except exceptions.NotFound:
+ return True
+ return False
diff --git a/tempest/services/compute/xml/security_groups_client.py b/tempest/services/compute/xml/security_groups_client.py
index 08b381c..5d86790 100644
--- a/tempest/services/compute/xml/security_groups_client.py
+++ b/tempest/services/compute/xml/security_groups_client.py
@@ -97,29 +97,20 @@
group_id : ID of the Source group
"""
group_rule = Element("security_group_rule")
- parent_group = Element("parent_group_id")
- parent_group.append(Text(content=parent_group_id))
- ip_protocol = Element("ip_protocol")
- ip_protocol.append(Text(content=ip_proto))
- from_port_num = Element("from_port")
- from_port_num.append(Text(content=str(from_port)))
- to_port_num = Element("to_port")
- to_port_num.append(Text(content=str(to_port)))
- cidr = kwargs.get('cidr')
- if cidr is not None:
- cidr_num = Element("cidr")
- cidr_num.append(Text(content=cidr))
+ elements = dict()
+ elements['cidr'] = kwargs.get('cidr')
+ elements['group_id'] = kwargs.get('group_id')
+ elements['parent_group_id'] = parent_group_id
+ elements['ip_protocol'] = ip_proto
+ elements['from_port'] = from_port
+ elements['to_port'] = to_port
- group_id = kwargs.get('group_id')
- if group_id is not None:
- group_id_num = Element("group_id")
- group_id_num.append(Text(content=group_id))
-
- group_rule.append(parent_group)
- group_rule.append(ip_protocol)
- group_rule.append(from_port_num)
- group_rule.append(to_port_num)
+ for k, v in elements.items():
+ if v is not None:
+ element = Element(k)
+ element.append(Text(content=str(v)))
+ group_rule.append(element)
url = 'os-security-group-rules'
resp, body = self.post(url, str(Document(group_rule)), self.headers)
diff --git a/tempest/services/compute/xml/servers_client.py b/tempest/services/compute/xml/servers_client.py
index f7e8915..1ec4df0 100644
--- a/tempest/services/compute/xml/servers_client.py
+++ b/tempest/services/compute/xml/servers_client.py
@@ -16,12 +16,12 @@
# License for the specific language governing permissions and limitations
# under the License.
-import logging
import time
import urllib
from lxml import etree
+from tempest.common import log as logging
from tempest.common.rest_client import RestClientXML
from tempest import exceptions
from tempest.services.compute.xml.common import Document
@@ -111,9 +111,11 @@
class ServersClientXML(RestClientXML):
- def __init__(self, config, username, password, auth_url, tenant_name=None):
+ def __init__(self, config, username, password, auth_url, tenant_name=None,
+ auth_version='v2'):
super(ServersClientXML, self).__init__(config, username, password,
- auth_url, tenant_name)
+ auth_url, tenant_name,
+ auth_version=auth_version)
self.service = self.config.compute.catalog_type
def _parse_key_value(self, node):
diff --git a/tempest/services/compute/xml/tenant_usages_client.py b/tempest/services/compute/xml/tenant_usages_client.py
new file mode 100644
index 0000000..cb92324
--- /dev/null
+++ b/tempest/services/compute/xml/tenant_usages_client.py
@@ -0,0 +1,54 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 NEC Corporation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import urllib
+
+from lxml import etree
+
+from tempest.common.rest_client import RestClientXML
+from tempest.services.compute.xml.common import xml_to_json
+
+
+class TenantUsagesClientXML(RestClientXML):
+
+ def __init__(self, config, username, password, auth_url, tenant_name=None):
+ super(TenantUsagesClientXML, self).__init__(config, username,
+ password, auth_url,
+ tenant_name)
+ self.service = self.config.compute.catalog_type
+
+ def _parse_array(self, node):
+ json = xml_to_json(node)
+ return json
+
+ def list_tenant_usages(self, params=None):
+ url = 'os-simple-tenant-usage'
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+
+ resp, body = self.get(url, self.headers)
+ tenant_usage = self._parse_array(etree.fromstring(body))
+ return resp, tenant_usage['tenant_usage']
+
+ def get_tenant_usage(self, tenant_id, params=None):
+ url = 'os-simple-tenant-usage/%s' % tenant_id
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+
+ resp, body = self.get(url, self.headers)
+ tenant_usage = self._parse_array(etree.fromstring(body))
+ return resp, tenant_usage
diff --git a/tempest/services/identity/v3/json/endpoints_client.py b/tempest/services/identity/v3/json/endpoints_client.py
old mode 100755
new mode 100644
diff --git a/tempest/services/identity/v3/json/identity_client.py b/tempest/services/identity/v3/json/identity_client.py
index 014df1e..adbdc83 100644
--- a/tempest/services/identity/v3/json/identity_client.py
+++ b/tempest/services/identity/v3/json/identity_client.py
@@ -160,3 +160,51 @@
(project_id, user_id, role_id), None,
self.headers)
return resp, body
+
+ def create_domain(self, name, **kwargs):
+ """Creates a domain."""
+ description = kwargs.get('description', None)
+ en = kwargs.get('enabled', True)
+ post_body = {
+ 'description': description,
+ 'enabled': en,
+ 'name': name
+ }
+ post_body = json.dumps({'domain': post_body})
+ resp, body = self.post('domains', post_body, self.headers)
+ body = json.loads(body)
+ return resp, body['domain']
+
+ def delete_domain(self, domain_id):
+ """Delete a domain."""
+ resp, body = self.delete('domains/%s' % str(domain_id))
+ return resp, body
+
+ def list_domains(self):
+ """List Domains."""
+ resp, body = self.get('domains')
+ body = json.loads(body)
+ return resp, body['domains']
+
+ def update_domain(self, domain_id, **kwargs):
+ """Updates a domain."""
+ resp, body = self.get_domain(domain_id)
+ description = kwargs.get('description', body['description'])
+ en = kwargs.get('enabled', body['enabled'])
+ name = kwargs.get('name', body['name'])
+ post_body = {
+ 'description': description,
+ 'enabled': en,
+ 'name': name
+ }
+ post_body = json.dumps({'domain': post_body})
+ resp, body = self.patch('domains/%s' % domain_id, post_body,
+ self.headers)
+ body = json.loads(body)
+ return resp, body['domain']
+
+ def get_domain(self, domain_id):
+ """Get Domain details."""
+ resp, body = self.get('domains/%s' % domain_id)
+ body = json.loads(body)
+ return resp, body['domain']
diff --git a/tempest/services/identity/v3/json/policy_client.py b/tempest/services/identity/v3/json/policy_client.py
new file mode 100644
index 0000000..27404c4
--- /dev/null
+++ b/tempest/services/identity/v3/json/policy_client.py
@@ -0,0 +1,82 @@
+# 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 json
+from urlparse import urlparse
+
+from tempest.common.rest_client import RestClient
+
+
+class PolicyClientJSON(RestClient):
+
+ def __init__(self, config, username, password, auth_url, tenant_name=None):
+ super(PolicyClientJSON, self).__init__(config, username, password,
+ auth_url, tenant_name)
+ self.service = self.config.identity.catalog_type
+ self.endpoint_url = 'adminURL'
+
+ def request(self, method, url, headers=None, body=None, wait=None):
+ """Overriding the existing HTTP request in super class rest_client."""
+ self._set_auth()
+ self.base_url = self.base_url.replace(urlparse(self.base_url).path,
+ "/v3")
+ return super(PolicyClientJSON, self).request(method, url,
+ headers=headers,
+ body=body)
+
+ def create_policy(self, blob, type):
+ """Creates a Policy."""
+ post_body = {
+ "blob": blob,
+ "type": type
+ }
+ post_body = json.dumps({'policy': post_body})
+ resp, body = self.post('policies', post_body, self.headers)
+ body = json.loads(body)
+ return resp, body['policy']
+
+ def list_policies(self):
+ """Lists the policies."""
+ resp, body = self.get('policies')
+ body = json.loads(body)
+ return resp, body['policies']
+
+ def get_policy(self, policy_id):
+ """Lists out the given policy."""
+ url = 'policies/%s' % policy_id
+ resp, body = self.get(url)
+ body = json.loads(body)
+ return resp, body['policy']
+
+ def update_policy(self, policy_id, **kwargs):
+ """Updates a policy."""
+ resp, body = self.get_policy(policy_id)
+ type = kwargs.get('type')
+ post_body = {
+ 'type': type
+ }
+ post_body = json.dumps({'policy': post_body})
+ url = 'policies/%s' % policy_id
+ resp, body = self.patch(url, post_body,
+ self.headers)
+ body = json.loads(body)
+ return resp, body['policy']
+
+ def delete_policy(self, policy_id):
+ """Deletes the policy."""
+ url = "policies/%s" % policy_id
+ return self.delete(url)
diff --git a/tempest/services/identity/v3/xml/endpoints_client.py b/tempest/services/identity/v3/xml/endpoints_client.py
old mode 100755
new mode 100644
diff --git a/tempest/services/identity/v3/xml/identity_client.py b/tempest/services/identity/v3/xml/identity_client.py
index 92151dd..708ee28 100644
--- a/tempest/services/identity/v3/xml/identity_client.py
+++ b/tempest/services/identity/v3/xml/identity_client.py
@@ -44,6 +44,14 @@
array.append(xml_to_json(child))
return array
+ def _parse_domains(self, node):
+ array = []
+ for child in node.getchildren():
+ tag_list = child.tag.split('}', 1)
+ if tag_list[1] == "domain":
+ array.append(xml_to_json(child))
+ return array
+
def _parse_array(self, node):
array = []
for child in node.getchildren():
@@ -185,3 +193,51 @@
resp, body = self.put('projects/%s/users/%s/roles/%s' %
(project_id, user_id, role_id), '', self.headers)
return resp, body
+
+ def create_domain(self, name, **kwargs):
+ """Creates a domain."""
+ description = kwargs.get('description', None)
+ en = kwargs.get('enabled', True)
+ post_body = Element("domain",
+ xmlns=XMLNS,
+ name=name,
+ description=description,
+ enabled=str(en).lower())
+ resp, body = self.post('domains', str(Document(post_body)),
+ self.headers)
+ body = self._parse_body(etree.fromstring(body))
+ return resp, body
+
+ def list_domains(self):
+ """Get the list of domains."""
+ resp, body = self.get("domains", self.headers)
+ body = self._parse_domains(etree.fromstring(body))
+ return resp, body
+
+ def delete_domain(self, domain_id):
+ """Delete a domain."""
+ resp, body = self.delete('domains/%s' % domain_id, self.headers)
+ return resp, body
+
+ def update_domain(self, domain_id, **kwargs):
+ """Updates a domain."""
+ resp, body = self.get_domain(domain_id)
+ description = kwargs.get('description', body['description'])
+ en = kwargs.get('enabled', body['enabled'])
+ name = kwargs.get('name', body['name'])
+ post_body = Element("domain",
+ xmlns=XMLNS,
+ name=name,
+ description=description,
+ enabled=str(en).lower())
+ resp, body = self.patch('domains/%s' % domain_id,
+ str(Document(post_body)),
+ self.headers)
+ body = self._parse_body(etree.fromstring(body))
+ return resp, body
+
+ def get_domain(self, domain_id):
+ """Get Domain details."""
+ resp, body = self.get('domains/%s' % domain_id, self.headers)
+ body = self._parse_body(etree.fromstring(body))
+ return resp, body
diff --git a/tempest/services/identity/v3/xml/policy_client.py b/tempest/services/identity/v3/xml/policy_client.py
new file mode 100644
index 0000000..c3f6d99
--- /dev/null
+++ b/tempest/services/identity/v3/xml/policy_client.py
@@ -0,0 +1,97 @@
+# 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.
+
+from urlparse import urlparse
+
+import httplib2
+from lxml import etree
+
+from tempest.common.rest_client import RestClientXML
+from tempest.services.compute.xml.common import Document
+from tempest.services.compute.xml.common import Element
+from tempest.services.compute.xml.common import xml_to_json
+
+XMLNS = "http://docs.openstack.org/identity/api/v3"
+
+
+class PolicyClientXML(RestClientXML):
+
+ def __init__(self, config, username, password, auth_url, tenant_name=None):
+ super(PolicyClientXML, self).__init__(config, username, password,
+ auth_url, tenant_name)
+ self.service = self.config.identity.catalog_type
+ self.endpoint_url = 'adminURL'
+
+ def _parse_array(self, node):
+ array = []
+ for child in node.getchildren():
+ tag_list = child.tag.split('}', 1)
+ if tag_list[1] == "policy":
+ array.append(xml_to_json(child))
+ return array
+
+ def _parse_body(self, body):
+ json = xml_to_json(body)
+ return json
+
+ def request(self, method, url, headers=None, body=None, wait=None):
+ """Overriding the existing HTTP request in super class RestClient."""
+ dscv = self.config.identity.disable_ssl_certificate_validation
+ self.http_obj = httplib2.Http(disable_ssl_certificate_validation=dscv)
+ self._set_auth()
+ self.base_url = self.base_url.replace(urlparse(self.base_url).path,
+ "/v3")
+ return super(PolicyClientXML, self).request(method, url,
+ headers=headers,
+ body=body)
+
+ def create_policy(self, blob, type):
+ """Creates a Policy."""
+ create_policy = Element("policy", xmlns=XMLNS, blob=blob, type=type)
+ resp, body = self.post('policies', str(Document(create_policy)),
+ self.headers)
+ body = self._parse_body(etree.fromstring(body))
+ return resp, body
+
+ def list_policies(self):
+ """Lists the policies."""
+ resp, body = self.get('policies', self.headers)
+ body = self._parse_array(etree.fromstring(body))
+ return resp, body
+
+ def get_policy(self, policy_id):
+ """Lists out the given policy."""
+ url = 'policies/%s' % policy_id
+ resp, body = self.get(url, self.headers)
+ body = self._parse_body(etree.fromstring(body))
+ return resp, body
+
+ def update_policy(self, policy_id, **kwargs):
+ """Updates a policy."""
+ resp, body = self.get_policy(policy_id)
+ type = kwargs.get('type')
+ update_policy = Element("policy", xmlns=XMLNS, type=type)
+ url = 'policies/%s' % policy_id
+ resp, body = self.patch(url, str(Document(update_policy)),
+ self.headers)
+ body = self._parse_body(etree.fromstring(body))
+ return resp, body
+
+ def delete_policy(self, policy_id):
+ """Deletes the policy."""
+ url = "policies/%s" % policy_id
+ return self.delete(url)
diff --git a/tempest/services/image/v1/json/image_client.py b/tempest/services/image/v1/json/image_client.py
index a8fab7f..dac77a2 100644
--- a/tempest/services/image/v1/json/image_client.py
+++ b/tempest/services/image/v1/json/image_client.py
@@ -18,12 +18,12 @@
import copy
import errno
import json
-import logging
import os
import time
import urllib
from tempest.common import glance_http
+from tempest.common import log as logging
from tempest.common.rest_client import RestClient
from tempest import exceptions
@@ -88,7 +88,7 @@
obj_size = obj.tell()
obj.seek(0)
return obj_size
- except IOError, e:
+ except IOError as e:
if e.errno == errno.ESPIPE:
# Illegal seek. This means the user is trying
# to pipe image data to the client, e.g.
diff --git a/tempest/services/object_storage/object_client.py b/tempest/services/object_storage/object_client.py
index 69df472..c894612 100644
--- a/tempest/services/object_storage/object_client.py
+++ b/tempest/services/object_storage/object_client.py
@@ -34,8 +34,11 @@
def create_object(self, container, object_name, data):
"""Create storage object."""
+ headers = dict(self.headers)
+ if not data:
+ headers['content-length'] = '0'
url = "%s/%s" % (str(container), str(object_name))
- resp, body = self.put(url, data, self.headers)
+ resp, body = self.put(url, data, headers)
return resp, body
def update_object(self, container, object_name, data):
@@ -194,6 +197,8 @@
for key in metadata:
headers[str(key)] = metadata[key]
+ if not data:
+ headers['content-length'] = '0'
url = "%s/%s" % (str(container), str(object_name))
resp, body = self.put(url, data, headers=headers)
return resp, body
diff --git a/tempest/services/volume/json/snapshots_client.py b/tempest/services/volume/json/snapshots_client.py
index db614f1..17f6cba 100644
--- a/tempest/services/volume/json/snapshots_client.py
+++ b/tempest/services/volume/json/snapshots_client.py
@@ -13,10 +13,10 @@
# under the License.
import json
-import logging
import time
import urllib
+from tempest.common import log as logging
from tempest.common.rest_client import RestClient
from tempest import exceptions
diff --git a/tempest/services/volume/xml/snapshots_client.py b/tempest/services/volume/xml/snapshots_client.py
index 2209fc7..b35c43e 100644
--- a/tempest/services/volume/xml/snapshots_client.py
+++ b/tempest/services/volume/xml/snapshots_client.py
@@ -12,12 +12,12 @@
# License for the specific language governing permissions and limitations
# under the License.
-import logging
import time
import urllib
from lxml import etree
+from tempest.common import log as logging
from tempest.common.rest_client import RestClientXML
from tempest import exceptions
from tempest.services.compute.xml.common import Document
@@ -48,7 +48,10 @@
resp, body = self.get(url, self.headers)
body = etree.fromstring(body)
- return resp, xml_to_json(body)
+ snapshots = []
+ for snap in body:
+ snapshots.append(xml_to_json(snap))
+ return resp, snapshots
def list_snapshots_with_detail(self, params=None):
"""List all the details of snapshot."""
@@ -60,7 +63,9 @@
resp, body = self.get(url, self.headers)
body = etree.fromstring(body)
snapshots = []
- return resp, snapshots(xml_to_json(body))
+ for snap in body:
+ snapshots.append(xml_to_json(snap))
+ return resp, snapshots
def get_snapshot(self, snapshot_id):
"""Returns the details of a single snapshot."""
diff --git a/tempest/stress/README.rst b/tempest/stress/README.rst
index 2c431ed..661763c 100644
--- a/tempest/stress/README.rst
+++ b/tempest/stress/README.rst
@@ -1,5 +1,5 @@
-Quanta Research Cambridge OpenStack Stress Test System
-======================================================
+Tempest Field Guide to Stress Tests
+===================================
Nova is a distributed, asynchronous system that is prone to race condition
bugs. These bugs will not be easily found during
@@ -9,8 +9,8 @@
Environment
-------------
-This particular framework assumes your working Nova cluster understands Nova
+-----------
+This particular framework assumes your working Nova cluster understands Nova
API 2.0. The stress tests can read the logs from the cluster. To enable this
you have to provide the hostname to call 'nova-manage' and
the private key and user name for ssh to the cluster in the
diff --git a/tempest/stress/actions/volume_create_delete.py b/tempest/stress/actions/volume_create_delete.py
new file mode 100644
index 0000000..e0c95b5
--- /dev/null
+++ b/tempest/stress/actions/volume_create_delete.py
@@ -0,0 +1,30 @@
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from tempest.common.utils.data_utils import rand_name
+
+
+def create_delete(manager, logger):
+ while True:
+ name = rand_name("volume")
+ logger.info("creating %s" % name)
+ resp, volume = manager.volumes_client.create_volume(size=1,
+ display_name=name)
+ assert(resp.status == 200)
+ manager.volumes_client.wait_for_volume_status(volume['id'],
+ 'available')
+ logger.info("created %s" % volume['id'])
+ logger.info("deleting %s" % name)
+ resp, _ = manager.volumes_client.delete_volume(volume['id'])
+ assert(resp.status == 202)
+ manager.volumes_client.wait_for_resource_deletion(volume['id'])
+ logger.info("deleted %s" % volume['id'])
diff --git a/tempest/stress/cleanup.py b/tempest/stress/cleanup.py
index b2cb70a..3b1c871 100644
--- a/tempest/stress/cleanup.py
+++ b/tempest/stress/cleanup.py
@@ -58,3 +58,16 @@
for tenant in tenants:
if tenant['name'].startswith("stress_tenant"):
admin_manager.identity_client.delete_tenant(tenant['id'])
+
+ _, vols = admin_manager.volumes_client.list_volumes({"all_tenants": True})
+ for v in vols:
+ try:
+ admin_manager.volumes_client.delete_volume(v['id'])
+ except Exception:
+ pass
+
+ for v in vols:
+ try:
+ admin_manager.volumes_client.wait_for_resource_deletion(v['id'])
+ except Exception:
+ pass
diff --git a/tempest/stress/etc/volume-create-delete-test.json b/tempest/stress/etc/volume-create-delete-test.json
new file mode 100644
index 0000000..ed0aaeb
--- /dev/null
+++ b/tempest/stress/etc/volume-create-delete-test.json
@@ -0,0 +1,7 @@
+[{"action": "tempest.stress.actions.volume_create_delete.create_delete",
+ "threads": 4,
+ "use_admin": false,
+ "use_isolated_tenants": false,
+ "kwargs": {}
+ }
+]
diff --git a/tempest/test.py b/tempest/test.py
index c69a94c..6be37be 100644
--- a/tempest/test.py
+++ b/tempest/test.py
@@ -15,18 +15,21 @@
# License for the specific language governing permissions and limitations
# under the License.
-import logging
import time
import nose.plugins.attrib
import testresources
import testtools
+from tempest.common import log as logging
from tempest import config
from tempest import manager
LOG = logging.getLogger(__name__)
+# All the successful HTTP status codes from RFC 2616
+HTTP_SUCCESS = (200, 201, 202, 203, 204, 205, 206)
+
def attr(*args, **kwargs):
"""A decorator which applies the nose and testtools attr decorator
diff --git a/tempest/thirdparty/boto/test.py b/tempest/thirdparty/boto/test.py
index afa5c69..9ff628c 100644
--- a/tempest/thirdparty/boto/test.py
+++ b/tempest/thirdparty/boto/test.py
@@ -16,7 +16,7 @@
# under the License.
import contextlib
-import logging
+import logging as orig_logging
import os
import re
import urlparse
@@ -28,6 +28,7 @@
import keystoneclient.exceptions
import tempest.clients
+from tempest.common import log as logging
from tempest.common.utils.file_utils import have_effective_read_access
import tempest.config
from tempest import exceptions
@@ -58,7 +59,7 @@
A_I_IMAGES_READY = all_read(ami_path, aki_path, ari_path)
boto_logger = logging.getLogger('boto')
level = boto_logger.level
- boto_logger.setLevel(logging.CRITICAL) # suppress logging for these
+ boto_logger.setLevel(orig_logging.CRITICAL) # suppress logging for these
def _cred_sub_check(connection_data):
if not id_matcher.match(connection_data["aws_access_key_id"]):
diff --git a/tempest/thirdparty/boto/test_ec2_instance_run.py b/tempest/thirdparty/boto/test_ec2_instance_run.py
index e8abe97..89891d2 100644
--- a/tempest/thirdparty/boto/test_ec2_instance_run.py
+++ b/tempest/thirdparty/boto/test_ec2_instance_run.py
@@ -15,12 +15,11 @@
# License for the specific language governing permissions and limitations
# under the License.
-import logging
-
from boto import exception
import testtools
from tempest import clients
+from tempest.common import log as logging
from tempest.common.utils.data_utils import rand_name
from tempest.common.utils.linux.remote_client import RemoteClient
from tempest import exceptions
@@ -140,6 +139,7 @@
#NOTE(afazekas): doctored test case,
# with normal validation it would fail
+ @testtools.skip("Until Bug #1182679 is fixed")
@attr(type='smoke')
def test_integration_1(self):
# EC2 1. integration test (not strict)
diff --git a/tempest/thirdparty/boto/test_ec2_volumes.py b/tempest/thirdparty/boto/test_ec2_volumes.py
index edf0180..c90c586 100644
--- a/tempest/thirdparty/boto/test_ec2_volumes.py
+++ b/tempest/thirdparty/boto/test_ec2_volumes.py
@@ -15,9 +15,8 @@
# License for the specific language governing permissions and limitations
# under the License.
-import logging
-
from tempest import clients
+from tempest.common import log as logging
from tempest.test import attr
from tempest.thirdparty.boto.test import BotoTestCase
diff --git a/tempest/thirdparty/boto/test_s3_ec2_images.py b/tempest/thirdparty/boto/test_s3_ec2_images.py
index 2c0d8ae..0f836d0 100644
--- a/tempest/thirdparty/boto/test_s3_ec2_images.py
+++ b/tempest/thirdparty/boto/test_s3_ec2_images.py
@@ -72,21 +72,22 @@
retrieved_image = self.images_client.get_image(image["image_id"])
self.assertTrue(retrieved_image.name == image["name"])
self.assertTrue(retrieved_image.id == image["image_id"])
- if retrieved_image.state != "available":
+ state = retrieved_image.state
+ if state != "available":
def _state():
retr = self.images_client.get_image(image["image_id"])
return retr.state
state = state_wait(_state, "available")
self.assertEqual("available", state)
self.images_client.deregister_image(image["image_id"])
- #TODO(afazekas): double deregister ?
+ self.assertNotIn(image["image_id"], str(
+ self.images_client.get_all_images()))
self.cancelResourceCleanUp(image["cleanUp"])
- @testtools.skip("Skipped until the Bug #1074904 is resolved")
def test_register_get_deregister_aki_image(self):
# Register and deregister aki image
image = {"name": rand_name("aki-name-"),
- "location": self.bucket_name + "/" + self.ari_manifest,
+ "location": self.bucket_name + "/" + self.aki_manifest,
"type": "aki"}
image["image_id"] = self.images_client.register_image(
name=image["name"],
@@ -102,9 +103,8 @@
if retrieved_image.state != "available":
self.assertImageStateWait(retrieved_image, "available")
self.images_client.deregister_image(image["image_id"])
- #TODO(afazekas): verify deregister in a better way
- retrieved_image = self.images_client.get_image(image["image_id"])
- self.assertIn(retrieved_image.state, self.valid_image_state)
+ self.assertNotIn(image["image_id"], str(
+ self.images_client.get_all_images()))
self.cancelResourceCleanUp(image["cleanUp"])
@testtools.skip("Skipped until the Bug #1074908 and #1074904 is resolved")
diff --git a/tempest/thirdparty/boto/utils/s3.py b/tempest/thirdparty/boto/utils/s3.py
index ea9869b..a309a12 100644
--- a/tempest/thirdparty/boto/utils/s3.py
+++ b/tempest/thirdparty/boto/utils/s3.py
@@ -16,13 +16,14 @@
# under the License.
import contextlib
-import logging
import os
import re
import boto
import boto.s3.key
+from tempest.common import log as logging
+
LOG = logging.getLogger(__name__)
diff --git a/tempest/thirdparty/boto/utils/wait.py b/tempest/thirdparty/boto/utils/wait.py
index 6cd17a9..6b3ef27 100644
--- a/tempest/thirdparty/boto/utils/wait.py
+++ b/tempest/thirdparty/boto/utils/wait.py
@@ -15,13 +15,13 @@
# License for the specific language governing permissions and limitations
# under the License.
-import logging
import re
import time
import boto.exception
from testtools import TestCase
+from tempest.common import log as logging
import tempest.config
LOG = logging.getLogger(__name__)
diff --git a/tempest/whitebox/README.rst b/tempest/whitebox/README.rst
index dabf758..0e45421 100644
--- a/tempest/whitebox/README.rst
+++ b/tempest/whitebox/README.rst
@@ -1,9 +1,9 @@
Tempest Guide to Whitebox tests
-========
+===============================
What are these tests?
---------
+---------------------
When you hit the OpenStack API, this causes internal state changes in
the system. This might be database transitions, vm modifications,
@@ -20,7 +20,7 @@
Why are these tests in tempest?
---------
+-------------------------------
Especially when it comes to something like VM state changing, which is
a coordination of numerous running daemons, and a functioning VM, it's
@@ -28,7 +28,7 @@
Scope of these tests
---------
+--------------------
White box tests should be limitted to tests where black box testing
(using the OpenStack API to verify results) isn't sufficient.
@@ -40,7 +40,7 @@
Example of a good test
---------
+----------------------
Pushing VMs through a series of state transitions, and ensuring along
the way the database state transitions match what's expected.
diff --git a/tempest/whitebox/manager.py b/tempest/whitebox/manager.py
index a75edb0..3bd057c 100644
--- a/tempest/whitebox/manager.py
+++ b/tempest/whitebox/manager.py
@@ -15,7 +15,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-import logging
import os
import shlex
import subprocess
@@ -23,6 +22,7 @@
from sqlalchemy import create_engine, MetaData
+from tempest.common import log as logging
from tempest.common.ssh import Client
from tempest.common.utils.data_utils import rand_name
from tempest import exceptions
@@ -128,7 +128,7 @@
meta = MetaData()
meta.reflect(bind=engine)
- except Exception, e:
+ except Exception as e:
raise exceptions.SQLException(message=e)
return connection, meta
diff --git a/tempest/whitebox/test_images_whitebox.py b/tempest/whitebox/test_images_whitebox.py
index 8a23e8f..dc68336 100644
--- a/tempest/whitebox/test_images_whitebox.py
+++ b/tempest/whitebox/test_images_whitebox.py
@@ -18,11 +18,13 @@
from tempest.api.compute import base
from tempest.common.utils.data_utils import rand_name
from tempest import exceptions
-from tempest.test import attr
from tempest.whitebox import manager
+#TODO(afazekas): The whitebox tests are using complex testclass/manager
+# hierarchy, without a real need. It is difficult to maintain.
+# They could share more code with scenario tests.
-@attr(type='whitebox')
+
class ImagesWhiteboxTest(manager.ComputeWhiteboxTest, base.BaseComputeTest):
_interface = 'json'
@@ -36,7 +38,8 @@
@classmethod
def tearDownClass(cls):
- """Delete images after a test is executed."""
+ """Delete images and server after a test is executed."""
+ cls.servers_client.delete_server(cls.shared_server['id'])
for image_id in cls.image_ids:
cls.client.delete_image(image_id)
cls.image_ids.remove(image_id)
diff --git a/tempest/whitebox/test_servers_whitebox.py b/tempest/whitebox/test_servers_whitebox.py
index 5db0dc6..2694b95 100644
--- a/tempest/whitebox/test_servers_whitebox.py
+++ b/tempest/whitebox/test_servers_whitebox.py
@@ -17,11 +17,9 @@
from tempest.api.identity.base import BaseIdentityAdminTest
from tempest import exceptions
-from tempest.test import attr
from tempest.whitebox import manager
-@attr(type='whitebox')
class ServersWhiteboxTest(manager.ComputeWhiteboxTest):
_interface = 'json'
@@ -52,70 +50,6 @@
except exceptions.NotFound:
continue
- def test_create_server_vcpu_quota_full(self):
- # Disallow server creation when tenant's vcpu quota is full
- quotas = self.meta.tables['quotas']
- stmt = (quotas.select().
- where(quotas.c.project_id == self.tenant_id).
- where(quotas.c.resource == 'cores'))
- result = self.connection.execute(stmt).first()
-
- # Set vcpu quota for tenant if not already set
- if not result:
- cores_hard_limit = 2
- stmt = quotas.insert().values(deleted=0,
- project_id=self.tenant_id,
- resource='cores',
- hard_limit=cores_hard_limit)
-
- self.connection.execute(stmt, autocommit=True)
- else:
- cores_hard_limit = result.hard_limit
-
- # Create servers assuming 1 VCPU per instance i.e flavor_id=1
- try:
- for count in range(cores_hard_limit + 1):
- self.create_server()
- except exceptions.OverLimit:
- pass
- else:
- self.fail("Could create servers over the VCPU quota limit")
- finally:
- stmt = quotas.delete()
- self.connection.execute(stmt, autocommit=True)
-
- def test_create_server_memory_quota_full(self):
- # Disallow server creation when tenant's memory quota is full
- quotas = self.meta.tables['quotas']
- stmt = (quotas.select().
- where(quotas.c.project_id == self.tenant_id).
- where(quotas.c.resource == 'ram'))
- result = self.connection.execute(stmt).first()
-
- # Set memory quota for tenant if not already set
- if not result:
- ram_hard_limit = 1024
- stmt = quotas.insert().values(deleted=0,
- project_id=self.tenant_id,
- resource='ram',
- hard_limit=ram_hard_limit)
-
- self.connection.execute(stmt, autocommit=True)
- else:
- ram_hard_limit = result.hard_limit
-
- try:
- # Set a hard range of 3 servers for reaching the RAM quota
- for count in range(3):
- self.create_server()
- except exceptions.OverLimit:
- pass
- else:
- self.fail("Could create servers over the RAM quota limit")
- finally:
- stmt = quotas.delete()
- self.connection.execute(stmt, autocommit=True)
-
def update_state(self, server_id, vm_state, task_state, deleted=0):
"""Update states of an instance in database for validation."""
if not task_state:
diff --git a/tools/test-requires b/test-requirements.txt
similarity index 77%
rename from tools/test-requires
rename to test-requirements.txt
index 27851da..3912695 100644
--- a/tools/test-requires
+++ b/test-requirements.txt
@@ -3,8 +3,6 @@
pyflakes==0.7.2
flake8==2.0
hacking>=0.5.3,<0.6
-#
-#TODO(afazekas): ensure pg_config installed
psycopg2
# needed for doc build
sphinx>=1.1.2
diff --git a/tools/find_stack_traces.py b/tools/find_stack_traces.py
index 3129484..0ce1500 100755
--- a/tools/find_stack_traces.py
+++ b/tools/find_stack_traces.py
@@ -110,7 +110,7 @@
def usage():
- print """
+ print("""
Usage: find_stack_traces.py <logurl>
Hunts for stack traces in a devstack run. Must provide it a base log url
@@ -118,20 +118,20 @@
Returns a report listing stack traces out of the various files where
they are found.
-"""
+""")
sys.exit(0)
def print_stats(items, fname, verbose=False):
errors = len(filter(lambda x: x.level == "ERROR", items))
traces = len(filter(lambda x: x.level == "TRACE", items))
- print "%d ERRORS found in %s" % (errors, fname)
- print "%d TRACES found in %s" % (traces, fname)
+ print("%d ERRORS found in %s" % (errors, fname))
+ print("%d TRACES found in %s" % (traces, fname))
if verbose:
for item in items:
- print item
- print "\n\n"
+ print(item)
+ print("\n\n")
def main():
diff --git a/tools/hacking/tempest.py b/tools/hacking/tempest.py
deleted file mode 100644
index 1db8419..0000000
--- a/tools/hacking/tempest.py
+++ /dev/null
@@ -1,42 +0,0 @@
-# vim: tabstop=4 shiftwidth=4 softtabstop=4
-
-# Copyright 2013 IBM Corp.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-import re
-
-
-SKIP_DECORATOR = '@testtools.skip('
-
-
-def skip_bugs(physical_line):
- """Check skip lines for proper bug entries
-
- T101: Bug not in skip line
- T102: Bug in message formatted incorrectly
- """
-
- pos = physical_line.find(SKIP_DECORATOR)
-
- skip_re = re.compile(r'^\s*@testtools.skip.*')
-
- if pos != -1 and skip_re.match(physical_line):
- bug = re.compile(r'^.*\bbug\b.*', re.IGNORECASE)
- if bug.match(physical_line) is None:
- return (pos, 'T101: skips must have an associated bug')
-
- bug_re = re.compile(r'.*skip\(.*Bug\s\#\d+', re.IGNORECASE)
-
- if bug_re.match(physical_line) is None:
- return (pos, 'T102: Bug number formatted incorrectly')
diff --git a/tools/install_venv.py b/tools/install_venv.py
index 5d4b290..1664b35 100644
--- a/tools/install_venv.py
+++ b/tools/install_venv.py
@@ -62,8 +62,8 @@
def main(argv):
root = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
venv = os.path.join(root, '.venv')
- pip_requires = os.path.join(root, 'tools', 'pip-requires')
- test_requires = os.path.join(root, 'tools', 'test-requires')
+ pip_requires = os.path.join(root, 'requirements.txt')
+ test_requires = os.path.join(root, 'test-requirements.txt')
py_version = "python%s.%s" % (sys.version_info[0], sys.version_info[1])
project = 'Tempest'
install = install_venv.InstallVenv(root, venv, pip_requires, test_requires,
diff --git a/tools/install_venv_common.py b/tools/install_venv_common.py
index fd9076f..42a44e8 100644
--- a/tools/install_venv_common.py
+++ b/tools/install_venv_common.py
@@ -1,6 +1,6 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
-# Copyright 2013 OpenStack, LLC
+# Copyright 2013 OpenStack Foundation
# Copyright 2013 IBM Corp.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@@ -18,10 +18,15 @@
"""Provides methods needed by installation script for OpenStack development
virtual environments.
+Since this script is used to bootstrap a virtualenv from the system's Python
+environment, it should be kept strictly compatible with Python 2.6.
+
Synced in from openstack-common
"""
-import argparse
+from __future__ import print_function
+
+import optparse
import os
import subprocess
import sys
@@ -39,7 +44,7 @@
self.project = project
def die(self, message, *args):
- print >> sys.stderr, message % args
+ print(message % args, file=sys.stderr)
sys.exit(1)
def check_python_version(self):
@@ -86,20 +91,20 @@
virtual environment.
"""
if not os.path.isdir(self.venv):
- print 'Creating venv...',
+ print('Creating venv...', end=' ')
if no_site_packages:
self.run_command(['virtualenv', '-q', '--no-site-packages',
self.venv])
else:
self.run_command(['virtualenv', '-q', self.venv])
- print 'done.'
- print 'Installing pip in venv...',
+ print('done.')
+ print('Installing pip in venv...', end=' ')
if not self.run_command(['tools/with_venv.sh', 'easy_install',
'pip>1.0']).strip():
self.die("Failed to install pip.")
- print 'done.'
+ print('done.')
else:
- print "venv already exists..."
+ print("venv already exists...")
pass
def pip_install(self, *args):
@@ -108,7 +113,7 @@
redirect_output=False)
def install_dependencies(self):
- print 'Installing dependencies with pip (this can take a while)...'
+ print('Installing dependencies with pip (this can take a while)...')
# First things first, make sure our venv has the latest pip and
# distribute.
@@ -131,12 +136,12 @@
def parse_args(self, argv):
"""Parses command-line arguments."""
- parser = argparse.ArgumentParser()
- parser.add_argument('-n', '--no-site-packages',
- action='store_true',
- help="Do not inherit packages from global Python "
- "install")
- return parser.parse_args(argv[1:])
+ parser = optparse.OptionParser()
+ parser.add_option('-n', '--no-site-packages',
+ action='store_true',
+ help="Do not inherit packages from global Python "
+ "install")
+ return parser.parse_args(argv[1:])[0]
class Distro(InstallVenv):
@@ -150,12 +155,12 @@
return
if self.check_cmd('easy_install'):
- print 'Installing virtualenv via easy_install...',
+ print('Installing virtualenv via easy_install...', end=' ')
if self.run_command(['easy_install', 'virtualenv']):
- print 'Succeeded'
+ print('Succeeded')
return
else:
- print 'Failed'
+ print('Failed')
self.die('ERROR: virtualenv not found.\n\n%s development'
' requires virtualenv, please install it using your'
@@ -180,19 +185,16 @@
return self.run_command_with_code(['rpm', '-q', pkg],
check_exit_code=False)[1] == 0
- def yum_install(self, pkg, **kwargs):
- print "Attempting to install '%s' via yum" % pkg
- self.run_command(['sudo', 'yum', 'install', '-y', pkg], **kwargs)
-
def apply_patch(self, originalfile, patchfile):
- self.run_command(['patch', originalfile, patchfile])
+ self.run_command(['patch', '-N', originalfile, patchfile],
+ check_exit_code=False)
def install_virtualenv(self):
if self.check_cmd('virtualenv'):
return
if not self.check_pkg('python-virtualenv'):
- self.yum_install('python-virtualenv', check_exit_code=False)
+ self.die("Please install 'python-virtualenv'.")
super(Fedora, self).install_virtualenv()
@@ -205,12 +207,13 @@
This can be removed when the fix is applied upstream.
Nova: https://bugs.launchpad.net/nova/+bug/884915
- Upstream: https://bitbucket.org/which_linden/eventlet/issue/89
+ Upstream: https://bitbucket.org/eventlet/eventlet/issue/89
+ RHEL: https://bugzilla.redhat.com/958868
"""
# Install "patch" program if it's not there
if not self.check_pkg('patch'):
- self.yum_install('patch')
+ self.die("Please install 'patch'.")
# Apply the eventlet patch
self.apply_patch(os.path.join(self.venv, 'lib', self.py_version,
diff --git a/tools/skip_tracker.py b/tools/skip_tracker.py
index a4cf394..1ed6961 100755
--- a/tools/skip_tracker.py
+++ b/tools/skip_tracker.py
@@ -28,7 +28,7 @@
from launchpadlib import launchpad
BASEDIR = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
-TESTDIR = os.path.join(BASEDIR, 'tempest', 'tests')
+TESTDIR = os.path.join(BASEDIR, 'tempest')
LPCACHEDIR = os.path.expanduser('~/.launchpadlib/cache')
@@ -118,8 +118,8 @@
unskips = sorted(set(unskips))
if unskips:
- print "The following bugs have been fixed and the corresponding skips"
- print "should be removed from the test cases:"
- print
+ print("The following bugs have been fixed and the corresponding skips")
+ print("should be removed from the test cases:")
+ print()
for bug in unskips:
- print " %7s" % bug
+ print(" %7s" % bug)
diff --git a/tools/tempest_coverage.py b/tools/tempest_coverage.py
index 5b926f9..ef2eacd 100755
--- a/tools/tempest_coverage.py
+++ b/tools/tempest_coverage.py
@@ -12,7 +12,7 @@
# 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
+# under the License.
import json
import os
@@ -151,14 +151,14 @@
elif CLI.command == 'stop':
resp, body = coverage_client.stop_coverage()
if not resp['status'] == '200':
- print 'coverage stop failed with: %s:' % (resp['status'] + ': '
- + body)
+ print('coverage stop failed with: %s:' % (resp['status'] + ': '
+ + body))
exit(int(resp['status']))
path = body['path']
if CLI.output:
shutil.copytree(path, CLI.output)
else:
- print "Data files located at: %s" % path
+ print("Data files located at: %s" % path)
elif CLI.command == 'report':
if CLI.xml:
@@ -169,8 +169,8 @@
else:
resp, body = coverage_client.report_coverage(file=CLI.filename)
if not resp['status'] == '200':
- print 'coverage report failed with: %s:' % (resp['status'] + ': '
- + body)
+ print('coverage report failed with: %s:' % (resp['status'] + ': '
+ + body))
exit(int(resp['status']))
path = body['path']
if CLI.output:
@@ -182,10 +182,10 @@
else:
if not CLI.html:
path = os.path.dirname(path)
- print 'Report files located at: %s' % path
+ print('Report files located at: %s' % path)
else:
- print 'Invalid command'
+ print('Invalid command')
exit(1)
diff --git a/tox.ini b/tox.ini
index d6c2c41..caa9403 100644
--- a/tox.ini
+++ b/tox.ini
@@ -10,6 +10,18 @@
NOSE_OPENSTACK_SHOW_ELAPSED=1
NOSE_OPENSTACK_STDOUT=1
+[testenv:all]
+sitepackages = True
+setenv = VIRTUAL_ENV={envdir}
+ NOSE_WITH_OPENSTACK=1
+ NOSE_OPENSTACK_COLOR=1
+ NOSE_OPENSTACK_RED=15
+ NOSE_OPENSTACK_YELLOW=3
+ NOSE_OPENSTACK_SHOW_ELAPSED=1
+ NOSE_OPENSTACK_STDOUT=1
+commands =
+ nosetests --logging-format '%(asctime)-15s %(message)s' --with-xunit --xunit-file=nosetests-all.xml -sv tempest
+
[testenv:full]
sitepackages = True
setenv = VIRTUAL_ENV={envdir}
@@ -47,20 +59,20 @@
commands =
python -m tools/tempest_coverage -c start --combine
nosetests --logging-format '%(asctime)-15s %(message)s' --with-xunit --xunit-file=nosetests-full.xml -sv tempest/api tempest/scenario tempest/thirdparty tempest/cli
- python -m tools/tempest_coverage -c report --html
+ python -m tools/tempest_coverage -c report --html {posargs}
[testenv:venv]
commands = {posargs}
-deps = -r{toxinidir}/tools/pip-requires
- -r{toxinidir}/tools/test-requires
+deps = -r{toxinidir}/requirements.txt
+ -r{toxinidir}/test-requirements.txt
[testenv:pep8]
commands = flake8
-deps = -r{toxinidir}/tools/pip-requires
- -r{toxinidir}/tools/test-requires
+deps = -r{toxinidir}/requirements.txt
+ -r{toxinidir}/test-requirements.txt
[hacking]
-local-check = tools.hacking.tempest.skip_bugs
+local-check-factory = tempest.hacking.checks.factory
[flake8]
ignore = E125,H302,H404