Merge "Separate negative tests for list_floating_ips"
diff --git a/.testr.conf b/.testr.conf
index 05b12c4..c25ebec 100644
--- a/.testr.conf
+++ b/.testr.conf
@@ -2,7 +2,8 @@
test_command=OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} \
OS_STDERR_CAPTURE=${OS_STDERR_CAPTURE:-1} \
OS_TEST_TIMEOUT=${OS_TEST_TIMEOUT:-500} \
- ${PYTHON:-python} -m subunit.run discover -t ./ ./tempest $LISTOPT $IDOPTION
+ OS_TEST_PATH=${OS_TEST_PATH:-./tempest} \
+ ${PYTHON:-python} -m subunit.run discover -t ./ $OS_TEST_PATH $LISTOPT $IDOPTION
test_id_option=--load-list $IDFILE
test_list_option=--list
group_regex=([^\.]*\.)*
diff --git a/HACKING.rst b/HACKING.rst
index a74ff73..3fa1ff5 100644
--- a/HACKING.rst
+++ b/HACKING.rst
@@ -9,8 +9,8 @@
------------------------------
- [T102] Cannot import OpenStack python clients in tempest/api tests
-- [T103] tempest/tests is deprecated
- [T104] Scenario tests require a services decorator
+- [T105] Unit tests cannot use setUpClass
Test Data/Configuration
-----------------------
@@ -39,7 +39,7 @@
when the additional operations leads to another exception.
Just letting an exception to propagate, is not bad idea in a test case,
- at all.
+at all.
Try to avoid using any exception handling construct which can hide the errors
origin.
@@ -53,10 +53,10 @@
test fails part way through.
Use the ``self.assert*`` methods provided by the unit test framework
- the signal failures early.
+the signal failures early.
Avoid using the ``self.fail`` alone, it's stack trace will signal
- the ``self.fail`` line as the origin of the error.
+the ``self.fail`` line as the origin of the error.
Avoid constructing complex boolean expressions for assertion.
The ``self.assertTrue`` or ``self.assertFalse`` without a ``msg`` argument,
@@ -192,3 +192,15 @@
The sample config file is autogenerated using a script. If any changes are made
to the config variables in tempest then the sample config file must be
regenerated. This can be done running the script: tools/generate_sample.sh
+
+Unit Tests
+----------
+Unit tests are a separate class of tests in tempest. They verify tempest
+itself, and thus have a different set of guidelines around them:
+
+1. They can not require anything running externally. All you should need to
+ run the unit tests is the git tree, python and the dependencies installed.
+ This includes running services, a config file, etc.
+
+2. The unit tests cannot use setUpClass, instead fixtures and testresources
+ should be used for shared state between tests.
diff --git a/etc/tempest.conf.sample b/etc/tempest.conf.sample
index 8742b67..1080ddf 100644
--- a/etc/tempest.conf.sample
+++ b/etc/tempest.conf.sample
@@ -43,7 +43,7 @@
#logging_exception_prefix=%(asctime)s.%(msecs)03d %(process)d TRACE %(name)s %(instance)s
# list of logger=LEVEL pairs (list value)
-#default_log_levels=amqplib=WARN,sqlalchemy=WARN,boto=WARN,suds=INFO,keystone=INFO
+#default_log_levels=amqplib=WARN,sqlalchemy=WARN,boto=WARN,suds=INFO,keystone=INFO,paramiko=INFO
# publish error events (boolean value)
#publish_errors=false
@@ -220,14 +220,6 @@
# admin credentials are known. (boolean value)
#allow_tenant_isolation=false
-# If allow_tenant_isolation is True and a tenant that would be
-# created for a given test already exists (such as from a
-# previously-failed run), re-use that tenant instead of
-# failing because of the conflict. Note that this would result
-# in the tenant being deleted at the end of a subsequent
-# successful run. (boolean value)
-#allow_tenant_reuse=true
-
# Valid secondary image reference to be used in tests. (string
# value)
#image_ref={$IMAGE_ID}
@@ -532,6 +524,16 @@
#disk_format=raw
+[debug]
+
+#
+# Options defined in tempest.config
+#
+
+# Enable diagnostic commands (boolean value)
+#enable=true
+
+
[dashboard]
#
@@ -618,14 +620,15 @@
#operator_role=Member
-[debug]
+[network-feature-enabled]
#
# Options defined in tempest.config
#
-# Enable diagnostic commands (boolean value)
-#enable=true
+# A list of enabled extensions with a special entry all which
+# indicates every extension is enabled (list value)
+#api_extensions=all
[service_available]
@@ -679,8 +682,13 @@
# If false, skip disk config tests (boolean value)
#disk_config=true
-# If false, skip flavor extra data test (boolean value)
-#flavor_extra=true
+# A list of enabled extensions with a special entry all which
+# indicates every extension is enabled (list value)
+#api_extensions=all
+
+# A list of enabled v3 extensions with a special entry all
+# which indicates every extension is enabled (list value)
+#api_v3_extensions=all
# Does the test environment support changing the admin
# password? (boolean value)
@@ -738,4 +746,8 @@
# (boolean value)
#multi_backend=false
+# A list of enabled extensions with a special entry all which
+# indicates every extension is enabled (list value)
+#api_extensions=all
+
diff --git a/etc/whitelist.yaml b/etc/whitelist.yaml
index e5a0d4d..a8c5276 100644
--- a/etc/whitelist.yaml
+++ b/etc/whitelist.yaml
@@ -160,14 +160,6 @@
- module: "cinder.brick.local_dev.lvm"
message: "Can't remove open logical volume"
-q-dhpc:
- - module: "neutron.common.legacy"
- message: "Skipping unknown group key: firewall_driver"
- - module: "neutron.agent.dhcp_agent"
- message: "Unable to enable dhcp"
- - module: "neutron.agent.dhcp_agent"
- message: " Network .* RPC info call failed"
-
ceilometer-collector:
- module: "stevedore.extension"
message: ".*"
@@ -226,3 +218,6 @@
- module: ".*"
message: ".*"
+s-proxy:
+ - module: "proxy-server"
+ message: "Timeout talking to memcached"
diff --git a/requirements.txt b/requirements.txt
index 4f6a1d3..cd11aa7 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -5,9 +5,9 @@
jsonschema>=1.3.0,!=1.4.0
testtools>=0.9.32
lxml>=2.3
-boto>=2.4.0,!=2.13.0
+boto>=2.12.0,!=2.13.0
paramiko>=1.8.0
-netaddr
+netaddr>=0.7.6
python-glanceclient>=0.9.0
python-keystoneclient>=0.4.1
python-novaclient>=2.15.0
@@ -20,5 +20,5 @@
oslo.config>=1.2.0
eventlet>=0.13.0
six>=1.4.1
-iso8601>=0.1.4
+iso8601>=0.1.8
fixtures>=0.3.14
diff --git a/run_tests.sh b/run_tests.sh
index 5c8ce7d..3c9c051 100755
--- a/run_tests.sh
+++ b/run_tests.sh
@@ -11,7 +11,6 @@
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, --nova-coverage Enable Nova coverage collection"
echo " -C, --config Config file location"
echo " -p, --pep8 Just run pep8"
echo " -h, --help Print this usage message"
@@ -31,13 +30,12 @@
no_site_packages=0
force=0
wrapper=""
-nova_coverage=0
config_file=""
update=0
logging=0
logging_config=etc/logging.conf
-if ! options=$(getopt -o VNnfustcphdC:lL: -l virtual-env,no-virtual-env,no-site-packages,force,update,smoke,serial,nova-coverage,pep8,help,debug,config:,logging,logging-config: -- "$@")
+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: -- "$@")
then
# parse error
usage
@@ -55,7 +53,6 @@
-f|--force) force=1;;
-u|--update) update=1;;
-d|--debug) set -o xtrace;;
- -c|--nova-coverage) let nova_coverage=1;;
-C|--config) config_file=$2; shift;;
-p|--pep8) let just_pep8=1;;
-s|--smoke) testrargs+="smoke"; noseargs+="--attr=type=smoke";;
@@ -131,16 +128,6 @@
${wrapper} flake8
}
-function run_coverage_start {
- echo "Starting nova-coverage"
- ${wrapper} python tools/tempest_coverage.py -c start
-}
-
-function run_coverage_report {
- echo "Generating nova-coverage report"
- ${wrapper} python tools/tempest_coverage.py -c report
-}
-
if [ $never_venv -eq 0 ]
then
# Remove the virtual environment if --force used
@@ -176,11 +163,6 @@
exit
fi
-if [ $nova_coverage -eq 1 ]; then
- run_coverage_start
-fi
-
-
py_version=`${wrapper} python --version 2>&1`
if [[ $py_version =~ "2.6" ]] ; then
run_tests_nose
@@ -189,10 +171,6 @@
fi
retval=$?
-if [ $nova_coverage -eq 1 ]; then
- run_coverage_report
-fi
-
if [ -z "$testrargs" ]; then
run_pep8
fi
diff --git a/tempest/api/compute/__init__.py b/tempest/api/compute/__init__.py
index a528754..d20068e 100644
--- a/tempest/api/compute/__init__.py
+++ b/tempest/api/compute/__init__.py
@@ -16,7 +16,6 @@
# under the License.
from tempest import config
-from tempest.exceptions import InvalidConfiguration
from tempest.openstack.common import log as logging
LOG = logging.getLogger(__name__)
@@ -26,30 +25,3 @@
RESIZE_AVAILABLE = CONFIG.compute_feature_enabled.resize
CHANGE_PASSWORD_AVAILABLE = CONFIG.compute_feature_enabled.change_password
DISK_CONFIG_ENABLED = CONFIG.compute_feature_enabled.disk_config
-FLAVOR_EXTRA_DATA_ENABLED = CONFIG.compute_feature_enabled.flavor_extra
-MULTI_USER = True
-
-
-# All compute tests -- single setup function
-def generic_setup_package():
- LOG.debug("Entering tempest.api.compute.setup_package")
-
- global MULTI_USER
-
- # Determine if there are two regular users that can be
- # used in testing. If the test cases are allowed to create
- # users (config.compute.allow_tenant_isolation is true,
- # then we allow multi-user.
- if not CONFIG.compute.allow_tenant_isolation:
- user1 = CONFIG.identity.username
- user2 = CONFIG.identity.alt_username
- if not user2 or user1 == user2:
- MULTI_USER = False
- else:
- user2_password = CONFIG.identity.alt_password
- user2_tenant_name = CONFIG.identity.alt_tenant_name
- if not user2_password or not user2_tenant_name:
- msg = ("Alternate user specified but not alternate "
- "tenant or password: alt_tenant_name=%s alt_password=%s"
- % (user2_tenant_name, user2_password))
- raise InvalidConfiguration(msg)
diff --git a/tempest/api/compute/admin/test_aggregates.py b/tempest/api/compute/admin/test_aggregates.py
index 467a6f9..609d2c6 100644
--- a/tempest/api/compute/admin/test_aggregates.py
+++ b/tempest/api/compute/admin/test_aggregates.py
@@ -18,7 +18,6 @@
from tempest.api.compute import base
from tempest.common import tempest_fixtures as fixtures
from tempest.common.utils import data_utils
-from tempest import exceptions
from tempest.test import attr
@@ -35,7 +34,6 @@
def setUpClass(cls):
super(AggregatesAdminTestJSON, cls).setUpClass()
cls.client = cls.os_adm.aggregates_client
- cls.user_client = cls.aggregates_client
cls.aggregate_name_prefix = 'test_aggregate_'
cls.az_name_prefix = 'test_az_'
@@ -141,54 +139,6 @@
(x['id'], x['name'], x['availability_zone']),
aggregates))
- @attr(type=['negative', 'gate'])
- def test_aggregate_create_as_user(self):
- # Regular user is not allowed to create an aggregate.
- aggregate_name = data_utils.rand_name(self.aggregate_name_prefix)
- self.assertRaises(exceptions.Unauthorized,
- self.user_client.create_aggregate,
- aggregate_name)
-
- @attr(type=['negative', 'gate'])
- def test_aggregate_delete_as_user(self):
- # Regular user is not allowed to delete an aggregate.
- aggregate_name = data_utils.rand_name(self.aggregate_name_prefix)
- resp, aggregate = self.client.create_aggregate(aggregate_name)
- self.addCleanup(self.client.delete_aggregate, aggregate['id'])
-
- self.assertRaises(exceptions.Unauthorized,
- self.user_client.delete_aggregate,
- aggregate['id'])
-
- @attr(type=['negative', 'gate'])
- def test_aggregate_list_as_user(self):
- # Regular user is not allowed to list aggregates.
- self.assertRaises(exceptions.Unauthorized,
- self.user_client.list_aggregates)
-
- @attr(type=['negative', 'gate'])
- def test_aggregate_get_details_as_user(self):
- # Regular user is not allowed to get aggregate details.
- aggregate_name = data_utils.rand_name(self.aggregate_name_prefix)
- resp, aggregate = self.client.create_aggregate(aggregate_name)
- self.addCleanup(self.client.delete_aggregate, aggregate['id'])
-
- self.assertRaises(exceptions.Unauthorized,
- self.user_client.get_aggregate,
- aggregate['id'])
-
- @attr(type=['negative', 'gate'])
- def test_aggregate_delete_with_invalid_id(self):
- # Delete an aggregate with invalid id should raise exceptions.
- self.assertRaises(exceptions.NotFound,
- self.client.delete_aggregate, -1)
-
- @attr(type=['negative', 'gate'])
- def test_aggregate_get_details_with_invalid_id(self):
- # Get aggregate details with invalid id should raise exceptions.
- self.assertRaises(exceptions.NotFound,
- self.client.get_aggregate, -1)
-
@attr(type='gate')
def test_aggregate_add_remove_host(self):
# Add an host to the given aggregate and remove.
@@ -262,48 +212,6 @@
resp, body = admin_servers_client.get_server(server['id'])
self.assertEqual(self.host, body[self._host_key])
- @attr(type=['negative', 'gate'])
- def test_aggregate_add_non_exist_host(self):
- # Adding a non-exist host to an aggregate should raise exceptions.
- resp, hosts_all = self.os_adm.hosts_client.list_hosts()
- hosts = map(lambda x: x['host_name'], hosts_all)
- while True:
- non_exist_host = data_utils.rand_name('nonexist_host_')
- if non_exist_host not in hosts:
- break
-
- aggregate_name = data_utils.rand_name(self.aggregate_name_prefix)
- resp, aggregate = self.client.create_aggregate(aggregate_name)
- self.addCleanup(self.client.delete_aggregate, aggregate['id'])
-
- self.assertRaises(exceptions.NotFound, self.client.add_host,
- aggregate['id'], non_exist_host)
-
- @attr(type=['negative', 'gate'])
- def test_aggregate_add_host_as_user(self):
- # Regular user is not allowed to add a host to an aggregate.
- aggregate_name = data_utils.rand_name(self.aggregate_name_prefix)
- resp, aggregate = self.client.create_aggregate(aggregate_name)
- self.addCleanup(self.client.delete_aggregate, aggregate['id'])
-
- self.assertRaises(exceptions.Unauthorized,
- self.user_client.add_host,
- aggregate['id'], self.host)
-
- @attr(type=['negative', 'gate'])
- def test_aggregate_remove_host_as_user(self):
- # Regular user is not allowed to remove a host from an aggregate.
- self.useFixture(fixtures.LockFixture('availability_zone'))
- aggregate_name = data_utils.rand_name(self.aggregate_name_prefix)
- resp, aggregate = self.client.create_aggregate(aggregate_name)
- self.addCleanup(self.client.delete_aggregate, aggregate['id'])
- self.client.add_host(aggregate['id'], self.host)
- self.addCleanup(self.client.remove_host, aggregate['id'], self.host)
-
- self.assertRaises(exceptions.Unauthorized,
- self.user_client.remove_host,
- aggregate['id'], self.host)
-
class AggregatesAdminTestXML(AggregatesAdminTestJSON):
_host_key = (
diff --git a/tempest/api/compute/admin/test_aggregates_negative.py b/tempest/api/compute/admin/test_aggregates_negative.py
new file mode 100644
index 0000000..8506206
--- /dev/null
+++ b/tempest/api/compute/admin/test_aggregates_negative.py
@@ -0,0 +1,196 @@
+# 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.compute import base
+from tempest.common import tempest_fixtures as fixtures
+from tempest.common.utils import data_utils
+from tempest import exceptions
+from tempest.test import attr
+
+
+class AggregatesAdminNegativeTestJSON(base.BaseV2ComputeAdminTest):
+
+ """
+ Tests Aggregates API that require admin privileges
+ """
+
+ _interface = 'json'
+
+ @classmethod
+ def setUpClass(cls):
+ super(AggregatesAdminNegativeTestJSON, cls).setUpClass()
+ cls.client = cls.os_adm.aggregates_client
+ cls.user_client = cls.aggregates_client
+ cls.aggregate_name_prefix = 'test_aggregate_'
+ cls.az_name_prefix = 'test_az_'
+
+ resp, hosts_all = cls.os_adm.hosts_client.list_hosts()
+ hosts = map(lambda x: x['host_name'],
+ filter(lambda y: y['service'] == 'compute', hosts_all))
+ cls.host = hosts[0]
+
+ @attr(type=['negative', 'gate'])
+ def test_aggregate_create_as_user(self):
+ # Regular user is not allowed to create an aggregate.
+ aggregate_name = data_utils.rand_name(self.aggregate_name_prefix)
+ self.assertRaises(exceptions.Unauthorized,
+ self.user_client.create_aggregate,
+ aggregate_name)
+
+ @attr(type=['negative', 'gate'])
+ def test_aggregate_create_aggregate_name_length_less_than_1(self):
+ # the length of aggregate name should >= 1 and <=255
+ self.assertRaises(exceptions.BadRequest,
+ self.client.create_aggregate,
+ '')
+
+ @attr(type=['negative', 'gate'])
+ def test_aggregate_create_aggregate_name_length_exceeds_255(self):
+ # the length of aggregate name should >= 1 and <=255
+ aggregate_name = 'a' * 256
+ self.assertRaises(exceptions.BadRequest,
+ self.client.create_aggregate,
+ aggregate_name)
+
+ @attr(type=['negative', 'gate'])
+ def test_aggregate_create_with_existent_aggregate_name(self):
+ # creating an aggregate with existent aggregate name is forbidden
+ aggregate_name = data_utils.rand_name(self.aggregate_name_prefix)
+ resp, aggregate = self.client.create_aggregate(aggregate_name)
+ self.assertEqual(200, resp.status)
+ self.addCleanup(self.client.delete_aggregate, aggregate['id'])
+
+ self.assertRaises(exceptions.Conflict,
+ self.client.create_aggregate,
+ aggregate_name)
+
+ @attr(type=['negative', 'gate'])
+ def test_aggregate_delete_as_user(self):
+ # Regular user is not allowed to delete an aggregate.
+ aggregate_name = data_utils.rand_name(self.aggregate_name_prefix)
+ resp, aggregate = self.client.create_aggregate(aggregate_name)
+ self.assertEqual(200, resp.status)
+ self.addCleanup(self.client.delete_aggregate, aggregate['id'])
+
+ self.assertRaises(exceptions.Unauthorized,
+ self.user_client.delete_aggregate,
+ aggregate['id'])
+
+ @attr(type=['negative', 'gate'])
+ def test_aggregate_list_as_user(self):
+ # Regular user is not allowed to list aggregates.
+ self.assertRaises(exceptions.Unauthorized,
+ self.user_client.list_aggregates)
+
+ @attr(type=['negative', 'gate'])
+ def test_aggregate_get_details_as_user(self):
+ # Regular user is not allowed to get aggregate details.
+ aggregate_name = data_utils.rand_name(self.aggregate_name_prefix)
+ resp, aggregate = self.client.create_aggregate(aggregate_name)
+ self.assertEqual(200, resp.status)
+ self.addCleanup(self.client.delete_aggregate, aggregate['id'])
+
+ self.assertRaises(exceptions.Unauthorized,
+ self.user_client.get_aggregate,
+ aggregate['id'])
+
+ @attr(type=['negative', 'gate'])
+ def test_aggregate_delete_with_invalid_id(self):
+ # Delete an aggregate with invalid id should raise exceptions.
+ self.assertRaises(exceptions.NotFound,
+ self.client.delete_aggregate, -1)
+
+ @attr(type=['negative', 'gate'])
+ def test_aggregate_get_details_with_invalid_id(self):
+ # Get aggregate details with invalid id should raise exceptions.
+ self.assertRaises(exceptions.NotFound,
+ self.client.get_aggregate, -1)
+
+ @attr(type=['negative', 'gate'])
+ def test_aggregate_add_non_exist_host(self):
+ # Adding a non-exist host to an aggregate should raise exceptions.
+ resp, hosts_all = self.os_adm.hosts_client.list_hosts()
+ hosts = map(lambda x: x['host_name'], hosts_all)
+ while True:
+ non_exist_host = data_utils.rand_name('nonexist_host_')
+ if non_exist_host not in hosts:
+ break
+
+ aggregate_name = data_utils.rand_name(self.aggregate_name_prefix)
+ resp, aggregate = self.client.create_aggregate(aggregate_name)
+ self.addCleanup(self.client.delete_aggregate, aggregate['id'])
+
+ self.assertRaises(exceptions.NotFound, self.client.add_host,
+ aggregate['id'], non_exist_host)
+
+ @attr(type=['negative', 'gate'])
+ def test_aggregate_add_host_as_user(self):
+ # Regular user is not allowed to add a host to an aggregate.
+ aggregate_name = data_utils.rand_name(self.aggregate_name_prefix)
+ resp, aggregate = self.client.create_aggregate(aggregate_name)
+ self.assertEqual(200, resp.status)
+ self.addCleanup(self.client.delete_aggregate, aggregate['id'])
+
+ self.assertRaises(exceptions.Unauthorized,
+ self.user_client.add_host,
+ aggregate['id'], self.host)
+
+ @attr(type=['negative', 'gate'])
+ def test_aggregate_add_existent_host(self):
+ self.useFixture(fixtures.LockFixture('availability_zone'))
+ aggregate_name = data_utils.rand_name(self.aggregate_name_prefix)
+ resp, aggregate = self.client.create_aggregate(aggregate_name)
+ self.assertEqual(200, resp.status)
+ self.addCleanup(self.client.delete_aggregate, aggregate['id'])
+
+ resp, body = self.client.add_host(aggregate['id'], self.host)
+ self.assertEqual(200, resp.status)
+ self.addCleanup(self.client.remove_host, aggregate['id'], self.host)
+
+ self.assertRaises(exceptions.Conflict, self.client.add_host,
+ aggregate['id'], self.host)
+
+ @attr(type=['negative', 'gate'])
+ def test_aggregate_remove_host_as_user(self):
+ # Regular user is not allowed to remove a host from an aggregate.
+ self.useFixture(fixtures.LockFixture('availability_zone'))
+ aggregate_name = data_utils.rand_name(self.aggregate_name_prefix)
+ resp, aggregate = self.client.create_aggregate(aggregate_name)
+ self.assertEqual(200, resp.status)
+ self.addCleanup(self.client.delete_aggregate, aggregate['id'])
+ resp, body = self.client.add_host(aggregate['id'], self.host)
+ self.assertEqual(200, resp.status)
+ self.addCleanup(self.client.remove_host, aggregate['id'], self.host)
+
+ self.assertRaises(exceptions.Unauthorized,
+ self.user_client.remove_host,
+ aggregate['id'], self.host)
+
+ @attr(type=['negative', 'gate'])
+ def test_aggregate_remove_nonexistent_host(self):
+ non_exist_host = data_utils.rand_name('nonexist_host_')
+ aggregate_name = data_utils.rand_name(self.aggregate_name_prefix)
+ resp, aggregate = self.client.create_aggregate(aggregate_name)
+ self.assertEqual(200, resp.status)
+ self.addCleanup(self.client.delete_aggregate, aggregate['id'])
+
+ self.assertRaises(exceptions.NotFound, self.client.remove_host,
+ aggregate['id'], non_exist_host)
+
+
+class AggregatesAdminNegativeTestXML(AggregatesAdminNegativeTestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/compute/admin/test_availability_zone.py b/tempest/api/compute/admin/test_availability_zone.py
index d6488c4..e1a1a5d 100644
--- a/tempest/api/compute/admin/test_availability_zone.py
+++ b/tempest/api/compute/admin/test_availability_zone.py
@@ -16,21 +16,20 @@
# under the License.
from tempest.api.compute import base
-from tempest import exceptions
from tempest.test import attr
-class AvailabilityZoneAdminTestJSON(base.BaseV2ComputeAdminTest):
+class AZAdminTestJSON(base.BaseV2ComputeAdminTest):
"""
- Tests Availability Zone API List that require admin privileges
+ Tests Availability Zone API List
"""
_interface = 'json'
@classmethod
def setUpClass(cls):
- super(AvailabilityZoneAdminTestJSON, cls).setUpClass()
+ super(AZAdminTestJSON, cls).setUpClass()
cls.client = cls.os_adm.availability_zone_client
cls.non_adm_client = cls.availability_zone_client
@@ -57,14 +56,6 @@
self.assertEqual(200, resp.status)
self.assertTrue(len(availability_zone) > 0)
- @attr(type=['negative', 'gate'])
- def test_get_availability_zone_list_detail_with_non_admin_user(self):
- # List of availability zones and available services with
- # non-administrator user
- self.assertRaises(
- exceptions.Unauthorized,
- self.non_adm_client.get_availability_zone_list_detail)
-
-class AvailabilityZoneAdminTestXML(AvailabilityZoneAdminTestJSON):
+class AZAdminTestXML(AZAdminTestJSON):
_interface = 'xml'
diff --git a/tempest/api/compute/admin/test_availability_zone_negative.py b/tempest/api/compute/admin/test_availability_zone_negative.py
new file mode 100644
index 0000000..6ba8d58
--- /dev/null
+++ b/tempest/api/compute/admin/test_availability_zone_negative.py
@@ -0,0 +1,45 @@
+# 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 import exceptions
+from tempest.test import attr
+
+
+class AZAdminNegativeTestJSON(base.BaseV2ComputeAdminTest):
+
+ """
+ Tests Availability Zone API List
+ """
+
+ _interface = 'json'
+
+ @classmethod
+ def setUpClass(cls):
+ super(AZAdminNegativeTestJSON, cls).setUpClass()
+ cls.non_adm_client = cls.availability_zone_client
+
+ @attr(type=['negative', 'gate'])
+ def test_get_availability_zone_list_detail_with_non_admin_user(self):
+ # List of availability zones and available services with
+ # non-administrator user
+ self.assertRaises(
+ exceptions.Unauthorized,
+ self.non_adm_client.get_availability_zone_list_detail)
+
+
+class AZAdminNegativeTestXML(AZAdminNegativeTestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/compute/admin/test_flavors.py b/tempest/api/compute/admin/test_flavors.py
index c5a8772..0fb9460 100644
--- a/tempest/api/compute/admin/test_flavors.py
+++ b/tempest/api/compute/admin/test_flavors.py
@@ -17,12 +17,10 @@
import uuid
-from tempest.api import compute
from tempest.api.compute import base
from tempest.common.utils import data_utils
from tempest import exceptions
-from tempest.test import attr
-from tempest.test import skip_because
+from tempest import test
class FlavorsAdminTestJSON(base.BaseV2ComputeAdminTest):
@@ -36,7 +34,7 @@
@classmethod
def setUpClass(cls):
super(FlavorsAdminTestJSON, cls).setUpClass()
- if not compute.FLAVOR_EXTRA_DATA_ENABLED:
+ if not test.is_extension_enabled('FlavorExtraData', 'compute'):
msg = "FlavorExtraData extension not enabled."
raise cls.skipException(msg)
@@ -87,19 +85,19 @@
return flavor['id']
- @attr(type='gate')
+ @test.attr(type='gate')
def test_create_flavor_with_int_id(self):
flavor_id = data_utils.rand_int_id(start=1000)
new_flavor_id = self._create_flavor(flavor_id)
self.assertEqual(new_flavor_id, str(flavor_id))
- @attr(type='gate')
+ @test.attr(type='gate')
def test_create_flavor_with_uuid_id(self):
flavor_id = str(uuid.uuid4())
new_flavor_id = self._create_flavor(flavor_id)
self.assertEqual(new_flavor_id, flavor_id)
- @attr(type='gate')
+ @test.attr(type='gate')
def test_create_flavor_with_none_id(self):
# If nova receives a request with None as flavor_id,
# nova generates flavor_id of uuid.
@@ -107,7 +105,7 @@
new_flavor_id = self._create_flavor(flavor_id)
self.assertEqual(new_flavor_id, str(uuid.UUID(new_flavor_id)))
- @attr(type='gate')
+ @test.attr(type='gate')
def test_create_flavor_verify_entry_in_list_details(self):
# Create a flavor and ensure it's details are listed
# This operation requires the user to have 'admin' role
@@ -132,41 +130,7 @@
flag = True
self.assertTrue(flag)
- @attr(type=['negative', 'gate'])
- def test_get_flavor_details_for_deleted_flavor(self):
- # Delete a flavor and ensure it is not listed
- # Create a test flavor
- flavor_name = data_utils.rand_name(self.flavor_name_prefix)
- new_flavor_id = data_utils.rand_int_id(start=1000)
-
- resp, flavor = self.client.create_flavor(flavor_name,
- self.ram,
- self.vcpus, self.disk,
- new_flavor_id,
- ephemeral=self.ephemeral,
- swap=self.swap,
- rxtx=self.rxtx)
- # Delete the flavor
- new_flavor_id = flavor['id']
- resp_delete, body = self.client.delete_flavor(new_flavor_id)
- self.assertEqual(200, resp.status)
- self.assertEqual(202, resp_delete.status)
-
- # Deleted flavors can be seen via detailed GET
- resp, flavor = self.client.get_flavor_details(new_flavor_id)
- self.assertEqual(resp.status, 200)
- self.assertEqual(flavor['name'], flavor_name)
-
- # Deleted flavors should not show up in a list however
- resp, flavors = self.client.list_flavors_with_detail()
- self.assertEqual(resp.status, 200)
- flag = True
- for flavor in flavors:
- if flavor['name'] == flavor_name:
- flag = False
- self.assertTrue(flag)
-
- @attr(type='gate')
+ @test.attr(type='gate')
def test_create_list_flavor_without_extra_data(self):
# Create a flavor and ensure it is listed
# This operation requires the user to have 'admin' role
@@ -210,8 +174,8 @@
flag = True
self.assertTrue(flag)
- @skip_because(bug="1209101")
- @attr(type='gate')
+ @test.skip_because(bug="1209101")
+ @test.attr(type='gate')
def test_list_non_public_flavor(self):
# Create a flavor with os-flavor-access:is_public false should
# be present in list_details.
@@ -244,7 +208,7 @@
flag = True
self.assertFalse(flag)
- @attr(type='gate')
+ @test.attr(type='gate')
def test_create_server_with_non_public_flavor(self):
# Create a flavor with os-flavor-access:is_public false
flavor_name = data_utils.rand_name(self.flavor_name_prefix)
@@ -264,7 +228,7 @@
self.os.servers_client.create_server,
'test', self.image_ref, flavor['id'])
- @attr(type='gate')
+ @test.attr(type='gate')
def test_list_public_flavor_with_other_user(self):
# Create a Flavor with public access.
# Try to List/Get flavor with another user
@@ -288,7 +252,7 @@
flag = True
self.assertTrue(flag)
- @attr(type='gate')
+ @test.attr(type='gate')
def test_is_public_string_variations(self):
flavor_id_not_public = data_utils.rand_int_id(start=1000)
flavor_name_not_public = data_utils.rand_name(self.flavor_name_prefix)
@@ -331,7 +295,7 @@
_test_string_variations(['t', 'true', 'yes', '1'],
flavor_name_public)
- @attr(type='gate')
+ @test.attr(type='gate')
def test_create_flavor_using_string_ram(self):
flavor_name = data_utils.rand_name(self.flavor_name_prefix)
new_flavor_id = data_utils.rand_int_id(start=1000)
@@ -349,49 +313,6 @@
self.assertEqual(flavor['ram'], int(ram))
self.assertEqual(int(flavor['id']), new_flavor_id)
- @attr(type=['negative', 'gate'])
- def test_invalid_is_public_string(self):
- self.assertRaises(exceptions.BadRequest,
- self.client.list_flavors_with_detail,
- {'is_public': 'invalid'})
-
- @attr(type=['negative', 'gate'])
- def test_create_flavor_as_user(self):
- flavor_name = data_utils.rand_name(self.flavor_name_prefix)
- new_flavor_id = data_utils.rand_int_id(start=1000)
-
- self.assertRaises(exceptions.Unauthorized,
- self.user_client.create_flavor,
- flavor_name, self.ram, self.vcpus, self.disk,
- new_flavor_id, ephemeral=self.ephemeral,
- swap=self.swap, rxtx=self.rxtx)
-
- @attr(type=['negative', 'gate'])
- def test_delete_flavor_as_user(self):
- self.assertRaises(exceptions.Unauthorized,
- self.user_client.delete_flavor,
- self.flavor_ref_alt)
-
- @attr(type=['negative', 'gate'])
- def test_create_flavor_using_invalid_ram(self):
- flavor_name = data_utils.rand_name(self.flavor_name_prefix)
- new_flavor_id = data_utils.rand_int_id(start=1000)
-
- self.assertRaises(exceptions.BadRequest,
- self.client.create_flavor,
- flavor_name, -1, self.vcpus,
- self.disk, new_flavor_id)
-
- @attr(type=['negative', 'gate'])
- def test_create_flavor_using_invalid_vcpus(self):
- flavor_name = data_utils.rand_name(self.flavor_name_prefix)
- new_flavor_id = data_utils.rand_int_id(start=1000)
-
- self.assertRaises(exceptions.BadRequest,
- self.client.create_flavor,
- flavor_name, self.ram, 0,
- self.disk, new_flavor_id)
-
class FlavorsAdminTestXML(FlavorsAdminTestJSON):
_interface = 'xml'
diff --git a/tempest/api/compute/admin/test_flavors_access.py b/tempest/api/compute/admin/test_flavors_access.py
index b866db1..048312b 100644
--- a/tempest/api/compute/admin/test_flavors_access.py
+++ b/tempest/api/compute/admin/test_flavors_access.py
@@ -15,10 +15,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest.api import compute
from tempest.api.compute import base
from tempest.common.utils import data_utils
-from tempest.test import attr
+from tempest import test
class FlavorsAccessTestJSON(base.BaseV2ComputeAdminTest):
@@ -33,7 +32,7 @@
@classmethod
def setUpClass(cls):
super(FlavorsAccessTestJSON, cls).setUpClass()
- if not compute.FLAVOR_EXTRA_DATA_ENABLED:
+ if not test.is_extension_enabled('FlavorExtraData', 'compute'):
msg = "FlavorExtraData extension not enabled."
raise cls.skipException(msg)
@@ -51,7 +50,7 @@
cls.vcpus = 1
cls.disk = 10
- @attr(type='gate')
+ @test.attr(type='gate')
def test_flavor_access_list_with_private_flavor(self):
# Test to list flavor access successfully by querying private flavor
flavor_name = data_utils.rand_name(self.flavor_name_prefix)
@@ -70,7 +69,7 @@
self.assertEqual(str(new_flavor_id), str(first_flavor['flavor_id']))
self.assertEqual(self.adm_tenant_id, first_flavor['tenant_id'])
- @attr(type='gate')
+ @test.attr(type='gate')
def test_flavor_access_add_remove(self):
# Test to add and remove flavor access to a given tenant.
flavor_name = data_utils.rand_name(self.flavor_name_prefix)
diff --git a/tempest/api/compute/admin/test_flavors_access_negative.py b/tempest/api/compute/admin/test_flavors_access_negative.py
index 340c1c7..976124e 100644
--- a/tempest/api/compute/admin/test_flavors_access_negative.py
+++ b/tempest/api/compute/admin/test_flavors_access_negative.py
@@ -17,11 +17,10 @@
import uuid
-from tempest.api import compute
from tempest.api.compute import base
from tempest.common.utils import data_utils
from tempest import exceptions
-from tempest.test import attr
+from tempest import test
class FlavorsAccessNegativeTestJSON(base.BaseV2ComputeAdminTest):
@@ -36,7 +35,7 @@
@classmethod
def setUpClass(cls):
super(FlavorsAccessNegativeTestJSON, cls).setUpClass()
- if not compute.FLAVOR_EXTRA_DATA_ENABLED:
+ if not test.is_extension_enabled('FlavorExtraData', 'compute'):
msg = "FlavorExtraData extension not enabled."
raise cls.skipException(msg)
@@ -54,7 +53,7 @@
cls.vcpus = 1
cls.disk = 10
- @attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'gate'])
def test_flavor_access_list_with_public_flavor(self):
# Test to list flavor access with exceptions by querying public flavor
flavor_name = data_utils.rand_name(self.flavor_name_prefix)
@@ -70,7 +69,7 @@
self.client.list_flavor_access,
new_flavor_id)
- @attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'gate'])
def test_flavor_non_admin_add(self):
# Test to add flavor access as a user without admin privileges.
flavor_name = data_utils.rand_name(self.flavor_name_prefix)
@@ -86,7 +85,7 @@
new_flavor['id'],
self.tenant_id)
- @attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'gate'])
def test_flavor_non_admin_remove(self):
# Test to remove flavor access as a user without admin privileges.
flavor_name = data_utils.rand_name(self.flavor_name_prefix)
@@ -106,7 +105,7 @@
new_flavor['id'],
self.tenant_id)
- @attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'gate'])
def test_add_flavor_access_duplicate(self):
# Create a new flavor.
flavor_name = data_utils.rand_name(self.flavor_name_prefix)
@@ -130,7 +129,7 @@
new_flavor['id'],
self.tenant_id)
- @attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'gate'])
def test_remove_flavor_access_not_found(self):
# Create a new flavor.
flavor_name = data_utils.rand_name(self.flavor_name_prefix)
diff --git a/tempest/api/compute/admin/test_flavors_extra_specs.py b/tempest/api/compute/admin/test_flavors_extra_specs.py
index 49b0429..875f742 100644
--- a/tempest/api/compute/admin/test_flavors_extra_specs.py
+++ b/tempest/api/compute/admin/test_flavors_extra_specs.py
@@ -15,10 +15,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest.api import compute
from tempest.api.compute import base
from tempest.common.utils import data_utils
-from tempest.test import attr
+from tempest import test
class FlavorsExtraSpecsTestJSON(base.BaseV2ComputeAdminTest):
@@ -34,7 +33,7 @@
@classmethod
def setUpClass(cls):
super(FlavorsExtraSpecsTestJSON, cls).setUpClass()
- if not compute.FLAVOR_EXTRA_DATA_ENABLED:
+ if not test.is_extension_enabled('FlavorExtraData', 'compute'):
msg = "FlavorExtraData extension not enabled."
raise cls.skipException(msg)
@@ -61,7 +60,7 @@
cls.client.wait_for_resource_deletion(cls.flavor['id'])
super(FlavorsExtraSpecsTestJSON, cls).tearDownClass()
- @attr(type='gate')
+ @test.attr(type='gate')
def test_flavor_set_get_update_show_unset_keys(self):
# Test to SET, GET, UPDATE, SHOW, UNSET flavor extra
# spec as a user with admin privileges.
@@ -101,7 +100,7 @@
self.client.unset_flavor_extra_spec(self.flavor['id'], "key2")
self.assertEqual(unset_resp.status, 200)
- @attr(type='gate')
+ @test.attr(type='gate')
def test_flavor_non_admin_get_all_keys(self):
specs = {"key1": "value1", "key2": "value2"}
set_resp, set_body = self.client.set_flavor_extra_spec(
@@ -113,7 +112,7 @@
for key in specs:
self.assertEqual(body[key], specs[key])
- @attr(type='gate')
+ @test.attr(type='gate')
def test_flavor_non_admin_get_specific_key(self):
specs = {"key1": "value1", "key2": "value2"}
resp, body = self.client.set_flavor_extra_spec(
diff --git a/tempest/api/compute/admin/test_flavors_extra_specs_negative.py b/tempest/api/compute/admin/test_flavors_extra_specs_negative.py
index d7e1f9f..fb09a63 100644
--- a/tempest/api/compute/admin/test_flavors_extra_specs_negative.py
+++ b/tempest/api/compute/admin/test_flavors_extra_specs_negative.py
@@ -16,11 +16,10 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest.api import compute
from tempest.api.compute import base
from tempest.common.utils import data_utils
from tempest import exceptions
-from tempest.test import attr
+from tempest import test
class FlavorsExtraSpecsNegativeTestJSON(base.BaseV2ComputeAdminTest):
@@ -35,7 +34,7 @@
@classmethod
def setUpClass(cls):
super(FlavorsExtraSpecsNegativeTestJSON, cls).setUpClass()
- if not compute.FLAVOR_EXTRA_DATA_ENABLED:
+ if not test.is_extension_enabled('FlavorExtraData', 'compute'):
msg = "FlavorExtraData extension not enabled."
raise cls.skipException(msg)
@@ -62,7 +61,7 @@
cls.client.wait_for_resource_deletion(cls.flavor['id'])
super(FlavorsExtraSpecsNegativeTestJSON, cls).tearDownClass()
- @attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'gate'])
def test_flavor_non_admin_set_keys(self):
# Test to SET flavor extra spec as a user without admin privileges.
specs = {"key1": "value1", "key2": "value2"}
@@ -71,7 +70,7 @@
self.flavor['id'],
specs)
- @attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'gate'])
def test_flavor_non_admin_update_specific_key(self):
# non admin user is not allowed to update flavor extra spec
specs = {"key1": "value1", "key2": "value2"}
@@ -86,7 +85,7 @@
'key1',
key1='value1_new')
- @attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'gate'])
def test_flavor_non_admin_unset_keys(self):
specs = {"key1": "value1", "key2": "value2"}
set_resp, set_body = self.client.set_flavor_extra_spec(
@@ -97,7 +96,7 @@
self.flavor['id'],
'key1')
- @attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'gate'])
def test_flavor_unset_nonexistent_key(self):
nonexistent_key = data_utils.rand_name('flavor_key')
self.assertRaises(exceptions.NotFound,
@@ -105,14 +104,14 @@
self.flavor['id'],
nonexistent_key)
- @attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'gate'])
def test_flavor_get_nonexistent_key(self):
self.assertRaises(exceptions.NotFound,
self.flavors_client.get_flavor_extra_spec_with_key,
self.flavor['id'],
"nonexistent_key")
- @attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'gate'])
def test_flavor_update_mismatch_key(self):
# the key will be updated should be match the key in the body
self.assertRaises(exceptions.BadRequest,
@@ -121,7 +120,7 @@
"key2",
key1="value")
- @attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'gate'])
def test_flavor_update_more_key(self):
# there should be just one item in the request body
self.assertRaises(exceptions.BadRequest,
diff --git a/tempest/api/compute/admin/test_flavors_negative.py b/tempest/api/compute/admin/test_flavors_negative.py
new file mode 100644
index 0000000..1d9198a
--- /dev/null
+++ b/tempest/api/compute/admin/test_flavors_negative.py
@@ -0,0 +1,344 @@
+# 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 uuid
+
+from tempest.api.compute import base
+from tempest.common.utils import data_utils
+from tempest import exceptions
+from tempest import test
+
+
+class FlavorsAdminNegativeTestJSON(base.BaseV2ComputeAdminTest):
+
+ """
+ Tests Flavors API Create and Delete that require admin privileges
+ """
+
+ _interface = 'json'
+
+ @classmethod
+ def setUpClass(cls):
+ super(FlavorsAdminNegativeTestJSON, cls).setUpClass()
+ if not test.is_extension_enabled('FlavorExtraData', 'compute'):
+ msg = "FlavorExtraData extension not enabled."
+ raise cls.skipException(msg)
+
+ cls.client = cls.os_adm.flavors_client
+ cls.user_client = cls.os.flavors_client
+ cls.flavor_name_prefix = 'test_flavor_'
+ cls.ram = 512
+ cls.vcpus = 1
+ cls.disk = 10
+ cls.ephemeral = 10
+ cls.swap = 1024
+ cls.rxtx = 2
+
+ def flavor_clean_up(self, flavor_id):
+ resp, body = self.client.delete_flavor(flavor_id)
+ self.assertEqual(resp.status, 202)
+ self.client.wait_for_resource_deletion(flavor_id)
+
+ @test.attr(type=['negative', 'gate'])
+ def test_get_flavor_details_for_deleted_flavor(self):
+ # Delete a flavor and ensure it is not listed
+ # Create a test flavor
+ flavor_name = data_utils.rand_name(self.flavor_name_prefix)
+
+ # no need to specify flavor_id, we can get the flavor_id from a
+ # response of create_flavor() call.
+ resp, flavor = self.client.create_flavor(flavor_name,
+ self.ram,
+ self.vcpus, self.disk,
+ '',
+ ephemeral=self.ephemeral,
+ swap=self.swap,
+ rxtx=self.rxtx)
+ # Delete the flavor
+ new_flavor_id = flavor['id']
+ resp_delete, body = self.client.delete_flavor(new_flavor_id)
+ self.assertEqual(200, resp.status)
+ self.assertEqual(202, resp_delete.status)
+
+ # Deleted flavors can be seen via detailed GET
+ resp, flavor = self.client.get_flavor_details(new_flavor_id)
+ self.assertEqual(resp.status, 200)
+ self.assertEqual(flavor['name'], flavor_name)
+
+ # Deleted flavors should not show up in a list however
+ resp, flavors = self.client.list_flavors_with_detail()
+ self.assertEqual(resp.status, 200)
+ flag = True
+ for flavor in flavors:
+ if flavor['name'] == flavor_name:
+ flag = False
+ self.assertTrue(flag)
+
+ @test.attr(type=['negative', 'gate'])
+ def test_invalid_is_public_string(self):
+ # the 'is_public' parameter can be 'none/true/false' if it exists
+ self.assertRaises(exceptions.BadRequest,
+ self.client.list_flavors_with_detail,
+ {'is_public': 'invalid'})
+
+ @test.attr(type=['negative', 'gate'])
+ def test_create_flavor_as_user(self):
+ # only admin user can create a flavor
+ flavor_name = data_utils.rand_name(self.flavor_name_prefix)
+ new_flavor_id = str(uuid.uuid4())
+
+ self.assertRaises(exceptions.Unauthorized,
+ self.user_client.create_flavor,
+ flavor_name, self.ram, self.vcpus, self.disk,
+ new_flavor_id, ephemeral=self.ephemeral,
+ swap=self.swap, rxtx=self.rxtx)
+
+ @test.attr(type=['negative', 'gate'])
+ def test_delete_flavor_as_user(self):
+ # only admin user can delete a flavor
+ self.assertRaises(exceptions.Unauthorized,
+ self.user_client.delete_flavor,
+ self.flavor_ref_alt)
+
+ @test.attr(type=['negative', 'gate'])
+ def test_create_flavor_using_invalid_ram(self):
+ # the 'ram' attribute must be positive integer
+ flavor_name = data_utils.rand_name(self.flavor_name_prefix)
+ new_flavor_id = str(uuid.uuid4())
+
+ self.assertRaises(exceptions.BadRequest,
+ self.client.create_flavor,
+ flavor_name, -1, self.vcpus,
+ self.disk, new_flavor_id)
+
+ @test.attr(type=['negative', 'gate'])
+ def test_create_flavor_using_invalid_vcpus(self):
+ # the 'vcpu' attribute must be positive integer
+ flavor_name = data_utils.rand_name(self.flavor_name_prefix)
+ new_flavor_id = str(uuid.uuid4())
+
+ self.assertRaises(exceptions.BadRequest,
+ self.client.create_flavor,
+ flavor_name, self.ram, -1,
+ self.disk, new_flavor_id)
+
+ @test.attr(type=['negative', 'gate'])
+ def test_create_flavor_with_name_length_less_than_1(self):
+ # ensure name length >= 1
+ new_flavor_id = str(uuid.uuid4())
+
+ self.assertRaises(exceptions.BadRequest,
+ self.client.create_flavor,
+ '',
+ self.ram, self.vcpus,
+ self.disk,
+ new_flavor_id,
+ ephemeral=self.ephemeral,
+ swap=self.swap,
+ rxtx=self.rxtx,
+ is_public='False')
+
+ @test.attr(type=['negative', 'gate'])
+ def test_create_flavor_with_name_length_exceeds_255(self):
+ # ensure name do not exceed 255 characters
+ new_flavor_name = 'a' * 256
+ new_flavor_id = str(uuid.uuid4())
+
+ self.assertRaises(exceptions.BadRequest,
+ self.client.create_flavor,
+ new_flavor_name,
+ self.ram, self.vcpus,
+ self.disk,
+ new_flavor_id,
+ ephemeral=self.ephemeral,
+ swap=self.swap,
+ rxtx=self.rxtx,
+ is_public='False')
+
+ @test.attr(type=['negative', 'gate'])
+ def test_create_flavor_with_invalid_name(self):
+ # the regex of flavor_name is '^[\w\.\- ]*$'
+ invalid_flavor_name = data_utils.rand_name('invalid-!@#$%-')
+ new_flavor_id = str(uuid.uuid4())
+
+ self.assertRaises(exceptions.BadRequest,
+ self.client.create_flavor,
+ invalid_flavor_name,
+ self.ram, self.vcpus,
+ self.disk,
+ new_flavor_id,
+ ephemeral=self.ephemeral,
+ swap=self.swap,
+ rxtx=self.rxtx,
+ is_public='False')
+
+ @test.attr(type=['negative', 'gate'])
+ def test_create_flavor_with_invalid_flavor_id(self):
+ # the regex of flavor_id is '^[\w\.\- ]*$', and it cannot contain
+ # leading and/or trailing whitespace
+ new_flavor_name = data_utils.rand_name(self.flavor_name_prefix)
+ invalid_flavor_id = '!@#$%'
+
+ self.assertRaises(exceptions.BadRequest,
+ self.client.create_flavor,
+ new_flavor_name,
+ self.ram, self.vcpus,
+ self.disk,
+ invalid_flavor_id,
+ ephemeral=self.ephemeral,
+ swap=self.swap,
+ rxtx=self.rxtx,
+ is_public='False')
+
+ @test.attr(type=['negative', 'gate'])
+ def test_create_flavor_with_id_length_exceeds_255(self):
+ # the length of flavor_id should not exceed 255 characters
+ new_flavor_name = data_utils.rand_name(self.flavor_name_prefix)
+ invalid_flavor_id = 'a' * 256
+
+ self.assertRaises(exceptions.BadRequest,
+ self.client.create_flavor,
+ new_flavor_name,
+ self.ram, self.vcpus,
+ self.disk,
+ invalid_flavor_id,
+ ephemeral=self.ephemeral,
+ swap=self.swap,
+ rxtx=self.rxtx,
+ is_public='False')
+
+ @test.attr(type=['negative', 'gate'])
+ def test_create_flavor_with_invalid_root_gb(self):
+ # root_gb attribute should be non-negative ( >= 0) integer
+ new_flavor_name = data_utils.rand_name(self.flavor_name_prefix)
+ new_flavor_id = str(uuid.uuid4())
+
+ self.assertRaises(exceptions.BadRequest,
+ self.client.create_flavor,
+ new_flavor_name,
+ self.ram, self.vcpus,
+ -1,
+ new_flavor_id,
+ ephemeral=self.ephemeral,
+ swap=self.swap,
+ rxtx=self.rxtx,
+ is_public='False')
+
+ @test.attr(type=['negative', 'gate'])
+ def test_create_flavor_with_invalid_ephemeral_gb(self):
+ # ephemeral_gb attribute should be non-negative ( >= 0) integer
+ new_flavor_name = data_utils.rand_name(self.flavor_name_prefix)
+ new_flavor_id = str(uuid.uuid4())
+
+ self.assertRaises(exceptions.BadRequest,
+ self.client.create_flavor,
+ new_flavor_name,
+ self.ram, self.vcpus,
+ self.disk,
+ new_flavor_id,
+ ephemeral=-1,
+ swap=self.swap,
+ rxtx=self.rxtx,
+ is_public='False')
+
+ @test.attr(type=['negative', 'gate'])
+ def test_create_flavor_with_invalid_swap(self):
+ # swap attribute should be non-negative ( >= 0) integer
+ new_flavor_name = data_utils.rand_name(self.flavor_name_prefix)
+ new_flavor_id = str(uuid.uuid4())
+
+ self.assertRaises(exceptions.BadRequest,
+ self.client.create_flavor,
+ new_flavor_name,
+ self.ram, self.vcpus,
+ self.disk,
+ new_flavor_id,
+ ephemeral=self.ephemeral,
+ swap=-1,
+ rxtx=self.rxtx,
+ is_public='False')
+
+ @test.attr(type=['negative', 'gate'])
+ def test_create_flavor_with_invalid_rxtx_factor(self):
+ # rxtx_factor attribute should be a positive float
+ new_flavor_name = data_utils.rand_name(self.flavor_name_prefix)
+ new_flavor_id = str(uuid.uuid4())
+
+ self.assertRaises(exceptions.BadRequest,
+ self.client.create_flavor,
+ new_flavor_name,
+ self.ram, self.vcpus,
+ self.disk,
+ new_flavor_id,
+ ephemeral=self.ephemeral,
+ swap=self.swap,
+ rxtx=-1.5,
+ is_public='False')
+
+ @test.attr(type=['negative', 'gate'])
+ def test_create_flavor_with_invalid_is_public(self):
+ # is_public attribute should be boolean
+ new_flavor_name = data_utils.rand_name(self.flavor_name_prefix)
+ new_flavor_id = str(uuid.uuid4())
+
+ self.assertRaises(exceptions.BadRequest,
+ self.client.create_flavor,
+ new_flavor_name,
+ self.ram, self.vcpus,
+ self.disk,
+ new_flavor_id,
+ ephemeral=self.ephemeral,
+ swap=self.swap,
+ rxtx=self.rxtx,
+ is_public='Invalid')
+
+ @test.attr(type=['negative', 'gate'])
+ def test_create_flavor_already_exists(self):
+ flavor_name = data_utils.rand_name(self.flavor_name_prefix)
+ new_flavor_id = str(uuid.uuid4())
+
+ resp, flavor = self.client.create_flavor(flavor_name,
+ self.ram, self.vcpus,
+ self.disk,
+ new_flavor_id,
+ ephemeral=self.ephemeral,
+ swap=self.swap,
+ rxtx=self.rxtx)
+ self.assertEqual(200, resp.status)
+ self.addCleanup(self.flavor_clean_up, flavor['id'])
+
+ self.assertRaises(exceptions.Conflict,
+ self.client.create_flavor,
+ flavor_name,
+ self.ram, self.vcpus,
+ self.disk,
+ new_flavor_id,
+ ephemeral=self.ephemeral,
+ swap=self.swap,
+ rxtx=self.rxtx)
+
+ @test.attr(type=['negative', 'gate'])
+ def test_delete_nonexistent_flavor(self):
+ nonexistent_flavor_id = str(uuid.uuid4())
+
+ self.assertRaises(exceptions.NotFound,
+ self.client.delete_flavor,
+ nonexistent_flavor_id)
+
+
+class FlavorsAdminNegativeTestXML(FlavorsAdminNegativeTestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/compute/admin/test_security_groups.py b/tempest/api/compute/admin/test_security_groups.py
new file mode 100644
index 0000000..5ed4823
--- /dev/null
+++ b/tempest/api/compute/admin/test_security_groups.py
@@ -0,0 +1,96 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 NTT Data
+# 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.common.utils import data_utils
+from tempest import config
+from tempest.test import attr
+
+
+class SecurityGroupsTestAdminJSON(base.BaseV2ComputeAdminTest):
+ _interface = 'json'
+
+ @classmethod
+ def setUpClass(cls):
+ super(SecurityGroupsTestAdminJSON, cls).setUpClass()
+ cls.adm_client = cls.os_adm.security_groups_client
+ cls.client = cls.security_groups_client
+
+ def _delete_security_group(self, securitygroup_id, admin=True):
+ if admin:
+ resp, _ = self.adm_client.delete_security_group(securitygroup_id)
+ else:
+ resp, _ = self.client.delete_security_group(securitygroup_id)
+
+ self.assertEqual(202, resp.status)
+
+ @testtools.skipIf(config.TempestConfig().service_available.neutron,
+ "Skipped because neutron do not support all_tenants"
+ "search filter.")
+ @attr(type='smoke')
+ def test_list_security_groups_list_all_tenants_filter(self):
+ # Admin can list security groups of all tenants
+ # List of all security groups created
+ security_group_list = []
+ # Create two security groups for a non-admin tenant
+ for i in range(2):
+ name = data_utils.rand_name('securitygroup-')
+ description = data_utils.rand_name('description-')
+ resp, securitygroup = (self.client
+ .create_security_group(name, description))
+ self.assertEqual(200, resp.status)
+ self.addCleanup(self._delete_security_group,
+ securitygroup['id'], admin=False)
+ security_group_list.append(securitygroup)
+
+ client_tenant_id = securitygroup['tenant_id']
+ # Create two security groups for admin tenant
+ for i in range(2):
+ name = data_utils.rand_name('securitygroup-')
+ description = data_utils.rand_name('description-')
+ resp, adm_securitygroup = (self.adm_client
+ .create_security_group(name,
+ description))
+ self.assertEqual(200, resp.status)
+ self.addCleanup(self._delete_security_group,
+ adm_securitygroup['id'])
+ security_group_list.append(adm_securitygroup)
+
+ # Fetch all security groups based on 'all_tenants' search filter
+ param = {'all_tenants': 'true'}
+ resp, fetched_list = self.adm_client.list_security_groups(params=param)
+ self.assertEqual(200, resp.status)
+ sec_group_id_list = map(lambda sg: sg['id'], fetched_list)
+ # Now check if all created Security Groups are present in fetched list
+ for sec_group in security_group_list:
+ self.assertIn(sec_group['id'], sec_group_id_list)
+
+ # Fetch all security groups for non-admin user with 'all_tenants'
+ # search filter
+ resp, fetched_list = self.client.list_security_groups(params=param)
+ self.assertEqual(200, resp.status)
+ # Now check if all created Security Groups are present in fetched list
+ for sec_group in fetched_list:
+ self.assertEqual(sec_group['tenant_id'], client_tenant_id,
+ "Failed to get all security groups for "
+ "non admin user.")
+
+
+class SecurityGroupsTestAdminXML(SecurityGroupsTestAdminJSON):
+ _interface = 'xml'
diff --git a/tempest/api/compute/admin/test_servers.py b/tempest/api/compute/admin/test_servers.py
index 5028cad..6fe3186 100644
--- a/tempest/api/compute/admin/test_servers.py
+++ b/tempest/api/compute/admin/test_servers.py
@@ -33,11 +33,8 @@
def setUpClass(cls):
super(ServersAdminTestJSON, cls).setUpClass()
cls.client = cls.os_adm.servers_client
+ cls.non_admin_client = cls.servers_client
cls.flavors_client = cls.os_adm.flavors_client
- cls.identity_client = cls._get_identity_admin_client()
- tenant = cls.identity_client.get_tenant_by_name(
- cls.client.tenant_name)
- cls.tenant_id = tenant['id']
cls.s1_name = data_utils.rand_name('server')
resp, server = cls.create_test_server(name=cls.s1_name,
@@ -116,6 +113,34 @@
for key in basic_attrs:
self.assertIn(key, str(diagnostic.keys()))
+ @attr(type='gate')
+ def test_rebuild_server_in_error_state(self):
+ # The server in error state should be rebuilt using the provided
+ # image and changed to ACTIVE state
+
+ # resetting vm state require admin priviledge
+ resp, server = self.client.reset_state(self.s1_id, state='error')
+ self.assertEqual(202, resp.status)
+ resp, rebuilt_server = self.non_admin_client.rebuild(
+ self.s1_id, self.image_ref_alt)
+ self.addCleanup(self.non_admin_client.wait_for_server_status,
+ self.s1_id, 'ACTIVE')
+ self.addCleanup(self.non_admin_client.rebuild, self.s1_id,
+ self.image_ref)
+
+ # Verify the properties in the initial response are correct
+ self.assertEqual(self.s1_id, rebuilt_server['id'])
+ rebuilt_image_id = rebuilt_server['image']['id']
+ self.assertEqual(self.image_ref_alt, rebuilt_image_id)
+ self.assertEqual(self.flavor_ref, rebuilt_server['flavor']['id'])
+ self.non_admin_client.wait_for_server_status(rebuilt_server['id'],
+ 'ACTIVE',
+ raise_on_error=False)
+ # Verify the server properties after rebuilding
+ resp, server = self.non_admin_client.get_server(rebuilt_server['id'])
+ rebuilt_image_id = server['image']['id']
+ self.assertEqual(self.image_ref_alt, rebuilt_image_id)
+
class ServersAdminTestXML(ServersAdminTestJSON):
_interface = 'xml'
diff --git a/tempest/api/compute/admin/test_services.py b/tempest/api/compute/admin/test_services.py
index 327d8b8..6122758 100644
--- a/tempest/api/compute/admin/test_services.py
+++ b/tempest/api/compute/admin/test_services.py
@@ -17,7 +17,6 @@
# under the License.
from tempest.api.compute import base
-from tempest import exceptions
from tempest.test import attr
@@ -41,11 +40,6 @@
self.assertEqual(200, resp.status)
self.assertNotEqual(0, len(services))
- @attr(type=['negative', 'gate'])
- def test_list_services_with_non_admin_user(self):
- self.assertRaises(exceptions.Unauthorized,
- self.non_admin_client.list_services)
-
@attr(type='gate')
def test_get_service_by_service_binary_name(self):
binary_name = 'nova-compute'
@@ -74,15 +68,6 @@
# on order.
self.assertEqual(sorted(s1), sorted(s2))
- @attr(type=['negative', 'gate'])
- def test_get_service_by_invalid_params(self):
- # return all services if send the request with invalid parameter
- resp, services = self.client.list_services()
- params = {'xxx': 'nova-compute'}
- resp, services_xxx = self.client.list_services(params)
- self.assertEqual(200, resp.status)
- self.assertEqual(len(services), len(services_xxx))
-
@attr(type='gate')
def test_get_service_by_service_and_host_name(self):
resp, services = self.client.list_services()
@@ -95,41 +80,6 @@
self.assertEqual(host_name, services[0]['host'])
self.assertEqual(binary_name, services[0]['binary'])
- @attr(type=['negative', 'gate'])
- def test_get_service_by_invalid_service_and_valid_host(self):
- resp, services = self.client.list_services()
- host_name = services[0]['host']
- params = {'host': host_name, 'binary': 'xxx'}
- resp, services = self.client.list_services(params)
- self.assertEqual(200, resp.status)
- self.assertEqual(0, len(services))
-
- @attr(type=['negative', 'gate'])
- def test_get_service_with_valid_service_and_invalid_host(self):
- resp, services = self.client.list_services()
- binary_name = services[0]['binary']
- params = {'host': 'xxx', 'binary': binary_name}
- resp, services = self.client.list_services(params)
- self.assertEqual(200, resp.status)
- self.assertEqual(0, len(services))
-
- @attr(type='gate')
- def test_service_enable_disable(self):
- resp, services = self.client.list_services()
- host_name = services[0]['host']
- binary_name = services[0]['binary']
-
- resp, service = self.client.disable_service(host_name, binary_name)
- self.assertEqual(200, resp.status)
- params = {'host': host_name, 'binary': binary_name}
- resp, services = self.client.list_services(params)
- self.assertEqual('disabled', services[0]['status'])
-
- resp, service = self.client.enable_service(host_name, binary_name)
- self.assertEqual(200, resp.status)
- resp, services = self.client.list_services(params)
- self.assertEqual('enabled', services[0]['status'])
-
class ServicesAdminTestXML(ServicesAdminTestJSON):
_interface = 'xml'
diff --git a/tempest/api/compute/admin/test_services_negative.py b/tempest/api/compute/admin/test_services_negative.py
new file mode 100644
index 0000000..da1482e
--- /dev/null
+++ b/tempest/api/compute/admin/test_services_negative.py
@@ -0,0 +1,70 @@
+# 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 import exceptions
+from tempest.test import attr
+
+
+class ServicesAdminNegativeTestJSON(base.BaseV2ComputeAdminTest):
+
+ """
+ Tests Services API. List and Enable/Disable require admin privileges.
+ """
+
+ _interface = 'json'
+
+ @classmethod
+ def setUpClass(cls):
+ super(ServicesAdminNegativeTestJSON, cls).setUpClass()
+ cls.client = cls.os_adm.services_client
+ cls.non_admin_client = cls.services_client
+
+ @attr(type=['negative', 'gate'])
+ def test_list_services_with_non_admin_user(self):
+ self.assertRaises(exceptions.Unauthorized,
+ self.non_admin_client.list_services)
+
+ @attr(type=['negative', 'gate'])
+ def test_get_service_by_invalid_params(self):
+ # return all services if send the request with invalid parameter
+ resp, services = self.client.list_services()
+ params = {'xxx': 'nova-compute'}
+ resp, services_xxx = self.client.list_services(params)
+ self.assertEqual(200, resp.status)
+ self.assertEqual(len(services), len(services_xxx))
+
+ @attr(type=['negative', 'gate'])
+ def test_get_service_by_invalid_service_and_valid_host(self):
+ resp, services = self.client.list_services()
+ host_name = services[0]['host']
+ params = {'host': host_name, 'binary': 'xxx'}
+ resp, services = self.client.list_services(params)
+ self.assertEqual(200, resp.status)
+ self.assertEqual(0, len(services))
+
+ @attr(type=['negative', 'gate'])
+ def test_get_service_with_valid_service_and_invalid_host(self):
+ resp, services = self.client.list_services()
+ binary_name = services[0]['binary']
+ params = {'host': 'xxx', 'binary': binary_name}
+ resp, services = self.client.list_services(params)
+ self.assertEqual(200, resp.status)
+ self.assertEqual(0, len(services))
+
+
+class ServicesAdminNegativeTestXML(ServicesAdminNegativeTestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/compute/base.py b/tempest/api/compute/base.py
index 7ef5466..d18b749 100644
--- a/tempest/api/compute/base.py
+++ b/tempest/api/compute/base.py
@@ -17,7 +17,6 @@
import time
-from tempest.api import compute
from tempest import clients
from tempest.common.utils import data_utils
from tempest import exceptions
@@ -31,7 +30,6 @@
class BaseComputeTest(tempest.test.BaseTestCase):
"""Base test case class for all Compute API tests."""
- conclusion = compute.generic_setup_package()
force_tenant_isolation = False
@classmethod
@@ -55,6 +53,30 @@
cls.image_ssh_password = cls.config.compute.image_ssh_password
cls.servers = []
cls.images = []
+ cls.multi_user = cls.get_multi_user()
+
+ @classmethod
+ def get_multi_user(cls):
+ multi_user = True
+ # Determine if there are two regular users that can be
+ # used in testing. If the test cases are allowed to create
+ # users (config.compute.allow_tenant_isolation is true,
+ # then we allow multi-user.
+ if not cls.config.compute.allow_tenant_isolation:
+ user1 = cls.config.identity.username
+ user2 = cls.config.identity.alt_username
+ if not user2 or user1 == user2:
+ multi_user = False
+ else:
+ user2_password = cls.config.identity.alt_password
+ user2_tenant_name = cls.config.identity.alt_tenant_name
+ if not user2_password or not user2_tenant_name:
+ msg = ("Alternate user specified but not alternate "
+ "tenant or password: alt_tenant_name=%s "
+ "alt_password=%s"
+ % (user2_tenant_name, user2_password))
+ raise exceptions.InvalidConfiguration(msg)
+ return multi_user
@classmethod
def clear_servers(cls):
@@ -178,20 +200,26 @@
kwargs['wait_until'])
resp, image = cls.images_client.get_image(image_id)
+ if kwargs['wait_until'] == 'ACTIVE':
+ if kwargs.get('wait_for_server', True):
+ cls.servers_client.wait_for_server_status(server_id,
+ 'ACTIVE')
+
return resp, image
@classmethod
- def rebuild_server(cls, **kwargs):
+ def rebuild_server(cls, server_id, **kwargs):
# Destroy an existing server and creates a new one
- try:
- cls.servers_client.delete_server(cls.server_id)
- cls.servers_client.wait_for_server_termination(cls.server_id)
- except Exception as exc:
- LOG.exception(exc)
- pass
+ if server_id:
+ try:
+ cls.servers_client.delete_server(server_id)
+ cls.servers_client.wait_for_server_termination(server_id)
+ except Exception as exc:
+ LOG.exception(exc)
+ pass
resp, server = cls.create_test_server(wait_until='ACTIVE', **kwargs)
- cls.server_id = server['id']
cls.password = server['adminPass']
+ return server['id']
class BaseV2ComputeAdminTest(BaseV2ComputeTest):
@@ -237,6 +265,8 @@
cls.availability_zone_client = cls.os.availability_zone_v3_client
cls.interfaces_client = cls.os.interfaces_v3_client
cls.hypervisor_client = cls.os.hypervisor_v3_client
+ cls.tenant_usages_client = cls.os.tenant_usages_v3_client
+ cls.volumes_client = cls.os.volumes_client
@classmethod
def create_image_from_server(cls, server_id, **kwargs):
@@ -258,17 +288,17 @@
return resp, image
@classmethod
- def rebuild_server(cls, **kwargs):
+ def rebuild_server(cls, server_id, **kwargs):
# Destroy an existing server and creates a new one
try:
- cls.servers_client.delete_server(cls.server_id)
- cls.servers_client.wait_for_server_termination(cls.server_id)
+ cls.servers_client.delete_server(server_id)
+ cls.servers_client.wait_for_server_termination(server_id)
except Exception as exc:
LOG.exception(exc)
pass
resp, server = cls.create_test_server(wait_until='ACTIVE', **kwargs)
- cls.server_id = server['id']
cls.password = server['admin_password']
+ return server['id']
class BaseV3ComputeAdminTest(BaseV3ComputeTest):
@@ -300,3 +330,4 @@
cls.availability_zone_admin_client = \
cls.os_adm.availability_zone_v3_client
cls.hypervisor_admin_client = cls.os_adm.hypervisor_v3_client
+ cls.tenant_usages_admin_client = cls.os_adm.tenant_usages_v3_client
diff --git a/tempest/api/compute/flavors/test_flavors.py b/tempest/api/compute/flavors/test_flavors.py
index fa99422..092fd65 100644
--- a/tempest/api/compute/flavors/test_flavors.py
+++ b/tempest/api/compute/flavors/test_flavors.py
@@ -16,7 +16,6 @@
# under the License.
from tempest.api.compute import base
-from tempest import exceptions
from tempest.test import attr
@@ -50,12 +49,6 @@
resp, flavor = self.client.get_flavor_details(self.flavor_ref)
self.assertEqual(self.flavor_ref, flavor['id'])
- @attr(type=['negative', 'gate'])
- def test_get_non_existant_flavor(self):
- # flavor details are not returned for non-existent flavors
- self.assertRaises(exceptions.NotFound, self.client.get_flavor_details,
- 999)
-
@attr(type='gate')
def test_list_flavors_limit_results(self):
# Only the expected number of flavors should be returned
@@ -136,24 +129,6 @@
resp, flavors = self.client.list_flavors(params)
self.assertFalse(any([i for i in flavors if i['id'] == flavor_id]))
- @attr(type=['negative', 'gate'])
- def test_invalid_minRam_filter(self):
- self.assertRaises(exceptions.BadRequest,
- self.client.list_flavors_with_detail,
- {'minRam': 'invalid'})
-
- @attr(type=['negative', 'gate'])
- def test_invalid_minDisk_filter(self):
- self.assertRaises(exceptions.BadRequest,
- self.client.list_flavors_with_detail,
- {'minDisk': 'invalid'})
-
- @attr(type=['negative', 'gate'])
- def test_get_flavor_details_for_invalid_flavor_id(self):
- # Ensure 404 returned for non-existent flavor ID
- self.assertRaises(exceptions.NotFound, self.client.get_flavor_details,
- 9999)
-
class FlavorsTestXML(FlavorsTestJSON):
_interface = 'xml'
diff --git a/tempest/api/compute/flavors/test_flavors_negative.py b/tempest/api/compute/flavors/test_flavors_negative.py
new file mode 100644
index 0000000..0b20e90
--- /dev/null
+++ b/tempest/api/compute/flavors/test_flavors_negative.py
@@ -0,0 +1,54 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 OpenStack Foundation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import uuid
+
+from tempest.api.compute import base
+from tempest import exceptions
+from tempest.test import attr
+
+
+class FlavorsNegativeTestJSON(base.BaseV2ComputeTest):
+ _interface = 'json'
+
+ @classmethod
+ def setUpClass(cls):
+ super(FlavorsNegativeTestJSON, cls).setUpClass()
+ cls.client = cls.flavors_client
+
+ @attr(type=['negative', 'gate'])
+ def test_invalid_minRam_filter(self):
+ self.assertRaises(exceptions.BadRequest,
+ self.client.list_flavors_with_detail,
+ {'minRam': 'invalid'})
+
+ @attr(type=['negative', 'gate'])
+ def test_invalid_minDisk_filter(self):
+ self.assertRaises(exceptions.BadRequest,
+ self.client.list_flavors_with_detail,
+ {'minDisk': 'invalid'})
+
+ @attr(type=['negative', 'gate'])
+ def test_non_existent_flavor_id(self):
+ # flavor details are not returned for non-existent flavors
+ nonexistent_flavor_id = str(uuid.uuid4())
+ self.assertRaises(exceptions.NotFound, self.client.get_flavor_details,
+ nonexistent_flavor_id)
+
+
+class FlavorsNegativeTestXML(FlavorsNegativeTestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/compute/floating_ips/test_floating_ips_actions.py b/tempest/api/compute/floating_ips/test_floating_ips_actions.py
index a06309a..f4ad449 100644
--- a/tempest/api/compute/floating_ips/test_floating_ips_actions.py
+++ b/tempest/api/compute/floating_ips/test_floating_ips_actions.py
@@ -15,10 +15,8 @@
# License for the specific language governing permissions and limitations
# under the License.
-import uuid
-
from tempest.api.compute import base
-from tempest.common.utils import data_utils
+from tempest.common.utils.data_utils import rand_name
from tempest import exceptions
from tempest.test import attr
@@ -32,7 +30,7 @@
def setUpClass(cls):
super(FloatingIPsTestJSON, cls).setUpClass()
cls.client = cls.floating_ips_client
- cls.servers_client = cls.servers_client
+ #cls.servers_client = cls.servers_client
# Server creation
resp, server = cls.create_test_server(wait_until='ACTIVE')
@@ -41,17 +39,6 @@
resp, body = cls.client.create_floating_ip()
cls.floating_ip_id = body['id']
cls.floating_ip = body['ip']
- # Generating a nonexistent floatingIP id
- cls.floating_ip_ids = []
- resp, body = cls.client.list_floating_ips()
- for i in range(len(body)):
- cls.floating_ip_ids.append(body[i]['id'])
- while True:
- cls.non_exist_id = data_utils.rand_int_id(start=999)
- if cls.config.service_available.neutron:
- cls.non_exist_id = str(uuid.uuid4())
- if cls.non_exist_id not in cls.floating_ip_ids:
- break
@classmethod
def tearDownClass(cls):
@@ -76,14 +63,6 @@
# Deleting the floating IP which is created in this method
self.client.delete_floating_ip(floating_ip_id_allocated)
- @attr(type=['negative', 'gate'])
- def test_allocate_floating_ip_from_nonexistent_pool(self):
- # Positive test:Allocation of a new floating IP from a nonexistent_pool
- # to a project should fail
- self.assertRaises(exceptions.NotFound,
- self.client.create_floating_ip,
- "non_exist_pool")
-
@attr(type='gate')
def test_delete_floating_ip(self):
# Positive test:Deletion of valid floating IP from project
@@ -115,38 +94,13 @@
self.server_id)
self.assertEqual(202, resp.status)
- @attr(type=['negative', 'gate'])
- def test_delete_nonexistant_floating_ip(self):
- # Negative test:Deletion of a nonexistent floating IP
- # from project should fail
-
- # Deleting the non existent floating IP
- self.assertRaises(exceptions.NotFound, self.client.delete_floating_ip,
- self.non_exist_id)
-
- @attr(type=['negative', 'gate'])
- def test_associate_nonexistant_floating_ip(self):
- # Negative test:Association of a non existent floating IP
- # to specific server should fail
- # Associating non existent floating IP
- self.assertRaises(exceptions.NotFound,
- self.client.associate_floating_ip_to_server,
- "0.0.0.0", self.server_id)
-
- @attr(type=['negative', 'gate'])
- def test_dissociate_nonexistant_floating_ip(self):
- # Negative test:Dissociation of a non existent floating IP should fail
- # Dissociating non existent floating IP
- self.assertRaises(exceptions.NotFound,
- self.client.disassociate_floating_ip_from_server,
- "0.0.0.0", self.server_id)
-
@attr(type='gate')
def test_associate_already_associated_floating_ip(self):
# positive test:Association of an already associated floating IP
# to specific server should change the association of the Floating IP
# Create server so as to use for Multiple association
- resp, body = self.servers_client.create_server('floating-server2',
+ new_name = rand_name('floating_server')
+ resp, body = self.servers_client.create_server(new_name,
self.image_ref,
self.flavor_ref)
self.servers_client.wait_for_server_status(body['id'], 'ACTIVE')
@@ -173,14 +127,6 @@
self.client.disassociate_floating_ip_from_server,
self.floating_ip, self.server_id)
- @attr(type=['negative', 'gate'])
- def test_associate_ip_to_server_without_passing_floating_ip(self):
- # Negative test:Association of empty floating IP to specific server
- # should raise NotFound exception
- self.assertRaises(exceptions.NotFound,
- self.client.associate_floating_ip_to_server,
- '', self.server_id)
-
class FloatingIPsTestXML(FloatingIPsTestJSON):
_interface = 'xml'
diff --git a/tempest/api/compute/floating_ips/test_floating_ips_actions_negative.py b/tempest/api/compute/floating_ips/test_floating_ips_actions_negative.py
new file mode 100644
index 0000000..89315bb
--- /dev/null
+++ b/tempest/api/compute/floating_ips/test_floating_ips_actions_negative.py
@@ -0,0 +1,94 @@
+# 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 uuid
+
+from tempest.api.compute import base
+from tempest.common.utils import data_utils
+from tempest import exceptions
+from tempest.test import attr
+
+
+class FloatingIPsNegativeTestJSON(base.BaseV2ComputeTest):
+ _interface = 'json'
+ server_id = None
+
+ @classmethod
+ def setUpClass(cls):
+ super(FloatingIPsNegativeTestJSON, cls).setUpClass()
+ cls.client = cls.floating_ips_client
+
+ # Server creation
+ resp, server = cls.create_test_server(wait_until='ACTIVE')
+ cls.server_id = server['id']
+ # Generating a nonexistent floatingIP id
+ cls.floating_ip_ids = []
+ resp, body = cls.client.list_floating_ips()
+ for i in range(len(body)):
+ cls.floating_ip_ids.append(body[i]['id'])
+ while True:
+ cls.non_exist_id = data_utils.rand_int_id(start=999)
+ if cls.config.service_available.neutron:
+ cls.non_exist_id = str(uuid.uuid4())
+ if cls.non_exist_id not in cls.floating_ip_ids:
+ break
+
+ @attr(type=['negative', 'gate'])
+ def test_allocate_floating_ip_from_nonexistent_pool(self):
+ # Negative test:Allocation of a new floating IP from a nonexistent_pool
+ # to a project should fail
+ self.assertRaises(exceptions.NotFound,
+ self.client.create_floating_ip,
+ "non_exist_pool")
+
+ @attr(type=['negative', 'gate'])
+ def test_delete_nonexistent_floating_ip(self):
+ # Negative test:Deletion of a nonexistent floating IP
+ # from project should fail
+
+ # Deleting the non existent floating IP
+ self.assertRaises(exceptions.NotFound, self.client.delete_floating_ip,
+ self.non_exist_id)
+
+ @attr(type=['negative', 'gate'])
+ def test_associate_nonexistent_floating_ip(self):
+ # Negative test:Association of a non existent floating IP
+ # to specific server should fail
+ # Associating non existent floating IP
+ self.assertRaises(exceptions.NotFound,
+ self.client.associate_floating_ip_to_server,
+ "0.0.0.0", self.server_id)
+
+ @attr(type=['negative', 'gate'])
+ def test_dissociate_nonexistent_floating_ip(self):
+ # Negative test:Dissociation of a non existent floating IP should fail
+ # Dissociating non existent floating IP
+ self.assertRaises(exceptions.NotFound,
+ self.client.disassociate_floating_ip_from_server,
+ "0.0.0.0", self.server_id)
+
+ @attr(type=['negative', 'gate'])
+ def test_associate_ip_to_server_without_passing_floating_ip(self):
+ # Negative test:Association of empty floating IP to specific server
+ # should raise NotFound exception
+ self.assertRaises(exceptions.NotFound,
+ self.client.associate_floating_ip_to_server,
+ '', self.server_id)
+
+
+class FloatingIPsNegativeTestXML(FloatingIPsNegativeTestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/compute/images/test_images.py b/tempest/api/compute/images/test_images.py
index 4539981..f7db89b 100644
--- a/tempest/api/compute/images/test_images.py
+++ b/tempest/api/compute/images/test_images.py
@@ -14,7 +14,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest.api import compute
from tempest.api.compute import base
from tempest import clients
from tempest.common.utils import data_utils
@@ -36,7 +35,7 @@
cls.image_ids = []
- if compute.MULTI_USER:
+ if cls.multi_user:
if cls.config.compute.allow_tenant_isolation:
creds = cls.isolated_creds.get_alt_creds()
username, tenant_name, password = creds
@@ -98,7 +97,8 @@
snapshot_name = data_utils.rand_name('test-snap-')
resp, image = self.create_image_from_server(server['id'],
name=snapshot_name,
- wait_until='ACTIVE')
+ wait_until='ACTIVE',
+ wait_for_server=False)
self.addCleanup(self.client.delete_image, image['id'])
self.assertEqual(snapshot_name, image['name'])
diff --git a/tempest/api/compute/images/test_images_oneserver.py b/tempest/api/compute/images/test_images_oneserver.py
index 612c110..6e4c8cb 100644
--- a/tempest/api/compute/images/test_images_oneserver.py
+++ b/tempest/api/compute/images/test_images_oneserver.py
@@ -49,7 +49,7 @@
LOG.exception(exc)
# Rebuild server if cannot reach the ACTIVE state
# Usually it means the server had a serius accident
- self.rebuild_server()
+ self.__class__.server_id = self.rebuild_server(self.server_id)
@classmethod
def setUpClass(cls):
@@ -68,7 +68,7 @@
cls.image_ids = []
- if compute.MULTI_USER:
+ if cls.multi_user:
if cls.config.compute.allow_tenant_isolation:
creds = cls.isolated_creds.get_alt_creds()
username, tenant_name, password = creds
@@ -117,6 +117,20 @@
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/images/test_images_oneserver_negative.py b/tempest/api/compute/images/test_images_oneserver_negative.py
index b4e778c..b8a4304 100644
--- a/tempest/api/compute/images/test_images_oneserver_negative.py
+++ b/tempest/api/compute/images/test_images_oneserver_negative.py
@@ -16,7 +16,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest.api import compute
from tempest.api.compute import base
from tempest import clients
from tempest.common.utils import data_utils
@@ -50,7 +49,10 @@
LOG.exception(exc)
# Rebuild server if cannot reach the ACTIVE state
# Usually it means the server had a serius accident
- self.rebuild_server()
+ self._reset_server()
+
+ def _reset_server(self):
+ self.__class__.server_id = self.rebuild_server(self.server_id)
@classmethod
def setUpClass(cls):
@@ -69,7 +71,7 @@
cls.image_ids = []
- if compute.MULTI_USER:
+ if cls.multi_user:
if cls.config.compute.allow_tenant_isolation:
creds = cls.isolated_creds.get_alt_creds()
username, tenant_name, password = creds
@@ -84,11 +86,15 @@
@skip_because(bug="1006725")
@attr(type=['negative', 'gate'])
def test_create_image_specify_multibyte_character_image_name(self):
- # Return an error if the image name has multi-byte characters
- snapshot_name = data_utils.rand_name('\xef\xbb\xbf')
+ 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,
- snapshot_name)
+ invalid_name)
@attr(type=['negative', 'gate'])
def test_create_image_specify_invalid_metadata(self):
@@ -117,12 +123,12 @@
self.assertEqual(202, resp.status)
image_id = data_utils.parse_image_id(resp['location'])
self.image_ids.append(image_id)
+ self.addCleanup(self._reset_server)
# Create second snapshot
alt_snapshot_name = data_utils.rand_name('test-snap-')
self.assertRaises(exceptions.Conflict, self.client.create_image,
self.server_id, alt_snapshot_name)
- self.client.wait_for_image_status(image_id, 'ACTIVE')
@attr(type=['negative', 'gate'])
def test_create_image_specify_name_over_256_chars(self):
@@ -141,6 +147,7 @@
self.assertEqual(202, resp.status)
image_id = data_utils.parse_image_id(resp['location'])
self.image_ids.append(image_id)
+ self.addCleanup(self._reset_server)
# Do not wait, attempt to delete the image, ensure it's successful
resp, body = self.client.delete_image(image_id)
diff --git a/tempest/api/compute/images/test_list_image_filters.py b/tempest/api/compute/images/test_list_image_filters.py
index 1401654..ac2ecba 100644
--- a/tempest/api/compute/images/test_list_image_filters.py
+++ b/tempest/api/compute/images/test_list_image_filters.py
@@ -16,7 +16,6 @@
# under the License.
from tempest.api.compute import base
-from tempest.common.utils import data_utils
from tempest import exceptions
from tempest.openstack.common import log as logging
from tempest.test import attr
@@ -45,24 +44,21 @@
'ACTIVE')
# Create images to be used in the filter tests
- resp, body = cls.create_image_from_server(cls.server1['id'])
- cls.image1_id = data_utils.parse_image_id(resp['location'])
- cls.client.wait_for_image_status(cls.image1_id, 'ACTIVE')
- resp, cls.image1 = cls.client.get_image(cls.image1_id)
+ resp, cls.image1 = cls.create_image_from_server(
+ cls.server1['id'], wait_until='ACTIVE')
+ cls.image1_id = cls.image1['id']
# Servers have a hidden property for when they are being imaged
# Performing back-to-back create image calls on a single
# server will sometimes cause failures
- resp, body = cls.create_image_from_server(cls.server2['id'])
- cls.image3_id = data_utils.parse_image_id(resp['location'])
- cls.client.wait_for_image_status(cls.image3_id, 'ACTIVE')
- resp, cls.image3 = cls.client.get_image(cls.image3_id)
+ resp, cls.image3 = cls.create_image_from_server(
+ cls.server2['id'], wait_until='ACTIVE')
+ cls.image3_id = cls.image3['id']
- resp, body = cls.create_image_from_server(cls.server1['id'])
- cls.image2_id = data_utils.parse_image_id(resp['location'])
-
- cls.client.wait_for_image_status(cls.image2_id, 'ACTIVE')
- resp, cls.image2 = cls.client.get_image(cls.image2_id)
+ # Wait for the server to be active after the image upload
+ resp, cls.image2 = cls.create_image_from_server(
+ cls.server1['id'], wait_until='ACTIVE')
+ cls.image2_id = cls.image2['id']
except Exception as exc:
LOG.exception(exc)
cls.tearDownClass()
diff --git a/tempest/api/compute/keypairs/test_keypairs.py b/tempest/api/compute/keypairs/test_keypairs.py
index 50b6c77..b36595c 100644
--- a/tempest/api/compute/keypairs/test_keypairs.py
+++ b/tempest/api/compute/keypairs/test_keypairs.py
@@ -17,7 +17,7 @@
from tempest.api.compute import base
from tempest.common.utils import data_utils
-from tempest.test import attr
+from tempest import test
class KeyPairsTestJSON(base.BaseV2ComputeTest):
@@ -28,14 +28,23 @@
super(KeyPairsTestJSON, cls).setUpClass()
cls.client = cls.keypairs_client
- @attr(type='gate')
+ def _delete_keypair(self, keypair_name):
+ resp, _ = self.client.delete_keypair(keypair_name)
+ self.assertEqual(202, resp.status)
+
+ def _create_keypair(self, keypair_name, pub_key=None):
+ resp, body = self.client.create_keypair(keypair_name, pub_key)
+ self.addCleanup(self._delete_keypair, keypair_name)
+ return resp, body
+
+ @test.attr(type='gate')
def test_keypairs_create_list_delete(self):
# Keypairs created should be available in the response list
# Create 3 keypairs
key_list = list()
for i in range(3):
k_name = data_utils.rand_name('keypair-')
- resp, keypair = self.client.create_keypair(k_name)
+ resp, keypair = self._create_keypair(k_name)
# Need to pop these keys so that our compare doesn't fail later,
# as the keypair dicts from list API doesn't have them.
keypair.pop('private_key')
@@ -57,16 +66,12 @@
self.assertFalse(missing_kps,
"Failed to find keypairs %s in fetched list"
% ', '.join(m_key['name'] for m_key in missing_kps))
- # Delete all the keypairs created
- for keypair in key_list:
- resp, _ = self.client.delete_keypair(keypair['name'])
- self.assertEqual(202, resp.status)
- @attr(type='gate')
+ @test.attr(type='gate')
def test_keypair_create_delete(self):
# Keypair should be created, verified and deleted
k_name = data_utils.rand_name('keypair-')
- resp, keypair = self.client.create_keypair(k_name)
+ resp, keypair = self._create_keypair(k_name)
self.assertEqual(200, resp.status)
private_key = keypair['private_key']
key_name = keypair['name']
@@ -75,15 +80,12 @@
"to the requested name")
self.assertTrue(private_key is not None,
"Field private_key is empty or not found.")
- resp, _ = self.client.delete_keypair(k_name)
- self.assertEqual(202, resp.status)
- @attr(type='gate')
+ @test.attr(type='gate')
def test_get_keypair_detail(self):
# Keypair should be created, Got details by name and deleted
k_name = data_utils.rand_name('keypair-')
- resp, keypair = self.client.create_keypair(k_name)
- self.addCleanup(self.client.delete_keypair, k_name)
+ resp, keypair = self._create_keypair(k_name)
resp, keypair_detail = self.client.get_keypair(k_name)
self.assertEqual(200, resp.status)
self.assertIn('name', keypair_detail)
@@ -95,7 +97,7 @@
self.assertTrue(public_key is not None,
"Field public_key is empty or not found.")
- @attr(type='gate')
+ @test.attr(type='gate')
def test_keypair_create_with_pub_key(self):
# Keypair should be created with a given public key
k_name = data_utils.rand_name('keypair-')
@@ -108,7 +110,7 @@
"LOeB1kYMOBaiUPLQTWXR3JpckqFIQwhIH0zoHlJvZE8hh90"
"XcPojYN56tI0OlrGqojbediJYD0rUsJu4weZpbn8vilb3JuDY+jws"
"snSA8wzBx3A/8y9Pp1B nova@ubuntu")
- resp, keypair = self.client.create_keypair(k_name, pub_key)
+ resp, keypair = self._create_keypair(k_name, pub_key)
self.assertEqual(200, resp.status)
self.assertFalse('private_key' in keypair,
"Field private_key is not empty!")
@@ -116,8 +118,6 @@
self.assertEqual(key_name, k_name,
"The created keypair name is not equal "
"to the requested name!")
- resp, _ = self.client.delete_keypair(k_name)
- self.assertEqual(202, resp.status)
class KeyPairsTestXML(KeyPairsTestJSON):
diff --git a/tempest/api/compute/keypairs/test_keypairs_negative.py b/tempest/api/compute/keypairs/test_keypairs_negative.py
index fad985e..621487c 100644
--- a/tempest/api/compute/keypairs/test_keypairs_negative.py
+++ b/tempest/api/compute/keypairs/test_keypairs_negative.py
@@ -19,7 +19,7 @@
from tempest.api.compute import base
from tempest.common.utils import data_utils
from tempest import exceptions
-from tempest.test import attr
+from tempest import test
class KeyPairsNegativeTestJSON(base.BaseV2ComputeTest):
@@ -30,67 +30,71 @@
super(KeyPairsNegativeTestJSON, cls).setUpClass()
cls.client = cls.keypairs_client
- @attr(type=['negative', 'gate'])
+ def _create_keypair(self, keypair_name, pub_key=None):
+ self.client.create_keypair(keypair_name, pub_key)
+ self.addCleanup(self.client.delete_keypair, keypair_name)
+
+ @test.attr(type=['negative', 'gate'])
def test_keypair_create_with_invalid_pub_key(self):
# Keypair should not be created with a non RSA public key
k_name = data_utils.rand_name('keypair-')
pub_key = "ssh-rsa JUNK nova@ubuntu"
self.assertRaises(exceptions.BadRequest,
- self.client.create_keypair, k_name, pub_key)
+ self._create_keypair, k_name, pub_key)
- @attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'gate'])
def test_keypair_delete_nonexistant_key(self):
# Non-existant key deletion should throw a proper error
k_name = data_utils.rand_name("keypair-non-existant-")
self.assertRaises(exceptions.NotFound, self.client.delete_keypair,
k_name)
- @attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'gate'])
def test_create_keypair_with_empty_public_key(self):
# Keypair should not be created with an empty public key
k_name = data_utils.rand_name("keypair-")
pub_key = ' '
- self.assertRaises(exceptions.BadRequest, self.client.create_keypair,
+ self.assertRaises(exceptions.BadRequest, self._create_keypair,
k_name, pub_key)
- @attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'gate'])
def test_create_keypair_when_public_key_bits_exceeds_maximum(self):
# Keypair should not be created when public key bits are too long
k_name = data_utils.rand_name("keypair-")
pub_key = 'ssh-rsa ' + 'A' * 2048 + ' openstack@ubuntu'
- self.assertRaises(exceptions.BadRequest, self.client.create_keypair,
+ self.assertRaises(exceptions.BadRequest, self._create_keypair,
k_name, pub_key)
- @attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'gate'])
def test_create_keypair_with_duplicate_name(self):
# Keypairs with duplicate names should not be created
k_name = data_utils.rand_name('keypair-')
resp, _ = self.client.create_keypair(k_name)
self.assertEqual(200, resp.status)
# Now try the same keyname to create another key
- self.assertRaises(exceptions.Conflict, self.client.create_keypair,
+ self.assertRaises(exceptions.Conflict, self._create_keypair,
k_name)
resp, _ = self.client.delete_keypair(k_name)
self.assertEqual(202, resp.status)
- @attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'gate'])
def test_create_keypair_with_empty_name_string(self):
# Keypairs with name being an empty string should not be created
- self.assertRaises(exceptions.BadRequest, self.client.create_keypair,
+ self.assertRaises(exceptions.BadRequest, self._create_keypair,
'')
- @attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'gate'])
def test_create_keypair_with_long_keynames(self):
# Keypairs with name longer than 255 chars should not be created
k_name = 'keypair-'.ljust(260, '0')
- self.assertRaises(exceptions.BadRequest, self.client.create_keypair,
+ self.assertRaises(exceptions.BadRequest, self._create_keypair,
k_name)
- @attr(type=['negative', 'gate'])
+ @test.attr(type=['negative', 'gate'])
def test_create_keypair_invalid_name(self):
# Keypairs with name being an invalid name should not be created
k_name = 'key_/.\@:'
- self.assertRaises(exceptions.BadRequest, self.client.create_keypair,
+ self.assertRaises(exceptions.BadRequest, self._create_keypair,
k_name)
diff --git a/tempest/api/compute/servers/test_create_server.py b/tempest/api/compute/servers/test_create_server.py
index 24ade96..cbd0eb1 100644
--- a/tempest/api/compute/servers/test_create_server.py
+++ b/tempest/api/compute/servers/test_create_server.py
@@ -93,13 +93,6 @@
@testtools.skipIf(not run_ssh, 'Instance validation tests are disabled.')
@attr(type='gate')
- def test_can_log_into_created_server(self):
- # Check that the user can authenticate with the generated password
- linux_client = RemoteClient(self.server, self.ssh_user, self.password)
- self.assertTrue(linux_client.can_authenticate())
-
- @testtools.skipIf(not run_ssh, 'Instance validation tests are disabled.')
- @attr(type='gate')
def test_verify_created_server_vcpus(self):
# Verify that the number of vcpus reported by the instance matches
# the amount stated by the flavor
diff --git a/tempest/api/compute/servers/test_disk_config.py b/tempest/api/compute/servers/test_disk_config.py
index 5e9ee5c..0121c42 100644
--- a/tempest/api/compute/servers/test_disk_config.py
+++ b/tempest/api/compute/servers/test_disk_config.py
@@ -80,7 +80,7 @@
def _get_alternative_flavor(self):
resp, server = self.client.get_server(self.server_id)
- if int(server['flavor']['id']) == self.flavor_ref:
+ if server['flavor']['id'] == self.flavor_ref:
return self.flavor_ref_alt
else:
return self.flavor_ref
diff --git a/tempest/api/compute/servers/test_server_actions.py b/tempest/api/compute/servers/test_server_actions.py
index e3441bd..e009888 100644
--- a/tempest/api/compute/servers/test_server_actions.py
+++ b/tempest/api/compute/servers/test_server_actions.py
@@ -45,13 +45,13 @@
self.client.wait_for_server_status(self.server_id, 'ACTIVE')
except Exception:
# Rebuild server if something happened to it during a test
- self.rebuild_server()
+ self.__class__.server_id = self.rebuild_server(self.server_id)
@classmethod
def setUpClass(cls):
super(ServerActionsTestJSON, cls).setUpClass()
cls.client = cls.servers_client
- cls.rebuild_server()
+ cls.server_id = cls.rebuild_server(None)
@testtools.skipUnless(compute.CHANGE_PASSWORD_AVAILABLE,
'Change password not available.')
@@ -67,7 +67,7 @@
# Verify that the user can authenticate with the new password
resp, server = self.client.get_server(self.server_id)
linux_client = RemoteClient(server, self.ssh_user, new_password)
- self.assertTrue(linux_client.can_authenticate())
+ linux_client.validate_authentication()
@attr(type='smoke')
def test_reboot_server_hard(self):
@@ -123,6 +123,7 @@
metadata=meta,
personality=personality,
adminPass=password)
+ self.addCleanup(self.client.rebuild, self.server_id, self.image_ref)
# Verify the properties in the initial response are correct
self.assertEqual(self.server_id, rebuilt_server['id'])
@@ -133,14 +134,43 @@
# Verify the server properties after the rebuild completes
self.client.wait_for_server_status(rebuilt_server['id'], 'ACTIVE')
resp, server = self.client.get_server(rebuilt_server['id'])
- rebuilt_image_id = rebuilt_server['image']['id']
+ rebuilt_image_id = server['image']['id']
self.assertTrue(self.image_ref_alt.endswith(rebuilt_image_id))
- self.assertEqual(new_name, rebuilt_server['name'])
+ self.assertEqual(new_name, server['name'])
if self.run_ssh:
# Verify that the user can authenticate with the provided password
linux_client = RemoteClient(server, self.ssh_user, password)
- self.assertTrue(linux_client.can_authenticate())
+ linux_client.validate_authentication()
+
+ @attr(type='gate')
+ def test_rebuild_server_in_stop_state(self):
+ # The server in stop state should be rebuilt using the provided
+ # image and remain in SHUTOFF state
+ resp, server = self.client.get_server(self.server_id)
+ old_image = server['image']['id']
+ new_image = self.image_ref_alt \
+ if old_image == self.image_ref else self.image_ref
+ resp, server = self.client.stop(self.server_id)
+ self.assertEqual(202, resp.status)
+ self.client.wait_for_server_status(self.server_id, 'SHUTOFF')
+ self.addCleanup(self.client.start, self.server_id)
+ resp, rebuilt_server = self.client.rebuild(self.server_id, new_image)
+ self.addCleanup(self.client.wait_for_server_status, self.server_id,
+ 'SHUTOFF')
+ self.addCleanup(self.client.rebuild, self.server_id, old_image)
+
+ # Verify the properties in the initial response are correct
+ self.assertEqual(self.server_id, rebuilt_server['id'])
+ rebuilt_image_id = rebuilt_server['image']['id']
+ self.assertEqual(new_image, rebuilt_image_id)
+ self.assertEqual(self.flavor_ref, rebuilt_server['flavor']['id'])
+
+ # Verify the server properties after the rebuild completes
+ self.client.wait_for_server_status(rebuilt_server['id'], 'SHUTOFF')
+ resp, server = self.client.get_server(rebuilt_server['id'])
+ rebuilt_image_id = server['image']['id']
+ self.assertEqual(new_image, rebuilt_image_id)
def _detect_server_image_flavor(self, server_id):
# Detects the current server image flavor ref.
@@ -217,6 +247,7 @@
image1_id = data_utils.parse_image_id(resp['location'])
self.addCleanup(_clean_oldest_backup, image1_id)
self.assertEqual(202, resp.status)
+ self.os.image_client.wait_for_image_status(image1_id, 'active')
backup2 = data_utils.rand_name('backup')
self.servers_client.wait_for_server_status(self.server_id, 'ACTIVE')
@@ -227,6 +258,7 @@
image2_id = data_utils.parse_image_id(resp['location'])
self.addCleanup(self.os.image_client.delete_image, image2_id)
self.assertEqual(202, resp.status)
+ self.os.image_client.wait_for_image_status(image2_id, 'active')
# verify they have been created
properties = {
@@ -266,6 +298,14 @@
self.assertEqual((backup2, backup3),
(image_list[0]['name'], image_list[1]['name']))
+ def _get_output(self):
+ resp, output = self.servers_client.get_console_output(
+ self.server_id, 10)
+ self.assertEqual(200, resp.status)
+ self.assertTrue(output, "Console output was empty.")
+ lines = len(output.split('\n'))
+ self.assertEqual(lines, 10)
+
@attr(type='gate')
def test_get_console_output(self):
# Positive test:Should be able to GET the console output
@@ -280,29 +320,24 @@
self.assertEqual(202, resp.status)
self.servers_client.wait_for_server_status(self.server_id, 'ACTIVE')
- def get_output():
- resp, output = self.servers_client.get_console_output(
- self.server_id, 10)
- self.assertEqual(200, resp.status)
- self.assertTrue(output, "Console output was empty.")
- lines = len(output.split('\n'))
- self.assertEqual(lines, 10)
- self.wait_for(get_output)
+ self.wait_for(self._get_output)
- @skip_because(bug="1014683")
@attr(type='gate')
- def test_get_console_output_server_id_in_reboot_status(self):
+ def test_get_console_output_server_id_in_shutoff_status(self):
# Positive test:Should be able to GET the console output
- # for a given server_id in reboot status
- resp, output = self.servers_client.reboot(self.server_id, 'SOFT')
- self.servers_client.wait_for_server_status(self.server_id,
- 'REBOOT')
- resp, output = self.servers_client.get_console_output(self.server_id,
- 10)
- self.assertEqual(200, resp.status)
- self.assertIsNotNone(output)
- lines = len(output.split('\n'))
- self.assertEqual(lines, 10)
+ # for a given server_id in SHUTOFF status
+
+ # NOTE: SHUTOFF is irregular status. To avoid test instability,
+ # one server is created only for this test without using
+ # the server that was created in setupClass.
+ resp, server = self.create_test_server(wait_until='ACTIVE')
+ temp_server_id = server['id']
+
+ resp, server = self.servers_client.stop(temp_server_id)
+ self.assertEqual(202, resp.status)
+ self.servers_client.wait_for_server_status(temp_server_id, 'SHUTOFF')
+
+ self.wait_for(self._get_output)
@attr(type='gate')
def test_pause_unpause_server(self):
diff --git a/tempest/api/compute/servers/test_servers_negative.py b/tempest/api/compute/servers/test_servers_negative.py
index 5ec0cbe..8142250 100644
--- a/tempest/api/compute/servers/test_servers_negative.py
+++ b/tempest/api/compute/servers/test_servers_negative.py
@@ -34,7 +34,7 @@
try:
self.client.wait_for_server_status(self.server_id, 'ACTIVE')
except Exception:
- self.rebuild_server()
+ self.__class__.server_id = self.rebuild_server(self.server_id)
@classmethod
def setUpClass(cls):
diff --git a/tempest/api/compute/test_authorization.py b/tempest/api/compute/test_authorization.py
index 49c4f32..327c7d1 100644
--- a/tempest/api/compute/test_authorization.py
+++ b/tempest/api/compute/test_authorization.py
@@ -15,7 +15,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest.api import compute
from tempest.api.compute import base
from tempest import clients
from tempest.common.utils import data_utils
@@ -31,12 +30,10 @@
@classmethod
def setUpClass(cls):
- if not compute.MULTI_USER:
+ super(AuthorizationTestJSON, cls).setUpClass()
+ if not cls.multi_user:
msg = "Need >1 user"
raise cls.skipException(msg)
-
- super(AuthorizationTestJSON, cls).setUpClass()
-
cls.client = cls.os.servers_client
cls.images_client = cls.os.images_client
cls.keypairs_client = cls.os.keypairs_client
@@ -85,7 +82,7 @@
@classmethod
def tearDownClass(cls):
- if compute.MULTI_USER:
+ if cls.multi_user:
cls.images_client.delete_image(cls.image['id'])
cls.keypairs_client.delete_keypair(cls.keypairname)
cls.security_client.delete_security_group(cls.security_group['id'])
diff --git a/tempest/api/compute/test_extensions.py b/tempest/api/compute/test_extensions.py
index 8f1e446..b0bffc4 100644
--- a/tempest/api/compute/test_extensions.py
+++ b/tempest/api/compute/test_extensions.py
@@ -17,18 +17,31 @@
from tempest.api.compute import base
-from tempest.test import attr
+from tempest import test
+import testtools
class ExtensionsTestJSON(base.BaseV2ComputeTest):
_interface = 'json'
- @attr(type='gate')
+ @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
resp, extensions = self.extensions_client.list_extensions()
self.assertIn("extensions", extensions)
self.assertEqual(200, resp.status)
+ self.assertTrue(self.extensions_client.is_enabled("Consoles"))
+
+ @testtools.skipIf(not test.is_extension_enabled('os-consoles', 'compute'),
+ 'os-consoles extension not enabled.')
+ @test.attr(type='gate')
+ def test_get_extension(self):
+ # get the specified extensions
+ resp, extension = self.extensions_client.get_extension('os-consoles')
+ self.assertEqual(200, resp.status)
+ self.assertEqual('os-consoles', extension['alias'])
class ExtensionsTestXML(ExtensionsTestJSON):
diff --git a/tempest/api/compute/v3/admin/test_availability_zone.py b/tempest/api/compute/v3/admin/test_availability_zone.py
index ff2765c..dca556f 100644
--- a/tempest/api/compute/v3/admin/test_availability_zone.py
+++ b/tempest/api/compute/v3/admin/test_availability_zone.py
@@ -16,21 +16,20 @@
# under the License.
from tempest.api.compute import base
-from tempest import exceptions
from tempest.test import attr
-class AvailabilityZoneAdminV3TestJSON(base.BaseV3ComputeAdminTest):
+class AZAdminV3TestJSON(base.BaseV3ComputeAdminTest):
"""
- Tests Availability Zone API List that require admin privileges
+ Tests Availability Zone API List
"""
_interface = 'json'
@classmethod
def setUpClass(cls):
- super(AvailabilityZoneAdminV3TestJSON, cls).setUpClass()
+ super(AZAdminV3TestJSON, cls).setUpClass()
cls.client = cls.availability_zone_admin_client
cls.non_adm_client = cls.availability_zone_client
@@ -57,14 +56,6 @@
self.assertEqual(200, resp.status)
self.assertTrue(len(availability_zone) > 0)
- @attr(type=['negative', 'gate'])
- def test_get_availability_zone_list_detail_with_non_admin_user(self):
- # List of availability zones and available services with
- # non-administrator user
- self.assertRaises(
- exceptions.Unauthorized,
- self.non_adm_client.get_availability_zone_list_detail)
-
-class AvailabilityZoneAdminV3TestXML(AvailabilityZoneAdminV3TestJSON):
+class AZAdminV3TestXML(AZAdminV3TestJSON):
_interface = 'xml'
diff --git a/tempest/api/compute/v3/admin/test_availability_zone_negative.py b/tempest/api/compute/v3/admin/test_availability_zone_negative.py
new file mode 100644
index 0000000..93a57e3
--- /dev/null
+++ b/tempest/api/compute/v3/admin/test_availability_zone_negative.py
@@ -0,0 +1,47 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 NEC Corporation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.api.compute import base
+from tempest import exceptions
+from tempest.test import attr
+
+
+class AZAdminNegativeV3TestJSON(base.BaseV3ComputeAdminTest):
+
+ """
+ Tests Availability Zone API List
+ """
+
+ _interface = 'json'
+
+ @classmethod
+ def setUpClass(cls):
+ super(AZAdminNegativeV3TestJSON, cls).setUpClass()
+ cls.client = cls.availability_zone_admin_client
+ cls.non_adm_client = cls.availability_zone_client
+
+ @attr(type=['negative', 'gate'])
+ def test_get_availability_zone_list_detail_with_non_admin_user(self):
+ # List of availability zones and available services with
+ # non-administrator user
+ self.assertRaises(
+ exceptions.Unauthorized,
+ self.non_adm_client.get_availability_zone_list_detail)
+
+
+class AZAdminNegativeV3TestXML(AZAdminNegativeV3TestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/compute/v3/admin/test_flavors_access.py b/tempest/api/compute/v3/admin/test_flavors_access.py
new file mode 100644
index 0000000..048312b
--- /dev/null
+++ b/tempest/api/compute/v3/admin/test_flavors_access.py
@@ -0,0 +1,111 @@
+# 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 test
+
+
+class FlavorsAccessTestJSON(base.BaseV2ComputeAdminTest):
+
+ """
+ Tests Flavor Access API extension.
+ Add and remove Flavor Access require admin privileges.
+ """
+
+ _interface = 'json'
+
+ @classmethod
+ def setUpClass(cls):
+ super(FlavorsAccessTestJSON, cls).setUpClass()
+ if not test.is_extension_enabled('FlavorExtraData', 'compute'):
+ msg = "FlavorExtraData extension not enabled."
+ raise cls.skipException(msg)
+
+ cls.client = cls.os_adm.flavors_client
+ admin_client = cls._get_identity_admin_client()
+ cls.tenant = admin_client.get_tenant_by_name(cls.flavors_client.
+ tenant_name)
+ cls.tenant_id = cls.tenant['id']
+ cls.adm_tenant = admin_client.get_tenant_by_name(cls.os_adm.
+ flavors_client.
+ tenant_name)
+ cls.adm_tenant_id = cls.adm_tenant['id']
+ cls.flavor_name_prefix = 'test_flavor_access_'
+ cls.ram = 512
+ cls.vcpus = 1
+ cls.disk = 10
+
+ @test.attr(type='gate')
+ def test_flavor_access_list_with_private_flavor(self):
+ # Test to list flavor access successfully by querying private flavor
+ flavor_name = data_utils.rand_name(self.flavor_name_prefix)
+ new_flavor_id = data_utils.rand_int_id(start=1000)
+ resp, new_flavor = self.client.create_flavor(flavor_name,
+ self.ram, self.vcpus,
+ self.disk,
+ new_flavor_id,
+ is_public='False')
+ self.addCleanup(self.client.delete_flavor, new_flavor['id'])
+ self.assertEqual(resp.status, 200)
+ resp, flavor_access = self.client.list_flavor_access(new_flavor_id)
+ self.assertEqual(resp.status, 200)
+ self.assertEqual(len(flavor_access), 1, str(flavor_access))
+ first_flavor = flavor_access[0]
+ self.assertEqual(str(new_flavor_id), str(first_flavor['flavor_id']))
+ self.assertEqual(self.adm_tenant_id, first_flavor['tenant_id'])
+
+ @test.attr(type='gate')
+ def test_flavor_access_add_remove(self):
+ # Test to add and remove flavor access to a given tenant.
+ flavor_name = data_utils.rand_name(self.flavor_name_prefix)
+ new_flavor_id = data_utils.rand_int_id(start=1000)
+ resp, new_flavor = self.client.create_flavor(flavor_name,
+ self.ram, self.vcpus,
+ self.disk,
+ new_flavor_id,
+ is_public='False')
+ self.addCleanup(self.client.delete_flavor, new_flavor['id'])
+ # Add flavor access to a tenant.
+ resp_body = {
+ "tenant_id": str(self.tenant_id),
+ "flavor_id": str(new_flavor['id']),
+ }
+ add_resp, add_body = \
+ self.client.add_flavor_access(new_flavor['id'], self.tenant_id)
+ self.assertEqual(add_resp.status, 200)
+ self.assertIn(resp_body, add_body)
+
+ # The flavor is present in list.
+ resp, flavors = self.flavors_client.list_flavors_with_detail()
+ self.assertEqual(resp.status, 200)
+ self.assertIn(new_flavor['id'], map(lambda x: x['id'], flavors))
+
+ # Remove flavor access from a tenant.
+ remove_resp, remove_body = \
+ self.client.remove_flavor_access(new_flavor['id'], self.tenant_id)
+ self.assertEqual(remove_resp.status, 200)
+ self.assertNotIn(resp_body, remove_body)
+
+ # The flavor is not present in list.
+ resp, flavors = self.flavors_client.list_flavors_with_detail()
+ self.assertEqual(resp.status, 200)
+ self.assertNotIn(new_flavor['id'], map(lambda x: x['id'], flavors))
+
+
+class FlavorsAdminTestXML(FlavorsAccessTestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/compute/v3/admin/test_flavors_access_negative.py b/tempest/api/compute/v3/admin/test_flavors_access_negative.py
new file mode 100644
index 0000000..976124e
--- /dev/null
+++ b/tempest/api/compute/v3/admin/test_flavors_access_negative.py
@@ -0,0 +1,152 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 IBM Corporation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import uuid
+
+from tempest.api.compute import base
+from tempest.common.utils import data_utils
+from tempest import exceptions
+from tempest import test
+
+
+class FlavorsAccessNegativeTestJSON(base.BaseV2ComputeAdminTest):
+
+ """
+ Tests Flavor Access API extension.
+ Add and remove Flavor Access require admin privileges.
+ """
+
+ _interface = 'json'
+
+ @classmethod
+ def setUpClass(cls):
+ super(FlavorsAccessNegativeTestJSON, cls).setUpClass()
+ if not test.is_extension_enabled('FlavorExtraData', 'compute'):
+ msg = "FlavorExtraData extension not enabled."
+ raise cls.skipException(msg)
+
+ cls.client = cls.os_adm.flavors_client
+ admin_client = cls._get_identity_admin_client()
+ cls.tenant = admin_client.get_tenant_by_name(cls.flavors_client.
+ tenant_name)
+ cls.tenant_id = cls.tenant['id']
+ cls.adm_tenant = admin_client.get_tenant_by_name(cls.os_adm.
+ flavors_client.
+ tenant_name)
+ cls.adm_tenant_id = cls.adm_tenant['id']
+ cls.flavor_name_prefix = 'test_flavor_access_'
+ cls.ram = 512
+ cls.vcpus = 1
+ cls.disk = 10
+
+ @test.attr(type=['negative', 'gate'])
+ def test_flavor_access_list_with_public_flavor(self):
+ # Test to list flavor access with exceptions by querying public flavor
+ flavor_name = data_utils.rand_name(self.flavor_name_prefix)
+ new_flavor_id = data_utils.rand_int_id(start=1000)
+ resp, new_flavor = self.client.create_flavor(flavor_name,
+ self.ram, self.vcpus,
+ self.disk,
+ new_flavor_id,
+ is_public='True')
+ self.addCleanup(self.client.delete_flavor, new_flavor['id'])
+ self.assertEqual(resp.status, 200)
+ self.assertRaises(exceptions.NotFound,
+ self.client.list_flavor_access,
+ new_flavor_id)
+
+ @test.attr(type=['negative', 'gate'])
+ def test_flavor_non_admin_add(self):
+ # Test to add flavor access as a user without admin privileges.
+ flavor_name = data_utils.rand_name(self.flavor_name_prefix)
+ new_flavor_id = data_utils.rand_int_id(start=1000)
+ resp, new_flavor = self.client.create_flavor(flavor_name,
+ self.ram, self.vcpus,
+ self.disk,
+ new_flavor_id,
+ is_public='False')
+ self.addCleanup(self.client.delete_flavor, new_flavor['id'])
+ self.assertRaises(exceptions.Unauthorized,
+ self.flavors_client.add_flavor_access,
+ new_flavor['id'],
+ self.tenant_id)
+
+ @test.attr(type=['negative', 'gate'])
+ def test_flavor_non_admin_remove(self):
+ # Test to remove flavor access as a user without admin privileges.
+ flavor_name = data_utils.rand_name(self.flavor_name_prefix)
+ new_flavor_id = data_utils.rand_int_id(start=1000)
+ resp, new_flavor = self.client.create_flavor(flavor_name,
+ self.ram, self.vcpus,
+ self.disk,
+ new_flavor_id,
+ is_public='False')
+ self.addCleanup(self.client.delete_flavor, new_flavor['id'])
+ # Add flavor access to a tenant.
+ self.client.add_flavor_access(new_flavor['id'], self.tenant_id)
+ self.addCleanup(self.client.remove_flavor_access,
+ new_flavor['id'], self.tenant_id)
+ self.assertRaises(exceptions.Unauthorized,
+ self.flavors_client.remove_flavor_access,
+ new_flavor['id'],
+ self.tenant_id)
+
+ @test.attr(type=['negative', 'gate'])
+ def test_add_flavor_access_duplicate(self):
+ # Create a new flavor.
+ flavor_name = data_utils.rand_name(self.flavor_name_prefix)
+ new_flavor_id = data_utils.rand_int_id(start=1000)
+ resp, new_flavor = self.client.create_flavor(flavor_name,
+ self.ram, self.vcpus,
+ self.disk,
+ new_flavor_id,
+ is_public='False')
+ self.addCleanup(self.client.delete_flavor, new_flavor['id'])
+
+ # Add flavor access to a tenant.
+ self.client.add_flavor_access(new_flavor['id'], self.tenant_id)
+ self.addCleanup(self.client.remove_flavor_access,
+ new_flavor['id'], self.tenant_id)
+
+ # An exception should be raised when adding flavor access to the same
+ # tenant
+ self.assertRaises(exceptions.Conflict,
+ self.client.add_flavor_access,
+ new_flavor['id'],
+ self.tenant_id)
+
+ @test.attr(type=['negative', 'gate'])
+ def test_remove_flavor_access_not_found(self):
+ # Create a new flavor.
+ flavor_name = data_utils.rand_name(self.flavor_name_prefix)
+ new_flavor_id = data_utils.rand_int_id(start=1000)
+ resp, new_flavor = self.client.create_flavor(flavor_name,
+ self.ram, self.vcpus,
+ self.disk,
+ new_flavor_id,
+ is_public='False')
+ self.addCleanup(self.client.delete_flavor, new_flavor['id'])
+
+ # An exception should be raised when flavor access is not found
+ self.assertRaises(exceptions.NotFound,
+ self.client.remove_flavor_access,
+ new_flavor['id'],
+ str(uuid.uuid4()))
+
+
+class FlavorsAdminNegativeTestXML(FlavorsAccessNegativeTestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/compute/v3/admin/test_flavors_extra_specs.py b/tempest/api/compute/v3/admin/test_flavors_extra_specs.py
new file mode 100644
index 0000000..875f742
--- /dev/null
+++ b/tempest/api/compute/v3/admin/test_flavors_extra_specs.py
@@ -0,0 +1,131 @@
+# 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 import test
+
+
+class FlavorsExtraSpecsTestJSON(base.BaseV2ComputeAdminTest):
+
+ """
+ Tests Flavor Extra Spec API extension.
+ SET, UNSET, UPDATE Flavor Extra specs require admin privileges.
+ GET Flavor Extra specs can be performed even by without admin privileges.
+ """
+
+ _interface = 'json'
+
+ @classmethod
+ def setUpClass(cls):
+ super(FlavorsExtraSpecsTestJSON, cls).setUpClass()
+ if not test.is_extension_enabled('FlavorExtraData', 'compute'):
+ msg = "FlavorExtraData extension not enabled."
+ raise cls.skipException(msg)
+
+ cls.client = cls.os_adm.flavors_client
+ flavor_name = data_utils.rand_name('test_flavor')
+ ram = 512
+ vcpus = 1
+ disk = 10
+ ephemeral = 10
+ cls.new_flavor_id = data_utils.rand_int_id(start=1000)
+ swap = 1024
+ rxtx = 1
+ # Create a flavor so as to set/get/unset extra specs
+ resp, cls.flavor = cls.client.create_flavor(flavor_name,
+ ram, vcpus,
+ disk,
+ cls.new_flavor_id,
+ ephemeral=ephemeral,
+ swap=swap, rxtx=rxtx)
+
+ @classmethod
+ def tearDownClass(cls):
+ resp, body = cls.client.delete_flavor(cls.flavor['id'])
+ cls.client.wait_for_resource_deletion(cls.flavor['id'])
+ super(FlavorsExtraSpecsTestJSON, cls).tearDownClass()
+
+ @test.attr(type='gate')
+ def test_flavor_set_get_update_show_unset_keys(self):
+ # Test to SET, GET, UPDATE, SHOW, UNSET flavor extra
+ # spec as a user with admin privileges.
+ # Assigning extra specs values that are to be set
+ specs = {"key1": "value1", "key2": "value2"}
+ # SET extra specs to the flavor created in setUp
+ set_resp, set_body = \
+ self.client.set_flavor_extra_spec(self.flavor['id'], specs)
+ self.assertEqual(set_resp.status, 200)
+ self.assertEqual(set_body, specs)
+ # GET extra specs and verify
+ get_resp, get_body = \
+ self.client.get_flavor_extra_spec(self.flavor['id'])
+ self.assertEqual(get_resp.status, 200)
+ self.assertEqual(get_body, specs)
+
+ # UPDATE the value of the extra specs key1
+ update_resp, update_body = \
+ self.client.update_flavor_extra_spec(self.flavor['id'],
+ "key1",
+ key1="value")
+ self.assertEqual(update_resp.status, 200)
+ self.assertEqual({"key1": "value"}, update_body)
+
+ # GET extra specs and verify the value of the key2
+ # is the same as before
+ get_resp, get_body = \
+ self.client.get_flavor_extra_spec(self.flavor['id'])
+ self.assertEqual(get_resp.status, 200)
+ self.assertEqual(get_body, {"key1": "value", "key2": "value2"})
+
+ # UNSET extra specs that were set in this test
+ unset_resp, _ = \
+ self.client.unset_flavor_extra_spec(self.flavor['id'], "key1")
+ self.assertEqual(unset_resp.status, 200)
+ unset_resp, _ = \
+ self.client.unset_flavor_extra_spec(self.flavor['id'], "key2")
+ self.assertEqual(unset_resp.status, 200)
+
+ @test.attr(type='gate')
+ def test_flavor_non_admin_get_all_keys(self):
+ specs = {"key1": "value1", "key2": "value2"}
+ set_resp, set_body = self.client.set_flavor_extra_spec(
+ self.flavor['id'], specs)
+ resp, body = self.flavors_client.get_flavor_extra_spec(
+ self.flavor['id'])
+ self.assertEqual(resp.status, 200)
+
+ for key in specs:
+ self.assertEqual(body[key], specs[key])
+
+ @test.attr(type='gate')
+ def test_flavor_non_admin_get_specific_key(self):
+ specs = {"key1": "value1", "key2": "value2"}
+ resp, body = self.client.set_flavor_extra_spec(
+ self.flavor['id'], specs)
+ self.assertEqual(resp.status, 200)
+ self.assertEqual(body['key1'], 'value1')
+ self.assertIn('key2', body)
+ resp, body = self.flavors_client.get_flavor_extra_spec_with_key(
+ self.flavor['id'], 'key1')
+ self.assertEqual(resp.status, 200)
+ self.assertEqual(body['key1'], 'value1')
+ self.assertNotIn('key2', body)
+
+
+class FlavorsExtraSpecsTestXML(FlavorsExtraSpecsTestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/compute/v3/admin/test_flavors_extra_specs_negative.py b/tempest/api/compute/v3/admin/test_flavors_extra_specs_negative.py
new file mode 100644
index 0000000..fb09a63
--- /dev/null
+++ b/tempest/api/compute/v3/admin/test_flavors_extra_specs_negative.py
@@ -0,0 +1,135 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 OpenStack Foundation
+# All Rights Reserved.
+# Copyright 2013 IBM Corp.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.api.compute import base
+from tempest.common.utils import data_utils
+from tempest import exceptions
+from tempest import test
+
+
+class FlavorsExtraSpecsNegativeTestJSON(base.BaseV2ComputeAdminTest):
+
+ """
+ Negative Tests Flavor Extra Spec API extension.
+ SET, UNSET, UPDATE Flavor Extra specs require admin privileges.
+ """
+
+ _interface = 'json'
+
+ @classmethod
+ def setUpClass(cls):
+ super(FlavorsExtraSpecsNegativeTestJSON, cls).setUpClass()
+ if not test.is_extension_enabled('FlavorExtraData', 'compute'):
+ msg = "FlavorExtraData extension not enabled."
+ raise cls.skipException(msg)
+
+ cls.client = cls.os_adm.flavors_client
+ flavor_name = data_utils.rand_name('test_flavor')
+ ram = 512
+ vcpus = 1
+ disk = 10
+ ephemeral = 10
+ cls.new_flavor_id = data_utils.rand_int_id(start=1000)
+ swap = 1024
+ rxtx = 1
+ # Create a flavor
+ resp, cls.flavor = cls.client.create_flavor(flavor_name,
+ ram, vcpus,
+ disk,
+ cls.new_flavor_id,
+ ephemeral=ephemeral,
+ swap=swap, rxtx=rxtx)
+
+ @classmethod
+ def tearDownClass(cls):
+ resp, body = cls.client.delete_flavor(cls.flavor['id'])
+ cls.client.wait_for_resource_deletion(cls.flavor['id'])
+ super(FlavorsExtraSpecsNegativeTestJSON, cls).tearDownClass()
+
+ @test.attr(type=['negative', 'gate'])
+ def test_flavor_non_admin_set_keys(self):
+ # Test to SET flavor extra spec as a user without admin privileges.
+ specs = {"key1": "value1", "key2": "value2"}
+ self.assertRaises(exceptions.Unauthorized,
+ self.flavors_client.set_flavor_extra_spec,
+ self.flavor['id'],
+ specs)
+
+ @test.attr(type=['negative', 'gate'])
+ def test_flavor_non_admin_update_specific_key(self):
+ # non admin user is not allowed to update flavor extra spec
+ specs = {"key1": "value1", "key2": "value2"}
+ resp, body = self.client.set_flavor_extra_spec(
+ self.flavor['id'], specs)
+ self.assertEqual(resp.status, 200)
+ self.assertEqual(body['key1'], 'value1')
+ self.assertRaises(exceptions.Unauthorized,
+ self.flavors_client.
+ update_flavor_extra_spec,
+ self.flavor['id'],
+ 'key1',
+ key1='value1_new')
+
+ @test.attr(type=['negative', 'gate'])
+ def test_flavor_non_admin_unset_keys(self):
+ specs = {"key1": "value1", "key2": "value2"}
+ set_resp, set_body = self.client.set_flavor_extra_spec(
+ self.flavor['id'], specs)
+
+ self.assertRaises(exceptions.Unauthorized,
+ self.flavors_client.unset_flavor_extra_spec,
+ self.flavor['id'],
+ 'key1')
+
+ @test.attr(type=['negative', 'gate'])
+ def test_flavor_unset_nonexistent_key(self):
+ nonexistent_key = data_utils.rand_name('flavor_key')
+ self.assertRaises(exceptions.NotFound,
+ self.client.unset_flavor_extra_spec,
+ self.flavor['id'],
+ nonexistent_key)
+
+ @test.attr(type=['negative', 'gate'])
+ def test_flavor_get_nonexistent_key(self):
+ self.assertRaises(exceptions.NotFound,
+ self.flavors_client.get_flavor_extra_spec_with_key,
+ self.flavor['id'],
+ "nonexistent_key")
+
+ @test.attr(type=['negative', 'gate'])
+ def test_flavor_update_mismatch_key(self):
+ # the key will be updated should be match the key in the body
+ self.assertRaises(exceptions.BadRequest,
+ self.client.update_flavor_extra_spec,
+ self.flavor['id'],
+ "key2",
+ key1="value")
+
+ @test.attr(type=['negative', 'gate'])
+ def test_flavor_update_more_key(self):
+ # there should be just one item in the request body
+ self.assertRaises(exceptions.BadRequest,
+ self.client.update_flavor_extra_spec,
+ self.flavor['id'],
+ "key1",
+ key1="value",
+ key2="value")
+
+
+class FlavorsExtraSpecsNegativeTestXML(FlavorsExtraSpecsNegativeTestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/compute/v3/admin/test_quotas.py b/tempest/api/compute/v3/admin/test_quotas.py
new file mode 100644
index 0000000..f49aae4
--- /dev/null
+++ b/tempest/api/compute/v3/admin/test_quotas.py
@@ -0,0 +1,220 @@
+# 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 import config
+from tempest import exceptions
+from tempest.test import attr
+from tempest.test import skip_because
+
+
+class QuotasAdminTestJSON(base.BaseV2ComputeAdminTest):
+ _interface = 'json'
+ force_tenant_isolation = True
+
+ @classmethod
+ def setUpClass(cls):
+ super(QuotasAdminTestJSON, cls).setUpClass()
+ cls.auth_url = cls.config.identity.uri
+ cls.client = cls.os.quotas_client
+ cls.adm_client = cls.os_adm.quotas_client
+ cls.identity_admin_client = cls._get_identity_admin_client()
+ cls.sg_client = cls.security_groups_client
+
+ # NOTE(afazekas): these test cases should always create and use a new
+ # tenant most of them should be skipped if we can't do that
+ cls.demo_tenant_id = cls.isolated_creds.get_primary_user().get(
+ 'tenantId')
+
+ cls.default_quota_set = set(('injected_file_content_bytes',
+ 'metadata_items', 'injected_files',
+ 'ram', 'floating_ips',
+ 'fixed_ips', 'key_pairs',
+ 'injected_file_path_bytes',
+ 'instances', 'security_group_rules',
+ 'cores', 'security_groups'))
+
+ @attr(type='smoke')
+ def test_get_default_quotas(self):
+ # Admin can get the default resource quota set for a tenant
+ expected_quota_set = self.default_quota_set | set(['id'])
+ resp, quota_set = self.client.get_default_quota_set(
+ self.demo_tenant_id)
+ self.assertEqual(200, resp.status)
+ self.assertEqual(sorted(expected_quota_set),
+ sorted(quota_set.keys()))
+ self.assertEqual(quota_set['id'], self.demo_tenant_id)
+
+ @attr(type='gate')
+ def test_update_all_quota_resources_for_tenant(self):
+ # Admin can update all the resource quota limits for a tenant
+ resp, default_quota_set = self.client.get_default_quota_set(
+ self.demo_tenant_id)
+ new_quota_set = {'injected_file_content_bytes': 20480,
+ 'metadata_items': 256, 'injected_files': 10,
+ 'ram': 10240, 'floating_ips': 20, 'fixed_ips': 10,
+ 'key_pairs': 200, 'injected_file_path_bytes': 512,
+ 'instances': 20, 'security_group_rules': 20,
+ 'cores': 2, 'security_groups': 20}
+ # Update limits for all quota resources
+ resp, quota_set = self.adm_client.update_quota_set(
+ self.demo_tenant_id,
+ force=True,
+ **new_quota_set)
+
+ default_quota_set.pop('id')
+ self.addCleanup(self.adm_client.update_quota_set,
+ self.demo_tenant_id, **default_quota_set)
+ self.assertEqual(200, resp.status)
+ self.assertEqual(new_quota_set, quota_set)
+
+ # TODO(afazekas): merge these test cases
+ @attr(type='gate')
+ def test_get_updated_quotas(self):
+ # Verify that GET shows the updated quota set
+ tenant_name = data_utils.rand_name('cpu_quota_tenant_')
+ tenant_desc = tenant_name + '-desc'
+ identity_client = self.os_adm.identity_client
+ _, tenant = identity_client.create_tenant(name=tenant_name,
+ description=tenant_desc)
+ tenant_id = tenant['id']
+ self.addCleanup(identity_client.delete_tenant,
+ tenant_id)
+
+ self.adm_client.update_quota_set(tenant_id,
+ ram='5120')
+ resp, quota_set = self.adm_client.get_quota_set(tenant_id)
+ self.assertEqual(200, resp.status)
+ self.assertEqual(quota_set['ram'], 5120)
+
+ # TODO(afazekas): Add dedicated tenant to the skiped quota tests
+ # it can be moved into the setUpClass as well
+ @attr(type='gate')
+ def test_create_server_when_cpu_quota_is_full(self):
+ # Disallow server creation when tenant's vcpu quota is full
+ resp, quota_set = self.client.get_quota_set(self.demo_tenant_id)
+ default_vcpu_quota = quota_set['cores']
+ vcpu_quota = 0 # Set the quota to zero to conserve resources
+
+ resp, quota_set = self.adm_client.update_quota_set(self.demo_tenant_id,
+ force=True,
+ cores=vcpu_quota)
+
+ self.addCleanup(self.adm_client.update_quota_set, self.demo_tenant_id,
+ cores=default_vcpu_quota)
+ self.assertRaises(exceptions.OverLimit, self.create_test_server)
+
+ @attr(type='gate')
+ def test_create_server_when_memory_quota_is_full(self):
+ # Disallow server creation when tenant's memory quota is full
+ resp, quota_set = self.client.get_quota_set(self.demo_tenant_id)
+ default_mem_quota = quota_set['ram']
+ mem_quota = 0 # Set the quota to zero to conserve resources
+
+ self.adm_client.update_quota_set(self.demo_tenant_id,
+ force=True,
+ ram=mem_quota)
+
+ self.addCleanup(self.adm_client.update_quota_set, self.demo_tenant_id,
+ ram=default_mem_quota)
+ self.assertRaises(exceptions.OverLimit, self.create_test_server)
+
+ @attr(type='gate')
+ def test_update_quota_normal_user(self):
+ self.assertRaises(exceptions.Unauthorized,
+ self.client.update_quota_set,
+ self.demo_tenant_id,
+ ram=0)
+
+ @attr(type=['negative', 'gate'])
+ def test_create_server_when_instances_quota_is_full(self):
+ # Once instances quota limit is reached, disallow server creation
+ resp, quota_set = self.client.get_quota_set(self.demo_tenant_id)
+ default_instances_quota = quota_set['instances']
+ instances_quota = 0 # Set quota to zero to disallow server creation
+
+ self.adm_client.update_quota_set(self.demo_tenant_id,
+ force=True,
+ instances=instances_quota)
+ self.addCleanup(self.adm_client.update_quota_set, self.demo_tenant_id,
+ instances=default_instances_quota)
+ self.assertRaises(exceptions.OverLimit, self.create_test_server)
+
+ @skip_because(bug="1186354",
+ condition=config.TempestConfig().service_available.neutron)
+ @attr(type=['negative', 'gate'])
+ def test_security_groups_exceed_limit(self):
+ # Negative test: Creation Security Groups over limit should FAIL
+
+ resp, quota_set = self.client.get_quota_set(self.demo_tenant_id)
+ default_sg_quota = quota_set['security_groups']
+ sg_quota = 0 # Set the quota to zero to conserve resources
+
+ resp, quota_set =\
+ self.adm_client.update_quota_set(self.demo_tenant_id,
+ force=True,
+ security_groups=sg_quota)
+
+ self.addCleanup(self.adm_client.update_quota_set,
+ self.demo_tenant_id,
+ security_groups=default_sg_quota)
+
+ # Check we cannot create anymore
+ self.assertRaises(exceptions.OverLimit,
+ self.sg_client.create_security_group,
+ "sg-overlimit", "sg-desc")
+
+ @skip_because(bug="1186354",
+ condition=config.TempestConfig().service_available.neutron)
+ @attr(type=['negative', 'gate'])
+ def test_security_groups_rules_exceed_limit(self):
+ # Negative test: Creation of Security Group Rules should FAIL
+ # when we reach limit maxSecurityGroupRules
+
+ resp, quota_set = self.client.get_quota_set(self.demo_tenant_id)
+ default_sg_rules_quota = quota_set['security_group_rules']
+ sg_rules_quota = 0 # Set the quota to zero to conserve resources
+
+ resp, quota_set =\
+ self.adm_client.update_quota_set(
+ self.demo_tenant_id,
+ force=True,
+ security_group_rules=sg_rules_quota)
+
+ self.addCleanup(self.adm_client.update_quota_set,
+ self.demo_tenant_id,
+ security_group_rules=default_sg_rules_quota)
+
+ s_name = data_utils.rand_name('securitygroup-')
+ s_description = data_utils.rand_name('description-')
+ resp, securitygroup =\
+ self.sg_client.create_security_group(s_name, s_description)
+ self.addCleanup(self.sg_client.delete_security_group,
+ securitygroup['id'])
+
+ secgroup_id = securitygroup['id']
+ ip_protocol = 'tcp'
+
+ # Check we cannot create SG rule anymore
+ self.assertRaises(exceptions.OverLimit,
+ self.sg_client.create_security_group_rule,
+ secgroup_id, ip_protocol, 1025, 1025)
+
+
+class QuotasAdminTestXML(QuotasAdminTestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/compute/v3/admin/test_services.py b/tempest/api/compute/v3/admin/test_services.py
index 67f9947..64135ed 100644
--- a/tempest/api/compute/v3/admin/test_services.py
+++ b/tempest/api/compute/v3/admin/test_services.py
@@ -113,23 +113,6 @@
self.assertEqual(200, resp.status)
self.assertEqual(0, len(services))
- @attr(type='gate')
- def test_service_enable_disable(self):
- resp, services = self.client.list_services()
- host_name = services[0]['host']
- binary_name = services[0]['binary']
-
- resp, service = self.client.disable_service(host_name, binary_name)
- self.assertEqual(200, resp.status)
- params = {'host': host_name, 'binary': binary_name}
- resp, services = self.client.list_services(params)
- self.assertEqual('disabled', services[0]['status'])
-
- resp, service = self.client.enable_service(host_name, binary_name)
- self.assertEqual(200, resp.status)
- resp, services = self.client.list_services(params)
- self.assertEqual('enabled', services[0]['status'])
-
class ServicesAdminV3TestXML(ServicesAdminV3TestJSON):
_interface = 'xml'
diff --git a/tempest/api/compute/v3/admin/test_simple_tenant_usage.py b/tempest/api/compute/v3/admin/test_simple_tenant_usage.py
index a599f06..3fc58eb 100644
--- a/tempest/api/compute/v3/admin/test_simple_tenant_usage.py
+++ b/tempest/api/compute/v3/admin/test_simple_tenant_usage.py
@@ -23,15 +23,15 @@
import time
-class TenantUsagesTestJSON(base.BaseV2ComputeAdminTest):
+class TenantUsagesV3TestJSON(base.BaseV3ComputeAdminTest):
_interface = 'json'
@classmethod
def setUpClass(cls):
- super(TenantUsagesTestJSON, cls).setUpClass()
- cls.adm_client = cls.os_adm.tenant_usages_client
- cls.client = cls.os.tenant_usages_client
+ super(TenantUsagesV3TestJSON, cls).setUpClass()
+ cls.adm_client = cls.tenant_usages_admin_client
+ cls.client = cls.tenant_usages_client
cls.identity_client = cls._get_identity_admin_client()
resp, tenants = cls.identity_client.list_tenants()
@@ -111,5 +111,5 @@
self.client.list_tenant_usages, params)
-class TenantUsagesTestXML(TenantUsagesTestJSON):
+class TenantUsagesV3TestXML(TenantUsagesV3TestJSON):
_interface = 'xml'
diff --git a/tempest/api/compute/v3/images/test_images.py b/tempest/api/compute/v3/images/test_images.py
index 3aacafb..a179d65 100644
--- a/tempest/api/compute/v3/images/test_images.py
+++ b/tempest/api/compute/v3/images/test_images.py
@@ -14,7 +14,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest.api import compute
from tempest.api.compute import base
from tempest import clients
from tempest.common.utils import data_utils
@@ -34,7 +33,7 @@
cls.client = cls.images_client
cls.servers_client = cls.servers_client
- if compute.MULTI_USER:
+ if cls.multi_user:
if cls.config.compute.allow_tenant_isolation:
creds = cls.isolated_creds.get_alt_creds()
username, tenant_name, password = creds
diff --git a/tempest/api/compute/v3/servers/test_create_server.py b/tempest/api/compute/v3/servers/test_create_server.py
new file mode 100644
index 0000000..24ade96
--- /dev/null
+++ b/tempest/api/compute/v3/servers/test_create_server.py
@@ -0,0 +1,130 @@
+# 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
+
+import netaddr
+import testtools
+
+from tempest.api import compute
+from tempest.api.compute import base
+from tempest.common.utils import data_utils
+from tempest.common.utils.linux.remote_client import RemoteClient
+import tempest.config
+from tempest.test import attr
+
+
+class ServersTestJSON(base.BaseV2ComputeTest):
+ _interface = 'json'
+ run_ssh = tempest.config.TempestConfig().compute.run_ssh
+ disk_config = 'AUTO'
+
+ @classmethod
+ def setUpClass(cls):
+ super(ServersTestJSON, 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
+ 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'])
+
+ @attr(type='smoke')
+ def test_create_server_response(self):
+ # Check that the required fields are returned with values
+ self.assertEqual(202, self.resp.status)
+ self.assertTrue(self.server_initial['id'] is not None)
+ self.assertTrue(self.server_initial['adminPass'] is not None)
+
+ @attr(type='smoke')
+ def test_verify_server_details(self):
+ # Verify the specified server attributes are set correctly
+ self.assertEqual(self.accessIPv4, self.server['accessIPv4'])
+ # NOTE(maurosr): See http://tools.ietf.org/html/rfc5952 (section 4)
+ # Here we compare directly with the canonicalized format.
+ self.assertEqual(self.server['accessIPv6'],
+ str(netaddr.IPAddress(self.accessIPv6)))
+ self.assertEqual(self.name, self.server['name'])
+ self.assertEqual(self.image_ref, self.server['image']['id'])
+ self.assertEqual(self.flavor_ref, self.server['flavor']['id'])
+ self.assertEqual(self.meta, self.server['metadata'])
+
+ @attr(type='smoke')
+ def test_list_servers(self):
+ # The created server should be in the list of all servers
+ resp, body = self.client.list_servers()
+ servers = body['servers']
+ found = any([i for i in servers if i['id'] == self.server['id']])
+ self.assertTrue(found)
+
+ @attr(type='smoke')
+ def test_list_servers_with_detail(self):
+ # The created server should be in the detailed list of all servers
+ resp, body = self.client.list_servers_with_detail()
+ servers = body['servers']
+ found = any([i for i in servers if i['id'] == self.server['id']])
+ self.assertTrue(found)
+
+ @testtools.skipIf(not run_ssh, 'Instance validation tests are disabled.')
+ @attr(type='gate')
+ def test_can_log_into_created_server(self):
+ # Check that the user can authenticate with the generated password
+ linux_client = RemoteClient(self.server, self.ssh_user, self.password)
+ self.assertTrue(linux_client.can_authenticate())
+
+ @testtools.skipIf(not run_ssh, 'Instance validation tests are disabled.')
+ @attr(type='gate')
+ def test_verify_created_server_vcpus(self):
+ # Verify that the number of vcpus reported by the instance matches
+ # the amount stated by the flavor
+ resp, flavor = self.flavors_client.get_flavor_details(self.flavor_ref)
+ linux_client = RemoteClient(self.server, self.ssh_user, self.password)
+ self.assertEqual(flavor['vcpus'], linux_client.get_number_of_vcpus())
+
+ @testtools.skipIf(not run_ssh, 'Instance validation tests are disabled.')
+ @attr(type='gate')
+ def test_host_name_is_same_as_server_name(self):
+ # Verify the instance host name is the same as the server name
+ linux_client = RemoteClient(self.server, self.ssh_user, self.password)
+ self.assertTrue(linux_client.hostname_equals_servername(self.name))
+
+
+class ServersTestManualDisk(ServersTestJSON):
+ disk_config = 'MANUAL'
+
+ @classmethod
+ def setUpClass(cls):
+ if not compute.DISK_CONFIG_ENABLED:
+ msg = "DiskConfig extension not enabled."
+ raise cls.skipException(msg)
+ super(ServersTestManualDisk, cls).setUpClass()
+
+
+class ServersTestXML(ServersTestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/compute/v3/servers/test_list_servers_negative.py b/tempest/api/compute/v3/servers/test_list_servers_negative.py
index 6225345..3f7f885 100644
--- a/tempest/api/compute/v3/servers/test_list_servers_negative.py
+++ b/tempest/api/compute/v3/servers/test_list_servers_negative.py
@@ -17,7 +17,6 @@
import datetime
-from tempest.api import compute
from tempest.api.compute import base
from tempest import clients
from tempest import exceptions
@@ -53,7 +52,7 @@
cls.client = cls.servers_client
cls.servers = []
- if compute.MULTI_USER:
+ if cls.multi_user:
if cls.config.compute.allow_tenant_isolation:
creds = cls.isolated_creds.get_alt_creds()
username, tenant_name, password = creds
diff --git a/tempest/api/compute/v3/servers/test_multiple_create.py b/tempest/api/compute/v3/servers/test_multiple_create.py
new file mode 100644
index 0000000..080bd1a
--- /dev/null
+++ b/tempest/api/compute/v3/servers/test_multiple_create.py
@@ -0,0 +1,95 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# 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.common.utils import data_utils
+from tempest import exceptions
+from tempest.test import attr
+
+
+class MultipleCreateTestJSON(base.BaseV2ComputeTest):
+ _interface = 'json'
+ _name = 'multiple-create-test'
+
+ def _generate_name(self):
+ return data_utils.rand_name(self._name)
+
+ def _create_multiple_servers(self, name=None, wait_until=None, **kwargs):
+ """
+ This is the right way to create_multiple servers and manage to get the
+ created servers into the servers list to be cleaned up after all.
+ """
+ kwargs['name'] = kwargs.get('name', self._generate_name())
+ resp, body = self.create_test_server(**kwargs)
+
+ return resp, body
+
+ @attr(type='gate')
+ def test_multiple_create(self):
+ resp, body = self._create_multiple_servers(wait_until='ACTIVE',
+ min_count=1,
+ max_count=2)
+ # NOTE(maurosr): do status response check and also make sure that
+ # reservation_id is not in the response body when the request send
+ # contains return_reservation_id=False
+ self.assertEqual('202', resp['status'])
+ self.assertNotIn('reservation_id', body)
+
+ @attr(type=['negative', 'gate'])
+ def test_min_count_less_than_one(self):
+ invalid_min_count = 0
+ self.assertRaises(exceptions.BadRequest, self._create_multiple_servers,
+ min_count=invalid_min_count)
+
+ @attr(type=['negative', 'gate'])
+ def test_min_count_non_integer(self):
+ invalid_min_count = 2.5
+ self.assertRaises(exceptions.BadRequest, self._create_multiple_servers,
+ min_count=invalid_min_count)
+
+ @attr(type=['negative', 'gate'])
+ def test_max_count_less_than_one(self):
+ invalid_max_count = 0
+ self.assertRaises(exceptions.BadRequest, self._create_multiple_servers,
+ max_count=invalid_max_count)
+
+ @attr(type=['negative', 'gate'])
+ def test_max_count_non_integer(self):
+ invalid_max_count = 2.5
+ self.assertRaises(exceptions.BadRequest, self._create_multiple_servers,
+ max_count=invalid_max_count)
+
+ @attr(type=['negative', 'gate'])
+ def test_max_count_less_than_min_count(self):
+ min_count = 3
+ max_count = 2
+ self.assertRaises(exceptions.BadRequest, self._create_multiple_servers,
+ min_count=min_count,
+ max_count=max_count)
+
+ @attr(type='gate')
+ def test_multiple_create_with_reservation_return(self):
+ resp, body = self._create_multiple_servers(wait_until='ACTIVE',
+ min_count=1,
+ max_count=2,
+ return_reservation_id=True)
+ self.assertEqual(resp['status'], '202')
+ self.assertIn('reservation_id', body)
+
+
+class MultipleCreateTestXML(MultipleCreateTestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/compute/v3/servers/test_server_actions.py b/tempest/api/compute/v3/servers/test_server_actions.py
index 20cdf30..ee37502 100644
--- a/tempest/api/compute/v3/servers/test_server_actions.py
+++ b/tempest/api/compute/v3/servers/test_server_actions.py
@@ -45,13 +45,13 @@
self.client.wait_for_server_status(self.server_id, 'ACTIVE')
except Exception:
# Rebuild server if something happened to it during a test
- self.rebuild_server()
+ self.__class__.server_id = self.rebuild_server(self.server_id)
@classmethod
def setUpClass(cls):
super(ServerActionsV3TestJSON, cls).setUpClass()
cls.client = cls.servers_client
- cls.rebuild_server()
+ cls.server_id = cls.rebuild_server(None)
@testtools.skipUnless(compute.CHANGE_PASSWORD_AVAILABLE,
'Change password not available.')
@@ -67,7 +67,7 @@
# Verify that the user can authenticate with the new password
resp, server = self.client.get_server(self.server_id)
linux_client = RemoteClient(server, self.ssh_user, new_password)
- self.assertTrue(linux_client.can_authenticate())
+ linux_client.validate_authentication()
@attr(type='smoke')
def test_reboot_server_hard(self):
@@ -140,7 +140,7 @@
if self.run_ssh:
# Verify that the user can authenticate with the provided password
linux_client = RemoteClient(server, self.ssh_user, password)
- self.assertTrue(linux_client.can_authenticate())
+ linux_client.validate_authentication()
def _detect_server_image_flavor(self, server_id):
# Detects the current server image flavor ref.
diff --git a/tempest/api/compute/v3/servers/test_server_metadata.py b/tempest/api/compute/v3/servers/test_server_metadata.py
new file mode 100644
index 0000000..ee0f4a9
--- /dev/null
+++ b/tempest/api/compute/v3/servers/test_server_metadata.py
@@ -0,0 +1,217 @@
+# 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.test import attr
+
+
+class ServerMetadataTestJSON(base.BaseV2ComputeTest):
+ _interface = 'json'
+
+ @classmethod
+ def setUpClass(cls):
+ super(ServerMetadataTestJSON, cls).setUpClass()
+ cls.client = cls.servers_client
+ cls.quotas = cls.quotas_client
+ cls.admin_client = cls._get_identity_admin_client()
+ resp, tenants = cls.admin_client.list_tenants()
+ cls.tenant_id = [tnt['id'] for tnt in tenants if tnt['name'] ==
+ cls.client.tenant_name][0]
+ resp, server = cls.create_test_server(meta={}, wait_until='ACTIVE')
+
+ cls.server_id = server['id']
+
+ def setUp(self):
+ super(ServerMetadataTestJSON, self).setUp()
+ meta = {'key1': 'value1', 'key2': 'value2'}
+ resp, _ = self.client.set_server_metadata(self.server_id, meta)
+ self.assertEqual(resp.status, 200)
+
+ @attr(type='gate')
+ def test_list_server_metadata(self):
+ # All metadata key/value pairs for a server should be returned
+ resp, resp_metadata = self.client.list_server_metadata(self.server_id)
+
+ # Verify the expected metadata items are in the list
+ self.assertEqual(200, resp.status)
+ expected = {'key1': 'value1', 'key2': 'value2'}
+ self.assertEqual(expected, resp_metadata)
+
+ @attr(type='gate')
+ def test_set_server_metadata(self):
+ # The server's metadata should be replaced with the provided values
+ # Create a new set of metadata for the server
+ req_metadata = {'meta2': 'data2', 'meta3': 'data3'}
+ resp, metadata = self.client.set_server_metadata(self.server_id,
+ req_metadata)
+ self.assertEqual(200, resp.status)
+
+ # Verify the expected values are correct, and that the
+ # previous values have been removed
+ resp, resp_metadata = self.client.list_server_metadata(self.server_id)
+ self.assertEqual(resp_metadata, req_metadata)
+
+ @attr(type='gate')
+ def test_server_create_metadata_key_too_long(self):
+ # Attempt to start a server with a meta-data key that is > 255
+ # characters
+
+ # Try a few values
+ for sz in [256, 257, 511, 1023]:
+ key = "k" * sz
+ meta = {key: 'data1'}
+ self.assertRaises(exceptions.OverLimit,
+ self.create_test_server,
+ meta=meta)
+
+ # no teardown - all creates should fail
+
+ @attr(type='gate')
+ def test_update_server_metadata(self):
+ # The server's metadata values should be updated to the
+ # provided values
+ meta = {'key1': 'alt1', 'key3': 'value3'}
+ resp, metadata = self.client.update_server_metadata(self.server_id,
+ meta)
+ self.assertEqual(200, resp.status)
+
+ # Verify the values have been updated to the proper values
+ resp, resp_metadata = self.client.list_server_metadata(self.server_id)
+ expected = {'key1': 'alt1', 'key2': 'value2', 'key3': 'value3'}
+ self.assertEqual(expected, resp_metadata)
+
+ @attr(type='gate')
+ def test_update_metadata_empty_body(self):
+ # The original metadata should not be lost if empty metadata body is
+ # passed
+ meta = {}
+ _, metadata = self.client.update_server_metadata(self.server_id, meta)
+ resp, resp_metadata = self.client.list_server_metadata(self.server_id)
+ expected = {'key1': 'value1', 'key2': 'value2'}
+ self.assertEqual(expected, resp_metadata)
+
+ @attr(type='gate')
+ def test_get_server_metadata_item(self):
+ # The value for a specific metadata key should be returned
+ resp, meta = self.client.get_server_metadata_item(self.server_id,
+ 'key2')
+ self.assertEqual('value2', meta['key2'])
+
+ @attr(type='gate')
+ def test_set_server_metadata_item(self):
+ # The item's value should be updated to the provided value
+ # Update the metadata value
+ meta = {'nova': 'alt'}
+ resp, body = self.client.set_server_metadata_item(self.server_id,
+ 'nova', meta)
+ self.assertEqual(200, resp.status)
+
+ # Verify the meta item's value has been updated
+ resp, resp_metadata = self.client.list_server_metadata(self.server_id)
+ expected = {'key1': 'value1', 'key2': 'value2', 'nova': 'alt'}
+ self.assertEqual(expected, resp_metadata)
+
+ @attr(type='gate')
+ def test_delete_server_metadata_item(self):
+ # The metadata value/key pair should be deleted from the server
+ resp, meta = self.client.delete_server_metadata_item(self.server_id,
+ 'key1')
+ self.assertEqual(204, resp.status)
+
+ # Verify the metadata item has been removed
+ resp, resp_metadata = self.client.list_server_metadata(self.server_id)
+ expected = {'key2': 'value2'}
+ self.assertEqual(expected, resp_metadata)
+
+ @attr(type=['negative', 'gate'])
+ def test_server_metadata_negative(self):
+ # Blank key should trigger an error.
+ meta = {'': 'data1'}
+ self.assertRaises(exceptions.BadRequest,
+ self.create_test_server,
+ meta=meta)
+
+ # GET on a non-existent server should not succeed
+ self.assertRaises(exceptions.NotFound,
+ self.client.get_server_metadata_item, 999, 'test2')
+
+ # List metadata on a non-existent server should not succeed
+ self.assertRaises(exceptions.NotFound,
+ self.client.list_server_metadata, 999)
+
+ # Raise BadRequest if key in uri does not match
+ # the key passed in body.
+ meta = {'testkey': 'testvalue'}
+ self.assertRaises(exceptions.BadRequest,
+ self.client.set_server_metadata_item,
+ self.server_id, 'key', meta)
+
+ # Set metadata on a non-existent server should not succeed
+ meta = {'meta1': 'data1'}
+ self.assertRaises(exceptions.NotFound,
+ self.client.set_server_metadata, 999, meta)
+
+ # An update should not happen for a non-existent image
+ meta = {'key1': 'value1', 'key2': 'value2'}
+ self.assertRaises(exceptions.NotFound,
+ self.client.update_server_metadata, 999, meta)
+
+ # Blank key should trigger an error
+ meta = {'': 'data1'}
+ self.assertRaises(exceptions.BadRequest,
+ self.client.update_server_metadata,
+ self.server_id, meta=meta)
+
+ # Should not be able to delete metadata item from a non-existent server
+ self.assertRaises(exceptions.NotFound,
+ self.client.delete_server_metadata_item, 999, 'd')
+
+ # Raise a 413 OverLimit exception while exceeding metadata items limit
+ # for tenant.
+ _, quota_set = self.quotas.get_quota_set(self.tenant_id)
+ quota_metadata = quota_set['metadata_items']
+ req_metadata = {}
+ for num in range(1, quota_metadata + 2):
+ req_metadata['key' + str(num)] = 'val' + str(num)
+ self.assertRaises(exceptions.OverLimit,
+ self.client.set_server_metadata,
+ self.server_id, req_metadata)
+
+ # Raise a 413 OverLimit exception while exceeding metadata items limit
+ # for tenant (update).
+ self.assertRaises(exceptions.OverLimit,
+ self.client.update_server_metadata,
+ self.server_id, req_metadata)
+
+ # Raise a bad request error for blank key.
+ # set_server_metadata will replace all metadata with new value
+ meta = {'': 'data1'}
+ self.assertRaises(exceptions.BadRequest,
+ self.client.set_server_metadata,
+ self.server_id, meta=meta)
+
+ # Raise a bad request error for a missing metadata field
+ # set_server_metadata will replace all metadata with new value
+ meta = {'meta1': 'data1'}
+ self.assertRaises(exceptions.BadRequest,
+ self.client.set_server_metadata,
+ self.server_id, meta=meta, no_metadata_field=True)
+
+
+class ServerMetadataTestXML(ServerMetadataTestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/compute/v3/servers/test_server_personality.py b/tempest/api/compute/v3/servers/test_server_personality.py
new file mode 100644
index 0000000..c6d2e44
--- /dev/null
+++ b/tempest/api/compute/v3/servers/test_server_personality.py
@@ -0,0 +1,68 @@
+# 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/servers/test_server_rescue.py b/tempest/api/compute/v3/servers/test_server_rescue.py
new file mode 100644
index 0000000..eebd4d8
--- /dev/null
+++ b/tempest/api/compute/v3/servers/test_server_rescue.py
@@ -0,0 +1,173 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 Hewlett-Packard Development Company, L.P.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.api.compute import base
+from tempest import exceptions
+from tempest.test import attr
+
+
+class ServerRescueV3TestJSON(base.BaseV3ComputeTest):
+ _interface = 'json'
+
+ @classmethod
+ def setUpClass(cls):
+ super(ServerRescueV3TestJSON, cls).setUpClass()
+ cls.device = 'vdf'
+
+ # Create a volume and wait for it to become ready for attach
+ resp, cls.volume_to_attach = \
+ cls.volumes_client.create_volume(1,
+ display_name=
+ 'test_attach')
+ cls.volumes_client.wait_for_volume_status(
+ cls.volume_to_attach['id'], 'available')
+
+ # Create a volume and wait for it to become ready for attach
+ resp, cls.volume_to_detach = \
+ cls.volumes_client.create_volume(1,
+ display_name=
+ 'test_detach')
+ cls.volumes_client.wait_for_volume_status(
+ cls.volume_to_detach['id'], 'available')
+
+ # Server for positive tests
+ resp, server = cls.create_test_server(wait_until='BUILD')
+ resp, resc_server = cls.create_test_server(wait_until='ACTIVE')
+ cls.server_id = server['id']
+ cls.password = server['admin_password']
+ cls.servers_client.wait_for_server_status(cls.server_id, 'ACTIVE')
+
+ # Server for negative tests
+ cls.rescue_id = resc_server['id']
+ cls.rescue_password = resc_server['admin_password']
+
+ cls.servers_client.rescue_server(
+ cls.rescue_id, cls.rescue_password)
+ cls.servers_client.wait_for_server_status(cls.rescue_id, 'RESCUE')
+
+ def setUp(self):
+ super(ServerRescueV3TestJSON, self).setUp()
+
+ @classmethod
+ def tearDownClass(cls):
+ client = cls.volumes_client
+ client.delete_volume(str(cls.volume_to_attach['id']).strip())
+ client.delete_volume(str(cls.volume_to_detach['id']).strip())
+ super(ServerRescueV3TestJSON, cls).tearDownClass()
+
+ def tearDown(self):
+ super(ServerRescueV3TestJSON, self).tearDown()
+
+ def _detach(self, server_id, volume_id):
+ self.servers_client.detach_volume(server_id, volume_id)
+ self.volumes_client.wait_for_volume_status(volume_id,
+ 'available')
+
+ def _delete(self, volume_id):
+ self.volumes_client.delete_volume(volume_id)
+
+ def _unrescue(self, server_id):
+ resp, body = self.servers_client.unrescue_server(server_id)
+ self.assertEqual(202, resp.status)
+ self.servers_client.wait_for_server_status(server_id, 'ACTIVE')
+
+ def _unpause(self, server_id):
+ resp, body = self.servers_client.unpause_server(server_id)
+ self.assertEqual(202, resp.status)
+ self.servers_client.wait_for_server_status(server_id, 'ACTIVE')
+
+ @attr(type='smoke')
+ def test_rescue_unrescue_instance(self):
+ resp, body = self.servers_client.rescue_server(
+ self.server_id, self.password)
+ self.assertEqual(202, resp.status)
+ self.servers_client.wait_for_server_status(self.server_id, 'RESCUE')
+ resp, body = self.servers_client.unrescue_server(self.server_id)
+ self.assertEqual(202, resp.status)
+ self.servers_client.wait_for_server_status(self.server_id, 'ACTIVE')
+
+ @attr(type=['negative', 'gate'])
+ def test_rescue_paused_instance(self):
+ # Rescue a paused server
+ resp, body = self.servers_client.pause_server(
+ self.server_id)
+ self.addCleanup(self._unpause, self.server_id)
+ self.assertEqual(202, resp.status)
+ self.servers_client.wait_for_server_status(self.server_id, 'PAUSED')
+ self.assertRaises(exceptions.Conflict,
+ self.servers_client.rescue_server,
+ self.server_id)
+
+ @attr(type=['negative', 'gate'])
+ def test_rescued_vm_reboot(self):
+ self.assertRaises(exceptions.Conflict, self.servers_client.reboot,
+ self.rescue_id, 'HARD')
+
+ @attr(type=['negative', 'gate'])
+ def test_rescue_non_existent_server(self):
+ # Rescue a non-existing server
+ self.assertRaises(exceptions.NotFound,
+ self.servers_client.rescue_server,
+ '999erra43')
+
+ @attr(type=['negative', 'gate'])
+ def test_rescued_vm_rebuild(self):
+ self.assertRaises(exceptions.Conflict,
+ self.servers_client.rebuild,
+ self.rescue_id,
+ self.image_ref_alt)
+
+ @attr(type=['negative', 'gate'])
+ def test_rescued_vm_attach_volume(self):
+ # Rescue the server
+ self.servers_client.rescue_server(self.server_id, self.password)
+ self.servers_client.wait_for_server_status(self.server_id, 'RESCUE')
+ self.addCleanup(self._unrescue, self.server_id)
+
+ # Attach the volume to the server
+ self.assertRaises(exceptions.Conflict,
+ self.servers_client.attach_volume,
+ self.server_id,
+ self.volume_to_attach['id'],
+ device='/dev/%s' % self.device)
+
+ @attr(type=['negative', 'gate'])
+ def test_rescued_vm_detach_volume(self):
+ # Attach the volume to the server
+ self.servers_client.attach_volume(self.server_id,
+ self.volume_to_detach['id'],
+ device='/dev/%s' % self.device)
+ self.volumes_client.wait_for_volume_status(
+ self.volume_to_detach['id'], 'in-use')
+
+ # Rescue the server
+ self.servers_client.rescue_server(self.server_id, self.password)
+ self.servers_client.wait_for_server_status(self.server_id, 'RESCUE')
+ # addCleanup is a LIFO queue
+ self.addCleanup(self._detach, self.server_id,
+ self.volume_to_detach['id'])
+ self.addCleanup(self._unrescue, self.server_id)
+
+ # Detach the volume from the server expecting failure
+ self.assertRaises(exceptions.Conflict,
+ self.servers_client.detach_volume,
+ self.server_id,
+ self.volume_to_detach['id'])
+
+
+class ServerRescueV3TestXML(ServerRescueV3TestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/compute/v3/servers/test_servers.py b/tempest/api/compute/v3/servers/test_servers.py
new file mode 100644
index 0000000..d72476d
--- /dev/null
+++ b/tempest/api/compute/v3/servers/test_servers.py
@@ -0,0 +1,133 @@
+# 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 ServersTestJSON(base.BaseV2ComputeTest):
+ _interface = 'json'
+
+ @classmethod
+ def setUpClass(cls):
+ super(ServersTestJSON, cls).setUpClass()
+ cls.client = cls.servers_client
+
+ def tearDown(self):
+ self.clear_servers()
+ super(ServersTestJSON, self).tearDown()
+
+ @attr(type='gate')
+ def test_create_server_with_admin_password(self):
+ # If an admin password is provided on server creation, the server's
+ # root password should be set to that password.
+ resp, server = self.create_test_server(adminPass='testpassword')
+
+ # Verify the password is set correctly in the response
+ self.assertEqual('testpassword', server['adminPass'])
+
+ @attr(type='gate')
+ def test_create_with_existing_server_name(self):
+ # Creating a server with a name that already exists is allowed
+
+ # TODO(sdague): clear out try, we do cleanup one layer up
+ server_name = data_utils.rand_name('server')
+ resp, server = self.create_test_server(name=server_name,
+ wait_until='ACTIVE')
+ id1 = server['id']
+ resp, server = self.create_test_server(name=server_name,
+ wait_until='ACTIVE')
+ id2 = server['id']
+ self.assertNotEqual(id1, id2, "Did not create a new server")
+ resp, server = self.client.get_server(id1)
+ name1 = server['name']
+ resp, server = self.client.get_server(id2)
+ name2 = server['name']
+ self.assertEqual(name1, name2)
+
+ @attr(type='gate')
+ def test_create_specify_keypair(self):
+ # Specify a keypair while creating a server
+
+ key_name = data_utils.rand_name('key')
+ resp, keypair = self.keypairs_client.create_keypair(key_name)
+ resp, body = self.keypairs_client.list_keypairs()
+ resp, server = self.create_test_server(key_name=key_name)
+ self.assertEqual('202', resp['status'])
+ self.client.wait_for_server_status(server['id'], 'ACTIVE')
+ resp, server = self.client.get_server(server['id'])
+ self.assertEqual(key_name, server['key_name'])
+
+ @attr(type='gate')
+ def test_update_server_name(self):
+ # The server name should be changed to the the provided value
+ resp, server = self.create_test_server(wait_until='ACTIVE')
+
+ # Update the server with a new name
+ resp, server = self.client.update_server(server['id'],
+ name='newname')
+ self.assertEqual(200, resp.status)
+ self.client.wait_for_server_status(server['id'], 'ACTIVE')
+
+ # Verify the name of the server has changed
+ resp, server = self.client.get_server(server['id'])
+ self.assertEqual('newname', server['name'])
+
+ @attr(type='gate')
+ def test_update_access_server_address(self):
+ # The server's access addresses should reflect the provided values
+ resp, server = self.create_test_server(wait_until='ACTIVE')
+
+ # Update the IPv4 and IPv6 access addresses
+ resp, body = self.client.update_server(server['id'],
+ accessIPv4='1.1.1.1',
+ accessIPv6='::babe:202:202')
+ self.assertEqual(200, resp.status)
+ self.client.wait_for_server_status(server['id'], 'ACTIVE')
+
+ # Verify the access addresses have been updated
+ resp, server = self.client.get_server(server['id'])
+ self.assertEqual('1.1.1.1', server['accessIPv4'])
+ self.assertEqual('::babe:202:202', server['accessIPv6'])
+
+ @attr(type='gate')
+ def test_delete_server_while_in_building_state(self):
+ # Delete a server while it's VM state is Building
+ resp, server = self.create_test_server(wait_until='BUILD')
+ resp, _ = self.client.delete_server(server['id'])
+ self.assertEqual('204', resp['status'])
+
+ @attr(type='gate')
+ def test_delete_active_server(self):
+ # Delete a server while it's VM state is Active
+ resp, server = self.create_test_server(wait_until='ACTIVE')
+ resp, _ = self.client.delete_server(server['id'])
+ self.assertEqual('204', resp['status'])
+
+ @attr(type='gate')
+ def test_create_server_with_ipv6_addr_only(self):
+ # Create a server without an IPv4 address(only IPv6 address).
+ resp, server = self.create_test_server(accessIPv6='2001:2001::3')
+ self.assertEqual('202', resp['status'])
+ self.client.wait_for_server_status(server['id'], 'ACTIVE')
+ resp, server = self.client.get_server(server['id'])
+ self.assertEqual('2001:2001::3', server['accessIPv6'])
+
+
+class ServersTestXML(ServersTestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/compute/v3/servers/test_servers_negative.py b/tempest/api/compute/v3/servers/test_servers_negative.py
index 5ec0cbe..8142250 100644
--- a/tempest/api/compute/v3/servers/test_servers_negative.py
+++ b/tempest/api/compute/v3/servers/test_servers_negative.py
@@ -34,7 +34,7 @@
try:
self.client.wait_for_server_status(self.server_id, 'ACTIVE')
except Exception:
- self.rebuild_server()
+ self.__class__.server_id = self.rebuild_server(self.server_id)
@classmethod
def setUpClass(cls):
diff --git a/tempest/api/compute/v3/test_extensions.py b/tempest/api/compute/v3/test_extensions.py
index d7269d1..2affd86 100644
--- a/tempest/api/compute/v3/test_extensions.py
+++ b/tempest/api/compute/v3/test_extensions.py
@@ -17,18 +17,26 @@
from tempest.api.compute import base
-from tempest.test import attr
+from tempest import test
class ExtensionsV3TestJSON(base.BaseV3ComputeTest):
_interface = 'json'
- @attr(type='gate')
+ @test.attr(type='gate')
def test_list_extensions(self):
# List of all extensions
resp, extensions = self.extensions_client.list_extensions()
self.assertIn("extensions", extensions)
self.assertEqual(200, resp.status)
+ self.assertTrue(self.extensions_client.is_enabled("Consoles"))
+
+ @test.attr(type='gate')
+ def test_get_extension(self):
+ # get the specified extensions
+ resp, extension = self.extensions_client.get_extension('servers')
+ self.assertEqual(200, resp.status)
+ self.assertEqual('servers', extension['alias'])
class ExtensionsV3TestXML(ExtensionsV3TestJSON):
diff --git a/tempest/api/compute/v3/test_quotas.py b/tempest/api/compute/v3/test_quotas.py
new file mode 100644
index 0000000..475d055
--- /dev/null
+++ b/tempest/api/compute/v3/test_quotas.py
@@ -0,0 +1,73 @@
+# 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.test import attr
+
+
+class QuotasTestJSON(base.BaseV2ComputeTest):
+ _interface = 'json'
+
+ @classmethod
+ def setUpClass(cls):
+ super(QuotasTestJSON, cls).setUpClass()
+ cls.client = cls.quotas_client
+ cls.admin_client = cls._get_identity_admin_client()
+ resp, tenants = cls.admin_client.list_tenants()
+ cls.tenant_id = [tnt['id'] for tnt in tenants if tnt['name'] ==
+ cls.client.tenant_name][0]
+ cls.default_quota_set = set(('injected_file_content_bytes',
+ 'metadata_items', 'injected_files',
+ 'ram', 'floating_ips',
+ 'fixed_ips', 'key_pairs',
+ 'injected_file_path_bytes',
+ 'instances', 'security_group_rules',
+ 'cores', 'security_groups'))
+
+ @attr(type='smoke')
+ def test_get_quotas(self):
+ # User can get the quota set for it's tenant
+ expected_quota_set = self.default_quota_set | set(['id'])
+ resp, quota_set = self.client.get_quota_set(self.tenant_id)
+ self.assertEqual(200, resp.status)
+ self.assertEqual(sorted(expected_quota_set),
+ sorted(quota_set.keys()))
+ self.assertEqual(quota_set['id'], self.tenant_id)
+
+ @attr(type='smoke')
+ def test_get_default_quotas(self):
+ # User can get the default quota set for it's tenant
+ expected_quota_set = self.default_quota_set | set(['id'])
+ resp, quota_set = self.client.get_default_quota_set(self.tenant_id)
+ self.assertEqual(200, resp.status)
+ self.assertEqual(sorted(expected_quota_set),
+ sorted(quota_set.keys()))
+ self.assertEqual(quota_set['id'], self.tenant_id)
+
+ @attr(type='smoke')
+ def test_compare_tenant_quotas_with_default_quotas(self):
+ # Tenants are created with the default quota values
+ resp, defualt_quota_set = \
+ self.client.get_default_quota_set(self.tenant_id)
+ self.assertEqual(200, resp.status)
+ resp, tenant_quota_set = self.client.get_quota_set(self.tenant_id)
+ self.assertEqual(200, resp.status)
+ self.assertEqual(defualt_quota_set, tenant_quota_set)
+
+
+class QuotasTestXML(QuotasTestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/compute/volumes/test_volumes_list.py b/tempest/api/compute/volumes/test_volumes_list.py
index f214641..b57dcfe 100644
--- a/tempest/api/compute/volumes/test_volumes_list.py
+++ b/tempest/api/compute/volumes/test_volumes_list.py
@@ -43,9 +43,8 @@
cls.volume_list = []
cls.volume_id_list = []
for i in range(3):
- v_name = data_utils.rand_name('volume-%s')
+ v_name = data_utils.rand_name('volume-%s' % cls._interface)
metadata = {'Type': 'work'}
- v_name += cls._interface
try:
resp, volume = cls.client.create_volume(size=1,
display_name=v_name,
@@ -110,6 +109,65 @@
', '.join(m_vol['displayName']
for m_vol in missing_volumes))
+ @attr(type='gate')
+ def test_volume_list_param_limit(self):
+ # Return the list of volumes based on limit set
+ params = {'limit': 2}
+ resp, fetched_vol_list = self.client.list_volumes(params=params)
+ self.assertEqual(200, resp.status)
+
+ self.assertEqual(len(fetched_vol_list), params['limit'],
+ "Failed to list volumes by limit set")
+
+ @attr(type='gate')
+ def test_volume_list_with_detail_param_limit(self):
+ # Return the list of volumes with details based on limit set.
+ params = {'limit': 2}
+ resp, fetched_vol_list = \
+ self.client.list_volumes_with_detail(params=params)
+ self.assertEqual(200, resp.status)
+
+ self.assertEqual(len(fetched_vol_list), params['limit'],
+ "Failed to list volume details by limit set")
+
+ @attr(type='gate')
+ def test_volume_list_param_offset_and_limit(self):
+ # Return the list of volumes based on offset and limit set.
+ # get all volumes list
+ response, all_vol_list = self.client.list_volumes()
+ params = {'offset': 1, 'limit': 1}
+ resp, fetched_vol_list = self.client.list_volumes(params=params)
+ self.assertEqual(200, resp.status)
+
+ # Validating length of the fetched volumes
+ self.assertEqual(len(fetched_vol_list), params['limit'],
+ "Failed to list volumes by offset and limit")
+ # Validating offset of fetched volume
+ for index, volume in enumerate(fetched_vol_list):
+ self.assertEqual(volume['id'],
+ all_vol_list[index + params['offset']]['id'],
+ "Failed to list volumes by offset and limit")
+
+ @attr(type='gate')
+ def test_volume_list_with_detail_param_offset_and_limit(self):
+ # Return the list of volumes details based on offset and limit set.
+ # get all volumes list
+ response, all_vol_list = self.client.list_volumes_with_detail()
+ params = {'offset': 1, 'limit': 1}
+ resp, fetched_vol_list = \
+ self.client.list_volumes_with_detail(params=params)
+ self.assertEqual(200, resp.status)
+
+ # Validating length of the fetched volumes
+ self.assertEqual(len(fetched_vol_list), params['limit'],
+ "Failed to list volume details by offset and limit")
+ # Validating offset of fetched volume
+ for index, volume in enumerate(fetched_vol_list):
+ self.assertEqual(volume['id'],
+ all_vol_list[index + params['offset']]['id'],
+ "Failed to list volume details by "
+ "offset and limit")
+
class VolumesTestXML(VolumesTestJSON):
_interface = 'xml'
diff --git a/tempest/api/identity/admin/v3/test_trusts.py b/tempest/api/identity/admin/v3/test_trusts.py
new file mode 100644
index 0000000..5e13a5a
--- /dev/null
+++ b/tempest/api/identity/admin/v3/test_trusts.py
@@ -0,0 +1,222 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.api.identity import base
+from tempest import clients
+from tempest.common.utils.data_utils import rand_name
+from tempest import exceptions
+from tempest.test import attr
+
+
+class BaseTrustsV3Test(base.BaseIdentityAdminTest):
+
+ def setUp(self):
+ 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_trust_user_and_roles)
+
+ def create_trustor_and_roles(self):
+ # Get trustor project ID, use the admin project
+ self.trustor_project_name = self.v3_client.tenant_name
+ self.trustor_project_id = self.get_tenant_by_name(
+ self.trustor_project_name)['id']
+ self.assertIsNotNone(self.trustor_project_id)
+
+ # Create a trustor User
+ self.trustor_username = rand_name('user-')
+ u_desc = self.trustor_username + 'description'
+ u_email = self.trustor_username + '@testmail.tm'
+ self.trustor_password = rand_name('pass-')
+ resp, user = self.v3_client.create_user(
+ self.trustor_username,
+ description=u_desc,
+ password=self.trustor_password,
+ email=u_email,
+ project_id=self.trustor_project_id)
+ self.assertEqual(resp['status'], '201')
+ self.trustor_user_id = user['id']
+
+ # And two roles, one we'll delegate and one we won't
+ self.delegated_role = rand_name('DelegatedRole-')
+ self.not_delegated_role = rand_name('NotDelegatedRole-')
+
+ resp, role = self.v3_client.create_role(self.delegated_role)
+ self.assertEqual(resp['status'], '201')
+ self.delegated_role_id = role['id']
+
+ resp, role = self.v3_client.create_role(self.not_delegated_role)
+ self.assertEqual(resp['status'], '201')
+ self.not_delegated_role_id = role['id']
+
+ # Assign roles to trustor
+ self.v3_client.assign_user_role(self.trustor_project_id,
+ self.trustor_user_id,
+ self.delegated_role_id)
+ self.v3_client.assign_user_role(self.trustor_project_id,
+ self.trustor_user_id,
+ self.not_delegated_role_id)
+
+ # Get trustee user ID, use the demo user
+ trustee_username = self.v3_non_admin_client.user
+ self.trustee_user_id = self.get_user_by_name(trustee_username)['id']
+ self.assertIsNotNone(self.trustee_user_id)
+
+ # Initialize a new client with the trustor credentials
+ os = clients.Manager(username=self.trustor_username,
+ password=self.trustor_password,
+ tenant_name=self.trustor_project_name,
+ interface=self._interface)
+ self.trustor_v3_client = os.identity_v3_client
+
+ def cleanup_trust_user_and_roles(self):
+ if self.trust_id:
+ try:
+ self.trustor_v3_client.delete_trust(self.trust_id)
+ except exceptions.NotFound:
+ pass
+ self.trust_id = None
+
+ if self.trustor_user_id:
+ self.v3_client.delete_user(self.trustor_user_id)
+ if self.delegated_role_id:
+ self.v3_client.delete_role(self.delegated_role_id)
+ if self.not_delegated_role_id:
+ self.v3_client.delete_role(self.not_delegated_role_id)
+
+ def create_trust(self, impersonate=True, expires=None):
+
+ resp, trust_create = self.trustor_v3_client.create_trust(
+ trustor_user_id=self.trustor_user_id,
+ trustee_user_id=self.trustee_user_id,
+ project_id=self.trustor_project_id,
+ role_names=[self.delegated_role],
+ impersonation=impersonate,
+ expires_at=expires)
+ self.assertEqual('201', resp['status'])
+ self.trust_id = trust_create['id']
+ return trust_create
+
+ def validate_trust(self, trust, impersonate=True, expires=None,
+ summary=False):
+ self.assertIsNotNone(trust['id'])
+ self.assertEqual(impersonate, trust['impersonation'])
+ self.assertEqual(expires, trust['expires_at'])
+ self.assertEqual(self.trustor_user_id, trust['trustor_user_id'])
+ self.assertEqual(self.trustee_user_id, trust['trustee_user_id'])
+ self.assertIn('v3/OS-TRUST/trusts', trust['links']['self'])
+ self.assertEqual(self.trustor_project_id, trust['project_id'])
+ if not summary:
+ self.assertEqual(self.delegated_role, trust['roles'][0]['name'])
+ self.assertEqual(1, len(trust['roles']))
+
+ def get_trust(self):
+ resp, trust_get = self.trustor_v3_client.get_trust(self.trust_id)
+ self.assertEqual('200', resp['status'])
+ return trust_get
+
+ def validate_role(self, role):
+ self.assertEqual(self.delegated_role_id, role['id'])
+ self.assertEqual(self.delegated_role, role['name'])
+ self.assertIn('v3/roles/%s' % self.delegated_role_id,
+ role['links']['self'])
+ self.assertNotEqual(self.not_delegated_role_id, role['id'])
+ self.assertNotEqual(self.not_delegated_role, role['name'])
+ self.assertNotIn('v3/roles/%s' % self.not_delegated_role_id,
+ role['links']['self'])
+
+ def check_trust_roles(self):
+ # Check we find the delegated role
+ resp, roles_get = self.trustor_v3_client.get_trust_roles(
+ self.trust_id)
+ self.assertEqual('200', resp['status'])
+ self.assertEqual(1, len(roles_get))
+ self.validate_role(roles_get[0])
+
+ resp, role_get = self.trustor_v3_client.get_trust_role(
+ self.trust_id, self.delegated_role_id)
+ self.assertEqual('200', resp['status'])
+ self.validate_role(role_get)
+
+ resp, role_get = self.trustor_v3_client.check_trust_role(
+ self.trust_id, self.delegated_role_id)
+ self.assertEqual('204', resp['status'])
+
+ # And that we don't find not_delegated_role
+ self.assertRaises(exceptions.NotFound,
+ self.trustor_v3_client.get_trust_role,
+ self.trust_id,
+ self.not_delegated_role_id)
+
+ self.assertRaises(exceptions.NotFound,
+ self.trustor_v3_client.check_trust_role,
+ self.trust_id,
+ self.not_delegated_role_id)
+
+ def delete_trust(self):
+ resp, trust_delete = self.trustor_v3_client.delete_trust(self.trust_id)
+ self.assertEqual('204', resp['status'])
+ self.assertRaises(exceptions.NotFound,
+ self.trustor_v3_client.get_trust,
+ self.trust_id)
+ self.trust_id = None
+
+
+class TrustsV3TestJSON(BaseTrustsV3Test):
+ _interface = 'json'
+
+ def setUp(self):
+ super(TrustsV3TestJSON, self).setUp()
+ self.create_trustor_and_roles()
+
+ @attr(type='smoke')
+ def test_trust_impersonate(self):
+ # Test case to check we can create, get and delete a trust
+ # updates are not supported for trusts
+ trust = self.create_trust()
+ self.validate_trust(trust)
+
+ trust_get = self.get_trust()
+ self.validate_trust(trust_get)
+
+ self.check_trust_roles()
+
+ self.delete_trust()
+
+ @attr(type='smoke')
+ def test_trust_noimpersonate(self):
+ # Test case to check we can create, get and delete a trust
+ # with impersonation=False
+ trust = self.create_trust(impersonate=False)
+ self.validate_trust(trust, impersonate=False)
+
+ trust_get = self.get_trust()
+ self.validate_trust(trust_get, impersonate=False)
+
+ self.check_trust_roles()
+
+ self.delete_trust()
+
+ @attr(type='smoke')
+ def test_trust_expire_invalid(self):
+ # Test case to check we can check an invlaid expiry time
+ # is rejected with the correct error
+ # with an expiry specified
+ expires_str = 'bad.123Z'
+ self.assertRaises(exceptions.BadRequest,
+ self.create_trust,
+ expires=expires_str)
diff --git a/tempest/api/network/admin/test_agent_management.py b/tempest/api/network/admin/test_agent_management.py
new file mode 100644
index 0000000..94659b2
--- /dev/null
+++ b/tempest/api/network/admin/test_agent_management.py
@@ -0,0 +1,82 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 IBM Corp.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.api.network import base
+from tempest.common import tempest_fixtures as fixtures
+from tempest.test import attr
+
+
+class AgentManagementTestJSON(base.BaseAdminNetworkTest):
+ _interface = 'json'
+
+ @classmethod
+ def setUpClass(cls):
+ super(AgentManagementTestJSON, cls).setUpClass()
+ resp, body = cls.admin_client.list_agents()
+ agents = body['agents']
+ cls.agent = agents[0]
+
+ @attr(type='smoke')
+ def test_list_agent(self):
+ resp, body = self.admin_client.list_agents()
+ self.assertEqual('200', resp['status'])
+ agents = body['agents']
+ self.assertIn(self.agent, agents)
+
+ @attr(type='smoke')
+ def test_show_agent(self):
+ resp, body = self.admin_client.show_agent(self.agent['id'])
+ agent = body['agent']
+ self.assertEqual('200', resp['status'])
+ self.assertEqual(agent['id'], self.agent['id'])
+
+ @attr(type='smoke')
+ def test_update_agent_status(self):
+ origin_status = self.agent['admin_state_up']
+ # Try to update the 'admin_state_up' to the original
+ # one to avoid the negative effect.
+ agent_status = {'admin_state_up': origin_status}
+ resp, body = self.admin_client.update_agent(agent_id=self.agent['id'],
+ agent_info=agent_status)
+ updated_status = body['agent']['admin_state_up']
+ self.assertEqual('200', resp['status'])
+ self.assertEqual(origin_status, updated_status)
+
+ @attr(type='smoke')
+ def test_update_agent_description(self):
+ self.useFixture(fixtures.LockFixture('agent_description'))
+ description = 'description for update agent.'
+ agent_description = {'description': description}
+ resp, body = self.admin_client.update_agent(
+ agent_id=self.agent['id'],
+ agent_info=agent_description)
+ self.assertEqual('200', resp['status'])
+ self.addCleanup(self._restore_agent)
+ updated_description = body['agent']['description']
+ self.assertEqual(updated_description, description)
+
+ def _restore_agent(self):
+ """
+ Restore the agent description after update test.
+ """
+ description = self.agent['description'] or ''
+ origin_agent = {'description': description}
+ self.admin_client.update_agent(agent_id=self.agent['id'],
+ agent_info=origin_agent)
+
+
+class AgentManagementTestXML(AgentManagementTestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/network/admin/test_dhcp_agent_scheduler.py b/tempest/api/network/admin/test_dhcp_agent_scheduler.py
new file mode 100644
index 0000000..ac10e68
--- /dev/null
+++ b/tempest/api/network/admin/test_dhcp_agent_scheduler.py
@@ -0,0 +1,78 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 IBM Corp.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.api.network import base
+from tempest.test import attr
+
+
+class DHCPAgentSchedulersTestJSON(base.BaseAdminNetworkTest):
+ _interface = 'json'
+
+ @classmethod
+ def setUpClass(cls):
+ super(DHCPAgentSchedulersTestJSON, cls).setUpClass()
+ # Create a network and make sure it will be hosted by a
+ # dhcp agent.
+ cls.network = cls.create_network()
+ cls.subnet = cls.create_subnet(cls.network)
+ cls.cidr = cls.subnet['cidr']
+ cls.port = cls.create_port(cls.network)
+
+ @attr(type='smoke')
+ def test_list_dhcp_agent_hosting_network(self):
+ resp, body = self.admin_client.list_dhcp_agent_hosting_network(
+ self.network['id'])
+ self.assertEqual(resp['status'], '200')
+
+ @attr(type='smoke')
+ def test_list_networks_hosted_by_one_dhcp(self):
+ resp, body = self.admin_client.list_dhcp_agent_hosting_network(
+ self.network['id'])
+ agents = body['agents']
+ self.assertIsNotNone(agents)
+ agent = agents[0]
+ self.assertTrue(self._check_network_in_dhcp_agent(
+ self.network['id'], agent))
+
+ def _check_network_in_dhcp_agent(self, network_id, agent):
+ network_ids = []
+ resp, body = self.admin_client.list_networks_hosted_by_one_dhcp_agent(
+ agent['id'])
+ self.assertEqual(resp['status'], '200')
+ networks = body['networks']
+ for network in networks:
+ network_ids.append(network['id'])
+ return network_id in network_ids
+
+ @attr(type='smoke')
+ def test_remove_network_from_dhcp_agent(self):
+ resp, body = self.admin_client.list_dhcp_agent_hosting_network(
+ self.network['id'])
+ agents = body['agents']
+ self.assertIsNotNone(agents)
+ # Get an agent.
+ agent = agents[0]
+ network_id = self.network['id']
+ resp, body = self.admin_client.remove_network_from_dhcp_agent(
+ agent_id=agent['id'],
+ network_id=network_id)
+ self.assertEqual(resp['status'], '204')
+ self.assertFalse(self._check_network_in_dhcp_agent(
+ network_id, agent))
+
+
+class DHCPAgentSchedulersTestXML(DHCPAgentSchedulersTestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/network/admin/test_l3_agent_scheduler.py b/tempest/api/network/admin/test_l3_agent_scheduler.py
index 9c187fd..64ab051 100644
--- a/tempest/api/network/admin/test_l3_agent_scheduler.py
+++ b/tempest/api/network/admin/test_l3_agent_scheduler.py
@@ -16,7 +16,7 @@
from tempest.api.network import base
from tempest.common.utils import data_utils
-from tempest.test import attr
+from tempest import test
class L3AgentSchedulerJSON(base.BaseAdminNetworkTest):
@@ -36,8 +36,11 @@
@classmethod
def setUpClass(cls):
super(L3AgentSchedulerJSON, cls).setUpClass()
+ if not test.is_extension_enabled('l3_agent_scheduler', 'network'):
+ msg = "L3 Agent Scheduler Extension not enabled."
+ raise cls.skipException(msg)
- @attr(type='smoke')
+ @test.attr(type='smoke')
def test_list_routers_on_l3_agent(self):
resp, body = self.admin_client.list_agents()
agents = body['agents']
@@ -48,7 +51,7 @@
agent['id'])
self.assertEqual('200', resp['status'])
- @attr(type='smoke')
+ @test.attr(type='smoke')
def test_list_l3_agents_hosting_router(self):
name = data_utils.rand_name('router-')
resp, router = self.client.create_router(name)
diff --git a/tempest/api/network/base.py b/tempest/api/network/base.py
index b222ae3..064eaff 100644
--- a/tempest/api/network/base.py
+++ b/tempest/api/network/base.py
@@ -65,12 +65,16 @@
cls.members = []
cls.health_monitors = []
cls.vpnservices = []
+ cls.ikepolicies = []
@classmethod
def tearDownClass(cls):
+ # Clean up ike policies
+ for ikepolicy in cls.ikepolicies:
+ cls.client.delete_ikepolicy(ikepolicy['id'])
# Clean up vpn services
for vpnservice in cls.vpnservices:
- cls.client.delete_vpn_service(vpnservice['id'])
+ cls.client.delete_vpnservice(vpnservice['id'])
# Clean up routers
for router in cls.routers:
resp, body = cls.client.list_router_interfaces(router['id'])
@@ -209,13 +213,21 @@
@classmethod
def create_vpnservice(cls, subnet_id, router_id):
"""Wrapper utility that returns a test vpn service."""
- resp, body = cls.client.create_vpn_service(
+ resp, body = cls.client.create_vpnservice(
subnet_id, router_id, admin_state_up=True,
name=data_utils.rand_name("vpnservice-"))
vpnservice = body['vpnservice']
cls.vpnservices.append(vpnservice)
return vpnservice
+ @classmethod
+ def create_ikepolicy(cls, name):
+ """Wrapper utility that returns a test ike policy."""
+ resp, body = cls.client.create_ikepolicy(name)
+ ikepolicy = body['ikepolicy']
+ cls.ikepolicies.append(ikepolicy)
+ return ikepolicy
+
class BaseAdminNetworkTest(BaseNetworkTest):
diff --git a/tempest/api/network/common.py b/tempest/api/network/common.py
index ab19fa8..528a204 100644
--- a/tempest/api/network/common.py
+++ b/tempest/api/network/common.py
@@ -47,6 +47,9 @@
def delete(self):
raise NotImplemented()
+ def __hash__(self):
+ return id(self)
+
class DeletableNetwork(DeletableResource):
@@ -86,6 +89,23 @@
class DeletableFloatingIp(DeletableResource):
+ def update(self, *args, **kwargs):
+ result = self.client.update_floatingip(floatingip=self.id,
+ body=dict(
+ floatingip=dict(*args,
+ **kwargs)
+ ))
+ super(DeletableFloatingIp, self).update(**result['floatingip'])
+
+ def __repr__(self):
+ return '<%s addr="%s">' % (self.__class__.__name__,
+ self.floating_ip_address)
+
+ def __str__(self):
+ return '<"FloatingIP" addr="%s" id="%s">' % (self.__class__.__name__,
+ self.floating_ip_address,
+ self.id)
+
def delete(self):
self.client.delete_floatingip(self.id)
diff --git a/tempest/api/network/test_extensions.py b/tempest/api/network/test_extensions.py
index 1b27d1b..9d872f9 100644
--- a/tempest/api/network/test_extensions.py
+++ b/tempest/api/network/test_extensions.py
@@ -17,7 +17,7 @@
from tempest.api.network import base
-from tempest.test import attr
+from tempest import test
class ExtensionsTestJSON(base.BaseNetworkTest):
@@ -38,7 +38,7 @@
def setUpClass(cls):
super(ExtensionsTestJSON, cls).setUpClass()
- @attr(type='smoke')
+ @test.attr(type='smoke')
def test_list_show_extensions(self):
# List available extensions for the tenant
expected_alias = ['security-group', 'l3_agent_scheduler',
@@ -55,7 +55,7 @@
ext_name = ext['name']
ext_alias = ext['alias']
actual_alias.append(ext['alias'])
- resp, ext_details = self.client.show_extension_details(ext_alias)
+ resp, ext_details = self.client.show_extension(ext_alias)
self.assertEqual('200', resp['status'])
ext_details = ext_details['extension']
@@ -70,9 +70,11 @@
self.assertEqual(ext_details['alias'], ext_alias)
self.assertEqual(ext_details, ext)
# Verify if expected extensions are present in the actual list
- # of extensions returned
+ # of extensions returned, but only for those that have been
+ # enabled via configuration
for e in expected_alias:
- self.assertIn(e, actual_alias)
+ if test.is_extension_enabled(e, 'network'):
+ self.assertIn(e, actual_alias)
class ExtensionsTestXML(ExtensionsTestJSON):
diff --git a/tempest/api/network/test_floating_ips.py b/tempest/api/network/test_floating_ips.py
index 35d4fa8..3a41f4f 100644
--- a/tempest/api/network/test_floating_ips.py
+++ b/tempest/api/network/test_floating_ips.py
@@ -68,10 +68,10 @@
def _delete_floating_ip(self, floating_ip_id):
# Deletes a floating IP and verifies if it is deleted or not
- resp, _ = self.client.delete_floating_ip(floating_ip_id)
+ resp, _ = self.client.delete_floatingip(floating_ip_id)
self.assertEqual(204, resp.status)
# Asserting that the floating_ip is not found in list after deletion
- resp, floating_ips = self.client.list_floating_ips()
+ resp, floating_ips = self.client.list_floatingips()
floatingip_id_list = list()
for f in floating_ips['floatingips']:
floatingip_id_list.append(f['id'])
@@ -106,7 +106,7 @@
self.assertEqual(show_floating_ip['port_id'], self.port[0]['id'])
# Verify the floating ip exists in the list of all floating_ips
- resp, floating_ips = self.client.list_floating_ips()
+ resp, floating_ips = self.client.list_floatingips()
self.assertEqual('200', resp['status'])
floatingip_id_list = list()
for f in floating_ips['floatingips']:
diff --git a/tempest/api/network/test_quotas.py b/tempest/api/network/test_quotas.py
index f7ba3cb..8b77637 100644
--- a/tempest/api/network/test_quotas.py
+++ b/tempest/api/network/test_quotas.py
@@ -74,12 +74,13 @@
resp, non_default_quotas = self.admin_client.list_quotas()
self.assertEqual('200', resp['status'])
found = False
- for qs in non_default_quotas:
+ for qs in non_default_quotas['quotas']:
if qs['tenant_id'] == tenant_id:
found = True
self.assertTrue(found)
# Confirm from APi quotas were changed as requested for tenant
resp, quota_set = self.admin_client.show_quotas(tenant_id)
+ quota_set = quota_set['quota']
self.assertEqual('200', resp['status'])
self.assertEqual(0, quota_set['network'])
self.assertEqual(0, quota_set['security_group'])
@@ -88,5 +89,5 @@
self.assertEqual('204', resp['status'])
resp, non_default_quotas = self.admin_client.list_quotas()
self.assertEqual('200', resp['status'])
- for q in non_default_quotas:
+ for q in non_default_quotas['quotas']:
self.assertNotEqual(tenant_id, q['tenant_id'])
diff --git a/tempest/api/network/test_routers.py b/tempest/api/network/test_routers.py
index 3cbe23f..06e07bb 100644
--- a/tempest/api/network/test_routers.py
+++ b/tempest/api/network/test_routers.py
@@ -15,6 +15,8 @@
# License for the specific language governing permissions and limitations
# under the License.
+import netaddr
+
from tempest.api.network import base
from tempest.common.utils import data_utils
from tempest.test import attr
@@ -229,3 +231,22 @@
{'network_id': self.network_cfg.public_network_id,
'enable_snat': False})
self._verify_gateway_port(router['id'])
+
+ @attr(type='smoke')
+ def test_update_extra_route(self):
+ self.network = self.create_network()
+ self.name = self.network['name']
+ self.subnet = self.create_subnet(self.network)
+ # Add router interface with subnet id
+ self.router = self.create_router(data_utils.rand_name('router-'), True)
+ self.create_router_interface(self.router['id'], self.subnet['id'])
+ self.addCleanup(
+ self._delete_extra_routes,
+ self.router['id'])
+ # Update router extra route
+ cidr = netaddr.IPNetwork(self.subnet['cidr'])
+ resp, extra_route = self.client.update_extra_routes(
+ self.router['id'], str(cidr[0]), str(self.subnet['cidr']))
+
+ def _delete_extra_routes(self, router_id):
+ resp, _ = self.client.delete_extra_routes(router_id)
diff --git a/tempest/api/network/test_security_groups_negative.py b/tempest/api/network/test_security_groups_negative.py
index cb0c247..32f4c95 100644
--- a/tempest/api/network/test_security_groups_negative.py
+++ b/tempest/api/network/test_security_groups_negative.py
@@ -15,10 +15,11 @@
# License for the specific language governing permissions and limitations
# under the License.
+import uuid
+
from tempest.api.network import base_security_groups as base
from tempest import exceptions
from tempest.test import attr
-import uuid
class NegativeSecGroupTest(base.BaseSecGroupTest):
@@ -74,6 +75,22 @@
port_range_max=pmax)
self.assertIn(msg, str(ex))
+ @attr(type=['negative', 'smoke'])
+ def test_create_additional_default_security_group_fails(self):
+ # Create security group named 'default', it should be failed.
+ name = 'default'
+ self.assertRaises(exceptions.Conflict,
+ self.client.create_security_group,
+ name)
+
+ @attr(type=['negative', 'smoke'])
+ def test_create_security_group_rule_with_non_existent_security_group(self):
+ # Create security group rules with not existing security group.
+ non_existent_sg = str(uuid.uuid4())
+ self.assertRaises(exceptions.NotFound,
+ self.client.create_security_group_rule,
+ non_existent_sg)
+
class NegativeSecGroupTestXML(NegativeSecGroupTest):
_interface = 'xml'
diff --git a/tempest/api/network/test_vpnaas_extensions.py b/tempest/api/network/test_vpnaas_extensions.py
index 9cbc7ac..fc3b1d9 100644
--- a/tempest/api/network/test_vpnaas_extensions.py
+++ b/tempest/api/network/test_vpnaas_extensions.py
@@ -32,6 +32,7 @@
Create VPN Services
Update VPN Services
Delete VPN Services
+ List, Show, Create, Delete, and Update IKE policy
"""
@classmethod
@@ -39,15 +40,35 @@
super(VPNaaSJSON, cls).setUpClass()
cls.network = cls.create_network()
cls.subnet = cls.create_subnet(cls.network)
- cls.router = cls.create_router(data_utils.rand_name("router-"))
+ cls.router = cls.create_router(
+ data_utils.rand_name("router-"),
+ external_network_id=cls.network_cfg.public_network_id)
cls.create_router_interface(cls.router['id'], cls.subnet['id'])
cls.vpnservice = cls.create_vpnservice(cls.subnet['id'],
cls.router['id'])
+ cls.ikepolicy = cls.create_ikepolicy(
+ data_utils.rand_name("ike-policy-"))
+
+ def _delete_ike_policy(self, ike_policy_id):
+ # Deletes a ike policy and verifies if it is deleted or not
+ ike_list = list()
+ resp, all_ike = self.client.list_ikepolicies()
+ for ike in all_ike['ikepolicies']:
+ ike_list.append(ike['id'])
+ if ike_policy_id in ike_list:
+ resp, _ = self.client.delete_ikepolicy(ike_policy_id)
+ self.assertEqual(204, resp.status)
+ # Asserting that the policy is not found in list after deletion
+ resp, ikepolicies = self.client.list_ikepolicies()
+ ike_id_list = list()
+ for i in ikepolicies['ikepolicies']:
+ ike_id_list.append(i['id'])
+ self.assertNotIn(ike_policy_id, ike_id_list)
@attr(type='smoke')
def test_list_vpn_services(self):
# Verify the VPN service exists in the list of all VPN services
- resp, body = self.client.list_vpn_services()
+ resp, body = self.client.list_vpnservices()
self.assertEqual('200', resp['status'])
vpnservices = body['vpnservices']
self.assertIn(self.vpnservice['id'], [v['id'] for v in vpnservices])
@@ -56,14 +77,14 @@
def test_create_update_delete_vpn_service(self):
# Creates a VPN service
name = data_utils.rand_name('vpn-service-')
- resp, body = self.client.create_vpn_service(self.subnet['id'],
- self.router['id'],
- name=name,
- admin_state_up=True)
+ resp, body = self.client.create_vpnservice(self.subnet['id'],
+ self.router['id'],
+ name=name,
+ admin_state_up=True)
self.assertEqual('201', resp['status'])
vpnservice = body['vpnservice']
# Assert if created vpnservices are not found in vpnservices list
- resp, body = self.client.list_vpn_services()
+ resp, body = self.client.list_vpnservices()
vpn_services = [vs['id'] for vs in body['vpnservices']]
self.assertIsNotNone(vpnservice['id'])
self.assertIn(vpnservice['id'], vpn_services)
@@ -74,17 +95,17 @@
# should be "ACTIVE" not "PENDING*"
# Verification of vpn service delete
- resp, body = self.client.delete_vpn_service(vpnservice['id'])
+ resp, body = self.client.delete_vpnservice(vpnservice['id'])
self.assertEqual('204', resp['status'])
# Asserting if vpn service is found in the list after deletion
- resp, body = self.client.list_vpn_services()
+ resp, body = self.client.list_vpnservices()
vpn_services = [vs['id'] for vs in body['vpnservices']]
self.assertNotIn(vpnservice['id'], vpn_services)
@attr(type='smoke')
def test_show_vpn_service(self):
# Verifies the details of a vpn service
- resp, body = self.client.show_vpn_service(self.vpnservice['id'])
+ resp, body = self.client.show_vpnservice(self.vpnservice['id'])
self.assertEqual('200', resp['status'])
vpnservice = body['vpnservice']
self.assertEqual(self.vpnservice['id'], vpnservice['id'])
@@ -94,3 +115,59 @@
self.assertEqual(self.vpnservice['router_id'], vpnservice['router_id'])
self.assertEqual(self.vpnservice['subnet_id'], vpnservice['subnet_id'])
self.assertEqual(self.vpnservice['tenant_id'], vpnservice['tenant_id'])
+
+ @attr(type='smoke')
+ def test_list_ike_policies(self):
+ # Verify the ike policy exists in the list of all IKE policies
+ resp, body = self.client.list_ikepolicies()
+ self.assertEqual('200', resp['status'])
+ ikepolicies = body['ikepolicies']
+ self.assertIn(self.ikepolicy['id'], [i['id'] for i in ikepolicies])
+
+ @attr(type='smoke')
+ def test_create_update_delete_ike_policy(self):
+ # Creates a IKE policy
+ name = data_utils.rand_name('ike-policy-')
+ resp, body = (self.client.create_ikepolicy(
+ name,
+ ike_version="v1",
+ encryption_algorithm="aes-128",
+ auth_algorithm="sha1"))
+ self.assertEqual('201', resp['status'])
+ ikepolicy = body['ikepolicy']
+ self.addCleanup(self._delete_ike_policy, ikepolicy['id'])
+ # Verification of ike policy update
+ description = "Updated ike policy"
+ new_ike = {'description': description, 'pfs': 'group5',
+ 'name': data_utils.rand_name("New-IKE-")}
+ resp, body = self.client.update_ikepolicy(ikepolicy['id'],
+ **new_ike)
+ self.assertEqual('200', resp['status'])
+ updated_ike_policy = body['ikepolicy']
+ self.assertEqual(updated_ike_policy['description'], description)
+ # Verification of ike policy delete
+ resp, body = self.client.delete_ikepolicy(ikepolicy['id'])
+ self.assertEqual('204', resp['status'])
+
+ @attr(type='smoke')
+ def test_show_ike_policy(self):
+ # Verifies the details of a ike policy
+ resp, body = self.client.show_ikepolicy(self.ikepolicy['id'])
+ self.assertEqual('200', resp['status'])
+ ikepolicy = body['ikepolicy']
+ self.assertEqual(self.ikepolicy['id'], ikepolicy['id'])
+ self.assertEqual(self.ikepolicy['name'], ikepolicy['name'])
+ self.assertEqual(self.ikepolicy['description'],
+ ikepolicy['description'])
+ self.assertEqual(self.ikepolicy['encryption_algorithm'],
+ ikepolicy['encryption_algorithm'])
+ self.assertEqual(self.ikepolicy['auth_algorithm'],
+ ikepolicy['auth_algorithm'])
+ self.assertEqual(self.ikepolicy['tenant_id'],
+ ikepolicy['tenant_id'])
+ self.assertEqual(self.ikepolicy['pfs'],
+ ikepolicy['pfs'])
+ self.assertEqual(self.ikepolicy['phase1_negotiation_mode'],
+ ikepolicy['phase1_negotiation_mode'])
+ self.assertEqual(self.ikepolicy['ike_version'],
+ ikepolicy['ike_version'])
diff --git a/tempest/api/object_storage/test_account_quotas.py b/tempest/api/object_storage/test_account_quotas.py
index b4128e2..c1b3391 100644
--- a/tempest/api/object_storage/test_account_quotas.py
+++ b/tempest/api/object_storage/test_account_quotas.py
@@ -107,6 +107,7 @@
object_name, data)
self.assertEqual(resp["status"], "201")
+ self.assertHeaders(resp, 'Object', 'PUT')
@testtools.skipIf(not accounts_quotas_available,
"Account Quotas middleware not available")
@@ -141,6 +142,7 @@
headers, "")
self.assertEqual(resp["status"], "204")
+ self.assertHeaders(resp, 'Account', 'POST')
@testtools.skipIf(not accounts_quotas_available,
"Account Quotas middleware not available")
diff --git a/tempest/api/object_storage/test_container_acl.py b/tempest/api/object_storage/test_container_acl.py
index 18000b9..b2dc20f 100644
--- a/tempest/api/object_storage/test_container_acl.py
+++ b/tempest/api/object_storage/test_container_acl.py
@@ -86,6 +86,7 @@
resp, _ = self.object_client.create_object(
self.container_name, object_name, 'data')
self.assertEqual(resp['status'], '201')
+ self.assertHeaders(resp, 'Object', 'PUT')
# trying to get object with non authorized user token
self.assertRaises(exceptions.Unauthorized,
self.custom_object_client.get_object,
@@ -100,6 +101,7 @@
resp, _ = self.object_client.create_object(
self.container_name, object_name, 'data')
self.assertEqual(resp['status'], '201')
+ self.assertHeaders(resp, 'Object', 'PUT')
# trying to delete object with non-authorized user token
self.assertRaises(exceptions.Unauthorized,
self.custom_object_client.delete_object,
@@ -115,11 +117,13 @@
self.container_name, metadata=cont_headers,
metadata_prefix='')
self.assertIn(int(resp_meta['status']), HTTP_SUCCESS)
+ self.assertHeaders(resp_meta, 'Container', 'POST')
# create object
object_name = data_utils.rand_name(name='Object')
resp, _ = self.object_client.create_object(self.container_name,
object_name, 'data')
self.assertEqual(resp['status'], '201')
+ self.assertHeaders(resp, 'Object', 'PUT')
# Trying to read the object without rights
self.assertRaises(exceptions.Unauthorized,
self.custom_object_client.get_object,
@@ -135,6 +139,7 @@
self.container_name, metadata=cont_headers,
metadata_prefix='')
self.assertIn(int(resp_meta['status']), HTTP_SUCCESS)
+ self.assertHeaders(resp_meta, 'Container', 'POST')
# Trying to write the object without rights
object_name = data_utils.rand_name(name='Object')
self.assertRaises(exceptions.Unauthorized,
@@ -153,16 +158,19 @@
self.container_name, metadata=cont_headers,
metadata_prefix='')
self.assertIn(int(resp_meta['status']), HTTP_SUCCESS)
+ self.assertHeaders(resp_meta, 'Container', 'POST')
# create object
object_name = data_utils.rand_name(name='Object')
resp, _ = self.object_client.create_object(self.container_name,
object_name, 'data')
self.assertEqual(resp['status'], '201')
+ self.assertHeaders(resp, 'Object', 'PUT')
# Trying to read the object with rights
resp, _ = self.custom_object_client.get_object(
self.container_name, object_name,
metadata=self.custom_headers)
self.assertIn(int(resp['status']), HTTP_SUCCESS)
+ self.assertHeaders(resp, 'Object', 'GET')
@attr(type='smoke')
def test_write_object_with_rights(self):
@@ -174,6 +182,7 @@
self.container_name, metadata=cont_headers,
metadata_prefix='')
self.assertIn(int(resp_meta['status']), HTTP_SUCCESS)
+ self.assertHeaders(resp_meta, 'Container', 'POST')
# Trying to write the object with rights
object_name = data_utils.rand_name(name='Object')
resp, _ = self.custom_object_client.create_object(
@@ -181,6 +190,7 @@
object_name, 'data',
metadata=self.custom_headers)
self.assertIn(int(resp['status']), HTTP_SUCCESS)
+ self.assertHeaders(resp, 'Object', 'PUT')
@attr(type=['negative', 'smoke'])
def test_write_object_without_write_rights(self):
@@ -193,6 +203,7 @@
self.container_name, metadata=cont_headers,
metadata_prefix='')
self.assertIn(int(resp_meta['status']), HTTP_SUCCESS)
+ self.assertHeaders(resp_meta, 'Container', 'POST')
# Trying to write the object without write rights
object_name = data_utils.rand_name(name='Object')
self.assertRaises(exceptions.Unauthorized,
@@ -212,11 +223,13 @@
self.container_name, metadata=cont_headers,
metadata_prefix='')
self.assertIn(int(resp_meta['status']), HTTP_SUCCESS)
+ self.assertHeaders(resp_meta, 'Container', 'POST')
# create object
object_name = data_utils.rand_name(name='Object')
resp, _ = self.object_client.create_object(self.container_name,
object_name, 'data')
self.assertEqual(resp['status'], '201')
+ self.assertHeaders(resp, 'Object', 'PUT')
# Trying to delete the object without write rights
self.assertRaises(exceptions.Unauthorized,
self.custom_object_client.delete_object,
diff --git a/tempest/api/object_storage/test_container_quotas.py b/tempest/api/object_storage/test_container_quotas.py
index 04536fe..c7b5e28 100644
--- a/tempest/api/object_storage/test_container_quotas.py
+++ b/tempest/api/object_storage/test_container_quotas.py
@@ -69,6 +69,7 @@
resp, _ = self.object_client.create_object(
self.container_name, object_name, data)
self.assertIn(int(resp['status']), HTTP_SUCCESS)
+ self.assertHeaders(resp, 'Object', 'PUT')
nafter = self._get_bytes_used()
self.assertEqual(nbefore + len(data), nafter)
diff --git a/tempest/api/object_storage/test_container_staticweb.py b/tempest/api/object_storage/test_container_staticweb.py
index 94048f7..9f9abd8 100644
--- a/tempest/api/object_storage/test_container_staticweb.py
+++ b/tempest/api/object_storage/test_container_staticweb.py
@@ -15,8 +15,9 @@
# under the License.
from tempest.api.object_storage import base
+from tempest.common import custom_matchers
from tempest.common.utils import data_utils
-from tempest.test import attr, HTTP_SUCCESS
+from tempest import test
class StaticWebTest(base.BaseObjectTest):
@@ -48,7 +49,7 @@
cls.data.teardown_all()
super(StaticWebTest, cls).tearDownClass()
- @attr('gate')
+ @test.attr('gate')
def test_web_index(self):
headers = {'web-index': self.object_name}
@@ -59,7 +60,9 @@
# we should retrieve the self.object_name file
resp, body = self.custom_account_client.request("GET",
self.container_name)
- self.assertIn(int(resp['status']), HTTP_SUCCESS)
+ self.assertIn(int(resp['status']), test.HTTP_SUCCESS)
+ # This request is equivalent to GET object
+ self.assertHeaders(resp, 'Object', 'GET')
self.assertEqual(body, self.object_data)
# clean up before exiting
@@ -70,7 +73,7 @@
self.container_name)
self.assertNotIn('x-container-meta-web-index', body)
- @attr('gate')
+ @test.attr('gate')
def test_web_listing(self):
headers = {'web-listings': 'true'}
@@ -81,7 +84,16 @@
# we should retrieve a listing of objects
resp, body = self.custom_account_client.request("GET",
self.container_name)
- self.assertIn(int(resp['status']), HTTP_SUCCESS)
+ self.assertIn(int(resp['status']), test.HTTP_SUCCESS)
+ # The target of the request is not any Swift resource. Therefore, the
+ # existence of response header is checked without a custom matcher.
+ self.assertIn('content-length', resp)
+ self.assertIn('content-type', resp)
+ self.assertIn('x-trans-id', resp)
+ self.assertIn('date', resp)
+ # Check only the format of common headers with custom matcher
+ self.assertThat(resp, custom_matchers.AreAllWellFormatted())
+
self.assertIn(self.object_name, body)
# clean up before exiting
@@ -92,7 +104,7 @@
self.container_name)
self.assertNotIn('x-container-meta-web-listings', body)
- @attr('gate')
+ @test.attr('gate')
def test_web_listing_css(self):
headers = {'web-listings': 'true',
'web-listings-css': 'listings.css'}
@@ -104,12 +116,12 @@
# we should retrieve a listing of objects
resp, body = self.custom_account_client.request("GET",
self.container_name)
- self.assertIn(int(resp['status']), HTTP_SUCCESS)
+ self.assertIn(int(resp['status']), test.HTTP_SUCCESS)
self.assertIn(self.object_name, body)
css = '<link rel="stylesheet" type="text/css" href="listings.css" />'
self.assertIn(css, body)
- @attr('gate')
+ @test.attr('gate')
def test_web_error(self):
headers = {'web-listings': 'true',
'web-error': self.object_name}
diff --git a/tempest/api/object_storage/test_crossdomain.py b/tempest/api/object_storage/test_crossdomain.py
index 0ae7e46..51ecd16 100644
--- a/tempest/api/object_storage/test_crossdomain.py
+++ b/tempest/api/object_storage/test_crossdomain.py
@@ -18,6 +18,7 @@
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
@@ -80,3 +81,12 @@
self.assertIn(int(resp['status']), HTTP_SUCCESS)
self.assertTrue(body.startswith(self.xml_start) and
body.endswith(self.xml_end))
+
+ # The target of the request is not any Swift resource. Therefore, the
+ # existence of response header is checked without a custom matcher.
+ self.assertIn('content-length', resp)
+ self.assertIn('content-type', resp)
+ self.assertIn('x-trans-id', resp)
+ self.assertIn('date', resp)
+ # Check only the format of common headers with custom matcher
+ self.assertThat(resp, custom_matchers.AreAllWellFormatted())
diff --git a/tempest/api/object_storage/test_healthcheck.py b/tempest/api/object_storage/test_healthcheck.py
index 798ea4f..7bbdd1e 100644
--- a/tempest/api/object_storage/test_healthcheck.py
+++ b/tempest/api/object_storage/test_healthcheck.py
@@ -19,6 +19,7 @@
from tempest.api.object_storage import base
from tempest import clients
+from tempest.common import custom_matchers
from tempest.test import attr
from tempest.test import HTTP_SUCCESS
@@ -63,3 +64,12 @@
# The status is expected to be 200
self.assertIn(int(resp['status']), HTTP_SUCCESS)
+
+ # The target of the request is not any Swift resource. Therefore, the
+ # existence of response header is checked without a custom matcher.
+ self.assertIn('content-length', resp)
+ self.assertIn('content-type', resp)
+ self.assertIn('x-trans-id', resp)
+ self.assertIn('date', resp)
+ # Check only the format of common headers with custom matcher
+ self.assertThat(resp, custom_matchers.AreAllWellFormatted())
diff --git a/tempest/api/object_storage/test_object_expiry.py b/tempest/api/object_storage/test_object_expiry.py
index 6fc3853..4958f70 100644
--- a/tempest/api/object_storage/test_object_expiry.py
+++ b/tempest/api/object_storage/test_object_expiry.py
@@ -62,10 +62,13 @@
self.object_client.list_object_metadata(self.container_name,
object_name)
self.assertEqual(resp['status'], '200')
+ self.assertHeaders(resp, 'Object', 'HEAD')
self.assertIn('x-delete-at', resp)
resp, body = self.object_client.get_object(self.container_name,
object_name)
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
diff --git a/tempest/api/object_storage/test_object_formpost.py b/tempest/api/object_storage/test_object_formpost.py
index 63393e4..817c892 100644
--- a/tempest/api/object_storage/test_object_formpost.py
+++ b/tempest/api/object_storage/test_object_formpost.py
@@ -109,11 +109,13 @@
resp, body = self.object_client.http_obj.request(url, "POST",
body, headers=headers)
self.assertIn(int(resp['status']), HTTP_SUCCESS)
+ self.assertHeaders(resp, "Object", "POST")
# Ensure object is available
resp, body = self.object_client.get("%s/%s%s" % (
self.container_name, self.object_name, "testfile"))
self.assertIn(int(resp['status']), HTTP_SUCCESS)
+ self.assertHeaders(resp, "Object", "GET")
self.assertEqual(body, "hello world")
@attr(type=['gate', 'negative'])
diff --git a/tempest/api/object_storage/test_object_slo.py b/tempest/api/object_storage/test_object_slo.py
new file mode 100644
index 0000000..997120a
--- /dev/null
+++ b/tempest/api/object_storage/test_object_slo.py
@@ -0,0 +1,193 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 NTT Corporation
+#
+# 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 hashlib
+import json
+import re
+
+from tempest.api.object_storage import base
+from tempest.common import custom_matchers
+from tempest.common.utils import data_utils
+from tempest import exceptions
+from tempest import test
+
+# Each segment, except for the final one, must be at least 1 megabyte
+MIN_SEGMENT_SIZE = 1024 * 1024
+
+
+class ObjectSloTest(base.BaseObjectTest):
+
+ def setUp(self):
+ super(ObjectSloTest, self).setUp()
+ self.container_name = data_utils.rand_name(name='TestContainer')
+ self.container_client.create_container(self.container_name)
+ self.objects = []
+
+ def tearDown(self):
+ for obj in self.objects:
+ try:
+ self.object_client.delete_object(
+ self.container_name,
+ obj)
+ except exceptions.NotFound:
+ pass
+ self.container_client.delete_container(self.container_name)
+ super(ObjectSloTest, self).tearDown()
+
+ def _create_object(self, container_name, object_name, data, params=None):
+ resp, _ = self.object_client.create_object(container_name,
+ object_name,
+ data,
+ params)
+ self.objects.append(object_name)
+
+ return resp
+
+ def _create_manifest(self):
+ # Create a manifest file for SLO uploading
+ object_name = data_utils.rand_name(name='TestObject')
+ object_name_base_1 = object_name + '_01'
+ object_name_base_2 = object_name + '_02'
+ data_size = MIN_SEGMENT_SIZE
+ self.data = data_utils.arbitrary_string(data_size)
+ self._create_object(self.container_name,
+ object_name_base_1,
+ self.data)
+ self._create_object(self.container_name,
+ object_name_base_2,
+ self.data)
+
+ path_object_1 = '/%s/%s' % (self.container_name,
+ object_name_base_1)
+ path_object_2 = '/%s/%s' % (self.container_name,
+ object_name_base_2)
+ data_manifest = [{'path': path_object_1,
+ 'etag': hashlib.md5(self.data).hexdigest(),
+ 'size_bytes': data_size},
+ {'path': path_object_2,
+ 'etag': hashlib.md5(self.data).hexdigest(),
+ 'size_bytes': data_size}]
+
+ return json.dumps(data_manifest)
+
+ def _create_large_object(self):
+ # Create a large object for preparation of testing various SLO
+ # features
+ manifest = self._create_manifest()
+
+ params = {'multipart-manifest': 'put'}
+ object_name = data_utils.rand_name(name='TestObject')
+ self._create_object(self.container_name,
+ object_name,
+ manifest,
+ params)
+ return object_name
+
+ def _assertHeadersSLO(self, resp, method):
+ # Check the existence of common headers with custom matcher
+ self.assertThat(resp, custom_matchers.ExistsAllResponseHeaders(
+ 'Object', method))
+ # When sending GET or HEAD requests to SLO the response contains
+ # 'X-Static-Large-Object' header
+ if method in ('GET', 'HEAD'):
+ self.assertIn('x-static-large-object', resp)
+
+ # Check common headers for all HTTP methods
+ self.assertTrue(re.match("^tx[0-9a-f]*-[0-9a-f]*$",
+ resp['x-trans-id']))
+ self.assertTrue(resp['content-length'].isdigit())
+ self.assertNotEqual(len(resp['date']), 0)
+ # Etag value of a large object is enclosed in double-quotations.
+ self.assertTrue(resp['etag'].startswith('\"'))
+ self.assertTrue(resp['etag'].endswith('\"'))
+ self.assertTrue(resp['etag'].strip('\"').isalnum())
+ # Check header formats for a specific method
+ if method in ('GET', 'HEAD'):
+ self.assertTrue(re.match("^\d+\.?\d*\Z", resp['x-timestamp']))
+ self.assertNotEqual(len(resp['content-type']), 0)
+ self.assertEqual(resp['accept-ranges'], 'bytes')
+ self.assertEqual(resp['x-static-large-object'], 'True')
+
+ @test.attr(type='gate')
+ def test_upload_manifest(self):
+ # create static large object from multipart manifest
+ manifest = self._create_manifest()
+
+ params = {'multipart-manifest': 'put'}
+ object_name = data_utils.rand_name(name='TestObject')
+ resp = self._create_object(self.container_name,
+ object_name,
+ manifest,
+ params)
+
+ self.assertIn(int(resp['status']), test.HTTP_SUCCESS)
+ self._assertHeadersSLO(resp, 'PUT')
+
+ @test.attr(type='gate')
+ def test_list_large_object_metadata(self):
+ # list static large object metadata using multipart manifest
+ object_name = self._create_large_object()
+
+ resp, body = self.object_client.list_object_metadata(
+ self.container_name,
+ object_name)
+
+ self.assertIn(int(resp['status']), test.HTTP_SUCCESS)
+ self._assertHeadersSLO(resp, 'HEAD')
+
+ @test.attr(type='gate')
+ def test_retrieve_large_object(self):
+ # list static large object using multipart manifest
+ object_name = self._create_large_object()
+
+ resp, body = self.object_client.get_object(
+ self.container_name,
+ object_name)
+
+ self.assertIn(int(resp['status']), test.HTTP_SUCCESS)
+ self._assertHeadersSLO(resp, 'GET')
+
+ sum_data = self.data + self.data
+ self.assertEqual(body, sum_data)
+
+ @test.attr(type='gate')
+ def test_delete_large_object(self):
+ # delete static large object using multipart manifest
+ object_name = self._create_large_object()
+
+ params_del = {'multipart-manifest': 'delete'}
+ resp, body = self.object_client.delete_object(
+ self.container_name,
+ object_name,
+ params=params_del)
+
+ self.assertIn(int(resp['status']), test.HTTP_SUCCESS)
+
+ # When deleting SLO using multipart manifest, the response contains
+ # not 'content-length' but 'transfer-encoding' header. This is the
+ # special case, therefore the existence of response headers is checked
+ # outside of custom matcher.
+ self.assertIn('transfer-encoding', resp)
+ self.assertIn('content-type', resp)
+ self.assertIn('x-trans-id', resp)
+ self.assertIn('date', resp)
+
+ # Check only the format of common headers with custom matcher
+ self.assertThat(resp, custom_matchers.AreAllWellFormatted())
+
+ resp, body = self.container_client.list_container_contents(
+ self.container_name)
+ self.assertEqual(int(resp['x-container-object-count']), 0)
diff --git a/tempest/api/object_storage/test_object_temp_url.py b/tempest/api/object_storage/test_object_temp_url.py
index c8ce57a..bb03932 100644
--- a/tempest/api/object_storage/test_object_temp_url.py
+++ b/tempest/api/object_storage/test_object_temp_url.py
@@ -113,13 +113,14 @@
# trying to get object using temp url within expiry time
resp, body = self.object_client.get_object_using_temp_url(url)
-
self.assertIn(int(resp['status']), HTTP_SUCCESS)
+ self.assertHeaders(resp, 'Object', 'GET')
self.assertEqual(body, self.data)
# Testing a HEAD on this Temp URL
resp, body = self.object_client.head(url)
self.assertIn(int(resp['status']), HTTP_SUCCESS)
+ self.assertHeaders(resp, 'Object', 'HEAD')
@attr(type='gate')
def test_get_object_using_temp_url_key_2(self):
@@ -157,10 +158,12 @@
url, new_data)
self.assertIn(int(resp['status']), 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.assertHeaders(resp, 'Object', 'HEAD')
# Validate that the content of the object has been modified
url = self._get_temp_url(self.container_name,
diff --git a/tempest/api/object_storage/test_object_version.py b/tempest/api/object_storage/test_object_version.py
index c47e1b6..d706eef 100644
--- a/tempest/api/object_storage/test_object_version.py
+++ b/tempest/api/object_storage/test_object_version.py
@@ -34,6 +34,7 @@
def assertContainer(self, container, count, byte, versioned):
resp, _ = self.container_client.list_container_metadata(container)
self.assertEqual(resp['status'], ('204'))
+ self.assertHeaders(resp, 'Container', 'HEAD')
header_value = resp.get('x-container-object-count', 'Missing Header')
self.assertEqual(header_value, count)
header_value = resp.get('x-container-bytes-used', 'Missing Header')
@@ -49,6 +50,7 @@
vers_container_name)
self.containers.append(vers_container_name)
self.assertIn(resp['status'], ('202', '201'))
+ self.assertHeaders(resp, 'Container', 'PUT')
self.assertContainer(vers_container_name, '0', '0', 'Missing Header')
base_container_name = data_utils.rand_name(name='TestBaseContainer')
@@ -59,6 +61,7 @@
metadata_prefix='')
self.containers.append(base_container_name)
self.assertIn(resp['status'], ('202', '201'))
+ self.assertHeaders(resp, 'Container', 'PUT')
self.assertContainer(base_container_name, '0', '0',
vers_container_name)
object_name = data_utils.rand_name(name='TestObject')
diff --git a/tempest/api/orchestration/stacks/test_non_empty_stack.py b/tempest/api/orchestration/stacks/test_non_empty_stack.py
index 35a7326..b19344d 100644
--- a/tempest/api/orchestration/stacks/test_non_empty_stack.py
+++ b/tempest/api/orchestration/stacks/test_non_empty_stack.py
@@ -28,75 +28,57 @@
template = """
HeatTemplateFormatVersion: '2012-12-12'
Description: |
- Template which creates single EC2 instance
+ Template which creates some simple resources
Parameters:
- KeyName:
+ trigger:
Type: String
- InstanceType:
- Type: String
- ImageId:
- Type: String
- Subnet:
- Type: String
+ Default: not_yet
Resources:
- SmokeServer:
- Type: AWS::EC2::Instance
+ fluffy:
+ Type: AWS::AutoScaling::LaunchConfiguration
Metadata:
- Name: SmokeServer
+ kittens:
+ - Tom
+ - Stinky
Properties:
- ImageId: {Ref: ImageId}
- InstanceType: {Ref: InstanceType}
- KeyName: {Ref: KeyName}
- SubnetId: {Ref: Subnet}
+ ImageId: not_used
+ InstanceType: not_used
UserData:
- Fn::Base64:
- Fn::Join:
- - ''
- - - '#!/bin/bash -v
-
- '
- - /opt/aws/bin/cfn-signal -e 0 -r "SmokeServer created" '
- - {Ref: WaitHandle}
- - '''
-
- '
- WaitHandle:
- Type: AWS::CloudFormation::WaitConditionHandle
- WaitCondition:
- Type: AWS::CloudFormation::WaitCondition
- DependsOn: SmokeServer
- Properties:
- Handle: {Ref: WaitHandle}
- Timeout: '600'
+ Fn::Replace:
+ - variable_a: {Ref: trigger}
+ variable_b: bee
+ - |
+ A == variable_a
+ B == variable_b
+Outputs:
+ fluffy:
+ Description: "fluffies irc nick"
+ Value:
+ Fn::Replace:
+ - nick: {Ref: fluffy}
+ - |
+ #nick
"""
@classmethod
def setUpClass(cls):
super(StacksTestJSON, cls).setUpClass()
- if not cls.orchestration_cfg.image_ref:
- raise cls.skipException("No image available to test")
cls.client = cls.orchestration_client
cls.stack_name = data_utils.rand_name('heat')
- keypair_name = (cls.orchestration_cfg.keypair_name or
- cls._create_keypair()['name'])
# create the stack
- subnet = cls._get_default_network()['subnets'][0]
cls.stack_identifier = cls.create_stack(
cls.stack_name,
cls.template,
parameters={
- 'KeyName': keypair_name,
- 'InstanceType': cls.orchestration_cfg.instance_type,
- 'ImageId': cls.orchestration_cfg.image_ref,
- 'Subnet': subnet
+ 'trigger': 'start'
})
cls.stack_id = cls.stack_identifier.split('/')[1]
- cls.resource_name = 'SmokeServer'
- cls.resource_type = 'AWS::EC2::Instance'
+ cls.resource_name = 'fluffy'
+ cls.resource_type = 'AWS::AutoScaling::LaunchConfiguration'
cls.client.wait_for_stack_status(cls.stack_id, 'CREATE_COMPLETE')
- @attr(type='slow')
+ @attr(type='gate')
def test_stack_list(self):
"""Created stack should be on the list of existing stacks."""
resp, stacks = self.client.list_stacks()
@@ -105,7 +87,7 @@
stacks_names = map(lambda stack: stack['stack_name'], stacks)
self.assertIn(self.stack_name, stacks_names)
- @attr(type='slow')
+ @attr(type='gate')
def test_stack_show(self):
"""Getting details about created stack should be possible."""
resp, stack = self.client.get_stack(self.stack_name)
@@ -114,7 +96,7 @@
self.assertEqual(self.stack_name, stack['stack_name'])
self.assertEqual(self.stack_id, stack['id'])
- @attr(type='slow')
+ @attr(type='gate')
def test_list_resources(self):
"""Getting list of created resources for the stack should be possible.
"""
@@ -128,7 +110,7 @@
resources)
self.assertIn(self.resource_type, resources_types)
- @attr(type='slow')
+ @attr(type='gate')
def test_show_resource(self):
"""Getting details about created resource should be possible."""
resp, resource = self.client.get_resource(self.stack_identifier,
@@ -137,7 +119,7 @@
self.assertEqual(self.resource_name, resource['logical_resource_id'])
self.assertEqual(self.resource_type, resource['resource_type'])
- @attr(type='slow')
+ @attr(type='gate')
def test_resource_metadata(self):
"""Getting metadata for created resource should be possible."""
resp, metadata = self.client.show_resource_metadata(
@@ -145,9 +127,9 @@
self.resource_name)
self.assertEqual('200', resp['status'])
self.assertIsInstance(metadata, dict)
- self.assertEqual(self.resource_name, metadata.get('Name', None))
+ self.assertEqual(['Tom', 'Stinky'], metadata.get('kittens', None))
- @attr(type='slow')
+ @attr(type='gate')
def test_list_events(self):
"""Getting list of created events for the stack should be possible."""
resp, events = self.client.list_events(self.stack_identifier)
@@ -157,7 +139,7 @@
self.assertIn('CREATE_IN_PROGRESS', resource_statuses)
self.assertIn('CREATE_COMPLETE', resource_statuses)
- @attr(type='slow')
+ @attr(type='gate')
def test_show_event(self):
"""Getting details about existing event should be possible."""
resp, events = self.client.list_resource_events(self.stack_identifier,
@@ -168,7 +150,7 @@
resp, event = self.client.show_event(self.stack_identifier,
self.resource_name, event_id)
self.assertEqual('200', resp['status'])
- self.assertEqual('CREATE_IN_PROGRESS', event['resource_status'])
+ self.assertIsInstance(event, dict)
+ self.assertEqual(self.resource_name, event['resource_name'])
self.assertEqual('state changed', event['resource_status_reason'])
self.assertEqual(self.resource_name, event['logical_resource_id'])
- self.assertIsInstance(event, dict)
diff --git a/tempest/api/orchestration/stacks/test_server_cfn_init.py b/tempest/api/orchestration/stacks/test_server_cfn_init.py
index 3c2a2d2..0480570 100644
--- a/tempest/api/orchestration/stacks/test_server_cfn_init.py
+++ b/tempest/api/orchestration/stacks/test_server_cfn_init.py
@@ -169,9 +169,9 @@
body['physical_resource_id'])
# Check that the user can authenticate with the generated password
- linux_client = RemoteClient(
- server, 'ec2-user', pkey=self.keypair['private_key'])
- self.assertTrue(linux_client.can_authenticate())
+ linux_client = RemoteClient(server, 'ec2-user',
+ pkey=self.keypair['private_key'])
+ linux_client.validate_authentication()
@attr(type='slow')
def test_stack_wait_condition_data(self):
diff --git a/tempest/api/volume/admin/test_volume_hosts.py b/tempest/api/volume/admin/test_volume_hosts.py
new file mode 100644
index 0000000..e7d8c02
--- /dev/null
+++ b/tempest/api/volume/admin/test_volume_hosts.py
@@ -0,0 +1,34 @@
+# 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.volume import base
+from tempest.test import attr
+
+
+class VolumeHostsAdminTestsJSON(base.BaseVolumeAdminTest):
+ _interface = "json"
+
+ @attr(type='gate')
+ def test_list_hosts(self):
+ resp, hosts = self.hosts_client.list_hosts()
+ self.assertEqual(200, resp.status)
+ self.assertTrue(len(hosts) >= 2, "No. of hosts are < 2,"
+ "response of list hosts is: % s" % hosts)
+
+
+class VolumeHostsAdminTestsXML(VolumeHostsAdminTestsJSON):
+ _interface = 'xml'
diff --git a/tempest/api/volume/admin/test_volumes_actions.py b/tempest/api/volume/admin/test_volumes_actions.py
index 4063eef..cb9ff11 100644
--- a/tempest/api/volume/admin/test_volumes_actions.py
+++ b/tempest/api/volume/admin/test_volumes_actions.py
@@ -47,7 +47,7 @@
super(VolumesActionsTest, cls).tearDownClass()
def _reset_volume_status(self, volume_id, status):
- #Reset the volume status
+ # Reset the volume status
resp, body = self.admin_volume_client.reset_volume_status(volume_id,
status)
return resp, body
@@ -57,6 +57,26 @@
self._reset_volume_status(self.volume['id'], 'available')
super(VolumesActionsTest, self).tearDown()
+ def _create_temp_volume(self):
+ # Create a temp volume for force delete tests
+ vol_name = utils.rand_name('Volume')
+ resp, temp_volume = self.client.create_volume(size=1,
+ display_name=vol_name)
+ self.client.wait_for_volume_status(temp_volume['id'], 'available')
+
+ return temp_volume
+
+ def _create_reset_and_force_delete_temp_volume(self, status=None):
+ # Create volume, reset volume status, and force delete temp volume
+ temp_volume = self._create_temp_volume()
+ if status:
+ resp, body = self._reset_volume_status(temp_volume['id'], status)
+ self.assertEqual(202, resp.status)
+ resp_delete, volume_delete = self.admin_volume_client.\
+ force_delete_volume(temp_volume['id'])
+ self.assertEqual(202, resp_delete.status)
+ self.client.wait_for_resource_deletion(temp_volume['id'])
+
@attr(type='gate')
def test_volume_reset_status(self):
# test volume reset status : available->error->available
@@ -84,6 +104,19 @@
resp_get, volume_get = self.client.get_volume(self.volume['id'])
self.assertEqual('in-use', volume_get['status'])
+ def test_volume_force_delete_when_volume_is_creating(self):
+ # test force delete when status of volume is creating
+ self._create_reset_and_force_delete_temp_volume('creating')
+
+ def test_volume_force_delete_when_volume_is_attaching(self):
+ # test force delete when status of volume is attaching
+ self._create_reset_and_force_delete_temp_volume('attaching')
+
+ @attr(type='gate')
+ def test_volume_force_delete_when_volume_is_error(self):
+ # test force delete when status of volume is error
+ self._create_reset_and_force_delete_temp_volume('error')
+
class VolumesActionsTestXML(VolumesActionsTest):
_interface = "xml"
diff --git a/tempest/api/volume/base.py b/tempest/api/volume/base.py
index cdf8638..465f570 100644
--- a/tempest/api/volume/base.py
+++ b/tempest/api/volume/base.py
@@ -151,3 +151,4 @@
else:
cls.os_adm = clients.AdminManager(interface=cls._interface)
cls.client = cls.os_adm.volume_types_client
+ cls.hosts_client = cls.os_adm.volume_hosts_client
diff --git a/tempest/api/volume/test_volumes_list.py b/tempest/api/volume/test_volumes_list.py
index 4dbc88a..def330e 100644
--- a/tempest/api/volume/test_volumes_list.py
+++ b/tempest/api/volume/test_volumes_list.py
@@ -57,13 +57,13 @@
# Create 3 test volumes
cls.volume_list = []
cls.volume_id_list = []
+ cls.metadata = {'Type': 'work'}
for i in range(3):
v_name = data_utils.rand_name('volume')
- metadata = {'Type': 'work'}
try:
resp, volume = cls.client.create_volume(size=1,
display_name=v_name,
- metadata=metadata)
+ metadata=cls.metadata)
cls.client.wait_for_volume_status(volume['id'], 'available')
resp, volume = cls.client.get_volume(volume['id'])
cls.volume_list.append(volume)
@@ -88,6 +88,25 @@
cls.client.wait_for_resource_deletion(volid)
super(VolumesListTest, cls).tearDownClass()
+ def _list_by_param_value_and_assert(self, params, with_detail=False):
+ """
+ Perform list or list_details action with given params
+ and validates result.
+ """
+ if with_detail:
+ resp, fetched_vol_list = \
+ self.client.list_volumes_with_detail(params=params)
+ else:
+ resp, fetched_vol_list = self.client.list_volumes(params=params)
+
+ self.assertEqual(200, resp.status)
+ # Validating params of fetched volumes
+ for volume in fetched_vol_list:
+ 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)
+
@attr(type='smoke')
def test_volume_list(self):
# Get a list of Volumes
@@ -164,6 +183,34 @@
self.assertEqual(zone, volume['availability_zone'])
self.assertVolumesIn(fetched_list, self.volume_list)
+ @attr(type='gate')
+ def test_volume_list_with_param_metadata(self):
+ # Test to list volumes when metadata param is given
+ params = {'metadata': self.metadata}
+ self._list_by_param_value_and_assert(params)
+
+ @attr(type='gate')
+ def test_volume_list_with_detail_param_metadata(self):
+ # Test to list volumes details when metadata param is given
+ params = {'metadata': self.metadata}
+ self._list_by_param_value_and_assert(params, with_detail=True)
+
+ @attr(type='gate')
+ def test_volume_list_param_display_name_and_status(self):
+ # Test to list volume when display name and status param is given
+ volume = self.volume_list[data_utils.rand_int_id(0, 2)]
+ params = {'display_name': volume['display_name'],
+ 'status': 'available'}
+ self._list_by_param_value_and_assert(params)
+
+ @attr(type='gate')
+ def test_volume_list_with_detail_param_display_name_and_status(self):
+ # Test to list volume when name and status param is given
+ volume = self.volume_list[data_utils.rand_int_id(0, 2)]
+ params = {'display_name': volume['display_name'],
+ 'status': 'available'}
+ self._list_by_param_value_and_assert(params, with_detail=True)
+
class VolumeListTestXML(VolumesListTest):
_interface = 'xml'
diff --git a/tempest/api/volume/test_volumes_snapshots.py b/tempest/api/volume/test_volumes_snapshots.py
index 99e8de7..6c45c3d 100644
--- a/tempest/api/volume/test_volumes_snapshots.py
+++ b/tempest/api/volume/test_volumes_snapshots.py
@@ -37,6 +37,27 @@
def tearDownClass(cls):
super(VolumesSnapshotTest, cls).tearDownClass()
+ def _list_by_param_values_and_assert(self, params, with_detail=False):
+ """
+ Perform list or list_details action with given params
+ and validates result.
+ """
+ if with_detail:
+ resp, fetched_snap_list = \
+ self.snapshots_client.\
+ list_snapshots_with_detail(params=params)
+ else:
+ resp, fetched_snap_list = \
+ self.snapshots_client.list_snapshots(params=params)
+
+ self.assertEqual(200, resp.status)
+ # Validating params of fetched snapshots
+ for snap in fetched_snap_list:
+ for key in params:
+ msg = "Failed to list snapshots %s by %s" % \
+ ('details' if with_detail else '', key)
+ self.assertEqual(params[key], snap[key], msg)
+
@attr(type='gate')
def test_snapshot_create_get_list_update_delete(self):
# Create a snapshot
@@ -83,6 +104,46 @@
self.snapshots.remove(snapshot)
@attr(type='gate')
+ def test_snapshots_list_with_params(self):
+ """list snapshots with params."""
+ # Create a snapshot
+ display_name = data_utils.rand_name('snap')
+ snapshot = self.create_snapshot(self.volume_origin['id'],
+ display_name=display_name)
+
+ # Verify list snapshots by display_name filter
+ params = {'display_name': snapshot['display_name']}
+ self._list_by_param_values_and_assert(params)
+
+ # Verify list snapshots by status filter
+ params = {'status': 'available'}
+ self._list_by_param_values_and_assert(params)
+
+ # Verify list snapshots by status and display name filter
+ params = {'status': 'available',
+ 'display_name': snapshot['display_name']}
+ self._list_by_param_values_and_assert(params)
+
+ @attr(type='gate')
+ def test_snapshots_list_details_with_params(self):
+ """list snapshot details with params."""
+ # Create a snapshot
+ display_name = data_utils.rand_name('snap')
+ snapshot = self.create_snapshot(self.volume_origin['id'],
+ display_name=display_name)
+
+ # Verify list snapshot details by display_name filter
+ params = {'display_name': snapshot['display_name']}
+ self._list_by_param_values_and_assert(params, with_detail=True)
+ # Verify list snapshot details by status filter
+ params = {'status': 'available'}
+ self._list_by_param_values_and_assert(params, with_detail=True)
+ # Verify list snapshot details by status and display name filter
+ params = {'status': 'available',
+ 'display_name': snapshot['display_name']}
+ self._list_by_param_values_and_assert(params, with_detail=True)
+
+ @attr(type='gate')
def test_volume_from_snapshot(self):
# Create a temporary snap using wrapper method from base, then
# create a snap based volume, check resp code and deletes it
diff --git a/tempest/cli/simple_read_only/test_neutron.py b/tempest/cli/simple_read_only/test_neutron.py
index 047b17d..80376ab 100644
--- a/tempest/cli/simple_read_only/test_neutron.py
+++ b/tempest/cli/simple_read_only/test_neutron.py
@@ -44,35 +44,43 @@
raise cls.skipException(msg)
super(SimpleReadOnlyNeutronClientTest, cls).setUpClass()
+ @test.attr(type='smoke')
def test_neutron_fake_action(self):
self.assertRaises(subprocess.CalledProcessError,
self.neutron,
'this-does-not-exist')
+ @test.attr(type='smoke')
def test_neutron_net_list(self):
self.neutron('net-list')
+ @test.attr(type='smoke')
def test_neutron_ext_list(self):
ext = self.parser.listing(self.neutron('ext-list'))
self.assertTableStruct(ext, ['alias', 'name'])
+ @test.attr(type='smoke')
def test_neutron_dhcp_agent_list_hosting_net(self):
self.neutron('dhcp-agent-list-hosting-net',
params=CONF.compute.fixed_network_name)
+ @test.attr(type='smoke')
def test_neutron_agent_list(self):
agents = self.parser.listing(self.neutron('agent-list'))
field_names = ['id', 'agent_type', 'host', 'alive', 'admin_state_up']
self.assertTableStruct(agents, field_names)
+ @test.attr(type='smoke')
def test_neutron_floatingip_list(self):
self.neutron('floatingip-list')
@test.skip_because(bug="1240694")
+ @test.attr(type='smoke')
def test_neutron_meter_label_list(self):
self.neutron('meter-label-list')
@test.skip_because(bug="1240694")
+ @test.attr(type='smoke')
def test_neutron_meter_label_rule_list(self):
self.neutron('meter-label-rule-list')
@@ -83,40 +91,52 @@
if '404 Not Found' not in e.stderr:
self.fail('%s: Unexpected failure.' % command)
+ @test.attr(type='smoke')
def test_neutron_lb_healthmonitor_list(self):
self._test_neutron_lbaas_command('lb-healthmonitor-list')
+ @test.attr(type='smoke')
def test_neutron_lb_member_list(self):
self._test_neutron_lbaas_command('lb-member-list')
+ @test.attr(type='smoke')
def test_neutron_lb_pool_list(self):
self._test_neutron_lbaas_command('lb-pool-list')
+ @test.attr(type='smoke')
def test_neutron_lb_vip_list(self):
self._test_neutron_lbaas_command('lb-vip-list')
+ @test.attr(type='smoke')
def test_neutron_net_external_list(self):
self.neutron('net-external-list')
+ @test.attr(type='smoke')
def test_neutron_port_list(self):
self.neutron('port-list')
+ @test.attr(type='smoke')
def test_neutron_quota_list(self):
self.neutron('quota-list')
+ @test.attr(type='smoke')
def test_neutron_router_list(self):
self.neutron('router-list')
+ @test.attr(type='smoke')
def test_neutron_security_group_list(self):
security_grp = self.parser.listing(self.neutron('security-group-list'))
self.assertTableStruct(security_grp, ['id', 'name', 'description'])
+ @test.attr(type='smoke')
def test_neutron_security_group_rule_list(self):
self.neutron('security-group-rule-list')
+ @test.attr(type='smoke')
def test_neutron_subnet_list(self):
self.neutron('subnet-list')
+ @test.attr(type='smoke')
def test_neutron_help(self):
help_text = self.neutron('help')
lines = help_text.split('\n')
@@ -136,11 +156,14 @@
# Optional arguments:
+ @test.attr(type='smoke')
def test_neutron_version(self):
self.neutron('', flags='--version')
+ @test.attr(type='smoke')
def test_neutron_debug_net_list(self):
self.neutron('net-list', flags='--debug')
+ @test.attr(type='smoke')
def test_neutron_quiet_net_list(self):
self.neutron('net-list', flags='--quiet')
diff --git a/tempest/clients.py b/tempest/clients.py
index 291b946..ac79ce0 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -62,6 +62,8 @@
ServersV3ClientJSON
from tempest.services.compute.v3.json.services_client import \
ServicesV3ClientJSON
+from tempest.services.compute.v3.json.tenant_usages_client import \
+ TenantUsagesV3ClientJSON
from tempest.services.compute.v3.xml.availability_zone_client import \
AvailabilityZoneV3ClientXML
from tempest.services.compute.v3.xml.extensions_client import \
@@ -73,6 +75,8 @@
from tempest.services.compute.v3.xml.servers_client import ServersV3ClientXML
from tempest.services.compute.v3.xml.services_client import \
ServicesV3ClientXML
+from tempest.services.compute.v3.xml.tenant_usages_client import \
+ TenantUsagesV3ClientXML
from tempest.services.compute.xml.aggregates_client import AggregatesClientXML
from tempest.services.compute.xml.availability_zone_client import \
AvailabilityZoneClientXML
@@ -136,10 +140,14 @@
ObjectClientCustomizedHeader
from tempest.services.orchestration.json.orchestration_client import \
OrchestrationClient
+from tempest.services.volume.json.admin.volume_hosts_client import \
+ VolumeHostsClientJSON
from tempest.services.volume.json.admin.volume_types_client import \
VolumeTypesClientJSON
from tempest.services.volume.json.snapshots_client import SnapshotsClientJSON
from tempest.services.volume.json.volumes_client import VolumesClientJSON
+from tempest.services.volume.xml.admin.volume_hosts_client import \
+ VolumeHostsClientXML
from tempest.services.volume.xml.admin.volume_types_client import \
VolumeTypesClientXML
from tempest.services.volume.xml.snapshots_client import SnapshotsClientXML
@@ -230,6 +238,8 @@
self.service_client = ServiceClientXML(*client_args)
self.aggregates_client = AggregatesClientXML(*client_args)
self.services_client = ServicesClientXML(*client_args)
+ self.tenant_usages_v3_client = TenantUsagesV3ClientXML(
+ *client_args)
self.tenant_usages_client = TenantUsagesClientXML(*client_args)
self.policy_client = PolicyClientXML(*client_args)
self.hypervisor_v3_client = HypervisorV3ClientXML(*client_args)
@@ -239,6 +249,7 @@
self.credentials_client = CredentialsClientXML(*client_args)
self.instance_usages_audit_log_client = \
InstanceUsagesAuditLogClientXML(*client_args)
+ self.volume_hosts_client = VolumeHostsClientXML(*client_args)
if client_args_v3_auth:
self.servers_client_v3_auth = ServersClientXML(
@@ -278,6 +289,8 @@
self.service_client = ServiceClientJSON(*client_args)
self.aggregates_client = AggregatesClientJSON(*client_args)
self.services_client = ServicesClientJSON(*client_args)
+ self.tenant_usages_v3_client = TenantUsagesV3ClientJSON(
+ *client_args)
self.tenant_usages_client = TenantUsagesClientJSON(*client_args)
self.policy_client = PolicyClientJSON(*client_args)
self.hypervisor_v3_client = HypervisorV3ClientJSON(*client_args)
@@ -287,6 +300,7 @@
self.credentials_client = CredentialsClientJSON(*client_args)
self.instance_usages_audit_log_client = \
InstanceUsagesAuditLogClientJSON(*client_args)
+ self.volume_hosts_client = VolumeHostsClientJSON(*client_args)
if client_args_v3_auth:
self.servers_client_v3_auth = ServersClientJSON(
diff --git a/tempest/common/rest_client.py b/tempest/common/rest_client.py
index 9322f1b..9aca2ff 100644
--- a/tempest/common/rest_client.py
+++ b/tempest/common/rest_client.py
@@ -506,18 +506,26 @@
if resp.status in (500, 501):
message = resp_body
if parse_resp:
- resp_body = self._parse_resp(resp_body)
- # I'm seeing both computeFault and cloudServersFault come back.
- # Will file a bug to fix, but leave as is for now.
- if 'cloudServersFault' in resp_body:
- message = resp_body['cloudServersFault']['message']
- elif 'computeFault' in resp_body:
- message = resp_body['computeFault']['message']
- elif 'error' in resp_body: # Keystone errors
- message = resp_body['error']['message']
- raise exceptions.IdentityError(message)
- elif 'message' in resp_body:
- message = resp_body['message']
+ try:
+ resp_body = self._parse_resp(resp_body)
+ except ValueError:
+ # If response body is a non-json string message.
+ # Use resp_body as is and raise InvalidResponseBody
+ # exception.
+ raise exceptions.InvalidHTTPResponseBody(message)
+ else:
+ # I'm seeing both computeFault
+ # and cloudServersFault come back.
+ # Will file a bug to fix, but leave as is for now.
+ if 'cloudServersFault' in resp_body:
+ message = resp_body['cloudServersFault']['message']
+ elif 'computeFault' in resp_body:
+ message = resp_body['computeFault']['message']
+ elif 'error' in resp_body: # Keystone errors
+ message = resp_body['error']['message']
+ raise exceptions.IdentityError(message)
+ elif 'message' in resp_body:
+ message = resp_body['message']
raise exceptions.ServerFault(message)
diff --git a/tempest/common/ssh.py b/tempest/common/ssh.py
index 742a354..bca2f9e 100644
--- a/tempest/common/ssh.py
+++ b/tempest/common/ssh.py
@@ -23,6 +23,7 @@
import warnings
from tempest import exceptions
+from tempest.openstack.common import log as logging
with warnings.catch_warnings():
@@ -30,6 +31,9 @@
import paramiko
+LOG = logging.getLogger(__name__)
+
+
class Client(object):
def __init__(self, host, username, password=None, timeout=300, pkey=None,
@@ -49,33 +53,44 @@
def _get_ssh_connection(self, sleep=1.5, backoff=1.01):
"""Returns an ssh connection to the specified host."""
- _timeout = True
bsleep = sleep
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(
paramiko.AutoAddPolicy())
_start_time = time.time()
-
- while not self._is_timed_out(_start_time):
+ if self.pkey is not None:
+ LOG.info("Creating ssh connection to '%s' as '%s'"
+ " with public key authentication",
+ self.host, self.username)
+ else:
+ LOG.info("Creating ssh connection to '%s' as '%s'"
+ " with password %s",
+ self.host, self.username, str(self.password))
+ attempts = 0
+ while True:
try:
ssh.connect(self.host, username=self.username,
password=self.password,
look_for_keys=self.look_for_keys,
key_filename=self.key_filename,
- timeout=self.timeout, pkey=self.pkey)
- _timeout = False
- break
+ timeout=self.channel_timeout, pkey=self.pkey)
+ LOG.info("ssh connection to %s@%s sucessfuly created",
+ self.username, self.host)
+ return ssh
except (socket.error,
- paramiko.AuthenticationException,
paramiko.SSHException):
+ attempts += 1
time.sleep(bsleep)
bsleep *= backoff
- continue
- if _timeout:
- raise exceptions.SSHTimeout(host=self.host,
- user=self.username,
- password=self.password)
- return ssh
+ if not self._is_timed_out(_start_time):
+ continue
+ else:
+ LOG.exception("Failed to establish authenticated ssh"
+ " connection to %s@%s after %d attempts",
+ self.username, self.host, attempts)
+ raise exceptions.SSHTimeout(host=self.host,
+ user=self.username,
+ password=self.password)
def _is_timed_out(self, start_time):
return (time.time() - self.timeout) > start_time
@@ -144,11 +159,6 @@
return ''.join(out_data)
def test_connection_auth(self):
- """Returns true if ssh can connect to server."""
- try:
- connection = self._get_ssh_connection()
- connection.close()
- except paramiko.AuthenticationException:
- return False
-
- return True
+ """Raises an exception when we can not connect to server via ssh."""
+ connection = self._get_ssh_connection()
+ connection.close()
diff --git a/tempest/common/utils/linux/remote_client.py b/tempest/common/utils/linux/remote_client.py
index 0d0e794..144536a 100644
--- a/tempest/common/utils/linux/remote_client.py
+++ b/tempest/common/utils/linux/remote_client.py
@@ -19,7 +19,6 @@
from tempest.common import utils
from tempest.config import TempestConfig
from tempest.exceptions import ServerUnreachable
-from tempest.exceptions import SSHTimeout
class RemoteClient():
@@ -40,16 +39,15 @@
break
else:
raise ServerUnreachable()
-
self.ssh_client = Client(ip_address, username, password, ssh_timeout,
pkey=pkey,
channel_timeout=ssh_channel_timeout)
- if not self.ssh_client.test_connection_auth():
- raise SSHTimeout()
- def can_authenticate(self):
- # Re-authenticate
- return self.ssh_client.test_connection_auth()
+ def validate_authentication(self):
+ """Validate ssh connection and authentication
+ This method raises an Exception when the validation fails.
+ """
+ self.ssh_client.test_connection_auth()
def hostname_equals_servername(self, expected_hostname):
# Get host name using command "hostname"
@@ -89,3 +87,11 @@
# usually to /dev/ttyS0
cmd = 'sudo sh -c "echo \\"%s\\" >/dev/console"' % message
return self.ssh_client.exec_command(cmd)
+
+ def ping_host(self, host):
+ cmd = 'ping -c1 -w1 %s' % host
+ return self.ssh_client.exec_command(cmd)
+
+ def get_mac_address(self):
+ cmd = "/sbin/ifconfig | awk '/HWaddr/ {print $5}'"
+ return self.ssh_client.exec_command(cmd)
diff --git a/tempest/common/waiters.py b/tempest/common/waiters.py
index bea2cdc..d2b40c9 100644
--- a/tempest/common/waiters.py
+++ b/tempest/common/waiters.py
@@ -25,7 +25,7 @@
# NOTE(afazekas): This function needs to know a token and a subject.
def wait_for_server_status(client, server_id, status, ready_wait=True,
- extra_timeout=0):
+ extra_timeout=0, raise_on_error=True):
"""Waits for a server to reach a given status."""
def _get_task_state(body):
@@ -69,16 +69,53 @@
'/'.join((old_status, str(old_task_state))),
'/'.join((server_status, str(task_state))),
time.time() - start_time)
- if server_status == 'ERROR':
+ if (server_status == 'ERROR') and raise_on_error:
raise exceptions.BuildErrorException(server_id=server_id)
timed_out = int(time.time()) - start_time >= timeout
if timed_out:
- message = ('Server %s failed to reach %s status within the '
- 'required time (%s s).' %
- (server_id, status, timeout))
+ expected_task_state = 'None' if ready_wait else 'n/a'
+ message = ('Server %(server_id)s failed to reach %(status)s '
+ 'status and task state "%(expected_task_state)s" '
+ 'within the required time (%(timeout)s s).' %
+ {'server_id': server_id,
+ 'status': status,
+ 'expected_task_state': expected_task_state,
+ 'timeout': timeout})
message += ' Current status: %s.' % server_status
+ message += ' Current task state: %s.' % task_state
raise exceptions.TimeoutException(message)
old_status = server_status
old_task_state = task_state
+
+
+def wait_for_image_status(client, image_id, status):
+ """Waits for an image to reach a given status.
+
+ The client should have a get_image(image_id) method to get the image.
+ The client should also have build_interval and build_timeout attributes.
+ """
+ resp, image = client.get_image(image_id)
+ start = int(time.time())
+
+ while image['status'] != status:
+ time.sleep(client.build_interval)
+ resp, image = client.get_image(image_id)
+ if image['status'] == 'ERROR':
+ raise exceptions.AddImageException(image_id=image_id)
+
+ # check the status again to avoid a false negative where we hit
+ # the timeout at the same time that the image reached the expected
+ # status
+ if image['status'] == status:
+ return
+
+ if int(time.time()) - start >= client.build_timeout:
+ message = ('Image %(image_id)s failed to reach %(status)s '
+ 'status within the required time (%(timeout)s s).' %
+ {'image_id': image_id,
+ 'status': status,
+ 'timeout': client.build_timeout})
+ message += ' Current status: %s.' % image['status']
+ raise exceptions.TimeoutException(message)
diff --git a/tempest/config.py b/tempest/config.py
index d97c7b2..3d9eba9 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -103,14 +103,6 @@
"users. This option enables isolated test cases and "
"better parallel execution, but also requires that "
"OpenStack Identity API admin credentials are known."),
- cfg.BoolOpt('allow_tenant_reuse',
- default=True,
- help="If allow_tenant_isolation is True and a tenant that "
- "would be created for a given test already exists (such "
- "as from a previously-failed run), re-use that tenant "
- "instead of failing because of the conflict. Note that "
- "this would result in the tenant being deleted at the "
- "end of a subsequent successful run."),
cfg.StrOpt('image_ref',
default="{$IMAGE_ID}",
help="Valid secondary image reference to be used in tests."),
@@ -216,9 +208,14 @@
cfg.BoolOpt('disk_config',
default=True,
help="If false, skip disk config tests"),
- cfg.BoolOpt('flavor_extra',
- default=True,
- help="If false, skip flavor extra data test"),
+ cfg.ListOpt('api_extensions',
+ default=['all'],
+ help='A list of enabled extensions with a special entry all '
+ 'which indicates every extension is enabled'),
+ cfg.ListOpt('api_v3_extensions',
+ default=['all'],
+ help='A list of enabled v3 extensions with a special entry all'
+ ' which indicates every extension is enabled'),
cfg.BoolOpt('change_password',
default=False,
help="Does the test environment support changing the admin "
@@ -325,6 +322,16 @@
"connectivity"),
]
+network_feature_group = cfg.OptGroup(name='network-feature-enabled',
+ title='Enabled network service features')
+
+NetworkFeaturesGroup = [
+ cfg.ListOpt('api_extensions',
+ default=['all'],
+ help='A list of enabled extensions with a special entry all '
+ 'which indicates every extension is enabled'),
+]
+
volume_group = cfg.OptGroup(name='volume',
title='Block Storage Options')
@@ -368,7 +375,11 @@
VolumeFeaturesGroup = [
cfg.BoolOpt('multi_backend',
default=False,
- help="Runs Cinder multi-backend test (requires 2 backends)")
+ help="Runs Cinder multi-backend test (requires 2 backends)"),
+ cfg.ListOpt('api_extensions',
+ default=['all'],
+ help='A list of enabled extensions with a special entry all '
+ 'which indicates every extension is enabled'),
]
@@ -667,6 +678,8 @@
register_opt_group(cfg.CONF, image_group, ImageGroup)
register_opt_group(cfg.CONF, image_feature_group, ImageFeaturesGroup)
register_opt_group(cfg.CONF, network_group, NetworkGroup)
+ register_opt_group(cfg.CONF, network_feature_group,
+ NetworkFeaturesGroup)
register_opt_group(cfg.CONF, volume_group, VolumeGroup)
register_opt_group(cfg.CONF, volume_feature_group,
VolumeFeaturesGroup)
@@ -688,6 +701,7 @@
self.images = cfg.CONF.image
self.image_feature_enabled = cfg.CONF['image-feature-enabled']
self.network = cfg.CONF.network
+ self.network_feature_enabled = cfg.CONF['network-feature-enabled']
self.volume = cfg.CONF.volume
self.volume_feature_enabled = cfg.CONF['volume-feature-enabled']
self.object_storage = cfg.CONF['object-storage']
diff --git a/tempest/exceptions.py b/tempest/exceptions.py
index 02fc231..0bab9c7 100644
--- a/tempest/exceptions.py
+++ b/tempest/exceptions.py
@@ -184,3 +184,7 @@
class ResponseWithEntity(RFCViolation):
message = ("RFC Violation! Response with 205 HTTP Status Code "
"MUST NOT have an entity")
+
+
+class InvalidHTTPResponseBody(RestClientException):
+ message = "HTTP response body is invalid json or xml"
diff --git a/tempest/hacking/checks.py b/tempest/hacking/checks.py
index 4c1c107..2adc98e 100644
--- a/tempest/hacking/checks.py
+++ b/tempest/hacking/checks.py
@@ -21,6 +21,7 @@
PYTHON_CLIENT_RE = re.compile('import (%s)client' % '|'.join(PYTHON_CLIENTS))
TEST_DEFINITION = re.compile(r'^\s*def test.*')
+SETUPCLASS_DEFINITION = re.compile(r'^\s*def setUpClass')
SCENARIO_DECORATOR = re.compile(r'\s*@.*services\(')
@@ -52,6 +53,14 @@
"T104: Scenario tests require a service decorator")
+def no_setupclass_for_unit_tests(physical_line, filename):
+ if 'tempest/tests' in filename:
+ if SETUPCLASS_DEFINITION.match(physical_line):
+ return (physical_line.find('def'),
+ "T105: setUpClass can not be used with unit tests")
+
+
def factory(register):
register(import_no_clients_in_api)
register(scenario_tests_need_service_tags)
+ register(no_setupclass_for_unit_tests)
diff --git a/tempest/openstack/common/log.py b/tempest/openstack/common/log.py
index 5cf8ed6..abb44ef 100644
--- a/tempest/openstack/common/log.py
+++ b/tempest/openstack/common/log.py
@@ -132,6 +132,7 @@
'boto=WARN',
'suds=INFO',
'keystone=INFO',
+ 'paramiko=INFO'
],
help='list of logger=LEVEL pairs'),
cfg.BoolOpt('publish_errors',
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
index 06841e1..9f97964 100644
--- a/tempest/scenario/manager.py
+++ b/tempest/scenario/manager.py
@@ -33,7 +33,6 @@
from tempest.api.network import common as net_common
from tempest.common import isolated_creds
-from tempest.common import ssh
from tempest.common.utils import data_utils
from tempest.common.utils.linux.remote_client import RemoteClient
from tempest import exceptions
@@ -248,7 +247,8 @@
thing.delete()
except Exception as e:
# If the resource is already missing, mission accomplished.
- if e.__class__.__name__ == 'NotFound':
+ # add status code as workaround for bug 1247568
+ if e.__class__.__name__ == 'NotFound' or e.status_code == 404:
continue
raise
@@ -343,7 +343,11 @@
raise
new_status = thing.status
- if new_status == error_status:
+
+ # Some components are reporting error status in lower case
+ # so case sensitive comparisons can really mess things
+ # up.
+ if new_status.lower() == error_status.lower():
message = "%s failed to get to expected status. \
In %s state." % (thing, new_status)
raise exceptions.BuildErrorException(message)
@@ -537,6 +541,27 @@
routers = self.network_client.list_routers()
return routers['routers']
+ def _list_ports(self):
+ ports = self.network_client.list_ports()
+ return ports['ports']
+
+ def _get_tenant_own_network_num(self, tenant_id):
+ nets = self._list_networks()
+ ownnets = [value for value in nets if tenant_id == value['tenant_id']]
+ return len(ownnets)
+
+ def _get_tenant_own_subnet_num(self, tenant_id):
+ subnets = self._list_subnets()
+ ownsubnets = ([value for value in subnets
+ if tenant_id == value['tenant_id']])
+ return len(ownsubnets)
+
+ def _get_tenant_own_port_num(self, tenant_id):
+ ports = self._list_ports()
+ ownports = ([value for value in ports
+ if tenant_id == value['tenant_id']])
+ return len(ownports)
+
def _create_subnet(self, network, namestart='subnet-smoke-'):
"""
Create a subnet for the given network within the cidr block
@@ -603,7 +628,15 @@
self.set_resource(data_utils.rand_name('floatingip-'), floating_ip)
return floating_ip
- def _ping_ip_address(self, ip_address):
+ def _disassociate_floating_ip(self, floating_ip):
+ """
+ :param floating_ip: type DeletableFloatingIp
+ """
+ floating_ip.update(port_id=None)
+ self.assertEqual(None, floating_ip.port_id)
+ return floating_ip
+
+ def _ping_ip_address(self, ip_address, should_succeed=True):
cmd = ['ping', '-c1', '-w1', ip_address]
def ping():
@@ -611,30 +644,38 @@
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
proc.wait()
- if proc.returncode == 0:
- return True
+ return (proc.returncode == 0) == should_succeed
return tempest.test.call_until_true(
ping, self.config.compute.ping_timeout, 1)
- def _is_reachable_via_ssh(self, ip_address, username, private_key,
- timeout):
- ssh_client = ssh.Client(ip_address, username,
- pkey=private_key,
- timeout=timeout)
- return ssh_client.test_connection_auth()
+ def _check_vm_connectivity(self, ip_address,
+ username=None,
+ private_key=None,
+ should_connect=True):
+ """
+ :param ip_address: server to test against
+ :param username: server's ssh username
+ :param private_key: server's ssh private key to be used
+ :param should_connect: True/False indicates positive/negative test
+ positive - attempt ping and ssh
+ negative - attempt ping and fail if succeed
- def _check_vm_connectivity(self, ip_address, username, private_key):
- self.assertTrue(self._ping_ip_address(ip_address),
- "Timed out waiting for %s to become "
- "reachable" % ip_address)
- self.assertTrue(self._is_reachable_via_ssh(
- ip_address,
- username,
- private_key,
- timeout=self.config.compute.ssh_timeout),
- 'Auth failure in connecting to %s@%s via ssh' %
- (username, ip_address))
+ :raises: AssertError if the result of the connectivity check does
+ not match the value of the should_connect param
+ """
+ if should_connect:
+ msg = "Timed out waiting for %s to become reachable" % ip_address
+ else:
+ msg = "ip address %s is reachable" % ip_address
+ self.assertTrue(self._ping_ip_address(ip_address,
+ should_succeed=should_connect),
+ msg=msg)
+ if should_connect:
+ # no need to check ssh for negative connectivity
+ linux_client = self.get_remote_client(ip_address, username,
+ private_key)
+ linux_client.validate_authentication()
def _create_security_group_nova(self, client=None,
namestart='secgroup-smoke-',
@@ -801,6 +842,78 @@
return rules
+ def _ssh_to_server(self, server, private_key):
+ ssh_login = self.config.compute.image_ssh_user
+ return self.get_remote_client(server,
+ username=ssh_login,
+ private_key=private_key)
+
+ def _show_quota_network(self, tenant_id):
+ quota = self.network_client.show_quota(tenant_id)
+ return quota['quota']['network']
+
+ def _show_quota_subnet(self, tenant_id):
+ quota = self.network_client.show_quota(tenant_id)
+ return quota['quota']['subnet']
+
+ def _show_quota_port(self, tenant_id):
+ quota = self.network_client.show_quota(tenant_id)
+ return quota['quota']['port']
+
+ def _get_router(self, tenant_id):
+ """Retrieve a router for the given tenant id.
+
+ If a public router has been configured, it will be returned.
+
+ If a public router has not been configured, but a public
+ network has, a tenant router will be created and returned that
+ routes traffic to the public network.
+ """
+ router_id = self.config.network.public_router_id
+ network_id = self.config.network.public_network_id
+ if router_id:
+ result = self.network_client.show_router(router_id)
+ return net_common.AttributeDict(**result['router'])
+ elif network_id:
+ router = self._create_router(tenant_id)
+ router.add_gateway(network_id)
+ return router
+ else:
+ raise Exception("Neither of 'public_router_id' or "
+ "'public_network_id' has been defined.")
+
+ def _create_router(self, tenant_id, namestart='router-smoke-'):
+ name = data_utils.rand_name(namestart)
+ body = dict(
+ router=dict(
+ name=name,
+ admin_state_up=True,
+ tenant_id=tenant_id,
+ ),
+ )
+ result = self.network_client.create_router(body=body)
+ router = net_common.DeletableRouter(client=self.network_client,
+ **result['router'])
+ self.assertEqual(router.name, name)
+ self.set_resource(name, router)
+ return router
+
+ def _create_networks(self, tenant_id=None):
+ """Create a network with a subnet connected to a router.
+
+ :returns: network, subnet, router
+ """
+ if tenant_id is None:
+ tenant_id = self.tenant_id
+ network = self._create_network(tenant_id)
+ router = self._get_router(tenant_id)
+ subnet = self._create_subnet(network)
+ subnet.add_to_router(router.id)
+ self.networks.append(network)
+ self.subnets.append(subnet)
+ self.routers.append(router)
+ return network, subnet, router
+
class OrchestrationScenarioTest(OfficialClientTest):
"""
diff --git a/tempest/scenario/orchestration/test_autoscaling.py b/tempest/scenario/orchestration/test_autoscaling.py
index e843793..90ef3a0 100644
--- a/tempest/scenario/orchestration/test_autoscaling.py
+++ b/tempest/scenario/orchestration/test_autoscaling.py
@@ -19,6 +19,7 @@
from tempest.test import attr
from tempest.test import call_until_true
from tempest.test import services
+from tempest.test import skip_because
class AutoScalingTest(manager.OrchestrationScenarioTest):
@@ -62,6 +63,7 @@
if not self.config.orchestration.keypair_name:
self.set_resource('stack', self.stack)
+ @skip_because(bug="1257575")
@attr(type='slow')
@services('orchestration', 'compute')
def test_scale_up_then_down(self):
diff --git a/tempest/scenario/orchestration/test_autoscaling.yaml b/tempest/scenario/orchestration/test_autoscaling.yaml
index 745eb05..4651284 100644
--- a/tempest/scenario/orchestration/test_autoscaling.yaml
+++ b/tempest/scenario/orchestration/test_autoscaling.yaml
@@ -23,11 +23,11 @@
Default: '420'
ScaleUpThreshold:
Description: Memory percentage threshold to scale up on
- Type: Number
+ Type: String
Default: '70'
ScaleDownThreshold:
Description: Memory percentage threshold to scale down on
- Type: Number
+ Type: String
Default: '60'
ConsumeMemoryLimit:
Description: Memory percentage threshold to consume
@@ -182,4 +182,4 @@
# wait ConsumeStartSeconds then ramp up memory consumption
# until it is over ConsumeMemoryLimit%
# then exits ConsumeStopSeconds seconds after stack launch
- /root/consume_memory > /root/consume_memory.log &
\ No newline at end of file
+ /root/consume_memory > /root/consume_memory.log &
diff --git a/tempest/scenario/test_cross_tenant_connectivity.py b/tempest/scenario/test_cross_tenant_connectivity.py
new file mode 100644
index 0000000..ad2c271
--- /dev/null
+++ b/tempest/scenario/test_cross_tenant_connectivity.py
@@ -0,0 +1,494 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 Red Hat, Inc.
+# 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.common import debug
+from tempest.common.utils import data_utils
+from tempest import exceptions
+from tempest.openstack.common import log as logging
+from tempest.scenario import manager
+from tempest.scenario.manager import OfficialClientManager
+from tempest.test import attr
+from tempest.test import call_until_true
+from tempest.test import services
+
+LOG = logging.getLogger(__name__)
+
+
+class TestNetworkCrossTenant(manager.NetworkScenarioTest):
+
+ """
+ This test suite assumes that Nova has been configured to
+ boot VM's with Neutron-managed networking, and attempts to
+ verify cross tenant connectivity as follows
+
+ ssh:
+ in order to overcome "ip namespace", each tenant has an "access point"
+ VM with floating-ip open to incoming ssh connection allowing network
+ commands (ping/ssh) to be executed from within the
+ tenant-network-namespace
+ Tempest host performs key-based authentication to the ssh server via
+ floating IP address
+
+ connectivity test is done by pinging destination server via source server
+ ssh connection.
+ success - ping returns
+ failure - ping_timeout reached
+
+ setup:
+ for each tenant (demo and alt):
+ 1. create a network&subnet
+ 2. create a router (if public router isn't configured)
+ 3. connect tenant network to public network via router
+ 4. create an access point:
+ a. a security group open to incoming ssh connection
+ b. a VM with a floating ip
+ 5. create a general empty security group (same as "default", but
+ without rules allowing in-tenant traffic)
+ 6. for demo tenant - create another server to test in-tenant
+ connections
+
+ tests:
+ 1. _verify_network_details
+ 2. _verify_mac_addr: for each access point verify that
+ (subnet, fix_ip, mac address) are as defined in the port list
+ 3. _test_in_tenant_block: test that in-tenant traffic is disabled
+ without rules allowing it
+ 4. _test_in_tenant_allow: test that in-tenant traffic is enabled
+ once an appropriate rule has been created
+ 5. _test_cross_tenant_block: test that cross-tenant traffic is disabled
+ without a rule allowing it on destination tenant
+ 6. _test_cross_tenant_allow:
+ * test that cross-tenant traffic is enabled once an appropriate
+ rule has been created on destination tenant.
+ * test that reverse traffic is still blocked
+ * test than revesre traffic is enabled once an appropriate rule has
+ been created on source tenant
+
+ assumptions:
+ 1. alt_tenant/user existed and is different from demo_tenant/user
+ 2. Public network is defined and reachable from the Tempest host
+ 3. Public router can either be:
+ * defined, in which case all tenants networks can connect directly
+ to it, and cross tenant check will be done on the private IP of the
+ destination tenant
+ or
+ * not defined (empty string), in which case each tanant will have
+ its own router connected to the public network
+ """
+
+ class TenantProperties():
+ '''
+ helper class to save tenant details
+ id
+ credentials
+ network
+ subnet
+ security groups
+ servers
+ access point
+ '''
+
+ def __init__(self, tenant_id, tenant_user, tenant_pass, tenant_name):
+ self.manager = OfficialClientManager(
+ tenant_user,
+ tenant_pass,
+ tenant_name
+ )
+ self.tenant_id = tenant_id
+ self.tenant_name = tenant_name
+ self.tenant_user = tenant_user
+ self.tenant_pass = tenant_pass
+ self.network = None
+ self.subnet = None
+ self.router = None
+ self.security_groups = {}
+ self.servers = list()
+
+ def _set_network(self, network, subnet, router):
+ self.network = network
+ self.subnet = subnet
+ self.router = router
+
+ def _get_tenant_credentials(self):
+ return self.tenant_user, self.tenant_pass, self.tenant_name
+
+ @classmethod
+ def check_preconditions(cls):
+ super(TestNetworkCrossTenant, cls).check_preconditions()
+ if (cls.alt_tenant_id is None) or (cls.tenant_id is cls.alt_tenant_id):
+ msg = 'No alt_tenant defined'
+ cls.enabled = False
+ raise cls.skipException(msg)
+ cfg = cls.config.network
+ if not (cfg.tenant_networks_reachable or cfg.public_network_id):
+ msg = ('Either tenant_networks_reachable must be "true", or '
+ 'public_network_id must be defined.')
+ cls.enabled = False
+ raise cls.skipException(msg)
+
+ @classmethod
+ def setUpClass(cls):
+ super(TestNetworkCrossTenant, cls).setUpClass()
+ cls.alt_tenant_id = cls.manager._get_identity_client(
+ cls.config.identity.alt_username,
+ cls.config.identity.alt_password,
+ cls.config.identity.alt_tenant_name
+ ).tenant_id
+ cls.check_preconditions()
+ # TODO(mnewby) Consider looking up entities as needed instead
+ # of storing them as collections on the class.
+ cls.keypairs = {}
+ cls.security_groups = {}
+ cls.networks = []
+ cls.subnets = []
+ cls.routers = []
+ cls.servers = []
+ cls.floating_ips = {}
+ cls.tenants = {}
+ cls.demo_tenant = cls.TenantProperties(
+ cls.tenant_id,
+ cls.config.identity.username,
+ cls.config.identity.password,
+ cls.config.identity.tenant_name
+ )
+ cls.alt_tenant = cls.TenantProperties(
+ cls.alt_tenant_id,
+ cls.config.identity.alt_username,
+ cls.config.identity.alt_password,
+ cls.config.identity.alt_tenant_name
+ )
+ for tenant in [cls.demo_tenant, cls.alt_tenant]:
+ cls.tenants[tenant.tenant_id] = tenant
+ if not cls.config.network.public_router_id:
+ cls.floating_ip_access = True
+ else:
+ cls.floating_ip_access = False
+
+ @classmethod
+ def tearDownClass(cls):
+ super(TestNetworkCrossTenant, cls).tearDownClass()
+
+ def _create_tenant_keypairs(self, tenant_id):
+ self.keypairs[tenant_id] = self.create_keypair(
+ name=data_utils.rand_name('keypair-smoke-'))
+
+ def _create_tenant_security_groups(self, tenant):
+ self.security_groups.setdefault(self.tenant_id, [])
+ access_sg = self._create_empty_security_group(
+ namestart='secgroup_access-',
+ tenant_id=tenant.tenant_id
+ )
+ # don't use default secgroup since it allows in-tenant traffic
+ def_sg = self._create_empty_security_group(
+ namestart='secgroup_general-',
+ tenant_id=tenant.tenant_id
+ )
+ tenant.security_groups.update(access=access_sg, default=def_sg)
+ ssh_rule = dict(
+ protocol='tcp',
+ port_range_min=22,
+ port_range_max=22,
+ direction='ingress',
+ )
+ self._create_security_group_rule(secgroup=access_sg,
+ **ssh_rule
+ )
+
+ def _verify_network_details(self, tenant):
+ # Checks that we see the newly created network/subnet/router via
+ # checking the result of list_[networks,routers,subnets]
+ # Check that (router, subnet) couple exist in port_list
+ seen_nets = self._list_networks()
+ seen_names = [n['name'] for n in seen_nets]
+ seen_ids = [n['id'] for n in seen_nets]
+
+ self.assertIn(tenant.network.name, seen_names)
+ self.assertIn(tenant.network.id, seen_ids)
+
+ seen_subnets = [(n['id'], n['cidr'], n['network_id'])
+ for n in self._list_subnets()]
+ mysubnet = (tenant.subnet.id, tenant.subnet.cidr, tenant.network.id)
+ self.assertIn(mysubnet, seen_subnets)
+
+ seen_routers = self._list_routers()
+ seen_router_ids = [n['id'] for n in seen_routers]
+ seen_router_names = [n['name'] for n in seen_routers]
+
+ self.assertIn(tenant.router.name, seen_router_names)
+ self.assertIn(tenant.router.id, seen_router_ids)
+
+ myport = (tenant.router.id, tenant.subnet.id)
+ router_ports = [(i['device_id'], i['fixed_ips'][0]['subnet_id']) for i
+ in self.network_client.list_ports()['ports']
+ if i['device_owner'] == 'network:router_interface']
+
+ self.assertIn(myport, router_ports)
+
+ def _create_server(self, name, tenant, security_groups=None):
+ """
+ creates a server and assigns to security group
+ """
+ self._set_compute_context(tenant)
+ if security_groups is None:
+ security_groups = [tenant.security_groups['default'].name]
+ create_kwargs = {
+ 'nics': [
+ {'net-id': tenant.network.id},
+ ],
+ 'key_name': self.keypairs[tenant.tenant_id].name,
+ 'security_groups': security_groups,
+ 'tenant_id': tenant.tenant_id
+ }
+ server = self.create_server(name=name, create_kwargs=create_kwargs)
+ return server
+
+ def _create_tenant_servers(self, tenant, num=1):
+ for i in range(num):
+ name = 'server-{tenant}-gen-{num}-'.format(
+ tenant=tenant.tenant_name,
+ num=i
+ )
+ name = data_utils.rand_name(name)
+ server = self._create_server(name, tenant)
+ self.servers.append(server)
+ tenant.servers.append(server)
+
+ def _set_access_point(self, tenant):
+ """
+ creates a server in a secgroup with rule allowing external ssh
+ in order to access tenant internal network
+ workaround ip namespace
+ """
+ secgroups = [sg.name for sg in tenant.security_groups.values()]
+ name = 'server-{tenant}-access_point-'.format(tenant=tenant.tenant_name
+ )
+ name = data_utils.rand_name(name)
+ server = self._create_server(name, tenant,
+ security_groups=secgroups)
+ self.servers.append(server)
+ tenant.access_point = server
+ self._assign_floating_ips(server)
+
+ def _assign_floating_ips(self, server):
+ public_network_id = self.config.network.public_network_id
+ floating_ip = self._create_floating_ip(server, public_network_id)
+ self.floating_ips.setdefault(server, floating_ip)
+
+ def _create_tenant_network(self, tenant):
+ tenant._set_network(*self._create_networks(tenant.tenant_id))
+
+ def _set_compute_context(self, tenant):
+ self.compute_client = tenant.manager.compute_client
+ return self.compute_client
+
+ def _deploy_tenant(self, tenant_or_id):
+ """
+ creates:
+ network
+ subnet
+ router (if public not defined)
+ access security group
+ access-point server
+ for demo_tenant:
+ creates general server to test against
+ """
+ if not isinstance(tenant_or_id, self.TenantProperties):
+ tenant = self.tenants[tenant_or_id]
+ tenant_id = tenant_or_id
+ else:
+ tenant = tenant_or_id
+ tenant_id = tenant.tenant_id
+ self._set_compute_context(tenant)
+ self._create_tenant_keypairs(tenant_id)
+ self._create_tenant_network(tenant)
+ self._create_tenant_security_groups(tenant)
+ if tenant is self.demo_tenant:
+ self._create_tenant_servers(tenant, num=1)
+ self._set_access_point(tenant)
+
+ def _get_server_ip(self, server, floating=False):
+ '''
+ returns the ip (floating/internal) of a server
+ '''
+ if floating:
+ return self.floating_ips[server].floating_ip_address
+ else:
+ network_name = self.tenants[server.tenant_id].network.name
+ return server.networks[network_name][0]
+
+ def _connect_to_access_point(self, tenant):
+ """
+ create ssh connection to tenant access point
+ """
+ access_point_ssh = \
+ self.floating_ips[tenant.access_point].floating_ip_address
+ private_key = self.keypairs[tenant.tenant_id].private_key
+ access_point_ssh = self._ssh_to_server(access_point_ssh,
+ private_key=private_key)
+ return access_point_ssh
+
+ def _test_remote_connectivity(self, source, dest, should_succeed=True):
+ """
+ check ping server via source ssh connection
+
+ :param source: RemoteClient: an ssh connection from which to ping
+ :param dest: and IP to ping against
+ :param should_succeed: boolean should ping succeed or not
+ :returns: boolean -- should_succeed == ping
+ :returns: ping is false if ping failed
+ """
+ def ping_remote():
+ try:
+ source.ping_host(dest)
+ except exceptions.SSHExecCommandFailed as ex:
+ LOG.debug(ex)
+ return not should_succeed
+ return should_succeed
+
+ return call_until_true(ping_remote,
+ self.config.compute.ping_timeout,
+ 1)
+
+ def _check_connectivity(self, access_point, ip, should_succeed=True):
+ if should_succeed:
+ msg = "Timed out waiting for %s to become reachable" % ip
+ else:
+ # todo(yfried): remove this line when bug 1252620 is fixed
+ return True
+ msg = "%s is reachable" % ip
+ try:
+ self.assertTrue(self._test_remote_connectivity(access_point, ip,
+ should_succeed),
+ msg)
+ except Exception:
+ debug.log_ip_ns()
+ raise
+
+ def _test_in_tenant_block(self, tenant):
+ access_point_ssh = self._connect_to_access_point(tenant)
+ for server in tenant.servers:
+ self._check_connectivity(access_point=access_point_ssh,
+ ip=self._get_server_ip(server),
+ should_succeed=False)
+
+ def _test_in_tenant_allow(self, tenant):
+ ruleset = dict(
+ protocol='icmp',
+ remote_group_id=tenant.security_groups['default'].id,
+ direction='ingress'
+ )
+ rule = self._create_security_group_rule(
+ secgroup=tenant.security_groups['default'],
+ **ruleset
+ )
+ access_point_ssh = self._connect_to_access_point(tenant)
+ for server in tenant.servers:
+ self._check_connectivity(access_point=access_point_ssh,
+ ip=self._get_server_ip(server))
+ rule.delete()
+
+ def _test_cross_tenant_block(self, source_tenant, dest_tenant):
+ '''
+ if public router isn't defined, then dest_tenant access is via
+ floating-ip
+ '''
+ access_point_ssh = self._connect_to_access_point(source_tenant)
+ ip = self._get_server_ip(dest_tenant.access_point,
+ floating=self.floating_ip_access)
+ self._check_connectivity(access_point=access_point_ssh, ip=ip,
+ should_succeed=False)
+
+ def _test_cross_tenant_allow(self, source_tenant, dest_tenant):
+ '''
+ check for each direction:
+ creating rule for tenant incoming traffic enables only 1way traffic
+ '''
+ ruleset = dict(
+ protocol='icmp',
+ direction='ingress'
+ )
+ rule_s2d = self._create_security_group_rule(
+ secgroup=dest_tenant.security_groups['default'],
+ **ruleset
+ )
+ try:
+ access_point_ssh = self._connect_to_access_point(source_tenant)
+ ip = self._get_server_ip(dest_tenant.access_point,
+ floating=self.floating_ip_access)
+ self._check_connectivity(access_point_ssh, ip)
+
+ # test that reverse traffic is still blocked
+ self._test_cross_tenant_block(dest_tenant, source_tenant)
+
+ # allow reverse traffic and check
+ rule_d2s = self._create_security_group_rule(
+ secgroup=source_tenant.security_groups['default'],
+ **ruleset
+ )
+ try:
+ access_point_ssh_2 = self._connect_to_access_point(dest_tenant)
+ ip = self._get_server_ip(source_tenant.access_point,
+ floating=self.floating_ip_access)
+ self._check_connectivity(access_point_ssh_2, ip)
+
+ # clean_rules
+ rule_s2d.delete()
+ rule_d2s.delete()
+
+ except Exception as e:
+ rule_d2s.delete()
+ raise e
+
+ except Exception as e:
+ rule_s2d.delete()
+ raise e
+
+ def _verify_mac_addr(self, tenant):
+ """
+ verify that VM (tenant's access point) has the same ip,mac as listed in
+ port list
+ """
+ access_point_ssh = self._connect_to_access_point(tenant)
+ mac_addr = access_point_ssh.get_mac_address()
+ mac_addr = mac_addr.strip().lower()
+ port_list = self.network_client.list_ports()['ports']
+ port_detail_list = [
+ (port['fixed_ips'][0]['subnet_id'],
+ port['fixed_ips'][0]['ip_address'],
+ port['mac_address'].lower()) for port in port_list
+ ]
+ server_ip = self._get_server_ip(tenant.access_point)
+ subnet_id = tenant.subnet.id
+ self.assertIn((subnet_id, server_ip, mac_addr), port_detail_list)
+
+ @attr(type='smoke')
+ @services('compute', 'network')
+ def test_cross_tenant_traffic(self):
+ for tenant_id in self.tenants.keys():
+ self._deploy_tenant(tenant_id)
+ self._verify_network_details(self.tenants[tenant_id])
+ self._verify_mac_addr(self.tenants[tenant_id])
+
+ # in-tenant check
+ self._test_in_tenant_block(self.demo_tenant)
+ self._test_in_tenant_allow(self.demo_tenant)
+
+ # cross tenant check
+ source_tenant = self.demo_tenant
+ dest_tenant = self.alt_tenant
+ self._test_cross_tenant_block(source_tenant, dest_tenant)
+ self._test_cross_tenant_allow(source_tenant, dest_tenant)
diff --git a/tempest/scenario/test_network_basic_ops.py b/tempest/scenario/test_network_basic_ops.py
index bfded53..54517ab 100644
--- a/tempest/scenario/test_network_basic_ops.py
+++ b/tempest/scenario/test_network_basic_ops.py
@@ -16,18 +16,59 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest.api.network import common as net_common
from tempest.common import debug
from tempest.common.utils import data_utils
from tempest import config
+from tempest.openstack.common import jsonutils
from tempest.openstack.common import log as logging
from tempest.scenario import manager
+
+import tempest.test
from tempest.test import attr
from tempest.test import services
LOG = logging.getLogger(__name__)
+class FloatingIPCheckTracker(object):
+ """
+ Checking VM connectivity through floating IP addresses is bound to fail
+ if the floating IP has not actually been associated with the VM yet.
+ This helper class facilitates checking for floating IP assignments on
+ VMs. It only checks for a given IP address once.
+ """
+
+ def __init__(self, compute_client, floating_ip_map):
+ self.compute_client = compute_client
+ self.unchecked = floating_ip_map.copy()
+
+ def run_checks(self):
+ """Check for any remaining unverified floating IPs
+
+ Gets VM details from nova and checks for floating IPs
+ within the returned information. Returns true when all
+ checks are complete and is suitable for use with
+ tempest.test.call_until_true()
+ """
+ to_delete = []
+ loggable_map = {}
+ for check_addr, server in self.unchecked.iteritems():
+ serverdata = self.compute_client.servers.get(server.id)
+ ip_addr = [addr for sublist in serverdata.networks.values() for
+ addr in sublist]
+ if check_addr.floating_ip_address in ip_addr:
+ to_delete.append(check_addr)
+ else:
+ loggable_map[server.id] = check_addr
+
+ for to_del in to_delete:
+ del self.unchecked[to_del]
+
+ LOG.debug('Unchecked floating IPs: %s',
+ jsonutils.dumps(loggable_map))
+ return len(self.unchecked) == 0
+
+
class TestNetworkBasicOps(manager.NetworkScenarioTest):
"""
@@ -46,6 +87,9 @@
ssh server hosted at the IP address. This check guarantees
that the IP address is associated with the target VM.
+ - detach the floating-ip from the VM and verify that it becomes
+ unreachable
+
# TODO(mnewby) - Need to implement the following:
- the Tempest host can ssh into the VM via the IP address and
successfully execute the following:
@@ -120,45 +164,6 @@
cls.servers = []
cls.floating_ips = {}
- def _get_router(self, tenant_id):
- """Retrieve a router for the given tenant id.
-
- If a public router has been configured, it will be returned.
-
- If a public router has not been configured, but a public
- network has, a tenant router will be created and returned that
- routes traffic to the public network.
-
- """
- router_id = self.config.network.public_router_id
- network_id = self.config.network.public_network_id
- if router_id:
- result = self.network_client.show_router(router_id)
- return net_common.AttributeDict(**result['router'])
- elif network_id:
- router = self._create_router(tenant_id)
- router.add_gateway(network_id)
- return router
- else:
- raise Exception("Neither of 'public_router_id' or "
- "'public_network_id' has been defined.")
-
- def _create_router(self, tenant_id, namestart='router-smoke-'):
- name = data_utils.rand_name(namestart)
- body = dict(
- router=dict(
- name=name,
- admin_state_up=True,
- tenant_id=tenant_id,
- ),
- )
- result = self.network_client.create_router(body=body)
- router = net_common.DeletableRouter(client=self.network_client,
- **result['router'])
- self.assertEqual(router.name, name)
- self.set_resource(name, router)
- return router
-
def _create_keypairs(self):
self.keypairs[self.tenant_id] = self.create_keypair(
name=data_utils.rand_name('keypair-smoke-'))
@@ -167,15 +172,6 @@
self.security_groups[self.tenant_id] =\
self._create_security_group_neutron(tenant_id=self.tenant_id)
- def _create_networks(self):
- network = self._create_network(self.tenant_id)
- router = self._get_router(self.tenant_id)
- subnet = self._create_subnet(network)
- subnet.add_to_router(router.id)
- self.networks.append(network)
- self.subnets.append(subnet)
- self.routers.append(router)
-
def _check_networks(self):
# Checks that we see the newly created network/subnet/router via
# checking the result of list_[networks,routers,subnets]
@@ -228,36 +224,56 @@
# key-based authentication by cloud-init.
ssh_login = self.config.compute.image_ssh_user
private_key = self.keypairs[self.tenant_id].private_key
- for server in self.servers:
- for net_name, ip_addresses in server.networks.iteritems():
- for ip_address in ip_addresses:
- self._check_vm_connectivity(ip_address, ssh_login,
- private_key)
+ try:
+ for server in self.servers:
+ for net_name, ip_addresses in server.networks.iteritems():
+ for ip_address in ip_addresses:
+ self._check_vm_connectivity(ip_address, ssh_login,
+ private_key)
+ except Exception as exc:
+ LOG.exception(exc)
+ debug.log_ip_ns()
+ raise exc
- def _assign_floating_ips(self):
+ def _wait_for_floating_ip_association(self):
+ ip_tracker = FloatingIPCheckTracker(self.compute_client,
+ self.floating_ips)
+
+ self.assertTrue(
+ tempest.test.call_until_true(
+ ip_tracker.run_checks, self.config.compute.build_timeout,
+ self.config.compute.build_interval),
+ "Timed out while waiting for the floating IP assignments "
+ "to propagate")
+
+ def _create_and_associate_floating_ips(self):
public_network_id = self.config.network.public_network_id
for server in self.servers:
floating_ip = self._create_floating_ip(server, public_network_id)
- self.floating_ips.setdefault(server, [])
- self.floating_ips[server].append(floating_ip)
+ self.floating_ips[floating_ip] = server
- def _check_public_network_connectivity(self):
+ def _check_public_network_connectivity(self, should_connect=True):
# The target login is assumed to have been configured for
# key-based authentication by cloud-init.
ssh_login = self.config.compute.image_ssh_user
private_key = self.keypairs[self.tenant_id].private_key
try:
- for server, floating_ips in self.floating_ips.iteritems():
- for floating_ip in floating_ips:
- ip_address = floating_ip.floating_ip_address
- self._check_vm_connectivity(ip_address,
- ssh_login,
- private_key)
+ for floating_ip, server in self.floating_ips.iteritems():
+ ip_address = floating_ip.floating_ip_address
+ self._check_vm_connectivity(ip_address,
+ ssh_login,
+ private_key,
+ should_connect=should_connect)
except Exception as exc:
LOG.exception(exc)
debug.log_ip_ns()
raise exc
+ def _disassociate_floating_ips(self):
+ for floating_ip, server in self.floating_ips.iteritems():
+ self._disassociate_floating_ip(floating_ip)
+ self.floating_ips[floating_ip] = None
+
@attr(type='smoke')
@services('compute', 'network')
def test_network_basic_ops(self):
@@ -266,6 +282,9 @@
self._create_networks()
self._check_networks()
self._create_servers()
- self._assign_floating_ips()
- self._check_public_network_connectivity()
+ self._create_and_associate_floating_ips()
+ self._wait_for_floating_ip_association()
self._check_tenant_network_connectivity()
+ self._check_public_network_connectivity(should_connect=True)
+ self._disassociate_floating_ips()
+ self._check_public_network_connectivity(should_connect=False)
diff --git a/tempest/scenario/test_network_quotas.py b/tempest/scenario/test_network_quotas.py
index 3268066..cb7aa0b 100644
--- a/tempest/scenario/test_network_quotas.py
+++ b/tempest/scenario/test_network_quotas.py
@@ -20,8 +20,6 @@
from tempest.scenario.manager import NetworkScenarioTest
from tempest.test import services
-MAX_REASONABLE_ITERATIONS = 51 # more than enough. Default for port is 50.
-
class TestNetworkQuotaBasic(NetworkScenarioTest):
"""
@@ -46,7 +44,9 @@
@services('network')
def test_create_network_until_quota_hit(self):
hit_limit = False
- for n in xrange(MAX_REASONABLE_ITERATIONS):
+ networknum = self._get_tenant_own_network_num(self.tenant_id)
+ max = self._show_quota_network(self.tenant_id) - networknum
+ for n in xrange(max):
try:
self.networks.append(
self._create_network(self.tenant_id,
@@ -56,6 +56,16 @@
raise
hit_limit = True
break
+ self.assertFalse(hit_limit, "Failed: Hit quota limit !")
+
+ try:
+ self.networks.append(
+ self._create_network(self.tenant_id,
+ namestart='network-quotatest-'))
+ except exc.NeutronClientException as e:
+ if (e.status_code != 409):
+ raise
+ hit_limit = True
self.assertTrue(hit_limit, "Failed: Did not hit quota limit !")
@services('network')
@@ -65,7 +75,9 @@
self._create_network(self.tenant_id,
namestart='network-quotatest-'))
hit_limit = False
- for n in xrange(MAX_REASONABLE_ITERATIONS):
+ subnetnum = self._get_tenant_own_subnet_num(self.tenant_id)
+ max = self._show_quota_subnet(self.tenant_id) - subnetnum
+ for n in xrange(max):
try:
self.subnets.append(
self._create_subnet(self.networks[0],
@@ -75,6 +87,16 @@
raise
hit_limit = True
break
+ self.assertFalse(hit_limit, "Failed: Hit quota limit !")
+
+ try:
+ self.subnets.append(
+ self._create_subnet(self.networks[0],
+ namestart='subnet-quotatest-'))
+ except exc.NeutronClientException as e:
+ if (e.status_code != 409):
+ raise
+ hit_limit = True
self.assertTrue(hit_limit, "Failed: Did not hit quota limit !")
@services('network')
@@ -84,7 +106,9 @@
self._create_network(self.tenant_id,
namestart='network-quotatest-'))
hit_limit = False
- for n in xrange(MAX_REASONABLE_ITERATIONS):
+ portnum = self._get_tenant_own_port_num(self.tenant_id)
+ max = self._show_quota_port(self.tenant_id) - portnum
+ for n in xrange(max):
try:
self.ports.append(
self._create_port(self.networks[0],
@@ -94,4 +118,14 @@
raise
hit_limit = True
break
+ self.assertFalse(hit_limit, "Failed: Hit quota limit !")
+
+ try:
+ self.ports.append(
+ self._create_port(self.networks[0],
+ namestart='port-quotatest-'))
+ except exc.NeutronClientException as e:
+ if (e.status_code != 409):
+ raise
+ hit_limit = True
self.assertTrue(hit_limit, "Failed: Did not hit quota limit !")
diff --git a/tempest/services/compute/json/extensions_client.py b/tempest/services/compute/json/extensions_client.py
index ce46a9b..ad5354c 100644
--- a/tempest/services/compute/json/extensions_client.py
+++ b/tempest/services/compute/json/extensions_client.py
@@ -37,3 +37,8 @@
_, extensions = self.list_extensions()
exts = extensions['extensions']
return any([e for e in exts if e['name'] == extension])
+
+ def get_extension(self, extension_alias):
+ resp, body = self.get('extensions/%s' % extension_alias)
+ body = json.loads(body)
+ return resp, body['extension']
diff --git a/tempest/services/compute/json/images_client.py b/tempest/services/compute/json/images_client.py
index 5f17894..c05571a 100644
--- a/tempest/services/compute/json/images_client.py
+++ b/tempest/services/compute/json/images_client.py
@@ -16,10 +16,10 @@
# under the License.
import json
-import time
import urllib
from tempest.common.rest_client import RestClient
+from tempest.common import waiters
from tempest import exceptions
@@ -82,18 +82,7 @@
def wait_for_image_status(self, image_id, status):
"""Waits for an image to reach a given status."""
- resp, image = self.get_image(image_id)
- start = int(time.time())
-
- while image['status'] != status:
- time.sleep(self.build_interval)
- resp, image = self.get_image(image_id)
-
- if image['status'] == 'ERROR':
- raise exceptions.AddImageException(image_id=image_id)
-
- if int(time.time()) - start >= self.build_timeout:
- raise exceptions.TimeoutException
+ waiters.wait_for_image_status(self, image_id, status)
def list_image_metadata(self, image_id):
"""Lists all metadata items for an image."""
diff --git a/tempest/services/compute/json/servers_client.py b/tempest/services/compute/json/servers_client.py
index 3c6a40f..eb1a0c3 100644
--- a/tempest/services/compute/json/servers_client.py
+++ b/tempest/services/compute/json/servers_client.py
@@ -154,10 +154,12 @@
body = json.loads(body)
return resp, body
- def wait_for_server_status(self, server_id, status, extra_timeout=0):
+ def wait_for_server_status(self, server_id, status, extra_timeout=0,
+ raise_on_error=True):
"""Waits for a server to reach a given status."""
return waiters.wait_for_server_status(self, server_id, status,
- extra_timeout=extra_timeout)
+ extra_timeout=extra_timeout,
+ raise_on_error=raise_on_error)
def wait_for_server_termination(self, server_id, ignore_error=False):
"""Waits for server to reach termination."""
diff --git a/tempest/services/compute/v3/json/extensions_client.py b/tempest/services/compute/v3/json/extensions_client.py
index 60c0217..6e0dc9d 100644
--- a/tempest/services/compute/v3/json/extensions_client.py
+++ b/tempest/services/compute/v3/json/extensions_client.py
@@ -38,3 +38,8 @@
_, extensions = self.list_extensions()
exts = extensions['extensions']
return any([e for e in exts if e['name'] == extension])
+
+ def get_extension(self, extension_alias):
+ resp, body = self.get('extensions/%s' % extension_alias)
+ body = json.loads(body)
+ return resp, body['extension']
diff --git a/tempest/services/compute/v3/json/flavors_client.py b/tempest/services/compute/v3/json/flavors_client.py
new file mode 100644
index 0000000..00d6f8a
--- /dev/null
+++ b/tempest/services/compute/v3/json/flavors_client.py
@@ -0,0 +1,156 @@
+# 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
+import urllib
+
+from tempest.common.rest_client import RestClient
+
+
+class FlavorsClientJSON(RestClient):
+
+ def __init__(self, config, username, password, auth_url, tenant_name=None):
+ super(FlavorsClientJSON, self).__init__(config, username, password,
+ auth_url, tenant_name)
+ self.service = self.config.compute.catalog_type
+
+ def list_flavors(self, params=None):
+ url = 'flavors'
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+
+ resp, body = self.get(url)
+ body = json.loads(body)
+ return resp, body['flavors']
+
+ def list_flavors_with_detail(self, params=None):
+ url = 'flavors/detail'
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+
+ resp, body = self.get(url)
+ body = json.loads(body)
+ return resp, body['flavors']
+
+ def get_flavor_details(self, flavor_id):
+ resp, body = self.get("flavors/%s" % str(flavor_id))
+ body = json.loads(body)
+ return resp, body['flavor']
+
+ def create_flavor(self, name, ram, vcpus, disk, flavor_id, **kwargs):
+ """Creates a new flavor or instance type."""
+ post_body = {
+ 'name': name,
+ 'ram': ram,
+ 'vcpus': vcpus,
+ 'disk': disk,
+ 'id': flavor_id,
+ }
+ if kwargs.get('ephemeral'):
+ post_body['OS-FLV-EXT-DATA:ephemeral'] = kwargs.get('ephemeral')
+ if kwargs.get('swap'):
+ post_body['swap'] = kwargs.get('swap')
+ if kwargs.get('rxtx'):
+ post_body['rxtx_factor'] = kwargs.get('rxtx')
+ if kwargs.get('is_public'):
+ post_body['os-flavor-access:is_public'] = kwargs.get('is_public')
+ post_body = json.dumps({'flavor': post_body})
+ resp, body = self.post('flavors', post_body, self.headers)
+
+ body = json.loads(body)
+ return resp, body['flavor']
+
+ def delete_flavor(self, flavor_id):
+ """Deletes the given flavor."""
+ return self.delete("flavors/%s" % str(flavor_id))
+
+ def is_resource_deleted(self, id):
+ # Did not use get_flavor_details(id) for verification as it gives
+ # 200 ok even for deleted id. LP #981263
+ # we can remove the loop here and use get by ID when bug gets sortedout
+ resp, flavors = self.list_flavors_with_detail()
+ for flavor in flavors:
+ if flavor['id'] == id:
+ return False
+ return True
+
+ def set_flavor_extra_spec(self, flavor_id, specs):
+ """Sets extra Specs to the mentioned flavor."""
+ post_body = json.dumps({'extra_specs': specs})
+ resp, body = self.post('flavors/%s/os-extra_specs' % flavor_id,
+ post_body, self.headers)
+ body = json.loads(body)
+ return resp, body['extra_specs']
+
+ def get_flavor_extra_spec(self, flavor_id):
+ """Gets extra Specs details of the mentioned flavor."""
+ resp, body = self.get('flavors/%s/os-extra_specs' % flavor_id)
+ body = json.loads(body)
+ return resp, body['extra_specs']
+
+ def get_flavor_extra_spec_with_key(self, flavor_id, key):
+ """Gets extra Specs key-value of the mentioned flavor and key."""
+ resp, body = self.get('flavors/%s/os-extra_specs/%s' % (str(flavor_id),
+ key))
+ body = json.loads(body)
+ return resp, body
+
+ def update_flavor_extra_spec(self, flavor_id, key, **kwargs):
+ """Update specified extra Specs of the mentioned flavor and key."""
+ resp, body = self.put('flavors/%s/os-extra_specs/%s' %
+ (flavor_id, key),
+ json.dumps(kwargs), self.headers)
+ body = json.loads(body)
+ return resp, body
+
+ def unset_flavor_extra_spec(self, flavor_id, key):
+ """Unsets extra Specs from the mentioned flavor."""
+ return self.delete('flavors/%s/os-extra_specs/%s' % (str(flavor_id),
+ key))
+
+ def list_flavor_access(self, flavor_id):
+ """Gets flavor access information given the flavor id."""
+ resp, body = self.get('flavors/%s/os-flavor-access' % flavor_id,
+ self.headers)
+ body = json.loads(body)
+ return resp, body['flavor_access']
+
+ def add_flavor_access(self, flavor_id, tenant_id):
+ """Add flavor access for the specified tenant."""
+ post_body = {
+ 'addTenantAccess': {
+ 'tenant': tenant_id
+ }
+ }
+ post_body = json.dumps(post_body)
+ resp, body = self.post('flavors/%s/action' % flavor_id,
+ post_body, self.headers)
+ body = json.loads(body)
+ return resp, body['flavor_access']
+
+ def remove_flavor_access(self, flavor_id, tenant_id):
+ """Remove flavor access from the specified tenant."""
+ post_body = {
+ 'removeTenantAccess': {
+ 'tenant': tenant_id
+ }
+ }
+ post_body = json.dumps(post_body)
+ resp, body = self.post('flavors/%s/action' % flavor_id,
+ post_body, self.headers)
+ body = json.loads(body)
+ return resp, body['flavor_access']
diff --git a/tempest/services/compute/v3/json/limits_client.py b/tempest/services/compute/v3/json/limits_client.py
new file mode 100644
index 0000000..3e53e3e
--- /dev/null
+++ b/tempest/services/compute/v3/json/limits_client.py
@@ -0,0 +1,40 @@
+# 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/quotas_client.py b/tempest/services/compute/v3/json/quotas_client.py
new file mode 100644
index 0000000..a910dec
--- /dev/null
+++ b/tempest/services/compute/v3/json/quotas_client.py
@@ -0,0 +1,103 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2012 NTT Data
+# 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 QuotasClientJSON(RestClient):
+
+ def __init__(self, config, username, password, auth_url, tenant_name=None):
+ super(QuotasClientJSON, self).__init__(config, username, password,
+ auth_url, tenant_name)
+ self.service = self.config.compute.catalog_type
+
+ def get_quota_set(self, tenant_id):
+ """List the quota set for a tenant."""
+
+ url = 'os-quota-sets/%s' % str(tenant_id)
+ resp, body = self.get(url)
+ body = json.loads(body)
+ return resp, body['quota_set']
+
+ def get_default_quota_set(self, tenant_id):
+ """List the default quota set for a tenant."""
+
+ url = 'os-quota-sets/%s/defaults' % str(tenant_id)
+ resp, body = self.get(url)
+ body = json.loads(body)
+ return resp, body['quota_set']
+
+ def update_quota_set(self, tenant_id, force=None,
+ injected_file_content_bytes=None,
+ metadata_items=None, ram=None, floating_ips=None,
+ fixed_ips=None, key_pairs=None, instances=None,
+ security_group_rules=None, injected_files=None,
+ cores=None, injected_file_path_bytes=None,
+ security_groups=None):
+ """
+ Updates the tenant's quota limits for one or more resources
+ """
+ post_body = {}
+
+ if force is not None:
+ post_body['force'] = force
+
+ if injected_file_content_bytes is not None:
+ post_body['injected_file_content_bytes'] = \
+ injected_file_content_bytes
+
+ if metadata_items is not None:
+ post_body['metadata_items'] = metadata_items
+
+ if ram is not None:
+ post_body['ram'] = ram
+
+ if floating_ips is not None:
+ post_body['floating_ips'] = floating_ips
+
+ if fixed_ips is not None:
+ post_body['fixed_ips'] = fixed_ips
+
+ if key_pairs is not None:
+ post_body['key_pairs'] = key_pairs
+
+ if instances is not None:
+ post_body['instances'] = instances
+
+ if security_group_rules is not None:
+ post_body['security_group_rules'] = security_group_rules
+
+ if injected_files is not None:
+ post_body['injected_files'] = injected_files
+
+ if cores is not None:
+ post_body['cores'] = cores
+
+ if injected_file_path_bytes is not None:
+ post_body['injected_file_path_bytes'] = injected_file_path_bytes
+
+ if security_groups is not None:
+ post_body['security_groups'] = security_groups
+
+ post_body = json.dumps({'quota_set': post_body})
+ resp, body = self.put('os-quota-sets/%s' % str(tenant_id), post_body,
+ self.headers)
+
+ body = json.loads(body)
+ return resp, body['quota_set']
diff --git a/tempest/services/compute/v3/json/servers_client.py b/tempest/services/compute/v3/json/servers_client.py
index cddbb53..a7fcc6d 100644
--- a/tempest/services/compute/v3/json/servers_client.py
+++ b/tempest/services/compute/v3/json/servers_client.py
@@ -121,7 +121,7 @@
post_body['access_ip_v6'] = access_ip_v6
if disk_config is not None:
- post_body['OS-DCF:diskConfig'] = disk_config
+ post_body['os-disk-config:disk_config'] = disk_config
post_body = json.dumps({'server': post_body})
resp, body = self.put("servers/%s" % str(server_id),
@@ -309,14 +309,6 @@
"""Detaches a volume from a server instance."""
return self.action(server_id, 'detach', None, volume_id=volume_id)
- def add_security_group(self, server_id, name):
- """Adds a security group to the server."""
- return self.action(server_id, 'add_security_group', None, name=name)
-
- def remove_security_group(self, server_id, name):
- """Removes a security group from the server."""
- return self.action(server_id, 'remove_security_group', None, name=name)
-
def live_migrate_server(self, server_id, dest_host, use_block_migration):
"""This should be called with administrator privileges ."""
diff --git a/tempest/services/compute/v3/json/tenant_usages_client.py b/tempest/services/compute/v3/json/tenant_usages_client.py
index 4dd6964..298f363 100644
--- a/tempest/services/compute/v3/json/tenant_usages_client.py
+++ b/tempest/services/compute/v3/json/tenant_usages_client.py
@@ -21,12 +21,12 @@
from tempest.common.rest_client import RestClient
-class TenantUsagesClientJSON(RestClient):
+class TenantUsagesV3ClientJSON(RestClient):
def __init__(self, config, username, password, auth_url, tenant_name=None):
- super(TenantUsagesClientJSON, self).__init__(
+ super(TenantUsagesV3ClientJSON, self).__init__(
config, username, password, auth_url, tenant_name)
- self.service = self.config.compute.catalog_type
+ self.service = self.config.compute.catalog_v3_type
def list_tenant_usages(self, params=None):
url = 'os-simple-tenant-usage'
diff --git a/tempest/services/compute/v3/xml/extensions_client.py b/tempest/services/compute/v3/xml/extensions_client.py
index e03251c..8f97692 100644
--- a/tempest/services/compute/v3/xml/extensions_client.py
+++ b/tempest/services/compute/v3/xml/extensions_client.py
@@ -43,3 +43,8 @@
_, extensions = self.list_extensions()
exts = extensions['extensions']
return any([e for e in exts if e['name'] == extension])
+
+ def get_extension(self, extension_alias):
+ resp, body = self.get('extensions/%s' % extension_alias, self.headers)
+ body = xml_to_json(etree.fromstring(body))
+ return resp, body
diff --git a/tempest/services/compute/v3/xml/flavors_client.py b/tempest/services/compute/v3/xml/flavors_client.py
new file mode 100644
index 0000000..a1c74d9
--- /dev/null
+++ b/tempest/services/compute/v3/xml/flavors_client.py
@@ -0,0 +1,220 @@
+# 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 urllib
+
+from lxml import etree
+
+from tempest.common.rest_client import RestClientXML
+from tempest.services.compute.xml.common import Document
+from tempest.services.compute.xml.common import Element
+from tempest.services.compute.xml.common import Text
+from tempest.services.compute.xml.common import xml_to_json
+from tempest.services.compute.xml.common import XMLNS_11
+
+
+XMLNS_OS_FLV_EXT_DATA = \
+ "http://docs.openstack.org/compute/ext/flavor_extra_data/api/v1.1"
+XMLNS_OS_FLV_ACCESS = \
+ "http://docs.openstack.org/compute/ext/flavor_access/api/v2"
+
+
+class FlavorsClientXML(RestClientXML):
+
+ def __init__(self, config, username, password, auth_url, tenant_name=None):
+ super(FlavorsClientXML, self).__init__(config, username, password,
+ auth_url, tenant_name)
+ self.service = self.config.compute.catalog_type
+
+ def _format_flavor(self, f):
+ flavor = {'links': []}
+ for k, v in f.items():
+ if k == 'id':
+ flavor['id'] = v
+ continue
+
+ if k == 'link':
+ flavor['links'].append(v)
+ continue
+
+ if k == '{%s}ephemeral' % XMLNS_OS_FLV_EXT_DATA:
+ k = 'OS-FLV-EXT-DATA:ephemeral'
+
+ if k == '{%s}is_public' % XMLNS_OS_FLV_ACCESS:
+ k = 'os-flavor-access:is_public'
+ v = True if v == 'True' else False
+
+ if k == 'extra_specs':
+ k = 'OS-FLV-WITH-EXT-SPECS:extra_specs'
+ flavor[k] = dict(v)
+ continue
+
+ try:
+ v = int(v)
+ except ValueError:
+ try:
+ v = float(v)
+ except ValueError:
+ pass
+
+ flavor[k] = v
+
+ return flavor
+
+ def _parse_array(self, node):
+ return [self._format_flavor(xml_to_json(x)) for x in node]
+
+ def _list_flavors(self, url, params):
+ if params:
+ url += "?%s" % urllib.urlencode(params)
+
+ resp, body = self.get(url, self.headers)
+ flavors = self._parse_array(etree.fromstring(body))
+ return resp, flavors
+
+ def list_flavors(self, params=None):
+ url = 'flavors'
+ return self._list_flavors(url, params)
+
+ def list_flavors_with_detail(self, params=None):
+ url = 'flavors/detail'
+ return self._list_flavors(url, params)
+
+ def get_flavor_details(self, flavor_id):
+ resp, body = self.get("flavors/%s" % str(flavor_id), self.headers)
+ body = xml_to_json(etree.fromstring(body))
+ flavor = self._format_flavor(body)
+ return resp, flavor
+
+ def create_flavor(self, name, ram, vcpus, disk, flavor_id, **kwargs):
+ """Creates a new flavor or instance type."""
+ flavor = Element("flavor",
+ xmlns=XMLNS_11,
+ ram=ram,
+ vcpus=vcpus,
+ disk=disk,
+ id=flavor_id,
+ name=name)
+ if kwargs.get('rxtx'):
+ flavor.add_attr('rxtx_factor', kwargs.get('rxtx'))
+ if kwargs.get('swap'):
+ flavor.add_attr('swap', kwargs.get('swap'))
+ if kwargs.get('ephemeral'):
+ flavor.add_attr('OS-FLV-EXT-DATA:ephemeral',
+ kwargs.get('ephemeral'))
+ if kwargs.get('is_public'):
+ flavor.add_attr('os-flavor-access:is_public',
+ kwargs.get('is_public'))
+ flavor.add_attr('xmlns:OS-FLV-EXT-DATA', XMLNS_OS_FLV_EXT_DATA)
+ flavor.add_attr('xmlns:os-flavor-access', XMLNS_OS_FLV_ACCESS)
+ resp, body = self.post('flavors', str(Document(flavor)), self.headers)
+ body = xml_to_json(etree.fromstring(body))
+ flavor = self._format_flavor(body)
+ return resp, flavor
+
+ def delete_flavor(self, flavor_id):
+ """Deletes the given flavor."""
+ return self.delete("flavors/%s" % str(flavor_id), self.headers)
+
+ def is_resource_deleted(self, id):
+ # Did not use get_flavor_details(id) for verification as it gives
+ # 200 ok even for deleted id. LP #981263
+ # we can remove the loop here and use get by ID when bug gets sortedout
+ resp, flavors = self.list_flavors_with_detail()
+ for flavor in flavors:
+ if flavor['id'] == id:
+ return False
+ return True
+
+ def set_flavor_extra_spec(self, flavor_id, specs):
+ """Sets extra Specs to the mentioned flavor."""
+ extra_specs = Element("extra_specs")
+ for key in specs.keys():
+ extra_specs.add_attr(key, specs[key])
+ resp, body = self.post('flavors/%s/os-extra_specs' % flavor_id,
+ str(Document(extra_specs)), self.headers)
+ body = xml_to_json(etree.fromstring(body))
+ return resp, body
+
+ def get_flavor_extra_spec(self, flavor_id):
+ """Gets extra Specs of the mentioned flavor."""
+ resp, body = self.get('flavors/%s/os-extra_specs' % flavor_id,
+ self.headers)
+ body = xml_to_json(etree.fromstring(body))
+ return resp, body
+
+ def get_flavor_extra_spec_with_key(self, flavor_id, key):
+ """Gets extra Specs key-value of the mentioned flavor and key."""
+ resp, xml_body = self.get('flavors/%s/os-extra_specs/%s' %
+ (str(flavor_id), key), self.headers)
+ body = {}
+ element = etree.fromstring(xml_body)
+ key = element.get('key')
+ body[key] = xml_to_json(element)
+ return resp, body
+
+ def update_flavor_extra_spec(self, flavor_id, key, **kwargs):
+ """Update extra Specs details of the mentioned flavor and key."""
+ doc = Document()
+ for (k, v) in kwargs.items():
+ element = Element(k)
+ doc.append(element)
+ value = Text(v)
+ element.append(value)
+
+ resp, body = self.put('flavors/%s/os-extra_specs/%s' %
+ (flavor_id, key),
+ str(doc), self.headers)
+ body = xml_to_json(etree.fromstring(body))
+ return resp, {key: body}
+
+ def unset_flavor_extra_spec(self, flavor_id, key):
+ """Unsets an extra spec based on the mentioned flavor and key."""
+ return self.delete('flavors/%s/os-extra_specs/%s' % (str(flavor_id),
+ key))
+
+ def _parse_array_access(self, node):
+ return [xml_to_json(x) for x in node]
+
+ def list_flavor_access(self, flavor_id):
+ """Gets flavor access information given the flavor id."""
+ resp, body = self.get('flavors/%s/os-flavor-access' % str(flavor_id),
+ self.headers)
+ body = self._parse_array(etree.fromstring(body))
+ return resp, body
+
+ def add_flavor_access(self, flavor_id, tenant_id):
+ """Add flavor access for the specified tenant."""
+ doc = Document()
+ server = Element("addTenantAccess")
+ doc.append(server)
+ server.add_attr("tenant", tenant_id)
+ resp, body = self.post('flavors/%s/action' % str(flavor_id),
+ str(doc), self.headers)
+ body = self._parse_array_access(etree.fromstring(body))
+ return resp, body
+
+ def remove_flavor_access(self, flavor_id, tenant_id):
+ """Remove flavor access from the specified tenant."""
+ doc = Document()
+ server = Element("removeTenantAccess")
+ doc.append(server)
+ server.add_attr("tenant", tenant_id)
+ resp, body = self.post('flavors/%s/action' % str(flavor_id),
+ str(doc), self.headers)
+ body = self._parse_array_access(etree.fromstring(body))
+ return resp, body
diff --git a/tempest/services/compute/v3/xml/limits_client.py b/tempest/services/compute/v3/xml/limits_client.py
new file mode 100644
index 0000000..704de52
--- /dev/null
+++ b/tempest/services/compute/v3/xml/limits_client.py
@@ -0,0 +1,55 @@
+# 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/quotas_client.py b/tempest/services/compute/v3/xml/quotas_client.py
new file mode 100644
index 0000000..ef5362c
--- /dev/null
+++ b/tempest/services/compute/v3/xml/quotas_client.py
@@ -0,0 +1,126 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2012 NTT Data
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from lxml import etree
+
+from tempest.common.rest_client import RestClientXML
+from tempest.services.compute.xml.common import Document
+from tempest.services.compute.xml.common import Element
+from tempest.services.compute.xml.common import xml_to_json
+from tempest.services.compute.xml.common import XMLNS_11
+
+
+class QuotasClientXML(RestClientXML):
+
+ def __init__(self, config, username, password, auth_url, tenant_name=None):
+ super(QuotasClientXML, self).__init__(config, username, password,
+ auth_url, tenant_name)
+ self.service = self.config.compute.catalog_type
+
+ def _format_quota(self, q):
+ quota = {}
+ for k, v in q.items():
+ try:
+ v = int(v)
+ except ValueError:
+ pass
+
+ quota[k] = v
+
+ return quota
+
+ def _parse_array(self, node):
+ return [self._format_quota(xml_to_json(x)) for x in node]
+
+ def get_quota_set(self, tenant_id):
+ """List the quota set for a tenant."""
+
+ url = 'os-quota-sets/%s' % str(tenant_id)
+ resp, body = self.get(url, self.headers)
+ body = xml_to_json(etree.fromstring(body))
+ body = self._format_quota(body)
+ return resp, body
+
+ def get_default_quota_set(self, tenant_id):
+ """List the default quota set for a tenant."""
+
+ url = 'os-quota-sets/%s/defaults' % str(tenant_id)
+ resp, body = self.get(url, self.headers)
+ body = xml_to_json(etree.fromstring(body))
+ body = self._format_quota(body)
+ return resp, body
+
+ def update_quota_set(self, tenant_id, force=None,
+ injected_file_content_bytes=None,
+ metadata_items=None, ram=None, floating_ips=None,
+ fixed_ips=None, key_pairs=None, instances=None,
+ security_group_rules=None, injected_files=None,
+ cores=None, injected_file_path_bytes=None,
+ security_groups=None):
+ """
+ Updates the tenant's quota limits for one or more resources
+ """
+ post_body = Element("quota_set",
+ xmlns=XMLNS_11)
+
+ if force is not None:
+ post_body.add_attr('force', force)
+
+ if injected_file_content_bytes is not None:
+ post_body.add_attr('injected_file_content_bytes',
+ injected_file_content_bytes)
+
+ if metadata_items is not None:
+ post_body.add_attr('metadata_items', metadata_items)
+
+ if ram is not None:
+ post_body.add_attr('ram', ram)
+
+ if floating_ips is not None:
+ post_body.add_attr('floating_ips', floating_ips)
+
+ if fixed_ips is not None:
+ post_body.add_attr('fixed_ips', fixed_ips)
+
+ if key_pairs is not None:
+ post_body.add_attr('key_pairs', key_pairs)
+
+ if instances is not None:
+ post_body.add_attr('instances', instances)
+
+ if security_group_rules is not None:
+ post_body.add_attr('security_group_rules', security_group_rules)
+
+ if injected_files is not None:
+ post_body.add_attr('injected_files', injected_files)
+
+ if cores is not None:
+ post_body.add_attr('cores', cores)
+
+ if injected_file_path_bytes is not None:
+ post_body.add_attr('injected_file_path_bytes',
+ injected_file_path_bytes)
+
+ if security_groups is not None:
+ post_body.add_attr('security_groups', security_groups)
+
+ resp, body = self.put('os-quota-sets/%s' % str(tenant_id),
+ str(Document(post_body)),
+ self.headers)
+ body = xml_to_json(etree.fromstring(body))
+ body = self._format_quota(body)
+ return resp, body
diff --git a/tempest/services/compute/v3/xml/servers_client.py b/tempest/services/compute/v3/xml/servers_client.py
index 2ad5849..7af4161 100644
--- a/tempest/services/compute/v3/xml/servers_client.py
+++ b/tempest/services/compute/v3/xml/servers_client.py
@@ -254,6 +254,10 @@
server.add_attr("access_ip_v4", access_ip_v4)
if access_ip_v6 is not None:
server.add_attr("access_ip_v6", access_ip_v6)
+ if disk_config is not None:
+ server.add_attr('xmlns:os-disk-config', "http://docs.openstack.org"
+ "/compute/ext/disk_config/api/v3")
+ server.add_attr("os-disk-config:disk_config", disk_config)
if meta is not None:
metadata = Element("metadata")
server.append(metadata)
@@ -511,12 +515,6 @@
str(Document(post_body)), self.headers)
return resp, body
- def add_security_group(self, server_id, name):
- return self.action(server_id, 'add_security_group', None, name=name)
-
- def remove_security_group(self, server_id, name):
- return self.action(server_id, 'remove_security_group', None, name=name)
-
def live_migrate_server(self, server_id, dest_host, use_block_migration):
"""This should be called with administrator privileges ."""
diff --git a/tempest/services/compute/v3/xml/tenant_usages_client.py b/tempest/services/compute/v3/xml/tenant_usages_client.py
index cb92324..790bd5c 100644
--- a/tempest/services/compute/v3/xml/tenant_usages_client.py
+++ b/tempest/services/compute/v3/xml/tenant_usages_client.py
@@ -23,13 +23,13 @@
from tempest.services.compute.xml.common import xml_to_json
-class TenantUsagesClientXML(RestClientXML):
+class TenantUsagesV3ClientXML(RestClientXML):
def __init__(self, config, username, password, auth_url, tenant_name=None):
- super(TenantUsagesClientXML, self).__init__(config, username,
- password, auth_url,
- tenant_name)
- self.service = self.config.compute.catalog_type
+ super(TenantUsagesV3ClientXML, self).__init__(config, username,
+ password, auth_url,
+ tenant_name)
+ self.service = self.config.compute.catalog_v3_type
def _parse_array(self, node):
json = xml_to_json(node)
diff --git a/tempest/services/compute/xml/extensions_client.py b/tempest/services/compute/xml/extensions_client.py
index 1395b5a..b17fc4f 100644
--- a/tempest/services/compute/xml/extensions_client.py
+++ b/tempest/services/compute/xml/extensions_client.py
@@ -43,3 +43,8 @@
_, extensions = self.list_extensions()
exts = extensions['extensions']
return any([e for e in exts if e['name'] == extension])
+
+ def get_extension(self, extension_alias):
+ resp, body = self.get('extensions/%s' % extension_alias, self.headers)
+ body = xml_to_json(etree.fromstring(body))
+ return resp, body
diff --git a/tempest/services/compute/xml/images_client.py b/tempest/services/compute/xml/images_client.py
index b17ae78..20fcc9b 100644
--- a/tempest/services/compute/xml/images_client.py
+++ b/tempest/services/compute/xml/images_client.py
@@ -15,12 +15,12 @@
# License for the specific language governing permissions and limitations
# under the License.
-import time
import urllib
from lxml import etree
from tempest.common.rest_client import RestClientXML
+from tempest.common import waiters
from tempest import exceptions
from tempest.services.compute.xml.common import Document
from tempest.services.compute.xml.common import Element
@@ -140,17 +140,7 @@
def wait_for_image_status(self, image_id, status):
"""Waits for an image to reach a given status."""
- resp, image = self.get_image(image_id)
- start = int(time.time())
-
- while image['status'] != status:
- time.sleep(self.build_interval)
- resp, image = self.get_image(image_id)
- if image['status'] == 'ERROR':
- raise exceptions.AddImageException(image_id=image_id)
-
- if int(time.time()) - start >= self.build_timeout:
- raise exceptions.TimeoutException
+ waiters.wait_for_image_status(self, image_id, status)
def _metadata_body(self, meta):
post_body = Element('metadata')
diff --git a/tempest/services/compute/xml/servers_client.py b/tempest/services/compute/xml/servers_client.py
index 7d40d0e..68f6cf0 100644
--- a/tempest/services/compute/xml/servers_client.py
+++ b/tempest/services/compute/xml/servers_client.py
@@ -363,10 +363,12 @@
server = self._parse_server(etree.fromstring(body))
return resp, server
- def wait_for_server_status(self, server_id, status, extra_timeout=0):
+ def wait_for_server_status(self, server_id, status, extra_timeout=0,
+ raise_on_error=True):
"""Waits for a server to reach a given status."""
return waiters.wait_for_server_status(self, server_id, status,
- extra_timeout=extra_timeout)
+ extra_timeout=extra_timeout,
+ raise_on_error=raise_on_error)
def wait_for_server_termination(self, server_id, ignore_error=False):
"""Waits for server to reach termination."""
diff --git a/tempest/services/identity/v3/json/identity_client.py b/tempest/services/identity/v3/json/identity_client.py
index ec99d37..e457c1f 100644
--- a/tempest/services/identity/v3/json/identity_client.py
+++ b/tempest/services/identity/v3/json/identity_client.py
@@ -359,6 +359,66 @@
(domain_id, group_id, role_id))
return resp, body
+ def create_trust(self, trustor_user_id, trustee_user_id, project_id,
+ role_names, impersonation, expires_at):
+ """Creates a trust."""
+ roles = [{'name': n} for n in role_names]
+ post_body = {
+ 'trustor_user_id': trustor_user_id,
+ 'trustee_user_id': trustee_user_id,
+ 'project_id': project_id,
+ 'impersonation': impersonation,
+ 'roles': roles,
+ 'expires_at': expires_at
+ }
+ post_body = json.dumps({'trust': post_body})
+ resp, body = self.post('OS-TRUST/trusts', post_body, self.headers)
+ body = json.loads(body)
+ return resp, body['trust']
+
+ def delete_trust(self, trust_id):
+ """Deletes a trust."""
+ resp, body = self.delete("OS-TRUST/trusts/%s" % trust_id)
+ return resp, body
+
+ def get_trusts(self, trustor_user_id=None, trustee_user_id=None):
+ """GET trusts."""
+ if trustor_user_id:
+ resp, body = self.get("OS-TRUST/trusts?trustor_user_id=%s"
+ % trustor_user_id)
+ elif trustee_user_id:
+ resp, body = self.get("OS-TRUST/trusts?trustee_user_id=%s"
+ % trustee_user_id)
+ else:
+ resp, body = self.get("OS-TRUST/trusts")
+ body = json.loads(body)
+ return resp, body['trusts']
+
+ def get_trust(self, trust_id):
+ """GET trust."""
+ resp, body = self.get("OS-TRUST/trusts/%s" % trust_id)
+ body = json.loads(body)
+ return resp, body['trust']
+
+ def get_trust_roles(self, trust_id):
+ """GET roles delegated by a trust."""
+ resp, body = self.get("OS-TRUST/trusts/%s/roles" % trust_id)
+ body = json.loads(body)
+ return resp, body['roles']
+
+ def get_trust_role(self, trust_id, role_id):
+ """GET role delegated by a trust."""
+ resp, body = self.get("OS-TRUST/trusts/%s/roles/%s"
+ % (trust_id, role_id))
+ body = json.loads(body)
+ return resp, body['role']
+
+ def check_trust_role(self, trust_id, role_id):
+ """HEAD Check if role is delegated by a trust."""
+ resp, body = self.head("OS-TRUST/trusts/%s/roles/%s"
+ % (trust_id, role_id))
+ return resp, body
+
class V3TokenClientJSON(RestClient):
diff --git a/tempest/services/network/json/network_client.py b/tempest/services/network/json/network_client.py
index aa7f2f2..b323dc6 100644
--- a/tempest/services/network/json/network_client.py
+++ b/tempest/services/network/json/network_client.py
@@ -15,9 +15,10 @@
import json
from tempest.common.rest_client import RestClient
+from tempest.services.network import network_client_base
-class NetworkClientJSON(RestClient):
+class NetworkClientJSON(network_client_base.NetworkClientBase):
"""
Tempest REST client for Neutron. Uses v2 of the Neutron API, since the
@@ -32,25 +33,25 @@
quotas
"""
- def __init__(self, config, username, password, auth_url, tenant_name=None):
- super(NetworkClientJSON, self).__init__(config, username, password,
- auth_url, tenant_name)
- self.service = self.config.network.catalog_type
- self.version = '2.0'
- self.uri_prefix = "v%s" % (self.version)
+ def get_rest_client(self, config, username,
+ password, auth_url, tenant_name=None):
+ return RestClient(config, username, password, auth_url, tenant_name)
- def list_networks(self):
- uri = '%s/networks' % (self.uri_prefix)
- resp, body = self.get(uri, self.headers)
- body = json.loads(body)
- return resp, body
+ def deserialize_single(self, body):
+ return json.loads(body)
+
+ def deserialize_list(self, body):
+ res = json.loads(body)
+ # expecting response in form
+ # {'resources': [ res1, res2] }
+ return res[res.keys()[0]]
def create_network(self, name, **kwargs):
post_body = {'network': kwargs}
post_body['network']['name'] = name
body = json.dumps(post_body)
uri = '%s/networks' % (self.uri_prefix)
- resp, body = self.post(uri, headers=self.headers, body=body)
+ resp, body = self.post(uri, body)
body = json.loads(body)
return resp, body
@@ -61,21 +62,10 @@
post_body = {'networks': network_list}
body = json.dumps(post_body)
uri = '%s/networks' % (self.uri_prefix)
- resp, body = self.post(uri, headers=self.headers, body=body)
+ resp, body = self.post(uri, body)
body = json.loads(body)
return resp, body
- def show_network(self, uuid):
- uri = '%s/networks/%s' % (self.uri_prefix, uuid)
- resp, body = self.get(uri, self.headers)
- body = json.loads(body)
- return resp, body
-
- def delete_network(self, uuid):
- uri = '%s/networks/%s' % (self.uri_prefix, uuid)
- resp, body = self.delete(uri, self.headers)
- return resp, body
-
def create_subnet(self, net_uuid, cidr, ip_version=4, **kwargs):
post_body = {'subnet': kwargs}
post_body['subnet']['ip_version'] = ip_version
@@ -83,24 +73,7 @@
post_body['subnet']['cidr'] = cidr
body = json.dumps(post_body)
uri = '%s/subnets' % (self.uri_prefix)
- resp, body = self.post(uri, headers=self.headers, body=body)
- body = json.loads(body)
- return resp, body
-
- def delete_subnet(self, uuid):
- uri = '%s/subnets/%s' % (self.uri_prefix, uuid)
- resp, body = self.delete(uri, self.headers)
- return resp, body
-
- def list_subnets(self):
- uri = '%s/subnets' % (self.uri_prefix)
- resp, body = self.get(uri, self.headers)
- body = json.loads(body)
- return resp, body
-
- def show_subnet(self, uuid):
- uri = '%s/subnets/%s' % (self.uri_prefix, uuid)
- resp, body = self.get(uri, self.headers)
+ resp, body = self.post(uri, body)
body = json.loads(body)
return resp, body
@@ -114,28 +87,7 @@
post_body['port'][key] = val
body = json.dumps(post_body)
uri = '%s/ports' % (self.uri_prefix)
- resp, body = self.post(uri, headers=self.headers, body=body)
- body = json.loads(body)
- return resp, body
-
- def delete_port(self, port_id):
- uri = '%s/ports/%s' % (self.uri_prefix, port_id)
- resp, body = self.delete(uri, self.headers)
- return resp, body
-
- def list_ports(self, **filters):
- uri = '%s/ports' % (self.uri_prefix)
- filter_items = ["%s=%s" % (k, v) for (k, v) in filters.iteritems()]
- querystring = "&".join(filter_items)
- if querystring:
- uri = "%s?%s" % (uri, querystring)
- resp, body = self.get(uri, self.headers)
- body = json.loads(body)
- return resp, body
-
- def show_port(self, port_id):
- uri = '%s/ports/%s' % (self.uri_prefix, port_id)
- resp, body = self.get(uri, self.headers)
+ resp, body = self.post(uri, body)
body = json.loads(body)
return resp, body
@@ -143,27 +95,15 @@
put_body = {'quota': kwargs}
body = json.dumps(put_body)
uri = '%s/quotas/%s' % (self.uri_prefix, tenant_id)
- resp, body = self.put(uri, body, self.headers)
- body = json.loads(body)
- return resp, body['quota']
-
- def show_quotas(self, tenant_id):
- uri = '%s/quotas/%s' % (self.uri_prefix, tenant_id)
- resp, body = self.get(uri, self.headers)
+ resp, body = self.put(uri, body)
body = json.loads(body)
return resp, body['quota']
def reset_quotas(self, tenant_id):
uri = '%s/quotas/%s' % (self.uri_prefix, tenant_id)
- resp, body = self.delete(uri, self.headers)
+ resp, body = self.delete(uri)
return resp, body
- def list_quotas(self):
- uri = '%s/quotas' % (self.uri_prefix)
- resp, body = self.get(uri, self.headers)
- body = json.loads(body)
- return resp, body['quotas']
-
def update_subnet(self, subnet_id, new_name):
put_body = {
'subnet': {
@@ -172,7 +112,7 @@
}
body = json.dumps(put_body)
uri = '%s/subnets/%s' % (self.uri_prefix, subnet_id)
- resp, body = self.put(uri, body=body, headers=self.headers)
+ resp, body = self.put(uri, body)
body = json.loads(body)
return resp, body
@@ -184,7 +124,7 @@
}
body = json.dumps(put_body)
uri = '%s/ports/%s' % (self.uri_prefix, port_id)
- resp, body = self.put(uri, body=body, headers=self.headers)
+ resp, body = self.put(uri, body)
body = json.loads(body)
return resp, body
@@ -196,13 +136,7 @@
}
body = json.dumps(put_body)
uri = '%s/networks/%s' % (self.uri_prefix, network_id)
- resp, body = self.put(uri, body=body, headers=self.headers)
- body = json.loads(body)
- return resp, body
-
- def list_routers(self):
- uri = '%s/routers' % (self.uri_prefix)
- resp, body = self.get(uri, self.headers)
+ resp, body = self.put(uri, body)
body = json.loads(body)
return resp, body
@@ -212,24 +146,13 @@
post_body['router']['admin_state_up'] = admin_state_up
body = json.dumps(post_body)
uri = '%s/routers' % (self.uri_prefix)
- resp, body = self.post(uri, headers=self.headers, body=body)
- body = json.loads(body)
- return resp, body
-
- def delete_router(self, router_id):
- uri = '%s/routers/%s' % (self.uri_prefix, router_id)
- resp, body = self.delete(uri, self.headers)
- return resp, body
-
- def show_router(self, router_id):
- uri = '%s/routers/%s' % (self.uri_prefix, router_id)
- resp, body = self.get(uri, self.headers)
+ resp, body = self.post(uri, body)
body = json.loads(body)
return resp, body
def _update_router(self, router_id, set_enable_snat, **kwargs):
uri = '%s/routers/%s' % (self.uri_prefix, router_id)
- resp, body = self.get(uri, self.headers)
+ resp, body = self.get(uri)
body = json.loads(body)
update_body = {}
update_body['name'] = kwargs.get('name', body['router']['name'])
@@ -242,7 +165,7 @@
'external_gateway_info', body['router']['external_gateway_info'])
update_body = dict(router=update_body)
update_body = json.dumps(update_body)
- resp, body = self.put(uri, update_body, self.headers)
+ resp, body = self.put(uri, update_body)
body = json.loads(body)
return resp, body
@@ -268,7 +191,7 @@
router_id)
update_body = {"subnet_id": subnet_id}
update_body = json.dumps(update_body)
- resp, body = self.put(uri, update_body, self.headers)
+ resp, body = self.put(uri, update_body)
body = json.loads(body)
return resp, body
@@ -277,7 +200,7 @@
router_id)
update_body = {"port_id": port_id}
update_body = json.dumps(update_body)
- resp, body = self.put(uri, update_body, self.headers)
+ resp, body = self.put(uri, update_body)
body = json.loads(body)
return resp, body
@@ -286,7 +209,7 @@
router_id)
update_body = {"subnet_id": subnet_id}
update_body = json.dumps(update_body)
- resp, body = self.put(uri, update_body, self.headers)
+ resp, body = self.put(uri, update_body)
body = json.loads(body)
return resp, body
@@ -295,7 +218,7 @@
router_id)
update_body = {"port_id": port_id}
update_body = json.dumps(update_body)
- resp, body = self.put(uri, update_body, self.headers)
+ resp, body = self.put(uri, update_body)
body = json.loads(body)
return resp, body
@@ -305,21 +228,10 @@
post_body['floatingip']['floating_network_id'] = ext_network_id
body = json.dumps(post_body)
uri = '%s/floatingips' % (self.uri_prefix)
- resp, body = self.post(uri, headers=self.headers, body=body)
+ resp, body = self.post(uri, body=body)
body = json.loads(body)
return resp, body
- def list_security_groups(self):
- uri = '%s/security-groups' % (self.uri_prefix)
- resp, body = self.get(uri, self.headers)
- body = json.loads(body)
- return resp, body
-
- def delete_security_group(self, secgroup_id):
- uri = '%s/security-groups/%s' % (self.uri_prefix, secgroup_id)
- resp, body = self.delete(uri, self.headers)
- return resp, body
-
def create_security_group(self, name, **kwargs):
post_body = {
'security_group': {
@@ -330,45 +242,16 @@
post_body['security_group'][str(key)] = value
body = json.dumps(post_body)
uri = '%s/security-groups' % (self.uri_prefix)
- resp, body = self.post(uri, headers=self.headers, body=body)
+ resp, body = self.post(uri, body)
body = json.loads(body)
return resp, body
- def show_floating_ip(self, floating_ip_id):
- uri = '%s/floatingips/%s' % (self.uri_prefix, floating_ip_id)
- resp, body = self.get(uri, self.headers)
- body = json.loads(body)
- return resp, body
-
- def show_security_group(self, secgroup_id):
- uri = '%s/security-groups/%s' % (self.uri_prefix, secgroup_id)
- resp, body = self.get(uri, self.headers)
- body = json.loads(body)
- return resp, body
-
- def list_floating_ips(self):
- uri = '%s/floatingips' % (self.uri_prefix)
- resp, body = self.get(uri, self.headers)
- body = json.loads(body)
- return resp, body
-
- def list_security_group_rules(self):
- uri = '%s/security-group-rules' % (self.uri_prefix)
- resp, body = self.get(uri, self.headers)
- body = json.loads(body)
- return resp, body
-
- def delete_floating_ip(self, floating_ip_id):
- uri = '%s/floatingips/%s' % (self.uri_prefix, floating_ip_id)
- resp, body = self.delete(uri, self.headers)
- return resp, body
-
def update_floating_ip(self, floating_ip_id, **kwargs):
post_body = {
'floatingip': kwargs}
body = json.dumps(post_body)
uri = '%s/floatingips/%s' % (self.uri_prefix, floating_ip_id)
- resp, body = self.put(uri, headers=self.headers, body=body)
+ resp, body = self.put(uri, body)
body = json.loads(body)
return resp, body
@@ -384,7 +267,7 @@
post_body['security_group_rule'][str(key)] = value
body = json.dumps(post_body)
uri = '%s/security-group-rules' % (self.uri_prefix)
- resp, body = self.post(uri, headers=self.headers, body=body)
+ resp, body = self.post(uri, body)
body = json.loads(body)
return resp, body
@@ -392,18 +275,7 @@
post_body = {'subnets': subnet_list}
body = json.dumps(post_body)
uri = '%s/subnets' % (self.uri_prefix)
- resp, body = self.post(uri, headers=self.headers, body=body)
- body = json.loads(body)
- return resp, body
-
- def delete_security_group_rule(self, rule_id):
- uri = '%s/security-group-rules/%s' % (self.uri_prefix, rule_id)
- resp, body = self.delete(uri, self.headers)
- return resp, body
-
- def show_security_group_rule(self, rule_id):
- uri = '%s/security-group-rules/%s' % (self.uri_prefix, rule_id)
- resp, body = self.get(uri, self.headers)
+ resp, body = self.post(uri, body)
body = json.loads(body)
return resp, body
@@ -411,13 +283,7 @@
post_body = {'ports': port_list}
body = json.dumps(post_body)
uri = '%s/ports' % (self.uri_prefix)
- resp, body = self.post(uri, headers=self.headers, body=body)
- body = json.loads(body)
- return resp, body
-
- def list_vips(self):
- uri = '%s/lb/vips' % (self.uri_prefix)
- resp, body = self.get(uri, self.headers)
+ resp, body = self.post(uri, body)
body = json.loads(body)
return resp, body
@@ -433,7 +299,7 @@
}
body = json.dumps(post_body)
uri = '%s/lb/vips' % (self.uri_prefix)
- resp, body = self.post(uri, headers=self.headers, body=body)
+ resp, body = self.post(uri, body)
body = json.loads(body)
return resp, body
@@ -448,26 +314,10 @@
}
body = json.dumps(post_body)
uri = '%s/lb/pools' % (self.uri_prefix)
- resp, body = self.post(uri, headers=self.headers, body=body)
+ resp, body = self.post(uri, body)
body = json.loads(body)
return resp, body
- def show_vip(self, uuid):
- uri = '%s/lb/vips/%s' % (self.uri_prefix, uuid)
- resp, body = self.get(uri, self.headers)
- body = json.loads(body)
- return resp, body
-
- def delete_vip(self, uuid):
- uri = '%s/lb/vips/%s' % (self.uri_prefix, uuid)
- resp, body = self.delete(uri, self.headers)
- return resp, body
-
- def delete_pool(self, uuid):
- uri = '%s/lb/pools/%s' % (self.uri_prefix, uuid)
- resp, body = self.delete(uri, self.headers)
- return resp, body
-
def update_vip(self, vip_id, new_name):
put_body = {
"vip": {
@@ -476,7 +326,7 @@
}
body = json.dumps(put_body)
uri = '%s/lb/vips/%s' % (self.uri_prefix, vip_id)
- resp, body = self.put(uri, body=body, headers=self.headers)
+ resp, body = self.put(uri, body)
body = json.loads(body)
return resp, body
@@ -488,25 +338,7 @@
}
body = json.dumps(put_body)
uri = '%s/lb/pools/%s' % (self.uri_prefix, pool_id)
- resp, body = self.put(uri, body=body, headers=self.headers)
- body = json.loads(body)
- return resp, body
-
- def list_pools(self):
- uri = '%s/lb/pools' % (self.uri_prefix)
- resp, body = self.get(uri, self.headers)
- body = json.loads(body)
- return resp, body
-
- def show_pool(self, uuid):
- uri = '%s/lb/pools/%s' % (self.uri_prefix, uuid)
- resp, body = self.get(uri, self.headers)
- body = json.loads(body)
- return resp, body
-
- def list_members(self):
- uri = '%s/lb/members' % (self.uri_prefix)
- resp, body = self.get(uri, self.headers)
+ resp, body = self.put(uri, body)
body = json.loads(body)
return resp, body
@@ -520,21 +352,10 @@
}
body = json.dumps(post_body)
uri = '%s/lb/members' % (self.uri_prefix)
- resp, body = self.post(uri, headers=self.headers, body=body)
+ resp, body = self.post(uri, body)
body = json.loads(body)
return resp, body
- def show_member(self, uuid):
- uri = '%s/lb/members/%s' % (self.uri_prefix, uuid)
- resp, body = self.get(uri, self.headers)
- body = json.loads(body)
- return resp, body
-
- def delete_member(self, uuid):
- uri = '%s/lb/members/%s' % (self.uri_prefix, uuid)
- resp, body = self.delete(uri, self.headers)
- return resp, body
-
def update_member(self, admin_state_up, member_id):
put_body = {
"member": {
@@ -543,13 +364,7 @@
}
body = json.dumps(put_body)
uri = '%s/lb/members/%s' % (self.uri_prefix, member_id)
- resp, body = self.put(uri, body=body, headers=self.headers)
- body = json.loads(body)
- return resp, body
-
- def list_health_monitors(self):
- uri = '%s/lb/health_monitors' % (self.uri_prefix)
- resp, body = self.get(uri, self.headers)
+ resp, body = self.put(uri, body)
body = json.loads(body)
return resp, body
@@ -564,21 +379,10 @@
}
body = json.dumps(post_body)
uri = '%s/lb/health_monitors' % (self.uri_prefix)
- resp, body = self.post(uri, headers=self.headers, body=body)
+ resp, body = self.post(uri, body)
body = json.loads(body)
return resp, body
- def show_health_monitor(self, uuid):
- uri = '%s/lb/health_monitors/%s' % (self.uri_prefix, uuid)
- resp, body = self.get(uri, self.headers)
- body = json.loads(body)
- return resp, body
-
- def delete_health_monitor(self, uuid):
- uri = '%s/lb/health_monitors/%s' % (self.uri_prefix, uuid)
- resp, body = self.delete(uri, self.headers)
- return resp, body
-
def update_health_monitor(self, admin_state_up, uuid):
put_body = {
"health_monitor": {
@@ -587,7 +391,7 @@
}
body = json.dumps(put_body)
uri = '%s/lb/health_monitors/%s' % (self.uri_prefix, uuid)
- resp, body = self.put(uri, body=body, headers=self.headers)
+ resp, body = self.put(uri, body)
body = json.loads(body)
return resp, body
@@ -601,7 +405,7 @@
body = json.dumps(post_body)
uri = '%s/lb/pools/%s/health_monitors' % (self.uri_prefix,
pool_id)
- resp, body = self.post(uri, headers=self.headers, body=body)
+ resp, body = self.post(uri, body)
body = json.loads(body)
return resp, body
@@ -609,28 +413,10 @@
pool_id):
uri = '%s/lb/pools/%s/health_monitors/%s' % (self.uri_prefix, pool_id,
health_monitor_id)
- resp, body = self.delete(uri, headers=self.headers)
+ resp, body = self.delete(uri)
return resp, body
- def list_extensions(self):
- uri = '%s/extensions' % (self.uri_prefix)
- resp, body = self.get(uri, self.headers)
- body = json.loads(body)
- return resp, body
-
- def show_extension_details(self, ext_alias):
- uri = '%s/extensions/%s' % (self.uri_prefix, ext_alias)
- resp, body = self.get(uri, headers=self.headers)
- body = json.loads(body)
- return resp, body
-
- def list_vpn_services(self):
- uri = '%s/vpn/vpnservices' % (self.uri_prefix)
- resp, body = self.get(uri, self.headers)
- body = json.loads(body)
- return resp, body
-
- def create_vpn_service(self, subnet_id, router_id, **kwargs):
+ def create_vpnservice(self, subnet_id, router_id, **kwargs):
post_body = {
"vpnservice": {
"subnet_id": subnet_id,
@@ -641,22 +427,11 @@
post_body['vpnservice'][key] = val
body = json.dumps(post_body)
uri = '%s/vpn/vpnservices' % (self.uri_prefix)
- resp, body = self.post(uri, headers=self.headers, body=body)
+ resp, body = self.post(uri, body)
body = json.loads(body)
return resp, body
- def show_vpn_service(self, uuid):
- uri = '%s/vpn/vpnservices/%s' % (self.uri_prefix, uuid)
- resp, body = self.get(uri, self.headers)
- body = json.loads(body)
- return resp, body
-
- def delete_vpn_service(self, uuid):
- uri = '%s/vpn/vpnservices/%s' % (self.uri_prefix, uuid)
- resp, body = self.delete(uri, self.headers)
- return resp, body
-
- def update_vpn_service(self, uuid, description):
+ def update_vpnservice(self, uuid, description):
put_body = {
"vpnservice": {
"description": description
@@ -664,36 +439,102 @@
}
body = json.dumps(put_body)
uri = '%s/vpn/vpnservices/%s' % (self.uri_prefix, uuid)
- resp, body = self.put(uri, body=body, headers=self.headers)
+ resp, body = self.put(uri, body)
body = json.loads(body)
return resp, body
def list_router_interfaces(self, uuid):
uri = '%s/ports?device_id=%s' % (self.uri_prefix, uuid)
- resp, body = self.get(uri, self.headers)
+ resp, body = self.get(uri)
body = json.loads(body)
return resp, body
- def list_agents(self):
- uri = '%s/agents' % self.uri_prefix
- resp, body = self.get(uri, self.headers)
+ def update_agent(self, agent_id, agent_info):
+ """
+ :param agent_info: Agent update information.
+ E.g {"admin_state_up": True}
+ """
+ uri = '%s/agents/%s' % (self.uri_prefix, agent_id)
+ agent = {"agent": agent_info}
+ body = json.dumps(agent)
+ resp, body = self.put(uri, body)
body = json.loads(body)
return resp, body
def list_routers_on_l3_agent(self, agent_id):
uri = '%s/agents/%s/l3-routers' % (self.uri_prefix, agent_id)
- resp, body = self.get(uri, self.headers)
+ resp, body = self.get(uri)
body = json.loads(body)
return resp, body
def list_l3_agents_hosting_router(self, router_id):
uri = '%s/routers/%s/l3-agents' % (self.uri_prefix, router_id)
- resp, body = self.get(uri, self.headers)
+ resp, body = self.get(uri)
body = json.loads(body)
return resp, body
- def list_service_providers(self):
- uri = '%s/service-providers' % self.uri_prefix
- resp, body = self.get(uri, self.headers)
+ def list_dhcp_agent_hosting_network(self, network_id):
+ uri = '%s/networks/%s/dhcp-agents' % (self.uri_prefix, network_id)
+ resp, body = self.get(uri)
+ body = json.loads(body)
+ return resp, body
+
+ def list_networks_hosted_by_one_dhcp_agent(self, agent_id):
+ uri = '%s/agents/%s/dhcp-networks' % (self.uri_prefix, agent_id)
+ resp, body = self.get(uri)
+ body = json.loads(body)
+ return resp, body
+
+ def remove_network_from_dhcp_agent(self, agent_id, network_id):
+ uri = '%s/agents/%s/dhcp-networks/%s' % (self.uri_prefix, agent_id,
+ network_id)
+ resp, body = self.delete(uri)
+ return resp, body
+
+ def create_ikepolicy(self, name, **kwargs):
+ post_body = {
+ "ikepolicy": {
+ "name": name,
+ }
+ }
+ for key, val in kwargs.items():
+ post_body['ikepolicy'][key] = val
+ body = json.dumps(post_body)
+ uri = '%s/vpn/ikepolicies' % (self.uri_prefix)
+ resp, body = self.post(uri, body)
+ body = json.loads(body)
+ return resp, body
+
+ def update_ikepolicy(self, uuid, **kwargs):
+ put_body = {'ikepolicy': kwargs}
+ body = json.dumps(put_body)
+ uri = '%s/vpn/ikepolicies/%s' % (self.uri_prefix, uuid)
+ resp, body = self.put(uri, body)
+ body = json.loads(body)
+ return resp, body
+
+ def update_extra_routes(self, router_id, nexthop, destination):
+ uri = '%s/routers/%s' % (self.uri_prefix, router_id)
+ put_body = {
+ 'router': {
+ 'routes': [{'nexthop': nexthop,
+ "destination": destination}]
+ }
+ }
+ body = json.dumps(put_body)
+ resp, body = self.put(uri, body)
+ body = json.loads(body)
+ return resp, body
+
+ def delete_extra_routes(self, router_id):
+ uri = '%s/routers/%s' % (self.uri_prefix, router_id)
+ null_routes = None
+ put_body = {
+ 'router': {
+ 'routes': null_routes
+ }
+ }
+ body = json.dumps(put_body)
+ resp, body = self.put(uri, body)
body = json.loads(body)
return resp, body
diff --git a/tempest/services/network/network_client_base.py b/tempest/services/network/network_client_base.py
new file mode 100644
index 0000000..b48dd34
--- /dev/null
+++ b/tempest/services/network/network_client_base.py
@@ -0,0 +1,136 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import urllib
+
+# the folliwing map is used to construct proper URI
+# for the given neutron resource
+service_resource_prefix_map = {
+ 'networks': '',
+ 'subnets': '',
+ 'ports': '',
+ 'pools': 'lb',
+ 'vips': 'lb',
+ 'health_monitors': 'lb',
+ 'members': 'lb',
+ 'vpnservices': 'vpn',
+ 'ikepolicies': 'vpn'
+}
+
+# The following list represents resource names that do not require
+# changing underscore to a hyphen
+hyphen_exceptions = ["health_monitors"]
+
+# map from resource name to a plural name
+# needed only for those which can't be constructed as name + 's'
+resource_plural_map = {
+ 'security_groups': 'security_groups',
+ 'security_group_rules': 'security_group_rules',
+ 'ikepolicy': 'ikepolicies',
+ 'floating_ip': 'floatingips',
+ 'quotas': 'quotas'
+}
+
+
+class NetworkClientBase(object):
+ def __init__(self, config, username, password,
+ auth_url, tenant_name=None):
+ self.rest_client = self.get_rest_client(
+ config, username, password, auth_url, tenant_name)
+ self.rest_client.service = self.rest_client.config.network.catalog_type
+ self.version = '2.0'
+ self.uri_prefix = "v%s" % (self.version)
+
+ def get_rest_client(self, config, username, password,
+ auth_url, tenant_name):
+ raise NotImplementedError
+
+ def post(self, uri, body, headers=None):
+ headers = headers or self.rest_client.headers
+ return self.rest_client.post(uri, body, headers)
+
+ def put(self, uri, body, headers=None):
+ headers = headers or self.rest_client.headers
+ return self.rest_client.put(uri, body, headers)
+
+ def get(self, uri, headers=None):
+ headers = headers or self.rest_client.headers
+ return self.rest_client.get(uri, headers)
+
+ def delete(self, uri, headers=None):
+ headers = headers or self.rest_client.headers
+ return self.rest_client.delete(uri, headers)
+
+ def deserialize_list(self, body):
+ raise NotImplementedError
+
+ def deserialize_single(self, body):
+ raise NotImplementedError
+
+ def get_uri(self, plural_name):
+ # get service prefix from resource name
+ service_prefix = service_resource_prefix_map.get(
+ plural_name)
+ if plural_name not in hyphen_exceptions:
+ plural_name = plural_name.replace("_", "-")
+ if service_prefix:
+ uri = '%s/%s/%s' % (self.uri_prefix, service_prefix,
+ plural_name)
+ else:
+ uri = '%s/%s' % (self.uri_prefix, plural_name)
+ return uri
+
+ def pluralize(self, resource_name):
+ # get plural from map or just add 's'
+ return resource_plural_map.get(resource_name, resource_name + 's')
+
+ def _lister(self, plural_name):
+ def _list(**filters):
+ uri = self.get_uri(plural_name)
+ if filters:
+ uri += '?' + urllib.urlencode(filters)
+ resp, body = self.get(uri)
+ result = {plural_name: self.deserialize_list(body)}
+ return resp, result
+
+ return _list
+
+ def _deleter(self, resource_name):
+ def _delete(resource_id):
+ plural = self.pluralize(resource_name)
+ uri = '%s/%s' % (self.get_uri(plural), resource_id)
+ return self.delete(uri)
+
+ return _delete
+
+ def _shower(self, resource_name):
+ def _show(resource_id):
+ plural = self.pluralize(resource_name)
+ uri = '%s/%s' % (self.get_uri(plural), resource_id)
+ resp, body = self.get(uri)
+ body = self.deserialize_single(body)
+ return resp, body
+
+ return _show
+
+ def __getattr__(self, name):
+ method_prefixes = ["list_", "delete_", "show_"]
+ method_functors = [self._lister,
+ self._deleter,
+ self._shower]
+ for index, prefix in enumerate(method_prefixes):
+ prefix_len = len(prefix)
+ if name[:prefix_len] == prefix:
+ return method_functors[index](name[prefix_len:])
+ raise AttributeError(name)
diff --git a/tempest/services/network/xml/network_client.py b/tempest/services/network/xml/network_client.py
old mode 100755
new mode 100644
index 5af2dfb..a999e31
--- a/tempest/services/network/xml/network_client.py
+++ b/tempest/services/network/xml/network_client.py
@@ -20,30 +20,28 @@
from tempest.services.compute.xml.common import Document
from tempest.services.compute.xml.common import Element
from tempest.services.compute.xml.common import xml_to_json
+from tempest.services.network import network_client_base as client_base
-class NetworkClientXML(RestClientXML):
+class NetworkClientXML(client_base.NetworkClientBase):
- def __init__(self, config, username, password, auth_url, tenant_name=None):
- super(NetworkClientXML, self).__init__(config, username, password,
- auth_url, tenant_name)
- self.service = self.config.network.catalog_type
- self.version = '2.0'
- self.uri_prefix = "v%s" % (self.version)
+ def get_rest_client(self, config, username, password,
+ auth_url, tenant_name=None):
+ return RestClientXML(config, username, password,
+ auth_url, tenant_name)
- def list_networks(self):
- uri = '%s/networks' % (self.uri_prefix)
- resp, body = self.get(uri, self.headers)
- networks = self._parse_array(etree.fromstring(body))
- networks = {"networks": networks}
- return resp, networks
+ def deserialize_list(self, body):
+ return self._parse_array(etree.fromstring(body))
+
+ def deserialize_single(self, body):
+ return _root_tag_fetcher_and_xml_to_json_parse(body)
def create_network(self, name):
uri = '%s/networks' % (self.uri_prefix)
post_body = Element("network")
p2 = Element("name", name)
post_body.append(p2)
- resp, body = self.post(uri, str(Document(post_body)), self.headers)
+ resp, body = self.post(uri, str(Document(post_body)))
body = _root_tag_fetcher_and_xml_to_json_parse(body)
return resp, body
@@ -55,21 +53,11 @@
p2 = Element("name", names[i])
p1.append(p2)
post_body.append(p1)
- resp, body = self.post(uri, str(Document(post_body)), self.headers)
+ resp, body = self.post(uri, str(Document(post_body)))
networks = self._parse_array(etree.fromstring(body))
networks = {"networks": networks}
return resp, networks
- def delete_network(self, uuid):
- uri = '%s/networks/%s' % (self.uri_prefix, str(uuid))
- return self.delete(uri, self.headers)
-
- def show_network(self, uuid):
- uri = '%s/networks/%s' % (self.uri_prefix, str(uuid))
- resp, body = self.get(uri, self.headers)
- body = _root_tag_fetcher_and_xml_to_json_parse(body)
- return resp, body
-
def create_subnet(self, net_uuid, cidr):
uri = '%s/subnets' % (self.uri_prefix)
subnet = Element("subnet")
@@ -79,24 +67,7 @@
subnet.append(p2)
subnet.append(p3)
subnet.append(p4)
- resp, body = self.post(uri, str(Document(subnet)), self.headers)
- body = _root_tag_fetcher_and_xml_to_json_parse(body)
- return resp, body
-
- def delete_subnet(self, subnet_id):
- uri = '%s/subnets/%s' % (self.uri_prefix, str(subnet_id))
- return self.delete(uri, self.headers)
-
- def list_subnets(self):
- uri = '%s/subnets' % (self.uri_prefix)
- resp, body = self.get(uri, self.headers)
- subnets = self._parse_array(etree.fromstring(body))
- subnets = {"subnets": subnets}
- return resp, subnets
-
- def show_subnet(self, uuid):
- uri = '%s/subnets/%s' % (self.uri_prefix, str(uuid))
- resp, body = self.get(uri, self.headers)
+ resp, body = self.post(uri, str(Document(subnet)))
body = _root_tag_fetcher_and_xml_to_json_parse(body)
return resp, body
@@ -108,39 +79,22 @@
for key, val in kwargs.items():
key = Element(key, val)
port.append(key)
- resp, body = self.post(uri, str(Document(port)), self.headers)
+ resp, body = self.post(uri, str(Document(port)))
body = _root_tag_fetcher_and_xml_to_json_parse(body)
return resp, body
- def delete_port(self, port_id):
- uri = '%s/ports/%s' % (self.uri_prefix, str(port_id))
- return self.delete(uri, self.headers)
-
def _parse_array(self, node):
array = []
for child in node.getchildren():
array.append(xml_to_json(child))
return array
- def list_ports(self):
- url = '%s/ports' % (self.uri_prefix)
- resp, body = self.get(url, self.headers)
- ports = self._parse_array(etree.fromstring(body))
- ports = {"ports": ports}
- return resp, ports
-
- def show_port(self, port_id):
- uri = '%s/ports/%s' % (self.uri_prefix, str(port_id))
- resp, body = self.get(uri, self.headers)
- body = _root_tag_fetcher_and_xml_to_json_parse(body)
- return resp, body
-
def update_port(self, port_id, name):
uri = '%s/ports/%s' % (self.uri_prefix, str(port_id))
port = Element("port")
p2 = Element("name", name)
port.append(p2)
- resp, body = self.put(uri, str(Document(port)), self.headers)
+ resp, body = self.put(uri, str(Document(port)))
body = _root_tag_fetcher_and_xml_to_json_parse(body)
return resp, body
@@ -149,7 +103,7 @@
subnet = Element("subnet")
p2 = Element("name", name)
subnet.append(p2)
- resp, body = self.put(uri, str(Document(subnet)), self.headers)
+ resp, body = self.put(uri, str(Document(subnet)))
body = _root_tag_fetcher_and_xml_to_json_parse(body)
return resp, body
@@ -158,7 +112,7 @@
network = Element("network")
p2 = Element("name", name)
network.append(p2)
- resp, body = self.put(uri, str(Document(network)), self.headers)
+ resp, body = self.put(uri, str(Document(network)))
body = _root_tag_fetcher_and_xml_to_json_parse(body)
return resp, body
@@ -167,34 +121,10 @@
post_body = Element("security_group")
p2 = Element("name", name)
post_body.append(p2)
- resp, body = self.post(uri, str(Document(post_body)), self.headers)
+ resp, body = self.post(uri, str(Document(post_body)))
body = _root_tag_fetcher_and_xml_to_json_parse(body)
return resp, body
- def list_security_groups(self):
- url = '%s/security-groups' % (self.uri_prefix)
- resp, body = self.get(url, self.headers)
- secgroups = self._parse_array(etree.fromstring(body))
- secgroups = {"security_groups": secgroups}
- return resp, secgroups
-
- def delete_security_group(self, secgroup_id):
- uri = '%s/security-groups/%s' % (self.uri_prefix, str(secgroup_id))
- return self.delete(uri, self.headers)
-
- def show_security_group(self, secgroup_id):
- uri = '%s/security-groups/%s' % (self.uri_prefix, str(secgroup_id))
- resp, body = self.get(uri, self.headers)
- body = _root_tag_fetcher_and_xml_to_json_parse(body)
- return resp, body
-
- def list_security_group_rules(self):
- url = '%s/security-group-rules' % (self.uri_prefix)
- resp, body = self.get(url, self.headers)
- rules = self._parse_array(etree.fromstring(body))
- rules = {"security_group_rules": rules}
- return resp, rules
-
def create_security_group_rule(self, secgroup_id,
direction='ingress', **kwargs):
uri = '%s/security-group-rules' % (self.uri_prefix)
@@ -206,17 +136,7 @@
for key, val in kwargs.items():
key = Element(key, val)
rule.append(key)
- resp, body = self.post(uri, str(Document(rule)), self.headers)
- body = _root_tag_fetcher_and_xml_to_json_parse(body)
- return resp, body
-
- def delete_security_group_rule(self, rule_id):
- uri = '%s/security-group-rules/%s' % (self.uri_prefix, str(rule_id))
- return self.delete(uri, self.headers)
-
- def show_security_group_rule(self, rule_id):
- uri = '%s/security-group-rules/%s' % (self.uri_prefix, str(rule_id))
- resp, body = self.get(uri, self.headers)
+ resp, body = self.post(uri, str(Document(rule)))
body = _root_tag_fetcher_and_xml_to_json_parse(body)
return resp, body
@@ -230,7 +150,7 @@
p2 = Element(k, kv)
p1.append(p2)
post_body.append(p1)
- resp, body = self.post(uri, str(Document(post_body)), self.headers)
+ resp, body = self.post(uri, str(Document(post_body)))
subnets = self._parse_array(etree.fromstring(body))
subnets = {"subnets": subnets}
return resp, subnets
@@ -245,18 +165,11 @@
p2 = Element(k, kv)
p1.append(p2)
post_body.append(p1)
- resp, body = self.post(uri, str(Document(post_body)), self.headers)
+ resp, body = self.post(uri, str(Document(post_body)))
ports = self._parse_array(etree.fromstring(body))
ports = {"ports": ports}
return resp, ports
- def list_vips(self):
- url = '%s/lb/vips' % (self.uri_prefix)
- resp, body = self.get(url, self.headers)
- vips = self._parse_array(etree.fromstring(body))
- vips = {"vips": vips}
- return resp, vips
-
def create_vip(self, name, protocol, protocol_port, subnet_id, pool_id):
uri = '%s/lb/vips' % (self.uri_prefix)
post_body = Element("vip")
@@ -270,17 +183,7 @@
post_body.append(p3)
post_body.append(p4)
post_body.append(p5)
- resp, body = self.post(uri, str(Document(post_body)), self.headers)
- body = _root_tag_fetcher_and_xml_to_json_parse(body)
- return resp, body
-
- def delete_vip(self, vip_id):
- uri = '%s/lb/vips/%s' % (self.uri_prefix, str(vip_id))
- return self.delete(uri, self.headers)
-
- def show_vip(self, vip_id):
- uri = '%s/lb/vips/%s' % (self.uri_prefix, str(vip_id))
- resp, body = self.get(uri, self.headers)
+ resp, body = self.post(uri, str(Document(post_body)))
body = _root_tag_fetcher_and_xml_to_json_parse(body)
return resp, body
@@ -289,17 +192,10 @@
put_body = Element("vip")
p2 = Element("name", new_name)
put_body.append(p2)
- resp, body = self.put(uri, str(Document(put_body)), self.headers)
+ resp, body = self.put(uri, str(Document(put_body)))
body = _root_tag_fetcher_and_xml_to_json_parse(body)
return resp, body
- def list_pools(self):
- url = '%s/lb/pools' % (self.uri_prefix)
- resp, body = self.get(url, self.headers)
- pools = self._parse_array(etree.fromstring(body))
- pools = {"pools": pools}
- return resp, pools
-
def create_pool(self, name, lb_method, protocol, subnet_id):
uri = '%s/lb/pools' % (self.uri_prefix)
post_body = Element("pool")
@@ -309,17 +205,7 @@
post_body.append(p1)
post_body.append(p2)
post_body.append(p3)
- resp, body = self.post(uri, str(Document(post_body)), self.headers)
- body = _root_tag_fetcher_and_xml_to_json_parse(body)
- return resp, body
-
- def delete_pool(self, pool_id):
- uri = '%s/lb/pools/%s' % (self.uri_prefix, str(pool_id))
- return self.delete(uri, self.headers)
-
- def show_pool(self, pool_id):
- uri = '%s/lb/pools/%s' % (self.uri_prefix, str(pool_id))
- resp, body = self.get(uri, self.headers)
+ resp, body = self.post(uri, str(Document(post_body)))
body = _root_tag_fetcher_and_xml_to_json_parse(body)
return resp, body
@@ -328,17 +214,10 @@
put_body = Element("pool")
p2 = Element("name", new_name)
put_body.append(p2)
- resp, body = self.put(uri, str(Document(put_body)), self.headers)
+ resp, body = self.put(uri, str(Document(put_body)))
body = _root_tag_fetcher_and_xml_to_json_parse(body)
return resp, body
- def list_members(self):
- url = '%s/lb/members' % (self.uri_prefix)
- resp, body = self.get(url, self.headers)
- members = self._parse_array(etree.fromstring(body))
- members = {"members": members}
- return resp, members
-
def create_member(self, address, protocol_port, pool_id):
uri = '%s/lb/members' % (self.uri_prefix)
post_body = Element("member")
@@ -348,17 +227,7 @@
post_body.append(p1)
post_body.append(p2)
post_body.append(p3)
- resp, body = self.post(uri, str(Document(post_body)), self.headers)
- body = _root_tag_fetcher_and_xml_to_json_parse(body)
- return resp, body
-
- def delete_member(self, member_id):
- uri = '%s/lb/members/%s' % (self.uri_prefix, str(member_id))
- return self.delete(uri, self.headers)
-
- def show_member(self, member_id):
- uri = '%s/lb/members/%s' % (self.uri_prefix, str(member_id))
- resp, body = self.get(uri, self.headers)
+ resp, body = self.post(uri, str(Document(post_body)))
body = _root_tag_fetcher_and_xml_to_json_parse(body)
return resp, body
@@ -367,17 +236,10 @@
put_body = Element("member")
p2 = Element("admin_state_up", admin_state_up)
put_body.append(p2)
- resp, body = self.put(uri, str(Document(put_body)), self.headers)
+ resp, body = self.put(uri, str(Document(put_body)))
body = _root_tag_fetcher_and_xml_to_json_parse(body)
return resp, body
- def list_health_monitors(self):
- uri = '%s/lb/health_monitors' % (self.uri_prefix)
- resp, body = self.get(uri, self.headers)
- body = self._parse_array(etree.fromstring(body))
- body = {"health_monitors": body}
- return resp, body
-
def create_health_monitor(self, delay, max_retries, Type, timeout):
uri = '%s/lb/health_monitors' % (self.uri_prefix)
post_body = Element("health_monitor")
@@ -389,17 +251,7 @@
post_body.append(p2)
post_body.append(p3)
post_body.append(p4)
- resp, body = self.post(uri, str(Document(post_body)), self.headers)
- body = _root_tag_fetcher_and_xml_to_json_parse(body)
- return resp, body
-
- def delete_health_monitor(self, uuid):
- uri = '%s/lb/health_monitors/%s' % (self.uri_prefix, str(uuid))
- return self.delete(uri, self.headers)
-
- def show_health_monitor(self, uuid):
- uri = '%s/lb/health_monitors/%s' % (self.uri_prefix, str(uuid))
- resp, body = self.get(uri, self.headers)
+ resp, body = self.post(uri, str(Document(post_body)))
body = _root_tag_fetcher_and_xml_to_json_parse(body)
return resp, body
@@ -408,7 +260,7 @@
put_body = Element("health_monitor")
p2 = Element("admin_state_up", admin_state_up)
put_body.append(p2)
- resp, body = self.put(uri, str(Document(put_body)), self.headers)
+ resp, body = self.put(uri, str(Document(put_body)))
body = _root_tag_fetcher_and_xml_to_json_parse(body)
return resp, body
@@ -419,7 +271,7 @@
post_body = Element("health_monitor")
p1 = Element("id", health_monitor_id,)
post_body.append(p1)
- resp, body = self.post(uri, str(Document(post_body)), self.headers)
+ resp, body = self.post(uri, str(Document(post_body)))
body = _root_tag_fetcher_and_xml_to_json_parse(body)
return resp, body
@@ -427,18 +279,11 @@
pool_id):
uri = '%s/lb/pools/%s/health_monitors/%s' % (self.uri_prefix, pool_id,
health_monitor_id)
- return self.delete(uri, self.headers)
-
- def list_extensions(self):
- url = '%s/extensions' % (self.uri_prefix)
- resp, body = self.get(url, self.headers)
- extensions = self._parse_array(etree.fromstring(body))
- extensions = {"extensions": extensions}
- return resp, extensions
+ return self.delete(uri)
def show_extension_details(self, ext_alias):
uri = '%s/extensions/%s' % (self.uri_prefix, str(ext_alias))
- resp, body = self.get(uri, self.headers)
+ resp, body = self.get(uri)
body = _root_tag_fetcher_and_xml_to_json_parse(body)
return resp, body
@@ -447,18 +292,7 @@
router = Element("router")
router.append(Element("name", name))
deep_dict_to_xml(router, kwargs)
- resp, body = self.post(uri, str(Document(router)), self.headers)
- body = _root_tag_fetcher_and_xml_to_json_parse(body)
- return resp, body
-
- def delete_router(self, router_id):
- uri = '%s/routers/%s' % (self.uri_prefix, router_id)
- resp, body = self.delete(uri, self.headers)
- return resp, body
-
- def show_router(self, router_id):
- uri = '%s/routers/%s' % (self.uri_prefix, router_id)
- resp, body = self.get(uri, self.headers)
+ resp, body = self.post(uri, str(Document(router)))
body = _root_tag_fetcher_and_xml_to_json_parse(body)
return resp, body
@@ -467,7 +301,7 @@
router = Element("router")
for element, content in kwargs.iteritems():
router.append(Element(element, content))
- resp, body = self.put(uri, str(Document(router)), self.headers)
+ resp, body = self.put(uri, str(Document(router)))
body = _root_tag_fetcher_and_xml_to_json_parse(body)
return resp, body
@@ -475,7 +309,7 @@
uri = '%s/routers/%s/add_router_interface' % (self.uri_prefix,
router_id)
subnet = Element("subnet_id", subnet_id)
- resp, body = self.put(uri, str(Document(subnet)), self.headers)
+ resp, body = self.put(uri, str(Document(subnet)))
body = _root_tag_fetcher_and_xml_to_json_parse(body)
return resp, body
@@ -483,7 +317,7 @@
uri = '%s/routers/%s/add_router_interface' % (self.uri_prefix,
router_id)
port = Element("port_id", port_id)
- resp, body = self.put(uri, str(Document(port)), self.headers)
+ resp, body = self.put(uri, str(Document(port)))
body = _root_tag_fetcher_and_xml_to_json_parse(body)
return resp, body
@@ -491,7 +325,7 @@
uri = '%s/routers/%s/remove_router_interface' % (self.uri_prefix,
router_id)
subnet = Element("subnet_id", subnet_id)
- resp, body = self.put(uri, str(Document(subnet)), self.headers)
+ resp, body = self.put(uri, str(Document(subnet)))
body = _root_tag_fetcher_and_xml_to_json_parse(body)
return resp, body
@@ -499,7 +333,7 @@
uri = '%s/routers/%s/remove_router_interface' % (self.uri_prefix,
router_id)
port = Element("port_id", port_id)
- resp, body = self.put(uri, str(Document(port)), self.headers)
+ resp, body = self.put(uri, str(Document(port)))
body = _root_tag_fetcher_and_xml_to_json_parse(body)
return resp, body
@@ -509,28 +343,10 @@
floatingip.append(Element("floating_network_id", ext_network_id))
for element, content in kwargs.iteritems():
floatingip.append(Element(element, content))
- resp, body = self.post(uri, str(Document(floatingip)), self.headers)
+ resp, body = self.post(uri, str(Document(floatingip)))
body = _root_tag_fetcher_and_xml_to_json_parse(body)
return resp, body
- def show_floating_ip(self, floating_ip_id):
- uri = '%s/floatingips/%s' % (self.uri_prefix, floating_ip_id)
- resp, body = self.get(uri, self.headers)
- body = _root_tag_fetcher_and_xml_to_json_parse(body)
- return resp, body
-
- def list_floating_ips(self):
- uri = '%s/floatingips' % (self.uri_prefix)
- resp, body = self.get(uri, self.headers)
- floatingips = self._parse_array(etree.fromstring(body))
- floatingips = {"floatingips": floatingips}
- return resp, floatingips
-
- def delete_floating_ip(self, floating_ip_id):
- uri = '%s/floatingips/%s' % (self.uri_prefix, floating_ip_id)
- resp, body = self.delete(uri, self.headers)
- return resp, body
-
def update_floating_ip(self, floating_ip_id, **kwargs):
uri = '%s/floatingips/%s' % (self.uri_prefix, floating_ip_id)
floatingip = Element('floatingip')
@@ -543,41 +359,57 @@
floatingip.append(xml_elem)
else:
floatingip.append(Element(element, content))
- resp, body = self.put(uri, str(Document(floatingip)), self.headers)
+ resp, body = self.put(uri, str(Document(floatingip)))
body = _root_tag_fetcher_and_xml_to_json_parse(body)
return resp, body
def list_router_interfaces(self, uuid):
uri = '%s/ports?device_id=%s' % (self.uri_prefix, uuid)
- resp, body = self.get(uri, self.headers)
+ resp, body = self.get(uri)
ports = self._parse_array(etree.fromstring(body))
ports = {"ports": ports}
return resp, ports
- def list_agents(self):
- uri = '%s/agents' % self.uri_prefix
- resp, body = self.get(uri, self.headers)
- agents = self._parse_array(etree.fromstring(body))
- agents = {'agents': agents}
- return resp, agents
+ def update_agent(self, agent_id, agent_info):
+ uri = '%s/agents/%s' % (self.uri_prefix, agent_id)
+ agent = Element('agent')
+ for (key, value) in agent_info.items():
+ p = Element(key, value)
+ agent.append(p)
+ resp, body = self.put(uri, str(Document(agent)))
+ body = _root_tag_fetcher_and_xml_to_json_parse(body)
+ return resp, body
def list_routers_on_l3_agent(self, agent_id):
uri = '%s/agents/%s/l3-routers' % (self.uri_prefix, agent_id)
- resp, body = self.get(uri, self.headers)
+ resp, body = self.get(uri)
body = _root_tag_fetcher_and_xml_to_json_parse(body)
return resp, body
def list_l3_agents_hosting_router(self, router_id):
uri = '%s/routers/%s/l3-agents' % (self.uri_prefix, router_id)
- resp, body = self.get(uri, self.headers)
+ resp, body = self.get(uri)
body = _root_tag_fetcher_and_xml_to_json_parse(body)
return resp, body
- def list_service_providers(self):
- uri = '%s/service-providers' % self.uri_prefix
- resp, body = self.get(uri, self.headers)
- providers = self._parse_array(etree.fromstring(body))
- body = {'service_providers': providers}
+ def list_dhcp_agent_hosting_network(self, network_id):
+ uri = '%s/networks/%s/dhcp-agents' % (self.uri_prefix, network_id)
+ resp, body = self.get(uri)
+ agents = self._parse_array(etree.fromstring(body))
+ body = {'agents': agents}
+ return resp, body
+
+ def list_networks_hosted_by_one_dhcp_agent(self, agent_id):
+ uri = '%s/agents/%s/dhcp-networks' % (self.uri_prefix, agent_id)
+ resp, body = self.get(uri)
+ networks = self._parse_array(etree.fromstring(body))
+ body = {'networks': networks}
+ return resp, body
+
+ def remove_network_from_dhcp_agent(self, agent_id, network_id):
+ uri = '%s/agents/%s/dhcp-networks/%s' % (self.uri_prefix, agent_id,
+ network_id)
+ resp, body = self.delete(uri)
return resp, body
diff --git a/tempest/services/object_storage/object_client.py b/tempest/services/object_storage/object_client.py
index 2fee042..9e0adff 100644
--- a/tempest/services/object_storage/object_client.py
+++ b/tempest/services/object_storage/object_client.py
@@ -15,6 +15,8 @@
# License for the specific language governing permissions and limitations
# under the License.
+import urllib
+
from tempest.common import http
from tempest.common.rest_client import RestClient
from tempest import exceptions
@@ -27,13 +29,16 @@
self.service = self.config.object_storage.catalog_type
- def create_object(self, container, object_name, data):
+ def create_object(self, container, object_name, data, params=None):
"""Create storage object."""
headers = dict(self.headers)
if not data:
headers['content-length'] = '0'
url = "%s/%s" % (str(container), str(object_name))
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+
resp, body = self.put(url, data, headers)
return resp, body
@@ -41,9 +46,11 @@
"""Upload data to replace current storage object."""
return self.create_object(container, object_name, data)
- def delete_object(self, container, object_name):
+ def delete_object(self, container, object_name, params=None):
"""Delete storage object."""
url = "%s/%s" % (str(container), str(object_name))
+ if params:
+ url += '?%s' % urllib.urlencode(params)
resp, body = self.delete(url)
return resp, body
diff --git a/tempest/services/volume/json/admin/volume_hosts_client.py b/tempest/services/volume/json/admin/volume_hosts_client.py
new file mode 100644
index 0000000..fc28ada
--- /dev/null
+++ b/tempest/services/volume/json/admin/volume_hosts_client.py
@@ -0,0 +1,46 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 OpenStack Foundation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import json
+import urllib
+
+from tempest.common.rest_client import RestClient
+
+
+class VolumeHostsClientJSON(RestClient):
+ """
+ Client class to send CRUD Volume Hosts API requests to a Cinder endpoint
+ """
+
+ def __init__(self, config, username, password, auth_url, tenant_name=None):
+ super(VolumeHostsClientJSON, self).__init__(config, username, password,
+ auth_url, tenant_name)
+
+ self.service = self.config.volume.catalog_type
+ self.build_interval = self.config.volume.build_interval
+ self.build_timeout = self.config.volume.build_timeout
+
+ def list_hosts(self, params=None):
+ """Lists all hosts."""
+
+ url = 'os-hosts'
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+
+ resp, body = self.get(url)
+ body = json.loads(body)
+ return resp, body['hosts']
diff --git a/tempest/services/volume/json/snapshots_client.py b/tempest/services/volume/json/snapshots_client.py
index 5d980eb..9435122 100644
--- a/tempest/services/volume/json/snapshots_client.py
+++ b/tempest/services/volume/json/snapshots_client.py
@@ -44,7 +44,7 @@
body = json.loads(body)
return resp, body['snapshots']
- def list_snapshot_with_detail(self, params=None):
+ def list_snapshots_with_detail(self, params=None):
"""List the details of all snapshots."""
url = 'snapshots/detail'
if params:
diff --git a/tempest/services/volume/json/volumes_client.py b/tempest/services/volume/json/volumes_client.py
index b4a1a68..967dc09 100644
--- a/tempest/services/volume/json/volumes_client.py
+++ b/tempest/services/volume/json/volumes_client.py
@@ -256,3 +256,10 @@
url = 'volumes/%s/action' % (volume_id)
resp, body = self.post(url, post_body, self.headers)
return resp, body
+
+ def force_delete_volume(self, volume_id):
+ """Force Delete Volume."""
+ post_body = json.dumps({'os-force_delete': {}})
+ resp, body = self.post('volumes/%s/action' % volume_id, post_body,
+ self.headers)
+ return resp, body
diff --git a/tempest/services/volume/xml/admin/volume_hosts_client.py b/tempest/services/volume/xml/admin/volume_hosts_client.py
new file mode 100644
index 0000000..59ce933
--- /dev/null
+++ b/tempest/services/volume/xml/admin/volume_hosts_client.py
@@ -0,0 +1,72 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2013 Openstack Foundation.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import urllib
+
+from lxml import etree
+
+from tempest.common.rest_client import RestClientXML
+from tempest.services.compute.xml.common import xml_to_json
+
+
+class VolumeHostsClientXML(RestClientXML):
+ """
+ Client class to send CRUD Volume Hosts API requests to a Cinder endpoint
+ """
+
+ def __init__(self, config, username, password, auth_url, tenant_name=None):
+ super(VolumeHostsClientXML, self).__init__(config, username, password,
+ auth_url, tenant_name)
+ self.service = self.config.volume.catalog_type
+ self.build_interval = self.config.compute.build_interval
+ self.build_timeout = self.config.compute.build_timeout
+
+ def _parse_array(self, node):
+ """
+ This method is to parse the "list" response body
+ Eg:
+
+ <?xml version='1.0' encoding='UTF-8'?>
+ <hosts>
+ <host service-status="available" service="cinder-scheduler"/>
+ <host service-status="available" service="cinder-volume"/>
+ </hosts>
+
+ This method will append the details of specified tag,
+ here it is "host"
+ Return value would be list of hosts as below
+
+ [{'service-status': 'available', 'service': 'cinder-scheduler'},
+ {'service-status': 'available', 'service': 'cinder-volume'}]
+ """
+ array = []
+ for child in node.getchildren():
+ tag_list = child.tag.split('}', 1)
+ if tag_list[0] == "host":
+ array.append(xml_to_json(child))
+ return array
+
+ def list_hosts(self, params=None):
+ """List all the hosts."""
+ url = 'os-hosts'
+
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+
+ resp, body = self.get(url, self.headers)
+ body = self._parse_array(etree.fromstring(body))
+ return resp, body
diff --git a/tempest/services/volume/xml/volumes_client.py b/tempest/services/volume/xml/volumes_client.py
index 21254aa..1fc63e9 100644
--- a/tempest/services/volume/xml/volumes_client.py
+++ b/tempest/services/volume/xml/volumes_client.py
@@ -347,3 +347,12 @@
if body:
body = xml_to_json(etree.fromstring(body))
return resp, body
+
+ def force_delete_volume(self, volume_id):
+ """Force Delete Volume."""
+ post_body = Element("os-force_delete")
+ url = 'volumes/%s/action' % str(volume_id)
+ resp, body = self.post(url, str(Document(post_body)), self.headers)
+ if body:
+ body = xml_to_json(etree.fromstring(body))
+ return resp, body
diff --git a/tempest/stress/run_stress.py b/tempest/stress/run_stress.py
index e5cc281..067b994 100755
--- a/tempest/stress/run_stress.py
+++ b/tempest/stress/run_stress.py
@@ -24,6 +24,7 @@
from unittest import loader
from tempest.openstack.common import log as logging
+from tempest.stress import driver
LOG = logging.getLogger(__name__)
@@ -68,8 +69,6 @@
def main(ns):
- # NOTE(mkoderer): moved import to make "-h" possible without OpenStack
- from tempest.stress import driver
result = 0
if not ns.all:
tests = json.load(open(ns.tests, 'r'))
diff --git a/tempest/test.py b/tempest/test.py
index 6ae7925..ceb2c80 100644
--- a/tempest/test.py
+++ b/tempest/test.py
@@ -123,6 +123,42 @@
return decorator
+def requires_ext(*args, **kwargs):
+ """A decorator to skip tests if an extension is not enabled
+
+ @param extension
+ @param service
+ """
+ def decorator(func):
+ @functools.wraps(func)
+ def wrapper(*func_args, **func_kwargs):
+ if not is_extension_enabled(kwargs['extension'],
+ kwargs['service']):
+ msg = "Skipped because %s extension: %s is not enabled" % (
+ kwargs['service'], kwargs['extension'])
+ raise testtools.TestCase.skipException(msg)
+ return func(*func_args, **func_kwargs)
+ return wrapper
+ return decorator
+
+
+def is_extension_enabled(extension_name, service):
+ """A function that will check the list of enabled extensions from config
+
+ """
+ configs = config.TempestConfig()
+ config_dict = {
+ 'compute': configs.compute_feature_enabled.api_extensions,
+ 'compute_v3': configs.compute_feature_enabled.api_v3_extensions,
+ 'volume': configs.volume_feature_enabled.api_extensions,
+ 'network': configs.network_feature_enabled.api_extensions,
+ }
+ if config_dict[service][0] == 'all':
+ return True
+ if extension_name in config_dict[service]:
+ return True
+ return False
+
# there is a mis-match between nose and testtools for older pythons.
# testtools will set skipException to be either
# unittest.case.SkipTest, unittest2.case.SkipTest or an internal skip
diff --git a/tempest/tests/stress/__init__.py b/tempest/tests/stress/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/tests/stress/__init__.py
diff --git a/tempest/tests/stress/test_stress.py b/tempest/tests/stress/test_stress.py
new file mode 100644
index 0000000..4d7de9d
--- /dev/null
+++ b/tempest/tests/stress/test_stress.py
@@ -0,0 +1,57 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 Deutsche Telekom AG
+# 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 shlex
+import subprocess
+
+import tempest.cli as cli
+from tempest.openstack.common import log as logging
+import tempest.test
+
+LOG = logging.getLogger(__name__)
+
+
+class StressFrameworkTest(tempest.test.BaseTestCase):
+ """Basic test for the stress test framework.
+ """
+
+ def _cmd(self, cmd, param):
+ """Executes specified command."""
+ cmd = ' '.join([cmd, param])
+ LOG.info("running: '%s'" % cmd)
+ cmd_str = cmd
+ cmd = shlex.split(cmd)
+ result = ''
+ result_err = ''
+ try:
+ stdout = subprocess.PIPE
+ stderr = subprocess.PIPE
+ proc = subprocess.Popen(
+ cmd, stdout=stdout, stderr=stderr)
+ result, result_err = proc.communicate()
+ if proc.returncode != 0:
+ LOG.debug('error of %s:\n%s' % (cmd_str, result_err))
+ raise cli.CommandFailed(proc.returncode,
+ cmd,
+ result)
+ finally:
+ LOG.debug('output of %s:\n%s' % (cmd_str, result))
+ return proc.returncode
+
+ def test_help_function(self):
+ result = self._cmd("python", "-m tempest.stress.run_stress -h")
+ self.assertEqual(0, result)
diff --git a/tempest/tests/stress/test_stressaction.py b/tempest/tests/stress/test_stressaction.py
new file mode 100644
index 0000000..3d2901e
--- /dev/null
+++ b/tempest/tests/stress/test_stressaction.py
@@ -0,0 +1,65 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 Deutsche Telekom AG
+# 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 tempest.stress.stressaction as stressaction
+import tempest.test
+
+
+class FakeStressAction(stressaction.StressAction):
+ def __init__(self, manager, max_runs=None, stop_on_error=False):
+ super(self.__class__, self).__init__(manager, max_runs, stop_on_error)
+ self._run_called = False
+
+ def run(self):
+ self._run_called = True
+
+ @property
+ def run_called(self):
+ return self._run_called
+
+
+class FakeStressActionFailing(stressaction.StressAction):
+ def run(self):
+ raise Exception('FakeStressActionFailing raise exception')
+
+
+class TestStressAction(tempest.test.BaseTestCase):
+ def _bulid_stats_dict(self, runs=0, fails=0):
+ return {'runs': runs, 'fails': fails}
+
+ def testStressTestRun(self):
+ stressAction = FakeStressAction(manager=None, max_runs=1)
+ stats = self._bulid_stats_dict()
+ stressAction.execute(stats)
+ self.assertTrue(stressAction.run_called)
+ self.assertEqual(stats['runs'], 1)
+ self.assertEqual(stats['fails'], 0)
+
+ def testStressMaxTestRuns(self):
+ stressAction = FakeStressAction(manager=None, max_runs=500)
+ stats = self._bulid_stats_dict(runs=499)
+ stressAction.execute(stats)
+ self.assertTrue(stressAction.run_called)
+ self.assertEqual(stats['runs'], 500)
+ self.assertEqual(stats['fails'], 0)
+
+ def testStressTestRunWithException(self):
+ stressAction = FakeStressActionFailing(manager=None, max_runs=1)
+ stats = self._bulid_stats_dict()
+ stressAction.execute(stats)
+ self.assertEqual(stats['runs'], 1)
+ self.assertEqual(stats['fails'], 1)
diff --git a/tempest/tests/test_wrappers.py b/tempest/tests/test_wrappers.py
index dbf1809..88bef9b 100644
--- a/tempest/tests/test_wrappers.py
+++ b/tempest/tests/test_wrappers.py
@@ -56,7 +56,7 @@
# version or an sdist to work. so make the test directory a git repo
# too.
subprocess.call(['git', 'init'])
- exit_code = subprocess.call('sh pretty_tox.sh tests.passing',
+ exit_code = subprocess.call('bash pretty_tox.sh tests.passing',
shell=True, stdout=DEVNULL, stderr=DEVNULL)
self.assertEqual(exit_code, 0)
@@ -71,7 +71,7 @@
# version or an sdist to work. so make the test directory a git repo
# too.
subprocess.call(['git', 'init'])
- exit_code = subprocess.call('sh pretty_tox.sh', shell=True,
+ exit_code = subprocess.call('bash pretty_tox.sh', shell=True,
stdout=DEVNULL, stderr=DEVNULL)
self.assertEqual(exit_code, 1)
@@ -82,7 +82,7 @@
# Change directory, run wrapper and check result
self.addCleanup(os.chdir, os.path.abspath(os.curdir))
os.chdir(self.directory)
- exit_code = subprocess.call('sh pretty_tox_serial.sh tests.passing',
+ exit_code = subprocess.call('bash pretty_tox_serial.sh tests.passing',
shell=True, stdout=DEVNULL, stderr=DEVNULL)
self.assertEqual(exit_code, 0)
@@ -93,6 +93,6 @@
# Change directory, run wrapper and check result
self.addCleanup(os.chdir, os.path.abspath(os.curdir))
os.chdir(self.directory)
- exit_code = subprocess.call('sh pretty_tox_serial.sh', shell=True,
+ exit_code = subprocess.call('bash pretty_tox_serial.sh', shell=True,
stdout=DEVNULL, stderr=DEVNULL)
self.assertEqual(exit_code, 1)
diff --git a/test-requirements.txt b/test-requirements.txt
index fbe7e43..9486244 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -1,7 +1,8 @@
-hacking>=0.5.6,<0.8
+hacking>=0.8.0,<0.9
# needed for doc build
docutils==0.9.1
-sphinx>=1.1.2
+sphinx>=1.1.2,<1.2
python-subunit
oslo.sphinx
mox>=0.5.3
+mock>=1.0
diff --git a/tools/check_logs.py b/tools/check_logs.py
index 6d4436e..963709b 100755
--- a/tools/check_logs.py
+++ b/tools/check_logs.py
@@ -27,11 +27,13 @@
is_neutron = os.environ.get('DEVSTACK_GATE_NEUTRON', "0") == "1"
+is_grenade = (os.environ.get('DEVSTACK_GATE_GRENADE', "0") == "1" or
+ os.environ.get('DEVSTACK_GATE_GRENADE_FORWARD', "0") == "1")
dump_all_errors = is_neutron
def process_files(file_specs, url_specs, whitelists):
- regexp = re.compile(r"^.*(ERROR|CRITICAL).*\[.*\-.*\]")
+ regexp = re.compile(r"^.* (ERROR|CRITICAL) .*\[.*\-.*\]")
had_errors = False
for (name, filename) in file_specs:
whitelist = whitelists.get(name, [])
@@ -125,6 +127,9 @@
if is_neutron:
print("Currently not failing neutron builds with errors")
return 0
+ if is_grenade:
+ print("Currently not failing grenade runs with errors")
+ return 0
print("FAILED")
return 1
else:
diff --git a/tools/pretty_tox.sh b/tools/pretty_tox.sh
index a5a6076..07c35a0 100755
--- a/tools/pretty_tox.sh
+++ b/tools/pretty_tox.sh
@@ -1,4 +1,6 @@
-#!/bin/sh
+#!/usr/bin/env bash
+
+set -o pipefail
TESTRARGS=$1
python setup.py testr --slowest --testr-args="--subunit $TESTRARGS" | subunit2pyunit
diff --git a/tools/pretty_tox_serial.sh b/tools/pretty_tox_serial.sh
index 45f05bd..42ce760 100755
--- a/tools/pretty_tox_serial.sh
+++ b/tools/pretty_tox_serial.sh
@@ -1,4 +1,6 @@
-#!/bin/sh
+#!/usr/bin/env bash
+
+set -o pipefail
TESTRARGS=$@
diff --git a/tools/tempest_coverage.py b/tools/tempest_coverage.py
deleted file mode 100755
index ef2eacd..0000000
--- a/tools/tempest_coverage.py
+++ /dev/null
@@ -1,193 +0,0 @@
-# vim: tabstop=4 shiftwidth=4 softtabstop=4
-
-# Copyright 2012 IBM Corp.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-import json
-import os
-import shutil
-import sys
-
-from oslo.config import cfg
-
-from tempest.common.rest_client import RestClient
-from tempest import config
-
-CONF = config.TempestConfig()
-
-
-class CoverageClientJSON(RestClient):
-
- def __init__(self, config, username, password, auth_url, tenant_name=None):
- super(CoverageClientJSON, self).__init__(config, username, password,
- auth_url, tenant_name)
- self.service = self.config.compute.catalog_type
-
- def start_coverage(self):
- post_body = {
- 'start': {},
- }
- post_body = json.dumps(post_body)
- return self.post('os-coverage/action', post_body, self.headers)
-
- def start_coverage_combine(self):
- post_body = {
- 'start': {
- 'combine': True,
- },
- }
- post_body = json.dumps(post_body)
- return self.post('os-coverage/action', post_body, self.headers)
-
- def stop_coverage(self):
- post_body = {
- 'stop': {},
- }
- post_body = json.dumps(post_body)
- resp, body = self.post('os-coverage/action', post_body, self.headers)
- body = json.loads(body)
- return resp, body
-
- def report_coverage_xml(self, file=None):
- post_body = {
- 'report': {
- 'file': 'coverage.report',
- 'xml': True,
- },
- }
- if file:
- post_body['report']['file'] = file
- post_body = json.dumps(post_body)
- resp, body = self.post('os-coverage/action', post_body, self.headers)
- body = json.loads(body)
- return resp, body
-
- def report_coverage(self, file=None):
- post_body = {
- 'report': {
- 'file': 'coverage.report',
- },
- }
- if file:
- post_body['report']['file'] = file
- post_body = json.dumps(post_body)
- resp, body = self.post('os-coverage/action', post_body, self.headers)
- body = json.loads(body)
- return resp, body
-
- def report_coverage_html(self, file=None):
- post_body = {
- 'report': {
- 'file': 'coverage.report',
- 'html': True,
- },
- }
- if file:
- post_body['report']['file'] = file
- post_body = json.dumps(post_body)
- resp, body = self.post('os-coverage/action', post_body, self.headers)
- body = json.loads(body)
- return resp, body
-
-
-def parse_opts(argv):
- cli_opts = [
- cfg.StrOpt('command',
- short='c',
- default='',
- help="This required argument is used to specify the "
- "coverage command to run. Only 'start', "
- "'stop', or 'report' are valid fields."),
- cfg.StrOpt('filename',
- default='tempest-coverage',
- help="Specify a filename to be used for generated report "
- "files"),
- cfg.BoolOpt('xml',
- default=False,
- help='Generate XML reports instead of text'),
- cfg.BoolOpt('html',
- default=False,
- help='Generate HTML reports instead of text'),
- cfg.BoolOpt('combine',
- default=False,
- help='Generate a single report for all services'),
- cfg.StrOpt('output',
- short='o',
- default=None,
- help='Optional directory to copy generated coverage data or'
- ' reports into. This directory must not already exist '
- 'it will be created')
- ]
- CLI = cfg.ConfigOpts()
- CLI.register_cli_opts(cli_opts)
- CLI(argv[1:])
- return CLI
-
-
-def main(argv):
- CLI = parse_opts(argv)
- client_args = (CONF, CONF.identity.admin_username,
- CONF.identity.admin_password, CONF.identity.uri,
- CONF.identity.admin_tenant_name)
- coverage_client = CoverageClientJSON(*client_args)
-
- if CLI.command == 'start':
- if CLI.combine:
- coverage_client.start_coverage_combine()
- else:
- coverage_client.start_coverage()
-
- elif CLI.command == 'stop':
- resp, body = coverage_client.stop_coverage()
- if not resp['status'] == '200':
- print('coverage stop failed with: %s:' % (resp['status'] + ': '
- + body))
- exit(int(resp['status']))
- path = body['path']
- if CLI.output:
- shutil.copytree(path, CLI.output)
- else:
- print("Data files located at: %s" % path)
-
- elif CLI.command == 'report':
- if CLI.xml:
- resp, body = coverage_client.report_coverage_xml(file=CLI.filename)
- elif CLI.html:
- resp, body = coverage_client.report_coverage_html(
- file=CLI.filename)
- else:
- resp, body = coverage_client.report_coverage(file=CLI.filename)
- if not resp['status'] == '200':
- print('coverage report failed with: %s:' % (resp['status'] + ': '
- + body))
- exit(int(resp['status']))
- path = body['path']
- if CLI.output:
- if CLI.html:
- shutil.copytree(path, CLI.output)
- else:
- path = os.path.dirname(path)
- shutil.copytree(path, CLI.output)
- else:
- if not CLI.html:
- path = os.path.dirname(path)
- print('Report files located at: %s' % path)
-
- else:
- print('Invalid command')
- exit(1)
-
-
-if __name__ == "__main__":
- main(sys.argv)
diff --git a/tools/verify_tempest_config.py b/tools/verify_tempest_config.py
index 1b5fe68..347659d 100755
--- a/tools/verify_tempest_config.py
+++ b/tools/verify_tempest_config.py
@@ -36,11 +36,11 @@
__, versions = os.image_client.get_versions()
if CONF.image_feature_enabled.api_v1 != ('v1.1' in versions or 'v1.0' in
versions):
- print 'Config option image api_v1 should be change to: %s' % (
- not CONF.image_feature_enabled.api_v1)
+ print('Config option image api_v1 should be change to: %s' % (
+ not CONF.image_feature_enabled.api_v1))
if CONF.image_feature_enabled.api_v2 != ('v2.0' in versions):
- print 'Config option image api_v2 should be change to: %s' % (
- not CONF.image_feature_enabled.api_v2)
+ print('Config option image api_v2 should be change to: %s' % (
+ not CONF.image_feature_enabled.api_v2))
def verify_extensions(os):
@@ -62,8 +62,8 @@
for option in NOVA_EXTENSIONS.keys():
config_value = getattr(CONF.compute_feature_enabled, option)
if config_value != results['nova_features'][option]:
- print "Config option: %s should be changed to: %s" % (
- option, not config_value)
+ print("Config option: %s should be changed to: %s" % (
+ option, not config_value))
def main(argv):
diff --git a/tox.ini b/tox.ini
index 6efac78..c7f92ae 100644
--- a/tox.ini
+++ b/tox.ini
@@ -9,14 +9,18 @@
LANGUAGE=en_US:en
LC_ALL=C
usedevelop = True
+install_command = pip install -U {opts} {packages}
[testenv:py26]
+setenv = OS_TEST_PATH=./tempest/tests
commands = python setup.py test --slowest --testr-arg='tempest\.tests {posargs}'
[testenv:py33]
+setenv = OS_TEST_PATH=./tempest/tests
commands = python setup.py test --slowest --testr-arg='tempest\.tests {posargs}'
[testenv:py27]
+setenv = OS_TEST_PATH=./tempest/tests
commands = python setup.py test --slowest --testr-arg='tempest\.tests {posargs}'
[testenv:all]
@@ -30,19 +34,19 @@
# The regex below is used to select which tests to run and exclude the slow tag:
# See the testrepostiory bug: https://bugs.launchpad.net/testrepository/+bug/1208610
commands =
- sh tools/pretty_tox.sh '(?!.*\[.*\bslow\b.*\])(^tempest\.(api|scenario|thirdparty|cli)) {posargs}'
+ bash tools/pretty_tox.sh '(?!.*\[.*\bslow\b.*\])(^tempest\.(api|scenario|thirdparty|cli)) {posargs}'
[testenv:testr-full]
sitepackages = True
commands =
- sh tools/pretty_tox.sh '(?!.*\[.*\bslow\b.*\])(^tempest\.(api|scenario|thirdparty|cli)) {posargs}'
+ bash tools/pretty_tox.sh '(?!.*\[.*\bslow\b.*\])(^tempest\.(api|scenario|thirdparty|cli)) {posargs}'
[testenv:heat-slow]
sitepackages = True
setenv = OS_TEST_TIMEOUT=1200
# The regex below is used to select heat api/scenario tests tagged as slow.
commands =
- sh tools/pretty_tox_serial.sh '(?=.*\[.*\bslow\b.*\])(^tempest\.(api|scenario)\.orchestration) {posargs}'
+ bash tools/pretty_tox_serial.sh '(?=.*\[.*\bslow\b.*\])(^tempest\.(api|scenario)\.orchestration) {posargs}'
[testenv:large-ops]
sitepackages = True
@@ -78,7 +82,7 @@
[testenv:smoke]
sitepackages = True
commands =
- sh tools/pretty_tox.sh '(?!.*\[.*\bslow\b.*\])((smoke)|(^tempest\.scenario)) {posargs}'
+ bash tools/pretty_tox.sh '(?!.*\[.*\bslow\b.*\])((smoke)|(^tempest\.scenario)) {posargs}'
[testenv:smoke-serial]
sitepackages = True
@@ -86,14 +90,7 @@
# https://bugs.launchpad.net/tempest/+bug/1216076 so the neutron smoke
# job would fail if we moved it to parallel.
commands =
- sh tools/pretty_tox_serial.sh '(?!.*\[.*\bslow\b.*\])((smoke)|(^tempest\.scenario)) {posargs}'
-
-[testenv:coverage]
-sitepackages = True
-commands =
- python -m tools/tempest_coverage -c start --combine
- sh tools/pretty_tox.sh '(?!.*\[.*\bslow\b.*\])(^tempest\.(api|scenario|thirdparty|cli))'
- python -m tools/tempest_coverage -c report --html {posargs}
+ bash tools/pretty_tox_serial.sh '(?!.*\[.*\bslow\b.*\])((smoke)|(^tempest\.scenario)) {posargs}'
[testenv:stress]
sitepackages = True