Merge "Adds test_list_XX_fields cases to list some fields"
diff --git a/etc/tempest.conf.sample b/etc/tempest.conf.sample
index d6d0964..7c604be 100644
--- a/etc/tempest.conf.sample
+++ b/etc/tempest.conf.sample
@@ -471,6 +471,29 @@
#api_v1=true
+[input-scenario]
+
+#
+# Options defined in tempest.config
+#
+
+# Matching images become parameters for scenario tests (string
+# value)
+#image_regex=^cirros-0.3.1-x86_64-uec$
+
+# Matching flavors become parameters for scenario tests
+# (string value)
+#flavor_regex=^m1.(micro|nano|tiny)$
+
+# SSH verification in tests is skippedfor matching images
+# (string value)
+#non_ssh_image_regex=^.*[Ww]in.*$
+
+# List of user mapped to regex to matching image names.
+# (string value)
+#ssh_user_regex=[["^.*[Cc]irros.*$", "root"]]
+
+
[network]
#
@@ -540,11 +563,11 @@
# (string value)
#region=
-# Number of seconds to time on waiting for a containerto
+# Number of seconds to time on waiting for a container to
# container synchronization complete. (integer value)
#container_sync_timeout=120
-# Number of seconds to wait while looping to check thestatus
+# Number of seconds to wait while looping to check the status
# of a container to container synchronization (integer value)
#container_sync_interval=5
@@ -559,21 +582,10 @@
# Options defined in tempest.config
#
-# Set to True if the Container Quota middleware is enabled
-# (boolean value)
-#container_quotas=true
-
-# Set to True if the Account Quota middleware is enabled
-# (boolean value)
-#accounts_quotas=true
-
-# Set to True if the Crossdomain middleware is enabled
-# (boolean value)
-#crossdomain=true
-
-# Set to True if the TempURL middleware is enabled (boolean
-# value)
-#tempurl=true
+# A list of the enabled optional discoverable apis. A single
+# entry, all, indicates that all of these features are
+# expected to be enabled (list value)
+#discoverable_apis=all
[orchestration]
diff --git a/etc/whitelist.yaml b/etc/whitelist.yaml
index a8c5276..1c12b6c 100644
--- a/etc/whitelist.yaml
+++ b/etc/whitelist.yaml
@@ -39,6 +39,8 @@
message: "Instance failed network setup after 1 attempt"
- module: "nova.compute.manager"
message: "Periodic sync_power_state task had an error"
+ - module: "nova.virt.driver"
+ message: "Info cache for instance .* could not be found"
g-api:
- module: "glance.store.sheepdog"
@@ -73,6 +75,10 @@
- module: "ceilometer.compute.pollsters.disk"
message: ".*"
+ceilometer-acentral:
+ - module: "ceilometer.central.manager"
+ message: "403 Forbidden"
+
ceilometer-alarm-evaluator:
- module: "ceilometer.alarm.service"
message: "alarm evaluation cycle failed"
diff --git a/requirements.txt b/requirements.txt
index 0c4a659..3b3e1fa 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -22,3 +22,4 @@
six>=1.4.1
iso8601>=0.1.8
fixtures>=0.3.14
+testscenarios>=0.4
diff --git a/run_tempest.sh b/run_tempest.sh
new file mode 100755
index 0000000..be9b38a
--- /dev/null
+++ b/run_tempest.sh
@@ -0,0 +1,158 @@
+#!/usr/bin/env bash
+
+function usage {
+ echo "Usage: $0 [OPTION]..."
+ echo "Run Tempest test suite"
+ echo ""
+ echo " -V, --virtual-env Always use virtualenv. Install automatically if not present"
+ echo " -N, --no-virtual-env Don't use virtualenv. Run tests in local environment"
+ echo " -n, --no-site-packages Isolate the virtualenv from the global Python environment"
+ echo " -f, --force Force a clean re-build of the virtual environment. Useful when dependencies have been added."
+ echo " -u, --update Update the virtual environment with any newer package versions"
+ echo " -s, --smoke Only run smoke tests"
+ echo " -t, --serial Run testr serially"
+ echo " -C, --config Config file location"
+ echo " -h, --help Print this usage message"
+ echo " -d, --debug Debug this script -- set -o xtrace"
+ echo " -l, --logging Enable logging"
+ echo " -L, --logging-config Logging config file location. Default is etc/logging.conf"
+ echo " -- [TESTROPTIONS] After the first '--' you can pass arbitrary arguments to testr "
+}
+
+testrargs=""
+venv=.venv
+with_venv=tools/with_venv.sh
+serial=0
+always_venv=0
+never_venv=0
+no_site_packages=0
+force=0
+wrapper=""
+config_file=""
+update=0
+logging=0
+logging_config=etc/logging.conf
+
+if ! options=$(getopt -o VNnfusthdC:lL: -l virtual-env,no-virtual-env,no-site-packages,force,update,smoke,serial,help,debug,config:,logging,logging-config: -- "$@")
+then
+ # parse error
+ usage
+ exit 1
+fi
+
+eval set -- $options
+first_uu=yes
+while [ $# -gt 0 ]; do
+ case "$1" in
+ -h|--help) usage; exit;;
+ -V|--virtual-env) always_venv=1; never_venv=0;;
+ -N|--no-virtual-env) always_venv=0; never_venv=1;;
+ -n|--no-site-packages) no_site_packages=1;;
+ -f|--force) force=1;;
+ -u|--update) update=1;;
+ -d|--debug) set -o xtrace;;
+ -C|--config) config_file=$2; shift;;
+ -s|--smoke) testrargs+="smoke"; noseargs+="--attr=type=smoke";;
+ -t|--serial) serial=1;;
+ -l|--logging) logging=1;;
+ -L|--logging-config) logging_config=$2; shift;;
+ --) [ "yes" == "$first_uu" ] || testrargs="$testrargs $1"; first_uu=no ;;
+ *) testrargs="$testrargs $1"; noseargs+=" $1" ;;
+ esac
+ shift
+done
+
+if [ -n "$config_file" ]; then
+ config_file=`readlink -f "$config_file"`
+ export TEMPEST_CONFIG_DIR=`dirname "$config_file"`
+ 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"`
+
+if [ $no_site_packages -eq 1 ]; then
+ installvenvopts="--no-site-packages"
+fi
+
+function testr_init {
+ if [ ! -d .testrepository ]; then
+ ${wrapper} testr init
+ fi
+}
+
+function run_tests {
+ testr_init
+ ${wrapper} find . -type f -name "*.pyc" -delete
+ export OS_TEST_PATH=./tempest/test_discover
+ if [ $serial -eq 1 ]; then
+ ${wrapper} testr run --subunit $testrargs | ${wrapper} subunit-2to1 | ${wrapper} tools/colorizer.py
+ else
+ ${wrapper} testr run --parallel --subunit $testrargs | ${wrapper} subunit-2to1 | ${wrapper} tools/colorizer.py
+ fi
+}
+
+function run_tests_nose {
+ export NOSE_WITH_OPENSTACK=1
+ export NOSE_OPENSTACK_COLOR=1
+ export NOSE_OPENSTACK_RED=15.00
+ export NOSE_OPENSTACK_YELLOW=3.00
+ export NOSE_OPENSTACK_SHOW_ELAPSED=1
+ export NOSE_OPENSTACK_STDOUT=1
+ export TEMPEST_PY26_NOSE_COMPAT=1
+ if [[ "x$noseargs" =~ "tempest" ]]; then
+ noseargs="$testrargs"
+ else
+ noseargs="$noseargs tempest"
+ fi
+ ${wrapper} nosetests $noseargs
+}
+
+if [ $never_venv -eq 0 ]
+then
+ # Remove the virtual environment if --force used
+ if [ $force -eq 1 ]; then
+ echo "Cleaning virtualenv..."
+ rm -rf ${venv}
+ fi
+ if [ $update -eq 1 ]; then
+ echo "Updating virtualenv..."
+ python tools/install_venv.py $installvenvopts
+ fi
+ if [ -e ${venv} ]; then
+ wrapper="${with_venv}"
+ else
+ if [ $always_venv -eq 1 ]; then
+ # Automatically install the virtualenv
+ python tools/install_venv.py $installvenvopts
+ wrapper="${with_venv}"
+ else
+ echo -e "No virtual environment found...create one? (Y/n) \c"
+ read use_ve
+ if [ "x$use_ve" = "xY" -o "x$use_ve" = "x" -o "x$use_ve" = "xy" ]; then
+ # Install the virtualenv and run the test suite in it
+ python tools/install_venv.py $installvenvopts
+ wrapper=${with_venv}
+ fi
+ fi
+ fi
+fi
+
+py_version=`${wrapper} python --version 2>&1`
+if [[ $py_version =~ "2.6" ]] ; then
+ run_tests_nose
+else
+ run_tests
+fi
+retval=$?
+
+exit $retval
diff --git a/run_tests.sh b/run_tests.sh
index 3c9c051..285b372 100755
--- a/run_tests.sh
+++ b/run_tests.sh
@@ -2,21 +2,18 @@
function usage {
echo "Usage: $0 [OPTION]..."
- echo "Run Tempest test suite"
+ echo "Run Tempest unit tests"
echo ""
echo " -V, --virtual-env Always use virtualenv. Install automatically if not present"
echo " -N, --no-virtual-env Don't use virtualenv. Run tests in local environment"
echo " -n, --no-site-packages Isolate the virtualenv from the global Python environment"
echo " -f, --force Force a clean re-build of the virtual environment. Useful when dependencies have been added."
echo " -u, --update Update the virtual environment with any newer package versions"
- echo " -s, --smoke Only run smoke tests"
echo " -t, --serial Run testr serially"
- echo " -C, --config Config file location"
echo " -p, --pep8 Just run pep8"
+ echo " -c, --coverage Generate coverage report"
echo " -h, --help Print this usage message"
echo " -d, --debug Debug this script -- set -o xtrace"
- echo " -l, --logging Enable logging"
- echo " -L, --logging-config Logging config file location. Default is etc/logging.conf"
echo " -- [TESTROPTIONS] After the first '--' you can pass arbitrary arguments to testr "
}
@@ -29,13 +26,12 @@
never_venv=0
no_site_packages=0
force=0
+coverage=0
wrapper=""
config_file=""
update=0
-logging=0
-logging_config=etc/logging.conf
-if ! options=$(getopt -o VNnfustphdC:lL: -l virtual-env,no-virtual-env,no-site-packages,force,update,smoke,serial,pep8,help,debug,config:,logging,logging-config: -- "$@")
+if ! options=$(getopt -o VNnfuctphd -l virtual-env,no-virtual-env,no-site-packages,force,update,serial,coverage,pep8,help,debug -- "$@")
then
# parse error
usage
@@ -53,33 +49,15 @@
-f|--force) force=1;;
-u|--update) update=1;;
-d|--debug) set -o xtrace;;
- -C|--config) config_file=$2; shift;;
-p|--pep8) let just_pep8=1;;
- -s|--smoke) testrargs+="smoke"; noseargs+="--attr=type=smoke";;
+ -c|--coverage) coverage=1;;
-t|--serial) serial=1;;
- -l|--logging) logging=1;;
- -L|--logging-config) logging_config=$2; shift;;
--) [ "yes" == "$first_uu" ] || testrargs="$testrargs $1"; first_uu=no ;;
*) testrargs="$testrargs $1"; noseargs+=" $1" ;;
esac
shift
done
-if [ -n "$config_file" ]; then
- config_file=`readlink -f "$config_file"`
- export TEMPEST_CONFIG_DIR=`dirname "$config_file"`
- 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"`
@@ -96,6 +74,7 @@
function run_tests {
testr_init
${wrapper} find . -type f -name "*.pyc" -delete
+ export OS_TEST_PATH=./tempest/tests
if [ $serial -eq 1 ]; then
${wrapper} testr run --subunit $testrargs | ${wrapper} subunit-2to1 | ${wrapper} tools/colorizer.py
else
@@ -103,22 +82,6 @@
fi
}
-function run_tests_nose {
- export NOSE_WITH_OPENSTACK=1
- export NOSE_OPENSTACK_COLOR=1
- export NOSE_OPENSTACK_RED=15.00
- export NOSE_OPENSTACK_YELLOW=3.00
- export NOSE_OPENSTACK_SHOW_ELAPSED=1
- export NOSE_OPENSTACK_STDOUT=1
- export TEMPEST_PY26_NOSE_COMPAT=1
- if [[ "x$noseargs" =~ "tempest" ]]; then
- noseargs="$testrargs"
- else
- noseargs="$noseargs tempest"
- fi
- ${wrapper} nosetests $noseargs
-}
-
function run_pep8 {
echo "Running flake8 ..."
if [ $never_venv -eq 1 ]; then
@@ -163,12 +126,11 @@
exit
fi
-py_version=`${wrapper} python --version 2>&1`
-if [[ $py_version =~ "2.6" ]] ; then
- run_tests_nose
-else
- run_tests
+if [$coverage -eq 1] ; then
+ $testrargs = "--coverage $testrargs"
fi
+
+run_tests
retval=$?
if [ -z "$testrargs" ]; then
diff --git a/tempest/api/__init__.py b/tempest/api/__init__.py
index 0b3d2db..e69de29 100644
--- a/tempest/api/__init__.py
+++ b/tempest/api/__init__.py
@@ -1,16 +0,0 @@
-# vim: tabstop=4 shiftwidth=4 softtabstop=4
-
-# Copyright 2012 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.
diff --git a/tempest/api/compute/admin/test_servers.py b/tempest/api/compute/admin/test_servers.py
index 6fe3186..2160949 100644
--- a/tempest/api/compute/admin/test_servers.py
+++ b/tempest/api/compute/admin/test_servers.py
@@ -44,6 +44,7 @@
cls.s2_name = data_utils.rand_name('server')
resp, server = cls.create_test_server(name=cls.s2_name,
wait_until='ACTIVE')
+ cls.s2_id = server['id']
def _get_unused_flavor_id(self):
flavor_id = data_utils.rand_int_id(start=1000)
@@ -64,6 +65,22 @@
self.assertEqual([], servers)
@attr(type='gate')
+ def test_list_servers_filter_by_error_status(self):
+ # Filter the list of servers by server error status
+ params = {'status': 'error'}
+ resp, server = self.client.reset_state(self.s1_id, state='error')
+ resp, body = self.non_admin_client.list_servers(params)
+ # Reset server's state to 'active'
+ resp, server = self.client.reset_state(self.s1_id, state='active')
+ # Verify server's state
+ resp, server = self.client.get_server(self.s1_id)
+ self.assertEqual(server['status'], 'ACTIVE')
+ servers = body['servers']
+ # Verify error server in list result
+ self.assertIn(self.s1_id, map(lambda x: x['id'], servers))
+ self.assertNotIn(self.s2_id, map(lambda x: x['id'], servers))
+
+ @attr(type='gate')
def test_list_servers_by_admin_with_all_tenants(self):
# Listing servers by admin user with all tenants parameter
# Here should be listed all servers
diff --git a/tempest/api/compute/base.py b/tempest/api/compute/base.py
index b060f15..311e158 100644
--- a/tempest/api/compute/base.py
+++ b/tempest/api/compute/base.py
@@ -130,12 +130,22 @@
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:
for server in servers:
- cls.servers_client.wait_for_server_status(
- server['id'], kwargs['wait_until'])
+ try:
+ cls.servers_client.wait_for_server_status(
+ server['id'], kwargs['wait_until'])
+ except Exception as ex:
+ if ('preserve_server_on_error' not in kwargs
+ or kwargs['preserve_server_on_error'] is False):
+ for server in servers:
+ try:
+ cls.servers_client.delete_server(server['id'])
+ except Exception:
+ pass
+ raise ex
+
+ cls.servers.extend(servers)
return resp, body
diff --git a/tempest/api/compute/servers/test_create_server.py b/tempest/api/compute/servers/test_create_server.py
index 5d62e1b..e45161b 100644
--- a/tempest/api/compute/servers/test_create_server.py
+++ b/tempest/api/compute/servers/test_create_server.py
@@ -109,6 +109,104 @@
self.assertTrue(linux_client.hostname_equals_servername(self.name))
+class ServersWithSpecificFlavorTestJSON(base.BaseV2ComputeAdminTest):
+ _interface = 'json'
+ run_ssh = CONF.compute.run_ssh
+ disk_config = 'AUTO'
+
+ @classmethod
+ def setUpClass(cls):
+ super(ServersWithSpecificFlavorTestJSON, cls).setUpClass()
+ cls.meta = {'hello': 'world'}
+ cls.accessIPv4 = '1.1.1.1'
+ cls.accessIPv6 = '0000:0000:0000:0000:0000:babe:220.12.22.2'
+ cls.name = data_utils.rand_name('server')
+ file_contents = 'This is a test file.'
+ personality = [{'path': '/test.txt',
+ 'contents': base64.b64encode(file_contents)}]
+ cls.client = cls.servers_client
+ cls.flavor_client = cls.os_adm.flavors_client
+ cli_resp = cls.create_test_server(name=cls.name,
+ meta=cls.meta,
+ accessIPv4=cls.accessIPv4,
+ accessIPv6=cls.accessIPv6,
+ personality=personality,
+ disk_config=cls.disk_config)
+ cls.resp, cls.server_initial = cli_resp
+ cls.password = cls.server_initial['adminPass']
+ cls.client.wait_for_server_status(cls.server_initial['id'], 'ACTIVE')
+ resp, cls.server = cls.client.get_server(cls.server_initial['id'])
+
+ @testtools.skipIf(not run_ssh, 'Instance validation tests are disabled.')
+ @attr(type='gate')
+ def test_verify_created_server_ephemeral_disk(self):
+ # Verify that the ephemeral disk is created when creating server
+
+ def create_flavor_with_extra_specs(self):
+ flavor_with_eph_disk_name = data_utils.rand_name('eph_flavor')
+ flavor_with_eph_disk_id = data_utils.rand_int_id(start=1000)
+ ram = 64
+ vcpus = 1
+ disk = 0
+
+ # Create a flavor with extra specs
+ resp, flavor = (self.flavor_client.
+ create_flavor(flavor_with_eph_disk_name,
+ ram, vcpus, disk,
+ flavor_with_eph_disk_id,
+ ephemeral=1))
+ self.addCleanup(self.flavor_clean_up, flavor['id'])
+ self.assertEqual(200, resp.status)
+
+ return flavor['id']
+
+ def create_flavor_without_extra_specs(self):
+ flavor_no_eph_disk_name = data_utils.rand_name('no_eph_flavor')
+ flavor_no_eph_disk_id = data_utils.rand_int_id(start=1000)
+
+ ram = 64
+ vcpus = 1
+ disk = 0
+
+ # Create a flavor without extra specs
+ resp, flavor = (self.flavor_client.
+ create_flavor(flavor_no_eph_disk_name,
+ ram, vcpus, disk,
+ flavor_no_eph_disk_id))
+ self.addCleanup(self.flavor_clean_up, flavor['id'])
+ self.assertEqual(200, resp.status)
+
+ return flavor['id']
+
+ def flavor_clean_up(self, flavor_id):
+ resp, body = self.flavor_client.delete_flavor(flavor_id)
+ self.assertEqual(resp.status, 202)
+ self.flavor_client.wait_for_resource_deletion(flavor_id)
+
+ flavor_with_eph_disk_id = self.create_flavor_with_extra_specs()
+ flavor_no_eph_disk_id = self.create_flavor_without_extra_specs()
+
+ admin_pass = self.image_ssh_password
+
+ resp, server_no_eph_disk = (self.
+ create_test_server(
+ wait_until='ACTIVE',
+ adminPass=admin_pass,
+ flavor=flavor_no_eph_disk_id))
+ resp, server_with_eph_disk = (self.create_test_server(
+ wait_until='ACTIVE',
+ adminPass=admin_pass,
+ flavor=flavor_with_eph_disk_id))
+ # Get partition number of server without extra specs.
+ linux_client = RemoteClient(server_no_eph_disk,
+ self.ssh_user, self.password)
+ partition_num = len(linux_client.get_partitions())
+
+ linux_client = RemoteClient(server_with_eph_disk,
+ self.ssh_user, self.password)
+ self.assertEqual(partition_num + 1, linux_client.get_partitions())
+
+
class ServersTestManualDisk(ServersTestJSON):
disk_config = 'MANUAL'
@@ -122,3 +220,7 @@
class ServersTestXML(ServersTestJSON):
_interface = 'xml'
+
+
+class ServersWithSpecificFlavorTestXML(ServersWithSpecificFlavorTestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/compute/servers/test_instance_actions.py b/tempest/api/compute/servers/test_instance_actions.py
index 5019003..5058211 100644
--- a/tempest/api/compute/servers/test_instance_actions.py
+++ b/tempest/api/compute/servers/test_instance_actions.py
@@ -16,8 +16,7 @@
# under the License.
from tempest.api.compute import base
-from tempest import exceptions
-from tempest.test import attr
+from tempest import test
class InstanceActionsTestJSON(base.BaseV2ComputeTest):
@@ -31,7 +30,7 @@
cls.request_id = resp['x-compute-request-id']
cls.server_id = server['id']
- @attr(type='gate')
+ @test.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 +42,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='gate')
+ @test.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,18 +51,6 @@
self.assertEqual(self.server_id, body['instance_uuid'])
self.assertEqual('create', body['action'])
- @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', '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,
- self.server_id, '999')
-
class InstanceActionsTestXML(InstanceActionsTestJSON):
_interface = 'xml'
diff --git a/tempest/api/compute/servers/test_instance_actions_negative.py b/tempest/api/compute/servers/test_instance_actions_negative.py
new file mode 100644
index 0000000..68f2dcb
--- /dev/null
+++ b/tempest/api/compute/servers/test_instance_actions_negative.py
@@ -0,0 +1,50 @@
+# 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.
+
+from tempest.api.compute import base
+from tempest.common.utils import data_utils
+from tempest import exceptions
+from tempest import test
+
+
+class InstanceActionsNegativeTestJSON(base.BaseV2ComputeTest):
+ _interface = 'json'
+
+ @classmethod
+ def setUpClass(cls):
+ super(InstanceActionsNegativeTestJSON, cls).setUpClass()
+ cls.client = cls.servers_client
+ resp, server = cls.create_test_server(wait_until='ACTIVE')
+ cls.server_id = server['id']
+
+ @test.attr(type=['negative', 'gate'])
+ def test_list_instance_actions_non_existent_server(self):
+ # List actions of the non-existent server id
+ non_existent_server_id = data_utils.rand_uuid()
+ self.assertRaises(exceptions.NotFound,
+ self.client.list_instance_actions,
+ non_existent_server_id)
+
+ @test.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,
+ self.server_id, '999')
+
+
+class InstanceActionsNegativeTestXML(InstanceActionsNegativeTestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/compute/servers/test_list_server_filters.py b/tempest/api/compute/servers/test_list_server_filters.py
index 3748e37..f3863b3 100644
--- a/tempest/api/compute/servers/test_list_server_filters.py
+++ b/tempest/api/compute/servers/test_list_server_filters.py
@@ -121,6 +121,23 @@
self.assertIn(self.s3['id'], map(lambda x: x['id'], servers))
@attr(type='gate')
+ def test_list_servers_filter_by_shutoff_status(self):
+ # Filter the list of servers by server shutoff status
+ params = {'status': 'shutoff'}
+ self.client.stop(self.s1['id'])
+ self.client.wait_for_server_status(self.s1['id'],
+ 'SHUTOFF')
+ resp, body = self.client.list_servers(params)
+ self.client.start(self.s1['id'])
+ self.client.wait_for_server_status(self.s1['id'],
+ 'ACTIVE')
+ servers = body['servers']
+
+ self.assertIn(self.s1['id'], map(lambda x: x['id'], servers))
+ self.assertNotIn(self.s2['id'], map(lambda x: x['id'], servers))
+ self.assertNotIn(self.s3['id'], map(lambda x: x['id'], servers))
+
+ @attr(type='gate')
def test_list_servers_filter_by_limit(self):
# Verify only the expected number of servers are returned
params = {'limit': 1}
diff --git a/tempest/api/compute/servers/test_server_actions.py b/tempest/api/compute/servers/test_server_actions.py
index f195562..e4af2e1 100644
--- a/tempest/api/compute/servers/test_server_actions.py
+++ b/tempest/api/compute/servers/test_server_actions.py
@@ -174,7 +174,7 @@
def _detect_server_image_flavor(self, server_id):
# Detects the current server image flavor ref.
- resp, server = self.client.get_server(self.server_id)
+ resp, server = self.client.get_server(server_id)
current_flavor = server['flavor']['id']
new_flavor_ref = self.flavor_ref_alt \
if current_flavor == self.flavor_ref else self.flavor_ref
diff --git a/tempest/api/compute/test_extensions.py b/tempest/api/compute/test_extensions.py
index b0bffc4..56929a3 100644
--- a/tempest/api/compute/test_extensions.py
+++ b/tempest/api/compute/test_extensions.py
@@ -17,25 +17,35 @@
from tempest.api.compute import base
+from tempest.openstack.common import log as logging
from tempest import test
-import testtools
+
+
+LOG = logging.getLogger(__name__)
class ExtensionsTestJSON(base.BaseV2ComputeTest):
_interface = 'json'
- @testtools.skipIf(not test.is_extension_enabled('os-consoles', 'compute'),
- 'os-consoles extension not enabled.')
@test.attr(type='gate')
def test_list_extensions(self):
# List of all extensions
+ if len(self.config.compute_feature_enabled.api_extensions) == 0:
+ raise self.skipException('There are not any extensions configured')
resp, extensions = self.extensions_client.list_extensions()
- self.assertIn("extensions", extensions)
self.assertEqual(200, resp.status)
- self.assertTrue(self.extensions_client.is_enabled("Consoles"))
+ ext = self.config.compute_feature_enabled.api_extensions[0]
+ if ext == 'all':
+ self.assertIn('Hosts', map(lambda x: x['name'], extensions))
+ elif ext:
+ self.assertIn(ext, map(lambda x: x['name'], extensions))
+ else:
+ raise self.skipException('There are not any extensions configured')
+ # Log extensions list
+ extension_list = map(lambda x: x['name'], extensions)
+ LOG.debug("Nova extensions: %s" % ','.join(extension_list))
- @testtools.skipIf(not test.is_extension_enabled('os-consoles', 'compute'),
- 'os-consoles extension not enabled.')
+ @test.requires_ext(extension='os-consoles', service='compute')
@test.attr(type='gate')
def test_get_extension(self):
# get the specified extensions
diff --git a/tempest/api/compute/v3/admin/test_servers.py b/tempest/api/compute/v3/admin/test_servers.py
index 6fe3186..becdd78 100644
--- a/tempest/api/compute/v3/admin/test_servers.py
+++ b/tempest/api/compute/v3/admin/test_servers.py
@@ -21,7 +21,7 @@
from tempest.test import skip_because
-class ServersAdminTestJSON(base.BaseV2ComputeAdminTest):
+class ServersAdminV3TestJSON(base.BaseV3ComputeAdminTest):
"""
Tests Servers API using admin privileges
@@ -31,10 +31,10 @@
@classmethod
def setUpClass(cls):
- super(ServersAdminTestJSON, cls).setUpClass()
- cls.client = cls.os_adm.servers_client
+ super(ServersAdminV3TestJSON, cls).setUpClass()
+ cls.client = cls.servers_admin_client
cls.non_admin_client = cls.servers_client
- cls.flavors_client = cls.os_adm.flavors_client
+ cls.flavors_client = cls.flavors_admin_client
cls.s1_name = data_utils.rand_name('server')
resp, server = cls.create_test_server(name=cls.s1_name,
@@ -44,6 +44,7 @@
cls.s2_name = data_utils.rand_name('server')
resp, server = cls.create_test_server(name=cls.s2_name,
wait_until='ACTIVE')
+ cls.s2_id = server['id']
def _get_unused_flavor_id(self):
flavor_id = data_utils.rand_int_id(start=1000)
@@ -114,6 +115,22 @@
self.assertIn(key, str(diagnostic.keys()))
@attr(type='gate')
+ def test_list_servers_filter_by_error_status(self):
+ # Filter the list of servers by server error status
+ params = {'status': 'error'}
+ resp, server = self.client.reset_state(self.s1_id, state='error')
+ resp, body = self.non_admin_client.list_servers(params)
+ # Reset server's state to 'active'
+ resp, server = self.client.reset_state(self.s1_id, state='active')
+ # Verify server's state
+ resp, server = self.client.get_server(self.s1_id)
+ self.assertEqual(server['status'], 'ACTIVE')
+ servers = body['servers']
+ # Verify error server in list result
+ self.assertIn(self.s1_id, map(lambda x: x['id'], servers))
+ self.assertNotIn(self.s2_id, map(lambda x: x['id'], servers))
+
+ @attr(type='gate')
def test_rebuild_server_in_error_state(self):
# The server in error state should be rebuilt using the provided
# image and changed to ACTIVE state
@@ -142,5 +159,5 @@
self.assertEqual(self.image_ref_alt, rebuilt_image_id)
-class ServersAdminTestXML(ServersAdminTestJSON):
+class ServersAdminV3TestXML(ServersAdminV3TestJSON):
_interface = 'xml'
diff --git a/tempest/api/compute/v3/admin/test_servers_negative.py b/tempest/api/compute/v3/admin/test_servers_negative.py
index 77d873b..670bc2c 100644
--- a/tempest/api/compute/v3/admin/test_servers_negative.py
+++ b/tempest/api/compute/v3/admin/test_servers_negative.py
@@ -22,7 +22,7 @@
from tempest.test import attr
-class ServersAdminNegativeTestJSON(base.BaseV2ComputeAdminTest):
+class ServersAdminNegativeV3TestJSON(base.BaseV3ComputeAdminTest):
"""
Tests Servers API using admin privileges
@@ -32,10 +32,10 @@
@classmethod
def setUpClass(cls):
- super(ServersAdminNegativeTestJSON, cls).setUpClass()
- cls.client = cls.os_adm.servers_client
+ super(ServersAdminNegativeV3TestJSON, cls).setUpClass()
+ cls.client = cls.servers_admin_client
cls.non_adm_client = cls.servers_client
- cls.flavors_client = cls.os_adm.flavors_client
+ cls.flavors_client = cls.flavors_admin_client
cls.identity_client = cls._get_identity_admin_client()
tenant = cls.identity_client.get_tenant_by_name(
cls.client.tenant_name)
@@ -139,5 +139,5 @@
server_id)
-class ServersAdminNegativeTestXML(ServersAdminNegativeTestJSON):
+class ServersAdminNegativeV3TestXML(ServersAdminNegativeV3TestJSON):
_interface = 'xml'
diff --git a/tempest/api/compute/v3/admin/test_simple_tenant_usage.py b/tempest/api/compute/v3/admin/test_simple_tenant_usage.py
index 99f2c52..e08f16a 100644
--- a/tempest/api/compute/v3/admin/test_simple_tenant_usage.py
+++ b/tempest/api/compute/v3/admin/test_simple_tenant_usage.py
@@ -18,6 +18,7 @@
import datetime
from tempest.api.compute import base
+from tempest import test
from tempest.test import attr
import time
@@ -50,6 +51,7 @@
# Returns formatted datetime
return at.strftime('%Y-%m-%dT%H:%M:%S.%f')
+ @test.skip_because(bug='1265416')
@attr(type='gate')
def test_list_usage_all_tenants(self):
# Get usage for all tenants
@@ -60,6 +62,7 @@
self.assertEqual(200, resp.status)
self.assertEqual(len(tenant_usage), 8)
+ @test.skip_because(bug='1265416')
@attr(type='gate')
def test_get_usage_tenant(self):
# Get usage for a specific tenant
@@ -71,6 +74,7 @@
self.assertEqual(200, resp.status)
self.assertEqual(len(tenant_usage), 8)
+ @test.skip_because(bug='1265416')
@attr(type='gate')
def test_get_usage_tenant_with_non_admin_user(self):
# Get usage for a specific tenant with non admin user
diff --git a/tempest/api/compute/v3/admin/test_simple_tenant_usage_negative.py b/tempest/api/compute/v3/admin/test_simple_tenant_usage_negative.py
index ef49ed7..f9dbe86 100644
--- a/tempest/api/compute/v3/admin/test_simple_tenant_usage_negative.py
+++ b/tempest/api/compute/v3/admin/test_simple_tenant_usage_negative.py
@@ -19,6 +19,7 @@
from tempest.api.compute import base
from tempest import exceptions
+from tempest import test
from tempest.test import attr
@@ -50,6 +51,7 @@
self.adm_client.get_tenant_usage,
'', params)
+ @test.skip_because(bug='1265416')
@attr(type=['negative', 'gate'])
def test_get_usage_tenant_with_invalid_date(self):
# Get usage for tenant with invalid date
@@ -62,6 +64,7 @@
self.adm_client.get_tenant_usage,
tenant_id, params)
+ @test.skip_because(bug='1265416')
@attr(type=['negative', 'gate'])
def test_list_usage_all_tenants_with_non_admin_user(self):
# Get usage for all tenants with non admin user
diff --git a/tempest/api/compute/v3/images/test_image_metadata.py b/tempest/api/compute/v3/images/test_image_metadata.py
new file mode 100644
index 0000000..76e0cae
--- /dev/null
+++ b/tempest/api/compute/v3/images/test_image_metadata.py
@@ -0,0 +1,114 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 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.compute import base
+from tempest.common.utils import data_utils
+from tempest.test import attr
+
+
+class ImagesMetadataTestJSON(base.BaseV2ComputeTest):
+ _interface = 'json'
+
+ @classmethod
+ def setUpClass(cls):
+ super(ImagesMetadataTestJSON, cls).setUpClass()
+ if not cls.config.service_available.glance:
+ skip_msg = ("%s skipped as glance is not available" % cls.__name__)
+ raise cls.skipException(skip_msg)
+
+ cls.servers_client = cls.servers_client
+ cls.client = cls.images_client
+
+ resp, server = cls.create_test_server(wait_until='ACTIVE')
+ cls.server_id = server['id']
+
+ # Snapshot the server once to save time
+ name = data_utils.rand_name('image')
+ resp, _ = cls.client.create_image(cls.server_id, name, {})
+ cls.image_id = resp['location'].rsplit('/', 1)[1]
+
+ cls.client.wait_for_image_status(cls.image_id, 'ACTIVE')
+
+ @classmethod
+ def tearDownClass(cls):
+ cls.client.delete_image(cls.image_id)
+ super(ImagesMetadataTestJSON, cls).tearDownClass()
+
+ def setUp(self):
+ super(ImagesMetadataTestJSON, self).setUp()
+ meta = {'key1': 'value1', 'key2': 'value2'}
+ 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'}
+ resp, body = self.client.set_image_metadata(self.image_id,
+ req_metadata)
+
+ 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'}
+ resp, metadata = self.client.update_image_metadata(self.image_id,
+ req_metadata)
+
+ resp, resp_metadata = self.client.list_image_metadata(self.image_id)
+ 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.assertEqual('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
+ meta = {'key1': 'alt'}
+ resp, body = self.client.set_image_metadata_item(self.image_id,
+ 'key1', meta)
+ resp, resp_metadata = self.client.list_image_metadata(self.image_id)
+ 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,
+ 'key1')
+ resp, resp_metadata = self.client.list_image_metadata(self.image_id)
+ expected = {'key2': 'value2'}
+ self.assertEqual(expected, resp_metadata)
+
+
+class ImagesMetadataTestXML(ImagesMetadataTestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/compute/v3/images/test_image_metadata_negative.py b/tempest/api/compute/v3/images/test_image_metadata_negative.py
new file mode 100644
index 0000000..1767e5d
--- /dev/null
+++ b/tempest/api/compute/v3/images/test_image_metadata_negative.py
@@ -0,0 +1,81 @@
+# 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.compute import base
+from tempest.common.utils import data_utils
+from tempest import exceptions
+from tempest.test import attr
+
+
+class ImagesMetadataTestJSON(base.BaseV2ComputeTest):
+ _interface = 'json'
+
+ @classmethod
+ def setUpClass(cls):
+ super(ImagesMetadataTestJSON, cls).setUpClass()
+ cls.client = cls.images_client
+
+ @attr(type=['negative', 'gate'])
+ def test_list_nonexistent_image_metadata(self):
+ # Negative test: List on nonexistent image
+ # metadata should not happen
+ self.assertRaises(exceptions.NotFound, self.client.list_image_metadata,
+ data_utils.rand_uuid())
+
+ @attr(type=['negative', 'gate'])
+ def test_update_nonexistent_image_metadata(self):
+ # Negative test:An update should not happen for a non-existent image
+ meta = {'key1': 'alt1', 'key2': 'alt2'}
+ self.assertRaises(exceptions.NotFound,
+ self.client.update_image_metadata,
+ data_utils.rand_uuid(), meta)
+
+ @attr(type=['negative', 'gate'])
+ def test_get_nonexistent_image_metadata_item(self):
+ # Negative test: Get on non-existent image should not happen
+ self.assertRaises(exceptions.NotFound,
+ self.client.get_image_metadata_item,
+ data_utils.rand_uuid(), 'key2')
+
+ @attr(type=['negative', 'gate'])
+ def test_set_nonexistent_image_metadata(self):
+ # Negative test: Metadata should not be set to a non-existent image
+ meta = {'key1': 'alt1', 'key2': 'alt2'}
+ self.assertRaises(exceptions.NotFound, self.client.set_image_metadata,
+ data_utils.rand_uuid(), meta)
+
+ @attr(type=['negative', 'gate'])
+ def test_set_nonexistent_image_metadata_item(self):
+ # Negative test: Metadata item should not be set to a
+ # nonexistent image
+ meta = {'key1': 'alt'}
+ self.assertRaises(exceptions.NotFound,
+ self.client.set_image_metadata_item,
+ data_utils.rand_uuid(), 'key1',
+ meta)
+
+ @attr(type=['negative', 'gate'])
+ def test_delete_nonexistent_image_metadata_item(self):
+ # Negative test: Shouldn't be able to delete metadata
+ # item from non-existent image
+ self.assertRaises(exceptions.NotFound,
+ self.client.delete_image_metadata_item,
+ data_utils.rand_uuid(), 'key1')
+
+
+class ImagesMetadataTestXML(ImagesMetadataTestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/compute/v3/images/test_images_oneserver.py b/tempest/api/compute/v3/images/test_images_oneserver.py
new file mode 100644
index 0000000..26cc3f6
--- /dev/null
+++ b/tempest/api/compute/v3/images/test_images_oneserver.py
@@ -0,0 +1,138 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 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 testtools
+
+from tempest.api.compute import base
+from tempest import clients
+from tempest.common.utils import data_utils
+from tempest import config
+from tempest.openstack.common import log as logging
+from tempest.test import attr
+
+CONF = config.CONF
+LOG = logging.getLogger(__name__)
+
+
+class ImagesOneServerTestJSON(base.BaseV2ComputeTest):
+ _interface = 'json'
+
+ def tearDown(self):
+ """Terminate test instances created after a test is executed."""
+ for image_id in self.image_ids:
+ self.client.delete_image(image_id)
+ self.image_ids.remove(image_id)
+ super(ImagesOneServerTestJSON, self).tearDown()
+
+ def setUp(self):
+ # NOTE(afazekas): Normally we use the same server with all test cases,
+ # but if it has an issue, we build a new one
+ super(ImagesOneServerTestJSON, self).setUp()
+ # Check if the server is in a clean state after test
+ try:
+ self.servers_client.wait_for_server_status(self.server_id,
+ 'ACTIVE')
+ except Exception:
+ LOG.exception('server %s timed out to become ACTIVE. rebuilding'
+ % self.server_id)
+ # Rebuild server if cannot reach the ACTIVE state
+ # Usually it means the server had a serious accident
+ self.__class__.server_id = self.rebuild_server(self.server_id)
+
+ @classmethod
+ def setUpClass(cls):
+ super(ImagesOneServerTestJSON, cls).setUpClass()
+ cls.client = cls.images_client
+ if not cls.config.service_available.glance:
+ skip_msg = ("%s skipped as glance is not available" % cls.__name__)
+ raise cls.skipException(skip_msg)
+
+ try:
+ resp, server = cls.create_test_server(wait_until='ACTIVE')
+ cls.server_id = server['id']
+ except Exception:
+ cls.tearDownClass()
+ raise
+
+ cls.image_ids = []
+
+ if cls.multi_user:
+ if cls.config.compute.allow_tenant_isolation:
+ creds = cls.isolated_creds.get_alt_creds()
+ username, tenant_name, password = creds
+ cls.alt_manager = clients.Manager(username=username,
+ password=password,
+ tenant_name=tenant_name)
+ else:
+ # Use the alt_XXX credentials in the config file
+ cls.alt_manager = clients.AltManager()
+ cls.alt_client = cls.alt_manager.images_client
+
+ def _get_default_flavor_disk_size(self, flavor_id):
+ resp, flavor = self.flavors_client.get_flavor_details(flavor_id)
+ return flavor['disk']
+
+ @testtools.skipUnless(CONF.compute_feature_enabled.create_image,
+ 'Environment unable to create images.')
+ @attr(type='smoke')
+ def test_create_delete_image(self):
+
+ # Create a new image
+ name = data_utils.rand_name('image')
+ meta = {'image_type': 'test'}
+ resp, body = self.client.create_image(self.server_id, name, meta)
+ self.assertEqual(202, resp.status)
+ image_id = data_utils.parse_image_id(resp['location'])
+ self.client.wait_for_image_status(image_id, 'ACTIVE')
+
+ # Verify the image was created correctly
+ resp, image = self.client.get_image(image_id)
+ self.assertEqual(name, image['name'])
+ self.assertEqual('test', image['metadata']['image_type'])
+
+ resp, original_image = self.client.get_image(self.image_ref)
+
+ # Verify minRAM is the same as the original image
+ self.assertEqual(image['minRam'], original_image['minRam'])
+
+ # Verify minDisk is the same as the original image or the flavor size
+ flavor_disk_size = self._get_default_flavor_disk_size(self.flavor_ref)
+ self.assertIn(str(image['minDisk']),
+ (str(original_image['minDisk']), str(flavor_disk_size)))
+
+ # Verify the image was deleted correctly
+ resp, body = self.client.delete_image(image_id)
+ self.assertEqual('204', resp['status'])
+ self.client.wait_for_resource_deletion(image_id)
+
+ @attr(type=['gate'])
+ def test_create_image_specify_multibyte_character_image_name(self):
+ if self.__class__._interface == "xml":
+ # NOTE(sdague): not entirely accurage, but we'd need a ton of work
+ # in our XML client to make this good
+ raise self.skipException("Not testable in XML")
+ # prefix character is:
+ # http://www.fileformat.info/info/unicode/char/1F4A9/index.htm
+ utf8_name = data_utils.rand_name(u'\xF0\x9F\x92\xA9')
+ resp, body = self.client.create_image(self.server_id, utf8_name)
+ image_id = data_utils.parse_image_id(resp['location'])
+ self.addCleanup(self.client.delete_image, image_id)
+ self.assertEqual('202', resp['status'])
+
+
+class ImagesOneServerTestXML(ImagesOneServerTestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/compute/v3/images/test_images_oneserver_negative.py b/tempest/api/compute/v3/images/test_images_oneserver_negative.py
new file mode 100644
index 0000000..5e235d1
--- /dev/null
+++ b/tempest/api/compute/v3/images/test_images_oneserver_negative.py
@@ -0,0 +1,162 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 OpenStack Foundation
+# Copyright 2013 IBM Corp.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.api.compute import base
+from tempest import clients
+from tempest.common.utils import data_utils
+from tempest import exceptions
+from tempest.openstack.common import log as logging
+from tempest.test import attr
+from tempest.test import skip_because
+
+LOG = logging.getLogger(__name__)
+
+
+class ImagesOneServerNegativeTestJSON(base.BaseV2ComputeTest):
+ _interface = 'json'
+
+ def tearDown(self):
+ """Terminate test instances created after a test is executed."""
+ for image_id in self.image_ids:
+ self.client.delete_image(image_id)
+ self.image_ids.remove(image_id)
+ super(ImagesOneServerNegativeTestJSON, self).tearDown()
+
+ def setUp(self):
+ # NOTE(afazekas): Normally we use the same server with all test cases,
+ # but if it has an issue, we build a new one
+ super(ImagesOneServerNegativeTestJSON, self).setUp()
+ # Check if the server is in a clean state after test
+ try:
+ self.servers_client.wait_for_server_status(self.server_id,
+ 'ACTIVE')
+ except Exception:
+ LOG.exception('server %s timed out to become ACTIVE. rebuilding'
+ % self.server_id)
+ # Rebuild server if cannot reach the ACTIVE state
+ # Usually it means the server had a serious accident
+ self._reset_server()
+
+ def _reset_server(self):
+ self.__class__.server_id = self.rebuild_server(self.server_id)
+
+ @classmethod
+ def setUpClass(cls):
+ super(ImagesOneServerNegativeTestJSON, cls).setUpClass()
+ cls.client = cls.images_client
+ if not cls.config.service_available.glance:
+ skip_msg = ("%s skipped as glance is not available" % cls.__name__)
+ raise cls.skipException(skip_msg)
+
+ try:
+ resp, server = cls.create_test_server(wait_until='ACTIVE')
+ cls.server_id = server['id']
+ except Exception:
+ cls.tearDownClass()
+ raise
+
+ cls.image_ids = []
+
+ if cls.multi_user:
+ if cls.config.compute.allow_tenant_isolation:
+ creds = cls.isolated_creds.get_alt_creds()
+ username, tenant_name, password = creds
+ cls.alt_manager = clients.Manager(username=username,
+ password=password,
+ tenant_name=tenant_name)
+ else:
+ # Use the alt_XXX credentials in the config file
+ cls.alt_manager = clients.AltManager()
+ cls.alt_client = cls.alt_manager.images_client
+
+ @skip_because(bug="1006725")
+ @attr(type=['negative', 'gate'])
+ def test_create_image_specify_multibyte_character_image_name(self):
+ if self.__class__._interface == "xml":
+ raise self.skipException("Not testable in XML")
+ # invalid multibyte sequence from:
+ # http://stackoverflow.com/questions/1301402/
+ # example-invalid-utf8-string
+ invalid_name = data_utils.rand_name(u'\xc3\x28')
+ self.assertRaises(exceptions.BadRequest,
+ self.client.create_image, self.server_id,
+ invalid_name)
+
+ @attr(type=['negative', 'gate'])
+ def test_create_image_specify_invalid_metadata(self):
+ # Return an error when creating image with invalid metadata
+ snapshot_name = data_utils.rand_name('test-snap-')
+ meta = {'': ''}
+ self.assertRaises(exceptions.BadRequest, self.client.create_image,
+ self.server_id, snapshot_name, meta)
+
+ @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 = data_utils.rand_name('test-snap-')
+ meta = {'a' * 260: 'b' * 260}
+ self.assertRaises(exceptions.BadRequest, self.client.create_image,
+ self.server_id, snapshot_name, meta)
+
+ @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
+
+ # Create first snapshot
+ snapshot_name = data_utils.rand_name('test-snap-')
+ resp, body = self.client.create_image(self.server_id,
+ snapshot_name)
+ self.assertEqual(202, resp.status)
+ image_id = data_utils.parse_image_id(resp['location'])
+ self.image_ids.append(image_id)
+ self.addCleanup(self._reset_server)
+
+ # Create second snapshot
+ alt_snapshot_name = data_utils.rand_name('test-snap-')
+ self.assertRaises(exceptions.Conflict, self.client.create_image,
+ self.server_id, alt_snapshot_name)
+
+ @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
+
+ snapshot_name = data_utils.rand_name('a' * 260)
+ self.assertRaises(exceptions.BadRequest, self.client.create_image,
+ self.server_id, snapshot_name)
+
+ @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
+
+ snapshot_name = data_utils.rand_name('test-snap-')
+ resp, body = self.client.create_image(self.server_id, snapshot_name)
+ self.assertEqual(202, resp.status)
+ image_id = data_utils.parse_image_id(resp['location'])
+ self.image_ids.append(image_id)
+ self.addCleanup(self._reset_server)
+
+ # Do not wait, attempt to delete the image, ensure it's successful
+ resp, body = self.client.delete_image(image_id)
+ self.assertEqual('204', resp['status'])
+ self.image_ids.remove(image_id)
+
+ self.assertRaises(exceptions.NotFound, self.client.get_image, image_id)
+
+
+class ImagesOneServerNegativeTestXML(ImagesOneServerNegativeTestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/compute/v3/images/test_list_image_filters.py b/tempest/api/compute/v3/images/test_list_image_filters.py
new file mode 100644
index 0000000..bfdd8b2
--- /dev/null
+++ b/tempest/api/compute/v3/images/test_list_image_filters.py
@@ -0,0 +1,231 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 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.compute import base
+from tempest import exceptions
+from tempest.openstack.common import log as logging
+from tempest.test import attr
+
+
+LOG = logging.getLogger(__name__)
+
+
+class ListImageFiltersTestJSON(base.BaseV2ComputeTest):
+ _interface = 'json'
+
+ @classmethod
+ def setUpClass(cls):
+ super(ListImageFiltersTestJSON, cls).setUpClass()
+ if not cls.config.service_available.glance:
+ skip_msg = ("%s skipped as glance is not available" % cls.__name__)
+ raise cls.skipException(skip_msg)
+ cls.client = cls.images_client
+ cls.image_ids = []
+
+ try:
+ resp, cls.server1 = cls.create_test_server()
+ resp, cls.server2 = cls.create_test_server(wait_until='ACTIVE')
+ # NOTE(sdague) this is faster than doing the sync wait_util on both
+ cls.servers_client.wait_for_server_status(cls.server1['id'],
+ 'ACTIVE')
+
+ # Create images to be used in the filter tests
+ resp, cls.image1 = cls.create_image_from_server(
+ cls.server1['id'], wait_until='ACTIVE')
+ cls.image1_id = cls.image1['id']
+
+ # Servers have a hidden property for when they are being imaged
+ # Performing back-to-back create image calls on a single
+ # server will sometimes cause failures
+ resp, cls.image3 = cls.create_image_from_server(
+ cls.server2['id'], wait_until='ACTIVE')
+ cls.image3_id = cls.image3['id']
+
+ # Wait for the server to be active after the image upload
+ resp, cls.image2 = cls.create_image_from_server(
+ cls.server1['id'], wait_until='ACTIVE')
+ cls.image2_id = cls.image2['id']
+ except Exception:
+ LOG.exception('setUpClass failed')
+ cls.tearDownClass()
+ raise
+
+ @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='gate')
+ def test_list_images_filter_by_status(self):
+ # The list of images should contain only images with the
+ # provided status
+ params = {'status': 'ACTIVE'}
+ resp, images = self.client.list_images(params)
+
+ self.assertTrue(any([i for i in images if i['id'] == self.image1_id]))
+ 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='gate')
+ def test_list_images_filter_by_name(self):
+ # List of all images should contain the expected images filtered
+ # by name
+ params = {'name': self.image1['name']}
+ resp, images = self.client.list_images(params)
+
+ self.assertTrue(any([i for i in images if i['id'] == self.image1_id]))
+ 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='gate')
+ def test_list_images_filter_by_server_id(self):
+ # The images should contain images filtered by server id
+ params = {'server': self.server1['id']}
+ resp, images = self.client.list_images(params)
+
+ self.assertTrue(any([i for i in images if i['id'] == self.image1_id]),
+ "Failed to find image %s in images. Got images %s" %
+ (self.image1_id, images))
+ 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='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']
+
+ # Try all server link types
+ for link in server_links:
+ params = {'server': link['href']}
+ resp, images = self.client.list_images(params)
+
+ self.assertFalse(any([i for i in images
+ if i['id'] == self.image1_id]))
+ self.assertFalse(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='gate')
+ def test_list_images_filter_by_type(self):
+ # The list of servers should be filtered by image type
+ params = {'type': 'snapshot'}
+ resp, images = self.client.list_images(params)
+
+ self.assertTrue(any([i for i in images if i['id'] == self.image1_id]))
+ 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]))
+ self.assertFalse(any([i for i in images if i['id'] == self.image_ref]))
+
+ @attr(type='gate')
+ def test_list_images_limit_results(self):
+ # Verify only the expected number of results are returned
+ params = {'limit': '1'}
+ resp, images = self.client.list_images(params)
+ # when _interface='xml', one element for images_links in images
+ # ref: Question #224349
+ self.assertEqual(1, len([x for x in images if 'id' in x]))
+
+ @attr(type='gate')
+ def test_list_images_filter_by_changes_since(self):
+ # Verify only updated images are returned in the detailed list
+
+ # Becoming ACTIVE will modify the updated time
+ # Filter by the image's created time
+ params = {'changes-since': self.image3['created']}
+ resp, images = self.client.list_images(params)
+ found = any([i for i in images if i['id'] == self.image3_id])
+ self.assertTrue(found)
+
+ @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
+ params = {'status': 'ACTIVE'}
+ resp, images = self.client.list_images_with_detail(params)
+
+ self.assertTrue(any([i for i in images if i['id'] == self.image1_id]))
+ 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='gate')
+ def test_list_images_with_detail_filter_by_name(self):
+ # Detailed list of all images should contain the expected
+ # images filtered by name
+ params = {'name': self.image1['name']}
+ resp, images = self.client.list_images_with_detail(params)
+
+ self.assertTrue(any([i for i in images if i['id'] == self.image1_id]))
+ 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='gate')
+ def test_list_images_with_detail_limit_results(self):
+ # Verify only the expected number of results (with full details)
+ # are returned
+ params = {'limit': '1'}
+ resp, images = self.client.list_images_with_detail(params)
+ self.assertEqual(1, len(images))
+
+ @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']
+
+ # Try all server link types
+ for link in server_links:
+ params = {'server': link['href']}
+ resp, images = self.client.list_images_with_detail(params)
+
+ self.assertFalse(any([i for i in images
+ if i['id'] == self.image1_id]))
+ self.assertFalse(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='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'}
+ resp, images = self.client.list_images_with_detail(params)
+ resp, image4 = self.client.get_image(self.image_ref)
+
+ self.assertTrue(any([i for i in images if i['id'] == self.image1_id]))
+ 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]))
+ self.assertFalse(any([i for i in images if i['id'] == self.image_ref]))
+
+ @attr(type='gate')
+ def test_list_images_with_detail_filter_by_changes_since(self):
+ # Verify an update image is returned
+
+ # Becoming ACTIVE will modify the updated time
+ # Filter by the image's created time
+ params = {'changes-since': self.image1['created']}
+ 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', 'gate'])
+ def test_get_nonexistant_image(self):
+ # Negative test: GET on non-existent image should fail
+ self.assertRaises(exceptions.NotFound, self.client.get_image, 999)
+
+
+class ListImageFiltersTestXML(ListImageFiltersTestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/compute/v3/servers/test_create_server.py b/tempest/api/compute/v3/servers/test_create_server.py
index e1bb160..1555442 100644
--- a/tempest/api/compute/v3/servers/test_create_server.py
+++ b/tempest/api/compute/v3/servers/test_create_server.py
@@ -117,6 +117,104 @@
self.assertTrue(linux_client.hostname_equals_servername(self.name))
+class ServersWithSpecificFlavorV3TestJSON(base.BaseV3ComputeAdminTest):
+ _interface = 'json'
+ run_ssh = CONF.compute.run_ssh
+ disk_config = 'AUTO'
+
+ @classmethod
+ def setUpClass(cls):
+ super(ServersWithSpecificFlavorV3TestJSON, cls).setUpClass()
+ cls.meta = {'hello': 'world'}
+ cls.accessIPv4 = '1.1.1.1'
+ cls.accessIPv6 = '0000:0000:0000:0000:0000:babe:220.12.22.2'
+ cls.name = data_utils.rand_name('server')
+ file_contents = 'This is a test file.'
+ personality = [{'path': '/test.txt',
+ 'contents': base64.b64encode(file_contents)}]
+ cls.client = cls.servers_client
+ cls.flavor_client = cls.flavors_admin_client
+ cli_resp = cls.create_test_server(name=cls.name,
+ meta=cls.meta,
+ access_ip_v4=cls.accessIPv4,
+ access_ip_v6=cls.accessIPv6,
+ personality=personality,
+ disk_config=cls.disk_config)
+ cls.resp, cls.server_initial = cli_resp
+ cls.password = cls.server_initial['admin_password']
+ cls.client.wait_for_server_status(cls.server_initial['id'], 'ACTIVE')
+ resp, cls.server = cls.client.get_server(cls.server_initial['id'])
+
+ @testtools.skipIf(not run_ssh, 'Instance validation tests are disabled.')
+ @test.attr(type='gate')
+ def test_verify_created_server_ephemeral_disk(self):
+ # Verify that the ephemeral disk is created when creating server
+
+ def create_flavor_with_extra_specs(self):
+ flavor_with_eph_disk_name = data_utils.rand_name('eph_flavor')
+ flavor_with_eph_disk_id = data_utils.rand_int_id(start=1000)
+ ram = 512
+ vcpus = 1
+ disk = 10
+
+ # Create a flavor with extra specs
+ resp, flavor = (self.flavor_client.
+ create_flavor(flavor_with_eph_disk_name,
+ ram, vcpus, disk,
+ flavor_with_eph_disk_id,
+ ephemeral=1, swap=1024, rxtx=1))
+ self.addCleanup(self.flavor_clean_up, flavor['id'])
+ self.assertEqual(200, resp.status)
+
+ return flavor['id']
+
+ def create_flavor_without_extra_specs(self):
+ flavor_no_eph_disk_name = data_utils.rand_name('no_eph_flavor')
+ flavor_no_eph_disk_id = data_utils.rand_int_id(start=1000)
+
+ ram = 512
+ vcpus = 1
+ disk = 10
+
+ # Create a flavor without extra specs
+ resp, flavor = (self.flavor_client.
+ create_flavor(flavor_no_eph_disk_name,
+ ram, vcpus, disk,
+ flavor_no_eph_disk_id))
+ self.addCleanup(self.flavor_clean_up, flavor['id'])
+ self.assertEqual(200, resp.status)
+
+ return flavor['id']
+
+ def flavor_clean_up(self, flavor_id):
+ resp, body = self.flavor_client.delete_flavor(flavor_id)
+ self.assertEqual(resp.status, 202)
+ self.flavor_client.wait_for_resource_deletion(flavor_id)
+
+ flavor_with_eph_disk_id = self.create_flavor_with_extra_specs()
+ flavor_no_eph_disk_id = self.create_flavor_without_extra_specs()
+
+ admin_pass = self.image_ssh_password
+
+ resp, server_no_eph_disk = (self.
+ create_test_server(
+ wait_until='ACTIVE',
+ adminPass=admin_pass,
+ flavor=flavor_no_eph_disk_id))
+ resp, server_with_eph_disk = (self.create_test_server(
+ wait_until='ACTIVE',
+ adminPass=admin_pass,
+ flavor=flavor_with_eph_disk_id))
+ # Get partition number of server without extra specs.
+ linux_client = RemoteClient(server_no_eph_disk,
+ self.ssh_user, self.password)
+ partition_num = len(linux_client.get_partitions())
+
+ linux_client = RemoteClient(server_with_eph_disk,
+ self.ssh_user, self.password)
+ self.assertEqual(partition_num + 1, linux_client.get_partitions())
+
+
class ServersV3TestManualDisk(ServersV3TestJSON):
disk_config = 'MANUAL'
@@ -130,3 +228,7 @@
class ServersV3TestXML(ServersV3TestJSON):
_interface = 'xml'
+
+
+class ServersWithSpecificFlavorV3TestXML(ServersWithSpecificFlavorV3TestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/compute/v3/servers/test_list_server_filters.py b/tempest/api/compute/v3/servers/test_list_server_filters.py
index 3dd7b0b..140cc2f 100644
--- a/tempest/api/compute/v3/servers/test_list_server_filters.py
+++ b/tempest/api/compute/v3/servers/test_list_server_filters.py
@@ -176,6 +176,23 @@
self.assertEqual(['ACTIVE'] * 3, [x['status'] for x in servers])
@attr(type='gate')
+ def test_list_servers_filter_by_shutoff_status(self):
+ # Filter the list of servers by server shutoff status
+ params = {'status': 'shutoff'}
+ self.client.stop(self.s1['id'])
+ self.client.wait_for_server_status(self.s1['id'],
+ 'SHUTOFF')
+ resp, body = self.client.list_servers(params)
+ self.client.start(self.s1['id'])
+ self.client.wait_for_server_status(self.s1['id'],
+ 'ACTIVE')
+ servers = body['servers']
+
+ self.assertIn(self.s1['id'], map(lambda x: x['id'], servers))
+ self.assertNotIn(self.s2['id'], map(lambda x: x['id'], servers))
+ self.assertNotIn(self.s3['id'], map(lambda x: x['id'], servers))
+
+ @attr(type='gate')
def test_list_servers_filtered_by_name_wildcard(self):
# List all servers that contains '-instance' in name
params = {'name': '-instance'}
diff --git a/tempest/api/compute/v3/servers/test_list_servers_negative.py b/tempest/api/compute/v3/servers/test_list_servers_negative.py
index 3f7f885..b8e3d10 100644
--- a/tempest/api/compute/v3/servers/test_list_servers_negative.py
+++ b/tempest/api/compute/v3/servers/test_list_servers_negative.py
@@ -18,69 +18,18 @@
import datetime
from tempest.api.compute import base
-from tempest import clients
from tempest import exceptions
from tempest.test import attr
class ListServersNegativeV3TestJSON(base.BaseV3ComputeTest):
_interface = 'json'
-
- @classmethod
- def _ensure_no_servers(cls, servers, username, tenant_name):
- """
- If there are servers and there is tenant isolation then a
- skipException is raised to skip the test since it requires no servers
- to already exist for the given user/tenant.
- If there are servers and there is not tenant isolation then the test
- blocks while the servers are being deleted.
- """
- if len(servers):
- if not cls.config.compute.allow_tenant_isolation:
- for srv in servers:
- cls.client.wait_for_server_termination(srv['id'],
- ignore_error=True)
- else:
- msg = ("User/tenant %(u)s/%(t)s already have "
- "existing server instances. Skipping test." %
- {'u': username, 't': tenant_name})
- raise cls.skipException(msg)
+ force_tenant_isolation = True
@classmethod
def setUpClass(cls):
super(ListServersNegativeV3TestJSON, cls).setUpClass()
cls.client = cls.servers_client
- cls.servers = []
-
- if cls.multi_user:
- if cls.config.compute.allow_tenant_isolation:
- creds = cls.isolated_creds.get_alt_creds()
- username, tenant_name, password = creds
- cls.alt_manager = clients.Manager(username=username,
- password=password,
- tenant_name=tenant_name)
- else:
- # Use the alt_XXX credentials in the config file
- cls.alt_manager = clients.AltManager()
- cls.alt_client = cls.alt_manager.servers_client
-
- # Under circumstances when there is not a tenant/user
- # created for the test case, the test case checks
- # to see if there are existing servers for the
- # either the normal user/tenant or the alt user/tenant
- # and if so, the whole test is skipped. We do this
- # because we assume a baseline of no servers at the
- # start of the test instead of destroying any existing
- # servers.
- resp, body = cls.client.list_servers()
- cls._ensure_no_servers(body['servers'],
- cls.os.username,
- cls.os.tenant_name)
-
- resp, body = cls.alt_client.list_servers()
- cls._ensure_no_servers(body['servers'],
- cls.alt_manager.username,
- cls.alt_manager.tenant_name)
# The following servers are created for use
# by the test methods in this class. These
diff --git a/tempest/api/compute/v3/servers/test_server_actions.py b/tempest/api/compute/v3/servers/test_server_actions.py
index 602bd5b..f462cc2 100644
--- a/tempest/api/compute/v3/servers/test_server_actions.py
+++ b/tempest/api/compute/v3/servers/test_server_actions.py
@@ -15,7 +15,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-import base64
import time
import testtools
@@ -113,15 +112,11 @@
# The server should be rebuilt using the provided image and data
meta = {'rebuild': 'server'}
new_name = data_utils.rand_name('server')
- file_contents = 'Test server rebuild.'
- personality = [{'path': 'rebuild.txt',
- 'contents': base64.b64encode(file_contents)}]
password = 'rebuildPassw0rd'
resp, rebuilt_server = self.client.rebuild(self.server_id,
self.image_ref_alt,
name=new_name,
metadata=meta,
- personality=personality,
admin_password=password)
self.addCleanup(self.client.rebuild, self.server_id, self.image_ref)
@@ -134,9 +129,9 @@
# Verify the server properties after the rebuild completes
self.client.wait_for_server_status(rebuilt_server['id'], 'ACTIVE')
resp, server = self.client.get_server(rebuilt_server['id'])
- rebuilt_image_id = rebuilt_server['image']['id']
+ rebuilt_image_id = server['image']['id']
self.assertTrue(self.image_ref_alt.endswith(rebuilt_image_id))
- self.assertEqual(new_name, rebuilt_server['name'])
+ self.assertEqual(new_name, server['name'])
if self.run_ssh:
# Verify that the user can authenticate with the provided password
@@ -174,7 +169,7 @@
def _detect_server_image_flavor(self, server_id):
# Detects the current server image flavor ref.
- resp, server = self.client.get_server(self.server_id)
+ resp, server = self.client.get_server(server_id)
current_flavor = server['flavor']['id']
new_flavor_ref = self.flavor_ref_alt \
if current_flavor == self.flavor_ref else self.flavor_ref
diff --git a/tempest/api/compute/v3/servers/test_server_metadata.py b/tempest/api/compute/v3/servers/test_server_metadata.py
index ee0f4a9..f36feb3 100644
--- a/tempest/api/compute/v3/servers/test_server_metadata.py
+++ b/tempest/api/compute/v3/servers/test_server_metadata.py
@@ -20,12 +20,12 @@
from tempest.test import attr
-class ServerMetadataTestJSON(base.BaseV2ComputeTest):
+class ServerMetadataV3TestJSON(base.BaseV3ComputeTest):
_interface = 'json'
@classmethod
def setUpClass(cls):
- super(ServerMetadataTestJSON, cls).setUpClass()
+ super(ServerMetadataV3TestJSON, cls).setUpClass()
cls.client = cls.servers_client
cls.quotas = cls.quotas_client
cls.admin_client = cls._get_identity_admin_client()
@@ -37,7 +37,7 @@
cls.server_id = server['id']
def setUp(self):
- super(ServerMetadataTestJSON, self).setUp()
+ super(ServerMetadataV3TestJSON, self).setUp()
meta = {'key1': 'value1', 'key2': 'value2'}
resp, _ = self.client.set_server_metadata(self.server_id, meta)
self.assertEqual(resp.status, 200)
@@ -88,7 +88,7 @@
meta = {'key1': 'alt1', 'key3': 'value3'}
resp, metadata = self.client.update_server_metadata(self.server_id,
meta)
- self.assertEqual(200, resp.status)
+ self.assertEqual(201, resp.status)
# Verify the values have been updated to the proper values
resp, resp_metadata = self.client.list_server_metadata(self.server_id)
@@ -213,5 +213,5 @@
self.server_id, meta=meta, no_metadata_field=True)
-class ServerMetadataTestXML(ServerMetadataTestJSON):
+class ServerMetadataV3TestXML(ServerMetadataV3TestJSON):
_interface = 'xml'
diff --git a/tempest/api/compute/v3/servers/test_server_personality.py b/tempest/api/compute/v3/servers/test_server_personality.py
deleted file mode 100644
index c6d2e44..0000000
--- a/tempest/api/compute/v3/servers/test_server_personality.py
+++ /dev/null
@@ -1,68 +0,0 @@
-# vim: tabstop=4 shiftwidth=4 softtabstop=4
-
-# Copyright 2012 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 base64
-
-from tempest.api.compute import base
-from tempest import exceptions
-from tempest.test import attr
-
-
-class ServerPersonalityTestJSON(base.BaseV2ComputeTest):
- _interface = 'json'
-
- @classmethod
- def setUpClass(cls):
- super(ServerPersonalityTestJSON, cls).setUpClass()
- 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.
- file_contents = 'This is a test file.'
- personality = []
- max_file_limit = \
- self.user_client.get_specific_absolute_limit("maxPersonality")
- for i in range(0, int(max_file_limit) + 1):
- path = 'etc/test' + str(i) + '.txt'
- personality.append({'path': path,
- 'contents': base64.b64encode(file_contents)})
- self.assertRaises(exceptions.OverLimit, self.create_test_server,
- personality=personality)
-
- @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.
- file_contents = 'This is a test file.'
- max_file_limit = \
- self.user_client.get_specific_absolute_limit("maxPersonality")
- person = []
- for i in range(0, int(max_file_limit)):
- path = 'etc/test' + str(i) + '.txt'
- person.append({
- 'path': path,
- 'contents': base64.b64encode(file_contents),
- })
- resp, server = self.create_test_server(personality=person)
- self.assertEqual('202', resp['status'])
-
-
-class ServerPersonalityTestXML(ServerPersonalityTestJSON):
- _interface = "xml"
diff --git a/tempest/api/compute/v3/test_extensions.py b/tempest/api/compute/v3/test_extensions.py
index 2affd86..f37ebcf 100644
--- a/tempest/api/compute/v3/test_extensions.py
+++ b/tempest/api/compute/v3/test_extensions.py
@@ -17,19 +17,33 @@
from tempest.api.compute import base
+from tempest.openstack.common import log as logging
from tempest import test
+LOG = logging.getLogger(__name__)
+
+
class ExtensionsV3TestJSON(base.BaseV3ComputeTest):
_interface = 'json'
@test.attr(type='gate')
def test_list_extensions(self):
# List of all extensions
+ if len(self.config.compute_feature_enabled.api_v3_extensions) == 0:
+ raise self.skipException('There are not any extensions configured')
resp, extensions = self.extensions_client.list_extensions()
- self.assertIn("extensions", extensions)
self.assertEqual(200, resp.status)
- self.assertTrue(self.extensions_client.is_enabled("Consoles"))
+ ext = self.config.compute_feature_enabled.api_v3_extensions[0]
+ if ext == 'all':
+ self.assertIn('Hosts', map(lambda x: x['name'], extensions))
+ elif ext:
+ self.assertIn(ext, map(lambda x: x['name'], extensions))
+ else:
+ raise self.skipException('There are not any extensions configured')
+ # Log extensions list
+ extension_list = map(lambda x: x['name'], extensions)
+ LOG.debug("Nova extensions: %s" % ','.join(extension_list))
@test.attr(type='gate')
def test_get_extension(self):
diff --git a/tempest/api/compute/volumes/test_volumes_get.py b/tempest/api/compute/volumes/test_volumes_get.py
index ae6996d..f6fed5b 100644
--- a/tempest/api/compute/volumes/test_volumes_get.py
+++ b/tempest/api/compute/volumes/test_volumes_get.py
@@ -18,6 +18,7 @@
from tempest.api.compute import base
from tempest.common.utils import data_utils
from tempest.test import attr
+from testtools.matchers import ContainsAll
class VolumesGetTestJSON(base.BaseV2ComputeTest):
@@ -65,29 +66,10 @@
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')
-
- @attr(type='gate')
- def test_volume_get_metadata_none(self):
- # CREATE, GET empty metadata dict
- v_name = data_utils.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.assertIn('id', volume)
- self.assertIn('displayName', 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'], {})
+ self.assertThat(fetched_volume['metadata'].items(),
+ ContainsAll(metadata.items()),
+ 'The fetched Volume metadata misses data '
+ 'from the created Volume')
def _delete_volume(self, volume):
# Delete the Volume created in this method
diff --git a/tempest/api/identity/admin/v3/test_trusts.py b/tempest/api/identity/admin/v3/test_trusts.py
index 1ecc90c..d316ae1 100644
--- a/tempest/api/identity/admin/v3/test_trusts.py
+++ b/tempest/api/identity/admin/v3/test_trusts.py
@@ -28,10 +28,7 @@
super(BaseTrustsV3Test, self).setUp()
# Use alt_username as the trustee
self.trustee_username = self.config.identity.alt_username
-
self.trust_id = None
- self.create_trustor_and_roles()
- self.addCleanup(self.cleanup_user_and_roles)
def tearDown(self):
if self.trust_id:
@@ -50,7 +47,7 @@
# Create a trustor User
self.trustor_username = rand_name('user-')
u_desc = self.trustor_username + 'description'
- u_email = self.trustor_username + '@testmail.tm'
+ u_email = self.trustor_username + '@testmail.xx'
self.trustor_password = rand_name('pass-')
resp, user = self.v3_client.create_user(
self.trustor_username,
@@ -193,6 +190,7 @@
def setUp(self):
super(TrustsV3TestJSON, self).setUp()
self.create_trustor_and_roles()
+ self.addCleanup(self.cleanup_user_and_roles)
@attr(type='smoke')
def test_trust_impersonate(self):
diff --git a/tempest/api/network/admin/test_agent_management.py b/tempest/api/network/admin/test_agent_management.py
index 94659b2..4b36197 100644
--- a/tempest/api/network/admin/test_agent_management.py
+++ b/tempest/api/network/admin/test_agent_management.py
@@ -36,6 +36,12 @@
agents = body['agents']
self.assertIn(self.agent, agents)
+ @attr(type=['smoke'])
+ def test_list_agents_non_admin(self):
+ resp, body = self.client.list_agents()
+ self.assertEqual('200', resp['status'])
+ self.assertEqual(len(body["agents"]), 0)
+
@attr(type='smoke')
def test_show_agent(self):
resp, body = self.admin_client.show_agent(self.agent['id'])
diff --git a/tempest/api/object_storage/base.py b/tempest/api/object_storage/base.py
index e7cb806..47d8cca 100644
--- a/tempest/api/object_storage/base.py
+++ b/tempest/api/object_storage/base.py
@@ -101,6 +101,7 @@
The containers should be visible from the container_client given.
Will not throw any error if the containers don't exist.
+ Will not check that object and container deletions succeed.
:param containers: list of container names to remove
:param container_client: if None, use cls.container_client, this means
diff --git a/tempest/api/object_storage/test_account_quotas.py b/tempest/api/object_storage/test_account_quotas.py
index ac1c7d1..6312f69 100644
--- a/tempest/api/object_storage/test_account_quotas.py
+++ b/tempest/api/object_storage/test_account_quotas.py
@@ -14,21 +14,17 @@
# License for the specific language governing permissions and limitations
# under the License.
-import testtools
-
from tempest.api.object_storage import base
from tempest import clients
from tempest.common.utils import data_utils
from tempest import config
from tempest import exceptions
-from tempest.test import attr
+from tempest import test
CONF = config.CONF
class AccountQuotasTest(base.BaseObjectTest):
- accounts_quotas_available = \
- CONF.object_storage_feature_enabled.accounts_quotas
@classmethod
def setUpClass(cls):
@@ -99,9 +95,8 @@
cls.data.teardown_all()
super(AccountQuotasTest, cls).tearDownClass()
- @testtools.skipIf(not accounts_quotas_available,
- "Account Quotas middleware not available")
- @attr(type="smoke")
+ @test.attr(type="smoke")
+ @test.requires_ext(extension='account_quotas', service='object')
def test_upload_valid_object(self):
object_name = data_utils.rand_name(name="TestObject")
data = data_utils.arbitrary_string()
@@ -111,9 +106,8 @@
self.assertEqual(resp["status"], "201")
self.assertHeaders(resp, 'Object', 'PUT')
- @testtools.skipIf(not accounts_quotas_available,
- "Account Quotas middleware not available")
- @attr(type=["negative", "smoke"])
+ @test.attr(type=["negative", "smoke"])
+ @test.requires_ext(extension='account_quotas', service='object')
def test_upload_large_object(self):
object_name = data_utils.rand_name(name="TestObject")
data = data_utils.arbitrary_string(30)
@@ -121,9 +115,8 @@
self.object_client.create_object,
self.container_name, object_name, data)
- @testtools.skipIf(not accounts_quotas_available,
- "Account Quotas middleware not available")
- @attr(type=["smoke"])
+ @test.attr(type=["smoke"])
+ @test.requires_ext(extension='account_quotas', service='object')
def test_admin_modify_quota(self):
"""Test that the ResellerAdmin is able to modify and remove the quota
on a user's account.
@@ -146,9 +139,8 @@
self.assertEqual(resp["status"], "204")
self.assertHeaders(resp, 'Account', 'POST')
- @testtools.skipIf(not accounts_quotas_available,
- "Account Quotas middleware not available")
- @attr(type=["negative", "smoke"])
+ @test.attr(type=["negative", "smoke"])
+ @test.requires_ext(extension='account_quotas', service='object')
def test_user_modify_quota(self):
"""Test that a user is not able to modify or remove a quota on
its account.
diff --git a/tempest/api/object_storage/test_container_quotas.py b/tempest/api/object_storage/test_container_quotas.py
index 513d24a..103ee89 100644
--- a/tempest/api/object_storage/test_container_quotas.py
+++ b/tempest/api/object_storage/test_container_quotas.py
@@ -15,25 +15,19 @@
# License for the specific language governing permissions and limitations
# under the License.
-import testtools
-
from tempest.api.object_storage import base
from tempest.common.utils import data_utils
from tempest import config
from tempest import exceptions
-from tempest.test import attr
-from tempest.test import HTTP_SUCCESS
+from tempest import test
CONF = config.CONF
QUOTA_BYTES = 10
QUOTA_COUNT = 3
-SKIP_MSG = "Container quotas middleware not available."
class ContainerQuotasTest(base.BaseObjectTest):
"""Attemps to test the perfect behavior of quotas in a container."""
- container_quotas_available = \
- CONF.object_storage_feature_enabled.container_quotas
def setUp(self):
"""Creates and sets a container with quotas.
@@ -58,8 +52,8 @@
self.delete_containers([self.container_name])
super(ContainerQuotasTest, self).tearDown()
- @testtools.skipIf(not container_quotas_available, SKIP_MSG)
- @attr(type="smoke")
+ @test.requires_ext(extension='container_quotas', service='object')
+ @test.attr(type="smoke")
def test_upload_valid_object(self):
"""Attempts to uploads an object smaller than the bytes quota."""
object_name = data_utils.rand_name(name="TestObject")
@@ -69,14 +63,14 @@
resp, _ = self.object_client.create_object(
self.container_name, object_name, data)
- self.assertIn(int(resp['status']), HTTP_SUCCESS)
+ self.assertIn(int(resp['status']), test.HTTP_SUCCESS)
self.assertHeaders(resp, 'Object', 'PUT')
nafter = self._get_bytes_used()
self.assertEqual(nbefore + len(data), nafter)
- @testtools.skipIf(not container_quotas_available, SKIP_MSG)
- @attr(type="smoke")
+ @test.requires_ext(extension='container_quotas', service='object')
+ @test.attr(type="smoke")
def test_upload_large_object(self):
"""Attempts to upload an object lagger than the bytes quota."""
object_name = data_utils.rand_name(name="TestObject")
@@ -91,8 +85,8 @@
nafter = self._get_bytes_used()
self.assertEqual(nbefore, nafter)
- @testtools.skipIf(not container_quotas_available, SKIP_MSG)
- @attr(type="smoke")
+ @test.requires_ext(extension='container_quotas', service='object')
+ @test.attr(type="smoke")
def test_upload_too_many_objects(self):
"""Attempts to upload many objects that exceeds the count limit."""
for _ in range(QUOTA_COUNT):
diff --git a/tempest/api/object_storage/test_container_sync.py b/tempest/api/object_storage/test_container_sync.py
index dcfe219..ec78774 100644
--- a/tempest/api/object_storage/test_container_sync.py
+++ b/tempest/api/object_storage/test_container_sync.py
@@ -16,25 +16,38 @@
# under the License.
import time
+import urlparse
from tempest.api.object_storage import base
from tempest.common.utils import data_utils
from tempest.test import attr
-from tempest.test import skip_because
+from tempest.test import HTTP_SUCCESS
+
+# This test can be quite long to run due to its
+# dependency on container-sync process running interval.
+# You can obviously reduce the container-sync interval in the
+# container-server configuration.
class ContainerSyncTest(base.BaseObjectTest):
+
@classmethod
def setUpClass(cls):
super(ContainerSyncTest, cls).setUpClass()
cls.containers = []
cls.objects = []
+
+ # Default container-server config only allows localhost
+ cls.local_ip = '127.0.0.1'
+
+ # Must be configure according to container-sync interval
container_sync_timeout = \
int(cls.config.object_storage.container_sync_timeout)
cls.container_sync_interval = \
int(cls.config.object_storage.container_sync_interval)
cls.attempts = \
int(container_sync_timeout / cls.container_sync_interval)
+
# define container and object clients
cls.clients = {}
cls.clients[data_utils.rand_name(name='TestContainerSync')] = \
@@ -51,8 +64,7 @@
cls.delete_containers(cls.containers, client[0], client[1])
super(ContainerSyncTest, cls).tearDownClass()
- @skip_because(bug="1093743")
- @attr(type='gate')
+ @attr(type='slow')
def test_container_synchronization(self):
# container to container synchronization
# to allow/accept sync requests to/from other accounts
@@ -62,51 +74,53 @@
cont_client = [self.clients[c][0] for c in cont]
obj_client = [self.clients[c][1] for c in cont]
# tell first container to synchronize to a second
+ client_proxy_ip = \
+ urlparse.urlparse(cont_client[1].base_url).netloc.split(':')[0]
+ client_base_url = \
+ cont_client[1].base_url.replace(client_proxy_ip,
+ self.local_ip)
headers = {'X-Container-Sync-Key': 'sync_key',
'X-Container-Sync-To': "%s/%s" %
- (cont_client[1].base_url, str(cont[1]))}
+ (client_base_url, str(cont[1]))}
resp, body = \
cont_client[0].put(str(cont[0]), body=None, headers=headers)
- self.assertIn(resp['status'], ('202', '201'),
- 'Error installing X-Container-Sync-To '
- 'for the container "%s"' % (cont[0]))
+ self.assertIn(int(resp['status']), HTTP_SUCCESS)
# create object in container
object_name = data_utils.rand_name(name='TestSyncObject')
data = object_name[::-1] # data_utils.arbitrary_string()
resp, _ = obj_client[0].create_object(cont[0], object_name, data)
- self.assertEqual(resp['status'], '201',
- 'Error creating the object "%s" in'
- 'the container "%s"'
- % (object_name, cont[0]))
+ self.assertIn(int(resp['status']), HTTP_SUCCESS)
self.objects.append(object_name)
# wait until container contents list is not empty
cont_client = [self.clients[c][0] for c in self.containers]
params = {'format': 'json'}
while self.attempts > 0:
- # get first container content
- resp, object_list_0 = \
- cont_client[0].\
- list_container_contents(self.containers[0], params=params)
- self.assertEqual(resp['status'], '200',
- 'Error listing the destination container`s'
- ' "%s" contents' % (self.containers[0]))
- object_list_0 = dict((obj['name'], obj) for obj in object_list_0)
- # get second container content
- resp, object_list_1 = \
- cont_client[1].\
- list_container_contents(self.containers[1], params=params)
- self.assertEqual(resp['status'], '200',
- 'Error listing the destination container`s'
- ' "%s" contents' % (self.containers[1]))
- object_list_1 = dict((obj['name'], obj) for obj in object_list_1)
+ object_lists = []
+ for client_index in (0, 1):
+ resp, object_list = \
+ cont_client[client_index].\
+ list_container_contents(self.containers[client_index],
+ params=params)
+ self.assertIn(int(resp['status']), HTTP_SUCCESS)
+ object_lists.append(dict(
+ (obj['name'], obj) for obj in object_list))
# check that containers are not empty and have equal keys()
# or wait for next attempt
- if not object_list_0 or not object_list_1 or \
- set(object_list_0.keys()) != set(object_list_1.keys()):
+ if not object_lists[0] or not object_lists[1] or \
+ set(object_lists[0].keys()) != set(object_lists[1].keys()):
time.sleep(self.container_sync_interval)
self.attempts -= 1
else:
break
- self.assertEqual(object_list_0, object_list_1,
+
+ self.assertEqual(object_lists[0], object_lists[1],
'Different object lists in containers.')
+
+ # Verify object content
+ obj_clients = [(self.clients[c][1], c) for c in self.containers]
+ for obj_client, cont in obj_clients:
+ for obj_name in object_lists[0]:
+ resp, object_content = obj_client.get_object(cont, obj_name)
+ self.assertIn(int(resp['status']), HTTP_SUCCESS)
+ self.assertEqual(object_content, obj_name[::-1])
diff --git a/tempest/api/object_storage/test_crossdomain.py b/tempest/api/object_storage/test_crossdomain.py
index 41430c8..debd432 100644
--- a/tempest/api/object_storage/test_crossdomain.py
+++ b/tempest/api/object_storage/test_crossdomain.py
@@ -19,27 +19,14 @@
from tempest.api.object_storage import base
from tempest import clients
from tempest.common import custom_matchers
-from tempest import config
-from tempest.test import attr
-from tempest.test import HTTP_SUCCESS
-
-CONF = config.CONF
+from tempest import test
class CrossdomainTest(base.BaseObjectTest):
- crossdomain_available = \
- CONF.object_storage_feature_enabled.crossdomain
@classmethod
def setUpClass(cls):
super(CrossdomainTest, cls).setUpClass()
-
- # skip this test if CORS isn't enabled in the conf file.
- if not cls.crossdomain_available:
- skip_msg = ("%s skipped as Crossdomain middleware not available"
- % cls.__name__)
- raise cls.skipException(skip_msg)
-
# creates a test user. The test user will set its base_url to the Swift
# endpoint and test the healthcheck feature.
cls.data.setup_test_user()
@@ -75,12 +62,13 @@
super(CrossdomainTest, self).tearDown()
- @attr('gate')
+ @test.attr('gate')
+ @test.requires_ext(extension='crossdomain', service='object')
def test_get_crossdomain_policy(self):
resp, body = self.os_test_user.account_client.get("crossdomain.xml",
{})
- self.assertIn(int(resp['status']), HTTP_SUCCESS)
+ self.assertIn(int(resp['status']), test.HTTP_SUCCESS)
self.assertTrue(body.startswith(self.xml_start) and
body.endswith(self.xml_end))
diff --git a/tempest/api/object_storage/test_object_expiry.py b/tempest/api/object_storage/test_object_expiry.py
index 4958f70..9c2834d 100644
--- a/tempest/api/object_storage/test_object_expiry.py
+++ b/tempest/api/object_storage/test_object_expiry.py
@@ -21,7 +21,6 @@
from tempest.common.utils import data_utils
from tempest import exceptions
from tempest.test import attr
-from tempest.test import skip_because
class ObjectExpiryTest(base.BaseObjectTest):
@@ -33,31 +32,20 @@
@classmethod
def tearDownClass(cls):
- """The test script fails in tear down class
- as the container contains expired objects (LP bug 1069849).
- But delete action for the expired object is raising
- NotFound exception and also non empty container cannot be deleted.
- """
cls.delete_containers([cls.container_name])
super(ObjectExpiryTest, cls).tearDownClass()
- @skip_because(bug="1069849")
- @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.
-
+ def _test_object_expiry(self, metadata):
# create object
object_name = data_utils.rand_name(name='TestObject')
- data = data_utils.arbitrary_string()
resp, _ = self.object_client.create_object(self.container_name,
- object_name, data)
- # update object metadata with expiry time of 3 seconds
- metadata = {'X-Delete-After': '3'}
+ object_name, '')
+ # update object metadata
resp, _ = \
self.object_client.update_object_metadata(self.container_name,
object_name, metadata,
metadata_prefix='')
+ # verify object metadata
resp, _ = \
self.object_client.list_object_metadata(self.container_name,
object_name)
@@ -69,10 +57,20 @@
self.assertEqual(resp['status'], '200')
self.assertHeaders(resp, 'Object', 'GET')
self.assertIn('x-delete-at', resp)
- # check data
- self.assertEqual(body, data)
+
# sleep for over 5 seconds, so that object expires
time.sleep(5)
+
# object should not be there anymore
self.assertRaises(exceptions.NotFound, self.object_client.get_object,
self.container_name, object_name)
+
+ @attr(type='gate')
+ def test_get_object_after_expiry_time(self):
+ metadata = {'X-Delete-After': '3'}
+ self._test_object_expiry(metadata)
+
+ @attr(type='gate')
+ def test_get_object_at_expiry_time(self):
+ metadata = {'X-Delete-At': str(int(time.time()) + 3)}
+ self._test_object_expiry(metadata)
diff --git a/tempest/api/object_storage/test_object_temp_url.py b/tempest/api/object_storage/test_object_temp_url.py
index d0e5353..d1b3674 100644
--- a/tempest/api/object_storage/test_object_temp_url.py
+++ b/tempest/api/object_storage/test_object_temp_url.py
@@ -24,27 +24,16 @@
from tempest.common.utils import data_utils
from tempest import config
from tempest import exceptions
-from tempest.test import attr
-from tempest.test import HTTP_SUCCESS
+from tempest import test
CONF = config.CONF
class ObjectTempUrlTest(base.BaseObjectTest):
- tempurl_available = \
- CONF.object_storage_feature_enabled.tempurl
-
@classmethod
def setUpClass(cls):
super(ObjectTempUrlTest, cls).setUpClass()
-
- # skip this test if TempUrl isn't enabled in the conf file.
- if not cls.tempurl_available:
- skip_msg = ("%s skipped as TempUrl middleware not available"
- % cls.__name__)
- raise cls.skipException(skip_msg)
-
# create a container
cls.container_name = data_utils.rand_name(name='TestContainer')
cls.container_client.create_container(cls.container_name)
@@ -108,7 +97,8 @@
return url
- @attr(type='gate')
+ @test.attr(type='gate')
+ @test.requires_ext(extension='tempurl', service='object')
def test_get_object_using_temp_url(self):
expires = self._get_expiry_date()
@@ -119,16 +109,17 @@
# trying to get object using temp url within expiry time
resp, body = self.object_client.get(url)
- self.assertIn(int(resp['status']), HTTP_SUCCESS)
+ self.assertIn(int(resp['status']), test.HTTP_SUCCESS)
self.assertHeaders(resp, 'Object', 'GET')
self.assertEqual(body, self.content)
# Testing a HEAD on this Temp URL
resp, body = self.object_client.head(url)
- self.assertIn(int(resp['status']), HTTP_SUCCESS)
+ self.assertIn(int(resp['status']), test.HTTP_SUCCESS)
self.assertHeaders(resp, 'Object', 'HEAD')
- @attr(type='gate')
+ @test.attr(type='gate')
+ @test.requires_ext(extension='tempurl', service='object')
def test_get_object_using_temp_url_key_2(self):
key2 = 'Meta2-'
metadata = {'Temp-URL-Key-2': key2}
@@ -149,10 +140,11 @@
self.object_name, "GET",
expires, key2)
resp, body = self.object_client.get(url)
- self.assertIn(int(resp['status']), HTTP_SUCCESS)
+ self.assertIn(int(resp['status']), test.HTTP_SUCCESS)
self.assertEqual(body, self.content)
- @attr(type='gate')
+ @test.attr(type='gate')
+ @test.requires_ext(extension='tempurl', service='object')
def test_put_object_using_temp_url(self):
new_data = data_utils.arbitrary_string(
size=len(self.object_name),
@@ -165,12 +157,12 @@
# trying to put random data in the object using temp url
resp, body = self.object_client.put(url, new_data, None)
- self.assertIn(int(resp['status']), HTTP_SUCCESS)
+ self.assertIn(int(resp['status']), test.HTTP_SUCCESS)
self.assertHeaders(resp, 'Object', 'PUT')
# Testing a HEAD on this Temp URL
resp, body = self.object_client.head(url)
- self.assertIn(int(resp['status']), HTTP_SUCCESS)
+ self.assertIn(int(resp['status']), test.HTTP_SUCCESS)
self.assertHeaders(resp, 'Object', 'HEAD')
# Validate that the content of the object has been modified
@@ -181,7 +173,8 @@
_, body = self.object_client.get(url)
self.assertEqual(body, new_data)
- @attr(type='gate')
+ @test.attr(type='gate')
+ @test.requires_ext(extension='tempurl', service='object')
def test_head_object_using_temp_url(self):
expires = self._get_expiry_date()
@@ -192,10 +185,11 @@
# Testing a HEAD on this Temp URL
resp, body = self.object_client.head(url)
- self.assertIn(int(resp['status']), HTTP_SUCCESS)
+ self.assertIn(int(resp['status']), test.HTTP_SUCCESS)
self.assertHeaders(resp, 'Object', 'HEAD')
- @attr(type=['gate', 'negative'])
+ @test.attr(type=['gate', 'negative'])
+ @test.requires_ext(extension='tempurl', service='object')
def test_get_object_after_expiration_time(self):
expires = self._get_expiry_date(1)
diff --git a/tempest/api/orchestration/stacks/test_non_empty_stack.py b/tempest/api/orchestration/stacks/test_non_empty_stack.py
index eead234..282758c 100644
--- a/tempest/api/orchestration/stacks/test_non_empty_stack.py
+++ b/tempest/api/orchestration/stacks/test_non_empty_stack.py
@@ -112,6 +112,18 @@
self.assertEqual('fluffy', stack['outputs'][0]['output_key'])
@attr(type='gate')
+ def test_suspend_resume_stack(self):
+ """suspend and resume a stack."""
+ resp, suspend_stack = self.client.suspend_stack(self.stack_identifier)
+ self.assertEqual('200', resp['status'])
+ self.client.wait_for_stack_status(self.stack_identifier,
+ 'SUSPEND_COMPLETE')
+ resp, resume_stack = self.client.resume_stack(self.stack_identifier)
+ self.assertEqual('200', resp['status'])
+ self.client.wait_for_stack_status(self.stack_identifier,
+ 'RESUME_COMPLETE')
+
+ @attr(type='gate')
def test_list_resources(self):
"""Getting list of created resources for the stack should be possible.
"""
diff --git a/tempest/api/telemetry/__init__.py b/tempest/api/telemetry/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/api/telemetry/__init__.py
diff --git a/tempest/api/telemetry/base.py b/tempest/api/telemetry/base.py
new file mode 100644
index 0000000..1f661a6
--- /dev/null
+++ b/tempest/api/telemetry/base.py
@@ -0,0 +1,27 @@
+# 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 import config
+import tempest.test
+
+CONF = config.CONF
+
+
+class BaseTelemetryTest(tempest.test.BaseTestCase):
+
+ """Base test case class for all Telemetry API tests."""
+
+ @classmethod
+ def setUpClass(cls):
+ super(BaseTelemetryTest, cls).setUpClass()
+ if not CONF.service_available.ceilometer:
+ raise cls.skipException("Ceilometer support is required")
diff --git a/tempest/api/volume/test_extensions.py b/tempest/api/volume/test_extensions.py
index 90988a2..546c430 100644
--- a/tempest/api/volume/test_extensions.py
+++ b/tempest/api/volume/test_extensions.py
@@ -17,9 +17,13 @@
from tempest.api.volume import base
+from tempest.openstack.common import log as logging
from tempest.test import attr
+LOG = logging.getLogger(__name__)
+
+
class ExtensionsTestJSON(base.BaseVolumeTest):
_interface = 'json'
@@ -30,6 +34,8 @@
self.assertEqual(200, resp.status)
if len(self.config.volume_feature_enabled.api_extensions) == 0:
raise self.skipException('There are not any extensions configured')
+ extension_list = [extension.get('alias') for extension in extensions]
+ LOG.debug("Cinder extensions: %s" % ','.join(extension_list))
ext = self.config.volume_feature_enabled.api_extensions[0]
if ext == 'all':
self.assertIn('Hosts', map(lambda x: x['name'], extensions))
diff --git a/tempest/api/volume/test_snapshot_metadata.py b/tempest/api/volume/test_snapshot_metadata.py
new file mode 100644
index 0000000..0326f3c
--- /dev/null
+++ b/tempest/api/volume/test_snapshot_metadata.py
@@ -0,0 +1,119 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 Huawei Technologies Co.,LTD
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.api.volume import base
+from tempest import test
+
+
+class SnapshotMetadataTest(base.BaseVolumeV1Test):
+ _interface = "json"
+
+ @classmethod
+ def setUpClass(cls):
+ super(SnapshotMetadataTest, cls).setUpClass()
+ cls.client = cls.snapshots_client
+ # Create a volume
+ cls.volume = cls.create_volume()
+ # Create a snapshot
+ cls.snapshot = cls.create_snapshot(volume_id=cls.volume['id'])
+ cls.snapshot_id = cls.snapshot['id']
+
+ def tearDown(self):
+ # Update the metadata to {}
+ self.client.update_snapshot_metadata(self.snapshot_id, {})
+ super(SnapshotMetadataTest, self).tearDown()
+
+ @test.attr(type='gate')
+ def test_create_get_delete_snapshot_metadata(self):
+ # Create metadata for the snapshot
+ metadata = {"key1": "value1",
+ "key2": "value2",
+ "key3": "value3"}
+ expected = {"key2": "value2",
+ "key3": "value3"}
+ resp, body = self.client.create_snapshot_metadata(self.snapshot_id,
+ metadata)
+ self.assertEqual(200, resp.status)
+ # Get the metadata of the snapshot
+ resp, body = self.client.get_snapshot_metadata(self.snapshot_id)
+ self.assertEqual(200, resp.status)
+ self.assertEqual(metadata, body)
+ # Delete one item metadata of the snapshot
+ resp, body = self.client.delete_snapshot_metadata_item(
+ self.snapshot_id,
+ "key1")
+ self.assertEqual(200, resp.status)
+ resp, body = self.client.get_snapshot_metadata(self.snapshot_id)
+ self.assertEqual(expected, body)
+
+ @test.attr(type='gate')
+ def test_update_snapshot_metadata(self):
+ # Update metadata for the snapshot
+ metadata = {"key1": "value1",
+ "key2": "value2",
+ "key3": "value3"}
+ update = {"key3": "value3_update",
+ "key4": "value4"}
+ # Create metadata for the snapshot
+ resp, body = self.client.create_snapshot_metadata(self.snapshot_id,
+ metadata)
+ self.assertEqual(200, resp.status)
+ # Get the metadata of the snapshot
+ resp, body = self.client.get_snapshot_metadata(self.snapshot_id)
+ self.assertEqual(200, resp.status)
+ self.assertEqual(metadata, body)
+ # Update metadata item
+ resp, body = self.client.update_snapshot_metadata(
+ self.snapshot_id,
+ update)
+ self.assertEqual(200, resp.status)
+ # Get the metadata of the snapshot
+ resp, body = self.client.get_snapshot_metadata(self.snapshot_id)
+ self.assertEqual(200, resp.status)
+ self.assertEqual(update, body)
+
+ @test.attr(type='gate')
+ def test_update_snapshot_metadata_item(self):
+ # Update metadata item for the snapshot
+ metadata = {"key1": "value1",
+ "key2": "value2",
+ "key3": "value3"}
+ update_item = {"key3": "value3_update"}
+ expect = {"key1": "value1",
+ "key2": "value2",
+ "key3": "value3_update"}
+ # Create metadata for the snapshot
+ resp, body = self.client.create_snapshot_metadata(self.snapshot_id,
+ metadata)
+ self.assertEqual(200, resp.status)
+ # Get the metadata of the snapshot
+ resp, body = self.client.get_snapshot_metadata(self.snapshot_id)
+ self.assertEqual(metadata, body)
+ # Update metadata item
+ resp, body = self.client.update_snapshot_metadata_item(
+ self.snapshot_id,
+ "key3",
+ update_item)
+ self.assertEqual(200, resp.status)
+ # Get the metadata of the snapshot
+ resp, body = self.client.get_snapshot_metadata(self.snapshot_id)
+ self.assertEqual(200, resp.status)
+ self.assertEqual(expect, body)
+
+
+class SnapshotMetadataTestXML(SnapshotMetadataTest):
+ _interface = "xml"
diff --git a/tempest/api/volume/test_volumes_list.py b/tempest/api/volume/test_volumes_list.py
index 49a2f74..fa3f924 100644
--- a/tempest/api/volume/test_volumes_list.py
+++ b/tempest/api/volume/test_volumes_list.py
@@ -21,6 +21,7 @@
from tempest.common.utils import data_utils
from tempest.openstack.common import log as logging
from tempest.test import attr
+from testtools.matchers import ContainsAll
LOG = logging.getLogger(__name__)
@@ -110,7 +111,12 @@
for key in params:
msg = "Failed to list volumes %s by %s" % \
('details' if with_detail else '', key)
- self.assertEqual(params[key], volume[key], msg)
+ if key == 'metadata':
+ self.assertThat(volume[key].items(),
+ ContainsAll(params[key].items()),
+ msg)
+ else:
+ self.assertEqual(params[key], volume[key], msg)
@attr(type='smoke')
def test_volume_list(self):
diff --git a/tempest/cli/simple_read_only/test_ceilometer.py b/tempest/cli/simple_read_only/test_ceilometer.py
index 8bdd633..f14b35b 100644
--- a/tempest/cli/simple_read_only/test_ceilometer.py
+++ b/tempest/cli/simple_read_only/test_ceilometer.py
@@ -15,17 +15,16 @@
# License for the specific language governing permissions and limitations
# under the License.
-from oslo.config import cfg
-
-import tempest.cli
+from tempest import cli
+from tempest import config
from tempest.openstack.common import log as logging
-CONF = cfg.CONF
+CONF = config.CONF
LOG = logging.getLogger(__name__)
-class SimpleReadOnlyCeilometerClientTest(tempest.cli.ClientTestBase):
+class SimpleReadOnlyCeilometerClientTest(cli.ClientTestBase):
"""Basic, read-only tests for Ceilometer CLI client.
Checks return values and output of read-only commands.
@@ -36,7 +35,7 @@
@classmethod
def setUpClass(cls):
if (not CONF.service_available.ceilometer):
- msg = ("Skiping all Ceilometer cli tests because it is"
+ msg = ("Skipping all Ceilometer cli tests because it is "
"not available")
raise cls.skipException(msg)
super(SimpleReadOnlyCeilometerClientTest, cls).setUpClass()
diff --git a/tempest/cli/simple_read_only/test_neutron.py b/tempest/cli/simple_read_only/test_neutron.py
index 61ffc25..7aa57c0 100644
--- a/tempest/cli/simple_read_only/test_neutron.py
+++ b/tempest/cli/simple_read_only/test_neutron.py
@@ -18,18 +18,17 @@
import re
import subprocess
-from oslo.config import cfg
-
-import tempest.cli
+from tempest import cli
+from tempest import config
from tempest.openstack.common import log as logging
from tempest import test
-CONF = cfg.CONF
+CONF = config.CONF
LOG = logging.getLogger(__name__)
-class SimpleReadOnlyNeutronClientTest(tempest.cli.ClientTestBase):
+class SimpleReadOnlyNeutronClientTest(cli.ClientTestBase):
"""Basic, read-only tests for Neutron CLI client.
Checks return values and output of read-only commands.
@@ -40,7 +39,7 @@
@classmethod
def setUpClass(cls):
if (not CONF.service_available.neutron):
- msg = "Skiping all Neutron cli tests because it is not available"
+ msg = "Skipping all Neutron cli tests because it is not available"
raise cls.skipException(msg)
super(SimpleReadOnlyNeutronClientTest, cls).setUpClass()
@@ -89,7 +88,7 @@
def _test_neutron_lbaas_command(self, command):
try:
self.neutron(command)
- except tempest.cli.CommandFailed as e:
+ except cli.CommandFailed as e:
if '404 Not Found' not in e.stderr:
self.fail('%s: Unexpected failure.' % command)
@@ -156,7 +155,7 @@
'router-show', 'agent-update', 'help'))
self.assertFalse(wanted_commands - commands)
- # Optional arguments:
+ # Optional arguments:
@test.attr(type='smoke')
def test_neutron_version(self):
diff --git a/tempest/common/utils/test_utils.py b/tempest/common/utils/test_utils.py
new file mode 100644
index 0000000..4f1e02c
--- /dev/null
+++ b/tempest/common/utils/test_utils.py
@@ -0,0 +1,136 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 Hewlett-Packard, Ltd.
+#
+# 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 import misc
+from tempest import config
+from tempest.scenario import manager
+
+import json
+import re
+import string
+import unicodedata
+
+CONF = config.CONF
+
+
+@misc.singleton
+class ImageUtils(object):
+
+ default_ssh_user = 'root'
+
+ def __init__(self):
+ # Load configuration items
+ self.ssh_users = json.loads(CONF.input_scenario.ssh_user_regex)
+ self.non_ssh_image_pattern = \
+ CONF.input_scenario.non_ssh_image_regex
+ # Setup clients
+ ocm = manager.OfficialClientManager(CONF.identity.username,
+ CONF.identity.password,
+ CONF.identity.tenant_name)
+ self.client = ocm.compute_client
+
+ def ssh_user(self, image_id):
+ _image = self.client.images.get(image_id)
+ for regex, user in self.ssh_users:
+ # First match wins
+ if re.match(regex, _image.name) is not None:
+ return user
+ else:
+ return self.default_ssh_user
+
+ def _is_sshable_image(self, image):
+ return not re.search(pattern=self.non_ssh_image_pattern,
+ string=str(image.name))
+
+ def is_sshable_image(self, image_id):
+ _image = self.client.images.get(image_id)
+ return self._is_sshable_image(_image)
+
+ def _is_flavor_enough(self, flavor, image):
+ return image.minDisk <= flavor.disk
+
+ def is_flavor_enough(self, flavor_id, image_id):
+ _image = self.client.images.get(image_id)
+ _flavor = self.client.flavors.get(flavor_id)
+ return self._is_flavor_enough(_flavor, _image)
+
+
+@misc.singleton
+class InputScenarioUtils(object):
+
+ """
+ Example usage:
+
+ import testscenarios
+ (...)
+ load_tests = testscenarios.load_tests_apply_scenarios
+
+
+ class TestInputScenario(manager.OfficialClientTest):
+
+ scenario_utils = test_utils.InputScenarioUtils()
+ scenario_flavor = scenario_utils.scenario_flavors
+ scenario_image = scenario_utils.scenario_images
+ scenarios = testscenarios.multiply_scenarios(scenario_image,
+ scenario_flavor)
+
+ def test_create_server_metadata(self):
+ name = rand_name('instance')
+ _ = self.compute_client.servers.create(name=name,
+ flavor=self.flavor_ref,
+ image=self.image_ref)
+ """
+ validchars = "-_.{ascii}{digit}".format(ascii=string.ascii_letters,
+ digit=string.digits)
+
+ def __init__(self):
+ ocm = manager.OfficialClientManager(CONF.identity.username,
+ CONF.identity.password,
+ CONF.identity.tenant_name)
+ self.client = ocm.compute_client
+ self.image_pattern = CONF.input_scenario.image_regex
+ self.flavor_pattern = CONF.input_scenario.flavor_regex
+
+ def _normalize_name(self, name):
+ nname = unicodedata.normalize('NFKD', name).encode('ASCII', 'ignore')
+ nname = ''.join(c for c in nname if c in self.validchars)
+ return nname
+
+ @property
+ def scenario_images(self):
+ """
+ :return: a scenario with name and uuid of images
+ """
+ if not hasattr(self, '_scenario_images'):
+ images = self.client.images.list(detailed=False)
+ self._scenario_images = [
+ (self._normalize_name(i.name), dict(image_ref=i.id))
+ for i in images if re.search(self.image_pattern, str(i.name))
+ ]
+ return self._scenario_images
+
+ @property
+ def scenario_flavors(self):
+ """
+ :return: a scenario with name and uuid of flavors
+ """
+ if not hasattr(self, '_scenario_flavors'):
+ flavors = self.client.flavors.list(detailed=False)
+ self._scenario_flavors = [
+ (self._normalize_name(f.name), dict(flavor_ref=f.id))
+ for f in flavors if re.search(self.flavor_pattern, str(f.name))
+ ]
+ return self._scenario_flavors
diff --git a/tempest/config.py b/tempest/config.py
index 8d6e52a..0342380 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -398,11 +398,11 @@
"one is used."),
cfg.IntOpt('container_sync_timeout',
default=120,
- help="Number of seconds to time on waiting for a container"
+ help="Number of seconds to time on waiting for a container "
"to container synchronization complete."),
cfg.IntOpt('container_sync_interval',
default=5,
- help="Number of seconds to wait while looping to check the"
+ help="Number of seconds to wait while looping to check the "
"status of a container to container synchronization"),
cfg.StrOpt('operator_role',
default='Member',
@@ -415,19 +415,11 @@
title='Enabled object-storage features')
ObjectStoreFeaturesGroup = [
- cfg.BoolOpt('container_quotas',
- default=True,
- help="Set to True if the Container Quota middleware "
- "is enabled"),
- cfg.BoolOpt('accounts_quotas',
- default=True,
- help="Set to True if the Account Quota middleware is enabled"),
- cfg.BoolOpt('crossdomain',
- default=True,
- help="Set to True if the Crossdomain middleware is enabled"),
- cfg.BoolOpt('tempurl',
- default=True,
- help="Set to True if the TempURL middleware is enabled"),
+ cfg.ListOpt('discoverable_apis',
+ default=['all'],
+ help="A list of the enabled optional discoverable apis. "
+ "A single entry, all, indicates that all of these "
+ "features are expected to be enabled"),
]
@@ -658,6 +650,27 @@
help="Enable diagnostic commands"),
]
+input_scenario_group = cfg.OptGroup(name="input-scenario",
+ title="Filters and values for"
+ " input scenarios")
+
+InputScenarioGroup = [
+ cfg.StrOpt('image_regex',
+ default='^cirros-0.3.1-x86_64-uec$',
+ help="Matching images become parameters for scenario tests"),
+ cfg.StrOpt('flavor_regex',
+ default='^m1.(micro|nano|tiny)$',
+ help="Matching flavors become parameters for scenario tests"),
+ cfg.StrOpt('non_ssh_image_regex',
+ default='^.*[Ww]in.*$',
+ help="SSH verification in tests is skipped"
+ "for matching images"),
+ cfg.StrOpt('ssh_user_regex',
+ default="[[\"^.*[Cc]irros.*$\", \"root\"]]",
+ help="List of user mapped to regex "
+ "to matching image names."),
+]
+
baremetal_group = cfg.OptGroup(name='baremetal',
title='Baremetal provisioning service options')
@@ -735,7 +748,7 @@
ServiceAvailableGroup)
register_opt_group(cfg.CONF, debug_group, DebugGroup)
register_opt_group(cfg.CONF, baremetal_group, BaremetalGroup)
-
+ register_opt_group(cfg.CONF, input_scenario_group, InputScenarioGroup)
self.compute = cfg.CONF.compute
self.compute_feature_enabled = cfg.CONF['compute-feature-enabled']
self.identity = cfg.CONF.identity
@@ -759,7 +772,7 @@
self.service_available = cfg.CONF.service_available
self.debug = cfg.CONF.debug
self.baremetal = cfg.CONF.baremetal
-
+ self.input_scenario = cfg.CONF['input-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/tempest/scenario/test_network_basic_ops.py b/tempest/scenario/test_network_basic_ops.py
index 3618a14..52a36e6 100644
--- a/tempest/scenario/test_network_basic_ops.py
+++ b/tempest/scenario/test_network_basic_ops.py
@@ -213,6 +213,11 @@
name = data_utils.rand_name('server-smoke-%d-' % i)
self._create_server(name, network)
+ def _log_console_output(self):
+ for server, key in self.servers.items():
+ LOG.debug('Console output for %s', server.id)
+ LOG.debug(server.get_console_output())
+
def _check_tenant_network_connectivity(self):
if not CONF.network.tenant_networks_reachable:
msg = 'Tenant networks not configured to be reachable.'
@@ -229,6 +234,7 @@
key.private_key)
except Exception:
LOG.exception('Tenant connectivity check failed')
+ self._log_console_output()
debug.log_ip_ns()
raise
@@ -253,6 +259,7 @@
# The target login is assumed to have been configured for
# key-based authentication by cloud-init.
ssh_login = CONF.compute.image_ssh_user
+ LOG.debug('checking network connections')
try:
for floating_ip, server in self.floating_ips.iteritems():
ip_address = floating_ip.floating_ip_address
@@ -265,6 +272,7 @@
should_connect=should_connect)
except Exception:
LOG.exception('Public network connectivity check failed')
+ self._log_console_output()
debug.log_ip_ns()
raise
diff --git a/tempest/scenario/test_server_basic_ops.py b/tempest/scenario/test_server_basic_ops.py
index 1e1a310..ca3035d 100644
--- a/tempest/scenario/test_server_basic_ops.py
+++ b/tempest/scenario/test_server_basic_ops.py
@@ -16,12 +16,21 @@
# under the License.
from tempest.common.utils import data_utils
+from tempest.common.utils import test_utils
from tempest.openstack.common import log as logging
from tempest.scenario import manager
from tempest.test import services
+import testscenarios
+
LOG = logging.getLogger(__name__)
+# NOTE(andreaf) - nose does not honour the load_tests protocol
+# however it's test discovery regex will match anything
+# which includes _tests. So nose would require some further
+# investigation to be supported with this
+load_tests = testscenarios.load_tests_apply_scenarios
+
class TestServerBasicOps(manager.OfficialClientTest):
@@ -37,6 +46,37 @@
* Terminate the instance
"""
+ scenario_utils = test_utils.InputScenarioUtils()
+ scenario_flavor = scenario_utils.scenario_flavors
+ scenario_image = scenario_utils.scenario_images
+
+ scenarios = testscenarios.multiply_scenarios(scenario_image,
+ scenario_flavor)
+
+ def setUp(self):
+ super(TestServerBasicOps, self).setUp()
+ # Setup image and flavor the test instance
+ # Support both configured and injected values
+ if not hasattr(self, 'image_ref'):
+ self.image_ref = self.config.compute.image_ref
+ if not hasattr(self, 'flavor_ref'):
+ self.flavor_ref = self.config.compute.flavor_ref
+ self.image_utils = test_utils.ImageUtils()
+ if not self.image_utils.is_flavor_enough(self.flavor_ref,
+ self.image_ref):
+ raise self.skipException(
+ '{image} does not fit in {flavor}'.format(
+ image=self.image_ref, flavor=self.flavor_ref
+ )
+ )
+ self.run_ssh = self.config.compute.run_ssh and \
+ self.image_utils.is_sshable_image(self.image_ref)
+ self.ssh_user = self.image_utils.ssh_user(self.image_ref)
+ LOG.debug('Starting test for i:{image}, f:{flavor}. '
+ 'Run ssh: {ssh}, user: {ssh_user}'.format(
+ image=self.image_ref, flavor=self.flavor_ref,
+ ssh=self.run_ssh, ssh_user=self.ssh_user))
+
def add_keypair(self):
self.keypair = self.create_keypair()
@@ -53,10 +93,13 @@
self._create_loginable_secgroup_rule_nova(secgroup_id=self.secgroup.id)
def boot_instance(self):
+ # Create server with image and flavor from input scenario
create_kwargs = {
'key_name': self.keypair.id
}
- instance = self.create_server(create_kwargs=create_kwargs)
+ instance = self.create_server(image=self.image_ref,
+ flavor=self.flavor_ref,
+ create_kwargs=create_kwargs)
self.set_resource('instance', instance)
def pause_server(self):
@@ -100,6 +143,19 @@
instance.delete()
self.remove_resource('instance')
+ def verify_ssh(self):
+ if self.run_ssh:
+ # Obtain a floating IP
+ floating_ip = self.compute_client.floating_ips.create()
+ # Attach a floating IP
+ instance = self.get_resource('instance')
+ instance.add_floating_ip(floating_ip)
+ # Check ssh
+ self.get_remote_client(
+ server_or_ip=floating_ip.ip,
+ username=self.image_utils.ssh_user(self.image_ref),
+ private_key=self.keypair.private)
+
@services('compute', 'network')
def test_server_basicops(self):
self.add_keypair()
@@ -109,4 +165,5 @@
self.unpause_server()
self.suspend_server()
self.resume_server()
+ self.verify_ssh()
self.terminate_instance()
diff --git a/tempest/services/compute/json/extensions_client.py b/tempest/services/compute/json/extensions_client.py
index ad5354c..c293f80 100644
--- a/tempest/services/compute/json/extensions_client.py
+++ b/tempest/services/compute/json/extensions_client.py
@@ -31,7 +31,7 @@
url = 'extensions'
resp, body = self.get(url)
body = json.loads(body)
- return resp, body
+ return resp, body['extensions']
def is_enabled(self, extension):
_, extensions = self.list_extensions()
diff --git a/tempest/services/compute/v3/json/extensions_client.py b/tempest/services/compute/v3/json/extensions_client.py
index 6e0dc9d..bd8287e 100644
--- a/tempest/services/compute/v3/json/extensions_client.py
+++ b/tempest/services/compute/v3/json/extensions_client.py
@@ -32,7 +32,7 @@
url = 'extensions'
resp, body = self.get(url)
body = json.loads(body)
- return resp, body
+ return resp, body['extensions']
def is_enabled(self, extension):
_, extensions = self.list_extensions()
diff --git a/tempest/services/compute/v3/json/limits_client.py b/tempest/services/compute/v3/json/limits_client.py
deleted file mode 100644
index 3e53e3e..0000000
--- a/tempest/services/compute/v3/json/limits_client.py
+++ /dev/null
@@ -1,40 +0,0 @@
-# vim: tabstop=4 shiftwidth=4 softtabstop=4
-
-# Copyright 2012 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 tempest.common.rest_client import RestClient
-
-
-class LimitsClientJSON(RestClient):
-
- def __init__(self, config, username, password, auth_url, tenant_name=None):
- super(LimitsClientJSON, self).__init__(config, username, password,
- auth_url, tenant_name)
- self.service = self.config.compute.catalog_type
-
- def get_absolute_limits(self):
- resp, body = self.get("limits")
- body = json.loads(body)
- return resp, body['limits']['absolute']
-
- def get_specific_absolute_limit(self, absolute_limit):
- resp, body = self.get("limits")
- body = json.loads(body)
- if absolute_limit not in body['limits']['absolute']:
- return None
- else:
- return body['limits']['absolute'][absolute_limit]
diff --git a/tempest/services/compute/v3/json/servers_client.py b/tempest/services/compute/v3/json/servers_client.py
index da31036..3342660 100644
--- a/tempest/services/compute/v3/json/servers_client.py
+++ b/tempest/services/compute/v3/json/servers_client.py
@@ -45,8 +45,6 @@
admin_password: Sets the initial root password.
key_name: Key name of keypair that was created earlier.
meta: A dictionary of values to be used as metadata.
- personality: A list of dictionaries for files to be injected into
- the server.
security_groups: A list of security group dicts.
networks: A list of network dicts with UUID and fixed_ip.
user_data: User data for instance.
@@ -64,7 +62,7 @@
'flavor_ref': flavor_ref
}
- for option in ['personality', 'admin_password', 'key_name', 'networks',
+ for option in ['admin_password', 'key_name', 'networks',
('os-security-groups:security_groups',
'security_groups'),
('os-user-data:user_data', 'user_data'),
@@ -103,7 +101,6 @@
Updates the properties of an existing server.
server_id: The id of an existing server.
name: The name of the server.
- personality: A list of files to be injected into the server.
access_ip_v4: The IPv4 access address for the server.
access_ip_v6: The IPv6 access address for the server.
"""
@@ -163,10 +160,12 @@
body = json.loads(body)
return resp, body
- def wait_for_server_status(self, server_id, status, extra_timeout=0):
+ def wait_for_server_status(self, server_id, status, extra_timeout=0,
+ raise_on_error=True):
"""Waits for a server to reach a given status."""
return waiters.wait_for_server_status(self, server_id, status,
- extra_timeout=extra_timeout)
+ extra_timeout=extra_timeout,
+ raise_on_error=raise_on_error)
def wait_for_server_termination(self, server_id, ignore_error=False):
"""Waits for server to reach termination."""
@@ -346,19 +345,19 @@
return self.action(server_id, 'unlock', None, **kwargs)
def suspend_server(self, server_id, **kwargs):
- """Suspends the provded server."""
+ """Suspends the provided server."""
return self.action(server_id, 'suspend', None, **kwargs)
def resume_server(self, server_id, **kwargs):
- """Un-suspends the provded server."""
+ """Un-suspends the provided server."""
return self.action(server_id, 'resume', None, **kwargs)
def pause_server(self, server_id, **kwargs):
- """Pauses the provded server."""
+ """Pauses the provided server."""
return self.action(server_id, 'pause', None, **kwargs)
def unpause_server(self, server_id, **kwargs):
- """Un-pauses the provded server."""
+ """Un-pauses the provided server."""
return self.action(server_id, 'unpause', None, **kwargs)
def reset_state(self, server_id, state='error'):
diff --git a/tempest/services/compute/v3/xml/extensions_client.py b/tempest/services/compute/v3/xml/extensions_client.py
index 8f97692..926770e 100644
--- a/tempest/services/compute/v3/xml/extensions_client.py
+++ b/tempest/services/compute/v3/xml/extensions_client.py
@@ -37,7 +37,7 @@
url = 'extensions'
resp, body = self.get(url, self.headers)
body = self._parse_array(etree.fromstring(body))
- return resp, {'extensions': body}
+ return resp, body
def is_enabled(self, extension):
_, extensions = self.list_extensions()
diff --git a/tempest/services/compute/v3/xml/limits_client.py b/tempest/services/compute/v3/xml/limits_client.py
deleted file mode 100644
index 704de52..0000000
--- a/tempest/services/compute/v3/xml/limits_client.py
+++ /dev/null
@@ -1,55 +0,0 @@
-# vim: tabstop=4 shiftwidth=4 softtabstop=4
-#
-# Copyright 2012 IBM Corp.
-# All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-from lxml import objectify
-
-from tempest.common.rest_client import RestClientXML
-
-NS = "{http://docs.openstack.org/common/api/v1.0}"
-
-
-class LimitsClientXML(RestClientXML):
-
- def __init__(self, config, username, password, auth_url, tenant_name=None):
- super(LimitsClientXML, self).__init__(config, username, password,
- auth_url, tenant_name)
- self.service = self.config.compute.catalog_type
-
- def get_absolute_limits(self):
- resp, body = self.get("limits", self.headers)
- body = objectify.fromstring(body)
- lim = NS + 'absolute'
- ret = {}
-
- for el in body[lim].iterchildren():
- attributes = el.attrib
- ret[attributes['name']] = attributes['value']
- return resp, ret
-
- def get_specific_absolute_limit(self, absolute_limit):
- resp, body = self.get("limits", self.headers)
- body = objectify.fromstring(body)
- lim = NS + 'absolute'
- ret = {}
-
- for el in body[lim].iterchildren():
- attributes = el.attrib
- ret[attributes['name']] = attributes['value']
- if absolute_limit not in ret:
- return None
- else:
- return ret[absolute_limit]
diff --git a/tempest/services/compute/v3/xml/servers_client.py b/tempest/services/compute/v3/xml/servers_client.py
index a5cc291..5aca7e8 100644
--- a/tempest/services/compute/v3/xml/servers_client.py
+++ b/tempest/services/compute/v3/xml/servers_client.py
@@ -194,6 +194,10 @@
server = self._parse_server(etree.fromstring(body))
return resp, server
+ def migrate_server(self, server_id, **kwargs):
+ """Migrates the given server ."""
+ return self.action(server_id, 'migrate', None, **kwargs)
+
def lock_server(self, server_id, **kwargs):
"""Locks the given server."""
return self.action(server_id, 'lock', None, **kwargs)
@@ -300,8 +304,6 @@
admin_password: Sets the initial root password.
key_name: Key name of keypair that was created earlier.
meta: A dictionary of values to be used as metadata.
- personality: A list of dictionaries for files to be injected into
- the server.
security_groups: A list of security group dicts.
networks: A list of network dicts with UUID and fixed_ip.
user_data: User data for instance.
@@ -400,22 +402,16 @@
meta.append(Text(v))
metadata.append(meta)
- if 'personality' in kwargs:
- personality = Element('personality')
- server.append(personality)
- for k in kwargs['personality']:
- temp = Element('file', path=k['path'])
- temp.append(Text(k['contents']))
- personality.append(temp)
-
resp, body = self.post('servers', str(Document(server)), self.headers)
server = self._parse_server(etree.fromstring(body))
return resp, server
- def wait_for_server_status(self, server_id, status, extra_timeout=0):
+ def wait_for_server_status(self, server_id, status, extra_timeout=0,
+ raise_on_error=True):
"""Waits for a server to reach a given status."""
return waiters.wait_for_server_status(self, server_id, status,
- extra_timeout=extra_timeout)
+ extra_timeout=extra_timeout,
+ raise_on_error=raise_on_error)
def wait_for_server_termination(self, server_id, ignore_error=False):
"""Waits for server to reach termination."""
@@ -608,7 +604,7 @@
def set_server_metadata_item(self, server_id, key, meta):
doc = Document()
for k, v in meta.items():
- meta_element = Element("meta", key=k)
+ meta_element = Element("metadata", key=k)
meta_element.append(Text(v))
doc.append(meta_element)
resp, body = self.put('servers/%s/metadata/%s' % (str(server_id), key),
diff --git a/tempest/services/compute/xml/common.py b/tempest/services/compute/xml/common.py
index d2a18a1..76ad1aa 100644
--- a/tempest/services/compute/xml/common.py
+++ b/tempest/services/compute/xml/common.py
@@ -124,9 +124,9 @@
if tag.startswith("{"):
ns, tag = tag.split("}", 1)
if plurals is not None and tag in plurals:
- json[tag] = parse_array(child)
+ json[tag] = parse_array(child, plurals)
else:
- json[tag] = xml_to_json(child)
+ json[tag] = xml_to_json(child, plurals)
return json
diff --git a/tempest/services/compute/xml/extensions_client.py b/tempest/services/compute/xml/extensions_client.py
index b17fc4f..8b7b90a 100644
--- a/tempest/services/compute/xml/extensions_client.py
+++ b/tempest/services/compute/xml/extensions_client.py
@@ -37,7 +37,7 @@
url = 'extensions'
resp, body = self.get(url, self.headers)
body = self._parse_array(etree.fromstring(body))
- return resp, {'extensions': body}
+ return resp, body
def is_enabled(self, extension):
_, extensions = self.list_extensions()
diff --git a/tempest/services/orchestration/json/orchestration_client.py b/tempest/services/orchestration/json/orchestration_client.py
index e896e0d..d5ae828 100644
--- a/tempest/services/orchestration/json/orchestration_client.py
+++ b/tempest/services/orchestration/json/orchestration_client.py
@@ -102,6 +102,20 @@
body = json.loads(body)
return resp, body['stack']
+ def suspend_stack(self, stack_identifier):
+ """Suspend a stack."""
+ url = 'stacks/%s/actions' % stack_identifier
+ body = {'suspend': None}
+ resp, body = self.post(url, json.dumps(body), self.headers)
+ return resp, body
+
+ def resume_stack(self, stack_identifier):
+ """Resume a stack."""
+ url = 'stacks/%s/actions' % stack_identifier
+ body = {'resume': None}
+ resp, body = self.post(url, json.dumps(body), self.headers)
+ return resp, body
+
def list_resources(self, stack_identifier):
"""Returns the details of a single resource."""
url = "stacks/%s/resources" % stack_identifier
diff --git a/tempest/services/volume/json/snapshots_client.py b/tempest/services/volume/json/snapshots_client.py
index 9435122..c270de8 100644
--- a/tempest/services/volume/json/snapshots_client.py
+++ b/tempest/services/volume/json/snapshots_client.py
@@ -149,3 +149,40 @@
url = 'snapshots/%s/action' % str(snapshot_id)
resp, body = self.post(url, post_body, self.headers)
return resp, body
+
+ def create_snapshot_metadata(self, snapshot_id, metadata):
+ """Create metadata for the snapshot."""
+ put_body = json.dumps({'metadata': metadata})
+ url = "snapshots/%s/metadata" % str(snapshot_id)
+ resp, body = self.post(url, put_body, self.headers)
+ body = json.loads(body)
+ return resp, body['metadata']
+
+ def get_snapshot_metadata(self, snapshot_id):
+ """Get metadata of the snapshot."""
+ url = "snapshots/%s/metadata" % str(snapshot_id)
+ resp, body = self.get(url, self.headers)
+ body = json.loads(body)
+ return resp, body['metadata']
+
+ def update_snapshot_metadata(self, snapshot_id, metadata):
+ """Update metadata for the snapshot."""
+ put_body = json.dumps({'metadata': metadata})
+ url = "snapshots/%s/metadata" % str(snapshot_id)
+ resp, body = self.put(url, put_body, self.headers)
+ body = json.loads(body)
+ return resp, body['metadata']
+
+ def update_snapshot_metadata_item(self, snapshot_id, id, meta_item):
+ """Update metadata item for the snapshot."""
+ put_body = json.dumps({'meta': meta_item})
+ url = "snapshots/%s/metadata/%s" % (str(snapshot_id), str(id))
+ resp, body = self.put(url, put_body, self.headers)
+ body = json.loads(body)
+ return resp, body['meta']
+
+ def delete_snapshot_metadata_item(self, snapshot_id, id):
+ """Delete metadata item for the snapshot."""
+ url = "snapshots/%s/metadata/%s" % (str(snapshot_id), str(id))
+ resp, body = self.delete(url, self.headers)
+ return resp, body
diff --git a/tempest/services/volume/xml/snapshots_client.py b/tempest/services/volume/xml/snapshots_client.py
index 5d59b07..3a70eab 100644
--- a/tempest/services/volume/xml/snapshots_client.py
+++ b/tempest/services/volume/xml/snapshots_client.py
@@ -22,6 +22,7 @@
from tempest.openstack.common import log as logging
from tempest.services.compute.xml.common import Document
from tempest.services.compute.xml.common import Element
+from tempest.services.compute.xml.common import Text
from tempest.services.compute.xml.common import xml_to_json
from tempest.services.compute.xml.common import XMLNS_11
@@ -170,3 +171,57 @@
if body:
body = xml_to_json(etree.fromstring(body))
return resp, body
+
+ def _metadata_body(self, meta):
+ post_body = Element('metadata')
+ for k, v in meta.items():
+ data = Element('meta', key=k)
+ data.append(Text(v))
+ post_body.append(data)
+ return post_body
+
+ def _parse_key_value(self, node):
+ """Parse <foo key='key'>value</foo> data into {'key': 'value'}."""
+ data = {}
+ for node in node.getchildren():
+ data[node.get('key')] = node.text
+ return data
+
+ def create_snapshot_metadata(self, snapshot_id, metadata):
+ """Create metadata for the snapshot."""
+ post_body = self._metadata_body(metadata)
+ resp, body = self.post('snapshots/%s/metadata' % snapshot_id,
+ str(Document(post_body)),
+ self.headers)
+ body = self._parse_key_value(etree.fromstring(body))
+ return resp, body
+
+ def get_snapshot_metadata(self, snapshot_id):
+ """Get metadata of the snapshot."""
+ url = "snapshots/%s/metadata" % str(snapshot_id)
+ resp, body = self.get(url, self.headers)
+ body = self._parse_key_value(etree.fromstring(body))
+ return resp, body
+
+ def update_snapshot_metadata(self, snapshot_id, metadata):
+ """Update metadata for the snapshot."""
+ put_body = self._metadata_body(metadata)
+ url = "snapshots/%s/metadata" % str(snapshot_id)
+ resp, body = self.put(url, str(Document(put_body)), self.headers)
+ body = self._parse_key_value(etree.fromstring(body))
+ return resp, body
+
+ def update_snapshot_metadata_item(self, snapshot_id, id, meta_item):
+ """Update metadata item for the snapshot."""
+ for k, v in meta_item.items():
+ put_body = Element('meta', key=k)
+ put_body.append(Text(v))
+ url = "snapshots/%s/metadata/%s" % (str(snapshot_id), str(id))
+ resp, body = self.put(url, str(Document(put_body)), self.headers)
+ body = xml_to_json(etree.fromstring(body))
+ return resp, body
+
+ def delete_snapshot_metadata_item(self, snapshot_id, id):
+ """Delete metadata item for the snapshot."""
+ url = "snapshots/%s/metadata/%s" % (str(snapshot_id), str(id))
+ return self.delete(url)
diff --git a/tempest/stress/__init__.py b/tempest/stress/__init__.py
index 1caf74a..e69de29 100644
--- a/tempest/stress/__init__.py
+++ b/tempest/stress/__init__.py
@@ -1,13 +0,0 @@
-# Copyright 2013 Quanta Research Cambridge, Inc.
-#
-# 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.
diff --git a/tempest/stress/actions/__init__.py b/tempest/stress/actions/__init__.py
index 1caf74a..e69de29 100644
--- a/tempest/stress/actions/__init__.py
+++ b/tempest/stress/actions/__init__.py
@@ -1,13 +0,0 @@
-# Copyright 2013 Quanta Research Cambridge, Inc.
-#
-# 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.
diff --git a/tempest/stress/etc/ssh_floating.json b/tempest/stress/etc/ssh_floating.json
index 0cb6776..e03fd4f 100644
--- a/tempest/stress/etc/ssh_floating.json
+++ b/tempest/stress/etc/ssh_floating.json
@@ -8,7 +8,7 @@
"new_floating": true,
"verify": ["check_icmp_echo", "check_port_ssh"],
"check_timeout": 120,
- "check_inerval": 1,
+ "check_interval": 1,
"wait_after_vm_create": true,
"wait_for_disassociate": true,
"reboot": false}
diff --git a/tempest/test.py b/tempest/test.py
index 342846f..d57ed83 100644
--- a/tempest/test.py
+++ b/tempest/test.py
@@ -163,6 +163,7 @@
'compute_v3': configs.compute_feature_enabled.api_v3_extensions,
'volume': configs.volume_feature_enabled.api_extensions,
'network': configs.network_feature_enabled.api_extensions,
+ 'object': configs.object_storage_feature_enabled.discoverable_apis,
}
if config_dict[service][0] == 'all':
return True