Merge "Remove redundant whitelist for DHCP agent"
diff --git a/etc/tempest.conf.sample b/etc/tempest.conf.sample
index 937bbd3..6101466 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
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/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 1ba9b16..e72f3fc 100644
--- a/tempest/api/compute/base.py
+++ b/tempest/api/compute/base.py
@@ -239,6 +239,7 @@
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):
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..b866db1
--- /dev/null
+++ b/tempest/api/compute/v3/admin/test_flavors_access.py
@@ -0,0 +1,112 @@
+# 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 import compute
+from tempest.api.compute import base
+from tempest.common.utils import data_utils
+from tempest.test import attr
+
+
+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 compute.FLAVOR_EXTRA_DATA_ENABLED:
+ 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
+
+ @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'])
+
+ @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..340c1c7
--- /dev/null
+++ b/tempest/api/compute/v3/admin/test_flavors_access_negative.py
@@ -0,0 +1,153 @@
+# 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 import compute
+from tempest.api.compute import base
+from tempest.common.utils import data_utils
+from tempest import exceptions
+from tempest.test import attr
+
+
+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 compute.FLAVOR_EXTRA_DATA_ENABLED:
+ 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
+
+ @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)
+
+ @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)
+
+ @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)
+
+ @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)
+
+ @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..49b0429
--- /dev/null
+++ b/tempest/api/compute/v3/admin/test_flavors_extra_specs.py
@@ -0,0 +1,132 @@
+# 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 import compute
+from tempest.api.compute import base
+from tempest.common.utils import data_utils
+from tempest.test import attr
+
+
+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 compute.FLAVOR_EXTRA_DATA_ENABLED:
+ 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()
+
+ @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)
+
+ @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])
+
+ @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..d7e1f9f
--- /dev/null
+++ b/tempest/api/compute/v3/admin/test_flavors_extra_specs_negative.py
@@ -0,0 +1,136 @@
+# 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 import compute
+from tempest.api.compute import base
+from tempest.common.utils import data_utils
+from tempest import exceptions
+from tempest.test import attr
+
+
+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 compute.FLAVOR_EXTRA_DATA_ENABLED:
+ 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()
+
+ @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)
+
+ @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')
+
+ @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')
+
+ @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)
+
+ @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'])
+ 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")
+
+ @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/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_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_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/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/network/base.py b/tempest/api/network/base.py
index b222ae3..61af91f 100644
--- a/tempest/api/network/base.py
+++ b/tempest/api/network/base.py
@@ -65,9 +65,13 @@
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_ike_policy(ikepolicy['id'])
# Clean up vpn services
for vpnservice in cls.vpnservices:
cls.client.delete_vpn_service(vpnservice['id'])
@@ -216,6 +220,14 @@
cls.vpnservices.append(vpnservice)
return vpnservice
+ @classmethod
+ def create_ike_policy(cls, name):
+ """Wrapper utility that returns a test ike policy."""
+ resp, body = cls.client.create_ike_policy(name)
+ ikepolicy = body['ikepolicy']
+ cls.ikepolicies.append(ikepolicy)
+ return ikepolicy
+
class BaseAdminNetworkTest(BaseNetworkTest):
diff --git a/tempest/api/network/test_vpnaas_extensions.py b/tempest/api/network/test_vpnaas_extensions.py
index 9cbc7ac..7905b97 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
@@ -43,6 +44,24 @@
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_ike_policy(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_ike_policies()
+ for ike in all_ike['ikepolicies']:
+ ike_list.append(ike['id'])
+ if ike_policy_id in ike_list:
+ resp, _ = self.client.delete_ike_policy(ike_policy_id)
+ self.assertEqual(204, resp.status)
+ # Asserting that the policy is not found in list after deletion
+ resp, ikepolicies = self.client.list_ike_policies()
+ 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):
@@ -94,3 +113,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_ike_policies()
+ 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_ike_policy(
+ 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_ike_policy(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_ike_policy(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_ike_policy(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 1eea30a..9f9abd8 100644
--- a/tempest/api/object_storage/test_container_staticweb.py
+++ b/tempest/api/object_storage/test_container_staticweb.py
@@ -15,6 +15,7 @@
# 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 import test
@@ -60,6 +61,8 @@
resp, body = self.custom_account_client.request("GET",
self.container_name)
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
@@ -82,6 +85,15 @@
resp, body = self.custom_account_client.request("GET",
self.container_name)
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
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_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/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/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/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/network/json/network_client.py b/tempest/services/network/json/network_client.py
index aab2b9b..c6bd423 100644
--- a/tempest/services/network/json/network_client.py
+++ b/tempest/services/network/json/network_client.py
@@ -715,3 +715,42 @@
network_id)
resp, body = self.delete(uri, self.headers)
return resp, body
+
+ def list_ike_policies(self):
+ uri = '%s/vpn/ikepolicies' % (self.uri_prefix)
+ resp, body = self.get(uri, self.headers)
+ body = json.loads(body)
+ return resp, body
+
+ def create_ike_policy(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, headers=self.headers, body=body)
+ body = json.loads(body)
+ return resp, body
+
+ def show_ike_policy(self, uuid):
+ uri = '%s/vpn/ikepolicies/%s' % (self.uri_prefix, uuid)
+ resp, body = self.get(uri, self.headers)
+ body = json.loads(body)
+ return resp, body
+
+ def delete_ike_policy(self, uuid):
+ uri = '%s/vpn/ikepolicies/%s' % (self.uri_prefix, uuid)
+ resp, body = self.delete(uri, self.headers)
+ return resp, body
+
+ def update_ike_policy(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, headers=self.headers)
+ body = json.loads(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/test-requirements.txt b/test-requirements.txt
index 41a784e..5cdb819 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -5,3 +5,4 @@
python-subunit
oslo.sphinx
mox>=0.5.3
+mock>=1.0
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/tox.ini b/tox.ini
index 6efac78..e5698d2 100644
--- a/tox.ini
+++ b/tox.ini
@@ -88,13 +88,6 @@
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}
-
[testenv:stress]
sitepackages = True
commands =