Merge "Replace "hardcode" in scenario tests"
diff --git a/README.rst b/README.rst
index 94a5352..af24569 100644
--- a/README.rst
+++ b/README.rst
@@ -117,8 +117,8 @@
Tempest suite.
Alternatively, you can use the run_tests.sh script which will create a venv and
-run the unit tests. There are also the py26, py27, or py33 tox jobs which will
-run the unit tests with the corresponding version of python.
+run the unit tests. There are also the py27 and py34 tox jobs which will run
+the unit tests with the corresponding version of python.
Python 2.6
----------
@@ -131,3 +131,16 @@
on an earlier release with python 2.6 you can easily run Tempest against it
from a remote system running python 2.7. (or deploy a cloud guest in your cloud
that has python 2.7)
+
+Python 3.4
+----------
+
+Starting during the Liberty release development cycle work began on enabling
+Tempest to run under both Python 2.7 and Python 3.4. Tempest strives to fully
+support running with Python 3.4. A gating unit test job was added to also run
+Tempest's unit tests under Python 3.4. This means that the Tempest code at
+least imports under Python 3.4 and things that have unit test coverage will
+work on Python 3.4. However, because large parts of Tempest are self verifying
+there might be uncaught issues running on Python 3.4. So until there is a gating
+job which does a full Tempest run using Python 3.4 there isn't any guarantee
+that running Tempest under Python 3.4 is bug free.
diff --git a/etc/tempest.conf.sample b/etc/tempest.conf.sample
index 3f9e70e..168560a 100644
--- a/etc/tempest.conf.sample
+++ b/etc/tempest.conf.sample
@@ -40,13 +40,15 @@
#log_dir = <None>
# Use syslog for logging. Existing syslog format is DEPRECATED during
-# I, and will change in J to honor RFC5424. (boolean value)
+# I, and changed in J to honor RFC5424. (boolean value)
#use_syslog = false
# (Optional) Enables or disables syslog rfc5424 format for logging. If
# enabled, prefixes the MSG part of the syslog message with APP-NAME
# (RFC5424). The format without the APP-NAME is deprecated in K, and
-# will be removed in L, along with this option. (boolean value)
+# will be removed in M, along with this option. (boolean value)
+# This option is deprecated for removal.
+# Its value may be silently ignored in the future.
#use_syslog_rfc_format = true
# Syslog facility to receive log lines. (string value)
@@ -67,7 +69,7 @@
# Prefix each line of exception output with this format. (string
# value)
-#logging_exception_prefix = %(asctime)s.%(msecs)03d %(process)d TRACE %(name)s %(instance)s
+#logging_exception_prefix = %(asctime)s.%(msecs)03d %(process)d ERROR %(name)s %(instance)s
# List of logger=LEVEL pairs. (list value)
#default_log_levels = amqp=WARN,amqplib=WARN,boto=WARN,qpid=WARN,sqlalchemy=WARN,suds=INFO,oslo.messaging=INFO,iso8601=WARN,requests.packages.urllib3.connectionpool=WARN,urllib3.connectionpool=WARN,websocket=WARN,requests.packages.urllib3.util.retry=WARN,urllib3.util.retry=WARN,keystonemiddleware=WARN,routes.middleware=WARN,stevedore=WARN
@@ -86,6 +88,9 @@
# (string value)
#instance_uuid_format = "[instance: %(uuid)s] "
+# Enables or disables fatal status of deprecations. (boolean value)
+#fatal_deprecations = false
+
[auth]
@@ -571,6 +576,9 @@
# applies to user and project (string value)
#admin_domain_name = <None>
+# ID of the default domain (string value)
+#default_domain_id = default
+
[identity-feature-enabled]
@@ -935,6 +943,10 @@
# Image container format (string value)
#img_container_format = bare
+# Glance image properties. Use for custom images which require them
+# (dict value)
+#img_properties = <None>
+
# AMI image file name (string value)
#ami_img_file = cirros-0.3.1-x86_64-blank.img
@@ -1171,3 +1183,7 @@
# Is the v2 volume API enabled (boolean value)
#api_v2 = true
+
+# Update bootable status of a volume Not implemented on icehouse
+# (boolean value)
+#bootable = false
diff --git a/setup.cfg b/setup.cfg
index 2de9f34..3a14bba 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -15,6 +15,8 @@
Programming Language :: Python
Programming Language :: Python :: 2
Programming Language :: Python :: 2.7
+ Programming Language :: Python :: 3
+ Programming Language :: Python :: 3.4
[entry_points]
console_scripts =
@@ -22,6 +24,7 @@
javelin2 = tempest.cmd.javelin:main
run-tempest-stress = tempest.cmd.run_stress:main
tempest-cleanup = tempest.cmd.cleanup:main
+ tempest-account-generator = tempest.cmd.account_generator:main
oslo.config.opts =
tempest.config = tempest.config:list_opts
diff --git a/tempest/api/compute/servers/test_list_server_filters.py b/tempest/api/compute/servers/test_list_server_filters.py
index 5374af0..3eae693 100644
--- a/tempest/api/compute/servers/test_list_server_filters.py
+++ b/tempest/api/compute/servers/test_list_server_filters.py
@@ -275,8 +275,14 @@
msg = 'fixed_network_name needs to be configured to run this test'
raise self.skipException(msg)
self.s1 = self.client.get_server(self.s1['id'])
- ip = self.s1['addresses'][self.fixed_network_name][0]['addr']
- params = {'ip': ip}
+ for addr_spec in self.s1['addresses'][self.fixed_network_name]:
+ ip = addr_spec['addr']
+ if addr_spec['version'] == 4:
+ params = {'ip': ip}
+ break
+ else:
+ msg = "Skipped until bug 1450859 is resolved"
+ raise self.skipException(msg)
body = self.client.list_servers(params)
servers = body['servers']
@@ -295,8 +301,12 @@
msg = 'fixed_network_name needs to be configured to run this test'
raise self.skipException(msg)
self.s1 = self.client.get_server(self.s1['id'])
- ip = self.s1['addresses'][self.fixed_network_name][0]['addr'][0:-3]
- params = {'ip': ip}
+ addr_spec = self.s1['addresses'][self.fixed_network_name][0]
+ ip = addr_spec['addr'][0:-3]
+ if addr_spec['version'] == 4:
+ params = {'ip': ip}
+ else:
+ params = {'ip6': ip}
body = self.client.list_servers(params)
servers = body['servers']
diff --git a/tempest/api/compute/servers/test_server_actions.py b/tempest/api/compute/servers/test_server_actions.py
index c4cabaa..c2ed0ce 100644
--- a/tempest/api/compute/servers/test_server_actions.py
+++ b/tempest/api/compute/servers/test_server_actions.py
@@ -47,8 +47,6 @@
self.__class__.server_id = self.rebuild_server(self.server_id)
def tearDown(self):
- server = self.client.get_server(self.server_id)
- self.assertEqual(self.image_ref, server['image']['id'])
self.server_check_teardown()
super(ServerActionsTestJSON, self).tearDown()
@@ -110,6 +108,14 @@
# The server should be signaled to reboot gracefully
self._test_reboot_server('SOFT')
+ def _rebuild_server_and_check(self, image_ref):
+ rebuilt_server = self.client.rebuild(self.server_id, image_ref)
+ self.client.wait_for_server_status(self.server_id, 'ACTIVE')
+ msg = ('Server was not rebuilt to the original image. '
+ 'The original image: {0}. The current image: {1}'
+ .format(image_ref, rebuilt_server['image']['id']))
+ self.assertEqual(image_ref, rebuilt_server['image']['id'], msg)
+
@test.idempotent_id('aaa6cdf3-55a7-461a-add9-1c8596b9a07c')
def test_rebuild_server(self):
# The server should be rebuilt using the provided image and data
@@ -129,8 +135,7 @@
# If the server was rebuilt on a different image, restore it to the
# original image once the test ends
if self.image_ref_alt != self.image_ref:
- self.addCleanup(self.client.rebuild,
- (self.server_id, self.image_ref))
+ self.addCleanup(self._rebuild_server_and_check, self.image_ref)
# Verify the properties in the initial response are correct
self.assertEqual(self.server_id, rebuilt_server['id'])
@@ -157,11 +162,15 @@
# image and remain in SHUTOFF state
server = self.client.get_server(self.server_id)
old_image = server['image']['id']
- new_image = self.image_ref_alt \
- if old_image == self.image_ref else self.image_ref
+ new_image = (self.image_ref_alt
+ if old_image == self.image_ref else self.image_ref)
self.client.stop(self.server_id)
self.client.wait_for_server_status(self.server_id, 'SHUTOFF')
rebuilt_server = self.client.rebuild(self.server_id, new_image)
+ # If the server was rebuilt on a different image, restore it to the
+ # original image once the test ends
+ if self.image_ref_alt != self.image_ref:
+ self.addCleanup(self._rebuild_server_and_check, old_image)
# Verify the properties in the initial response are correct
self.assertEqual(self.server_id, rebuilt_server['id'])
@@ -175,10 +184,6 @@
rebuilt_image_id = server['image']['id']
self.assertEqual(new_image, rebuilt_image_id)
- # Restore to the original image (The tearDown will test it again)
- if self.image_ref_alt != self.image_ref:
- self.client.rebuild(self.server_id, old_image)
- self.client.wait_for_server_status(self.server_id, 'SHUTOFF')
self.client.start(self.server_id)
def _test_resize_server_confirm(self, stop=False):
diff --git a/tempest/api/compute/servers/test_servers.py b/tempest/api/compute/servers/test_servers.py
index b333122..31078e3 100644
--- a/tempest/api/compute/servers/test_servers.py
+++ b/tempest/api/compute/servers/test_servers.py
@@ -64,6 +64,7 @@
key_name = data_utils.rand_name('key')
self.keypairs_client.create_keypair(key_name)
+ self.addCleanup(self.keypairs_client.delete_keypair, key_name)
self.keypairs_client.list_keypairs()
server = self.create_test_server(key_name=key_name)
self.client.wait_for_server_status(server['id'], 'ACTIVE')
diff --git a/tempest/api/identity/admin/v3/test_domains.py b/tempest/api/identity/admin/v3/test_domains.py
index 79943bb..b775e91 100644
--- a/tempest/api/identity/admin/v3/test_domains.py
+++ b/tempest/api/identity/admin/v3/test_domains.py
@@ -13,10 +13,13 @@
# License for the specific language governing permissions and limitations
# under the License.
+from tempest.api.identity import base
+from tempest import config
+from tempest import test
+
from tempest_lib.common.utils import data_utils
-from tempest.api.identity import base
-from tempest import test
+CONF = config.CONF
class DomainsTestJSON(base.BaseIdentityV3AdminTest):
@@ -105,3 +108,18 @@
expected_data = {'name': d_name, 'enabled': True}
self.assertIsNone(domain['description'])
self.assertDictContainsSubset(expected_data, domain)
+
+
+class DefaultDomainTestJSON(base.BaseIdentityV3AdminTest):
+
+ @classmethod
+ def resource_setup(cls):
+ cls.domain_id = CONF.identity.default_domain_id
+ super(DefaultDomainTestJSON, cls).resource_setup()
+
+ @test.attr(type='smoke')
+ @test.idempotent_id('17a5de24-e6a0-4e4a-a9ee-d85b6e5612b5')
+ def test_default_domain_exists(self):
+ domain = self.client.get_domain(self.domain_id)
+
+ self.assertTrue(domain['enabled'])
diff --git a/tempest/api/identity/admin/v3/test_domains_negative.py b/tempest/api/identity/admin/v3/test_domains_negative.py
new file mode 100644
index 0000000..e2f3ef5
--- /dev/null
+++ b/tempest/api/identity/admin/v3/test_domains_negative.py
@@ -0,0 +1,38 @@
+# Copyright 2015 Red Hat Inc.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.api.identity import base
+from tempest import test
+
+from tempest_lib.common.utils import data_utils
+from tempest_lib import exceptions as lib_exc
+
+
+class DomainsNegativeTestJSON(base.BaseIdentityV3AdminTest):
+ _interface = 'json'
+
+ @test.attr(type=['negative', 'gate'])
+ @test.idempotent_id('1f3fbff5-4e44-400d-9ca1-d953f05f609b')
+ def test_delete_active_domain(self):
+ d_name = data_utils.rand_name('domain')
+ d_desc = data_utils.rand_name('domain-desc')
+ domain = self.client.create_domain(d_name, description=d_desc)
+ domain_id = domain['id']
+
+ self.addCleanup(self.delete_domain, domain_id)
+
+ # domain need to be disabled before deleting
+ self.assertRaises(lib_exc.Forbidden, self.client.delete_domain,
+ domain_id)
diff --git a/tempest/api/identity/base.py b/tempest/api/identity/base.py
index 5d66b9c..913e807 100644
--- a/tempest/api/identity/base.py
+++ b/tempest/api/identity/base.py
@@ -66,16 +66,9 @@
credentials = ['primary']
- @classmethod
- def setup_credentials(cls):
- super(BaseIdentityV2Test, cls).setup_credentials()
- cls.os = cls.get_client_manager(identity_version='v2')
-
- @classmethod
- def skip_checks(cls):
- super(BaseIdentityV2Test, cls).skip_checks()
- if not CONF.identity_feature_enabled.api_v2:
- raise cls.skipException("Identity api v2 is not enabled")
+ # identity v2 tests should obtain tokens and create accounts via v2
+ # regardless of the configured CONF.identity.auth_version
+ identity_version = 'v2'
@classmethod
def setup_clients(cls):
@@ -94,7 +87,7 @@
class BaseIdentityV2AdminTest(BaseIdentityV2Test):
- credentials = ['admin']
+ credentials = ['primary', 'admin']
@classmethod
def setup_clients(cls):
@@ -117,16 +110,9 @@
credentials = ['primary']
- @classmethod
- def setup_credentials(cls):
- super(BaseIdentityV3Test, cls).setup_credentials()
- cls.os = cls.get_client_manager(identity_version='v3')
-
- @classmethod
- def skip_checks(cls):
- super(BaseIdentityV3Test, cls).skip_checks()
- if not CONF.identity_feature_enabled.api_v3:
- raise cls.skipException("Identity api v3 is not enabled")
+ # identity v3 tests should obtain tokens and create accounts via v3
+ # regardless of the configured CONF.identity.auth_version
+ identity_version = 'v3'
@classmethod
def setup_clients(cls):
@@ -146,7 +132,7 @@
class BaseIdentityV3AdminTest(BaseIdentityV3Test):
- credentials = ['admin']
+ credentials = ['primary', 'admin']
@classmethod
def setup_clients(cls):
@@ -186,6 +172,12 @@
if len(role) > 0:
return role[0]
+ def delete_domain(self, domain_id):
+ # NOTE(mpavlase) It is necessary to disable the domain before deleting
+ # otherwise it raises Forbidden exception
+ self.client.update_domain(domain_id, enabled=False)
+ self.client.delete_domain(domain_id)
+
class DataGenerator(object):
diff --git a/tempest/api/image/base.py b/tempest/api/image/base.py
index acf8272..00959d9 100644
--- a/tempest/api/image/base.py
+++ b/tempest/api/image/base.py
@@ -146,7 +146,7 @@
cls.alt_tenant_id = cls.alt_img_client.tenant_id
def _list_image_ids_as_alt(self):
- image_list = self.alt_img_client.image_list()
+ image_list = self.alt_img_client.list_images()
image_ids = map(lambda x: x['id'], image_list)
return image_ids
diff --git a/tempest/api/image/v1/test_images.py b/tempest/api/image/v1/test_images.py
index ea95059..fc75d95 100644
--- a/tempest/api/image/v1/test_images.py
+++ b/tempest/api/image/v1/test_images.py
@@ -168,14 +168,14 @@
@test.idempotent_id('246178ab-3b33-4212-9a4b-a7fe8261794d')
def test_index_no_params(self):
# Simple test to see all fixture images returned
- images_list = self.client.image_list()
+ images_list = self.client.list_images()
image_list = map(lambda x: x['id'], images_list)
for image_id in self.created_images:
self.assertIn(image_id, image_list)
@test.idempotent_id('f1755589-63d6-4468-b098-589820eb4031')
def test_index_disk_format(self):
- images_list = self.client.image_list(disk_format='ami')
+ images_list = self.client.list_images(disk_format='ami')
for image in images_list:
self.assertEqual(image['disk_format'], 'ami')
result_set = set(map(lambda x: x['id'], images_list))
@@ -184,7 +184,7 @@
@test.idempotent_id('2143655d-96d9-4bec-9188-8674206b4b3b')
def test_index_container_format(self):
- images_list = self.client.image_list(container_format='bare')
+ images_list = self.client.list_images(container_format='bare')
for image in images_list:
self.assertEqual(image['container_format'], 'bare')
result_set = set(map(lambda x: x['id'], images_list))
@@ -193,7 +193,7 @@
@test.idempotent_id('feb32ac6-22bb-4a16-afd8-9454bb714b14')
def test_index_max_size(self):
- images_list = self.client.image_list(size_max=42)
+ images_list = self.client.list_images(size_max=42)
for image in images_list:
self.assertTrue(image['size'] <= 42)
result_set = set(map(lambda x: x['id'], images_list))
@@ -202,7 +202,7 @@
@test.idempotent_id('6ffc16d0-4cbf-4401-95c8-4ac63eac34d8')
def test_index_min_size(self):
- images_list = self.client.image_list(size_min=142)
+ images_list = self.client.list_images(size_min=142)
for image in images_list:
self.assertTrue(image['size'] >= 142)
result_set = set(map(lambda x: x['id'], images_list))
diff --git a/tempest/api/image/v2/test_images.py b/tempest/api/image/v2/test_images.py
index 32f80a2..7e538ec 100644
--- a/tempest/api/image/v2/test_images.py
+++ b/tempest/api/image/v2/test_images.py
@@ -88,7 +88,7 @@
self.client.wait_for_resource_deletion(image_id)
# Verifying deletion
- images = self.client.image_list()
+ images = self.client.list_images()
images_id = [item['id'] for item in images]
self.assertNotIn(image_id, images_id)
@@ -164,7 +164,7 @@
"""
Perform list action with given params and validates result.
"""
- images_list = self.client.image_list(params=params)
+ images_list = self.client.list_images(params=params)
# Validating params of fetched images
for image in images_list:
for key in params:
@@ -174,7 +174,7 @@
@test.idempotent_id('1e341d7a-90a9-494c-b143-2cdf2aeb6aee')
def test_index_no_params(self):
# Simple test to see all fixture images returned
- images_list = self.client.image_list()
+ images_list = self.client.list_images()
image_list = map(lambda x: x['id'], images_list)
for image in self.created_images:
@@ -217,7 +217,7 @@
size = image['size']
params = {"size_min": size - 500, "size_max": size + 500}
- images_list = self.client.image_list(params=params)
+ images_list = self.client.list_images(params=params)
image_size_list = map(lambda x: x['size'], images_list)
for image_size in image_size_list:
@@ -235,7 +235,7 @@
def test_list_images_param_limit(self):
# Test to get images by limit
params = {"limit": 2}
- images_list = self.client.image_list(params=params)
+ images_list = self.client.list_images(params=params)
self.assertEqual(len(images_list), params['limit'],
"Failed to get images by limit")
diff --git a/tempest/api/object_storage/base.py b/tempest/api/object_storage/base.py
index 0c7fa6b..41a7d65 100644
--- a/tempest/api/object_storage/base.py
+++ b/tempest/api/object_storage/base.py
@@ -24,6 +24,8 @@
class BaseObjectTest(tempest.test.BaseTestCase):
+ credentials = [['operator', CONF.object_storage.operator_role]]
+
@classmethod
def skip_checks(cls):
super(BaseObjectTest, cls).skip_checks()
@@ -35,8 +37,9 @@
def setup_credentials(cls):
cls.set_network_resources()
super(BaseObjectTest, cls).setup_credentials()
- operator_role = CONF.object_storage.operator_role
- cls.os = cls.get_client_manager(roles=[operator_role])
+ # credentials may be overwritten by children classes
+ if hasattr(cls, 'os_roles_operator'):
+ cls.os = cls.os_roles_operator
@classmethod
def setup_clients(cls):
diff --git a/tempest/api/object_storage/test_account_quotas.py b/tempest/api/object_storage/test_account_quotas.py
index 04bfee4..bbdf367 100644
--- a/tempest/api/object_storage/test_account_quotas.py
+++ b/tempest/api/object_storage/test_account_quotas.py
@@ -23,12 +23,14 @@
class AccountQuotasTest(base.BaseObjectTest):
+ credentials = [['operator', CONF.object_storage.operator_role],
+ ['reseller', CONF.object_storage.reseller_admin_role]]
+
@classmethod
def setup_credentials(cls):
super(AccountQuotasTest, cls).setup_credentials()
- reseller_admin_role = CONF.object_storage.reseller_admin_role
- cls.os_reselleradmin = cls.get_client_manager(
- roles=[reseller_admin_role])
+ cls.os = cls.os_roles_operator
+ cls.os_reselleradmin = cls.os_roles_reseller
@classmethod
def resource_setup(cls):
diff --git a/tempest/api/object_storage/test_account_quotas_negative.py b/tempest/api/object_storage/test_account_quotas_negative.py
index a11b407..e945e1e 100644
--- a/tempest/api/object_storage/test_account_quotas_negative.py
+++ b/tempest/api/object_storage/test_account_quotas_negative.py
@@ -25,12 +25,14 @@
class AccountQuotasNegativeTest(base.BaseObjectTest):
+ credentials = [['operator', CONF.object_storage.operator_role],
+ ['reseller', CONF.object_storage.reseller_admin_role]]
+
@classmethod
def setup_credentials(cls):
super(AccountQuotasNegativeTest, cls).setup_credentials()
- reseller_admin_role = CONF.object_storage.reseller_admin_role
- cls.os_reselleradmin = cls.get_client_manager(
- roles=[reseller_admin_role])
+ cls.os = cls.os_roles_operator
+ cls.os_reselleradmin = cls.os_roles_reseller
@classmethod
def resource_setup(cls):
diff --git a/tempest/api/object_storage/test_account_services.py b/tempest/api/object_storage/test_account_services.py
index c28a3e0..ac41148 100644
--- a/tempest/api/object_storage/test_account_services.py
+++ b/tempest/api/object_storage/test_account_services.py
@@ -29,13 +29,15 @@
class AccountTest(base.BaseObjectTest):
+ credentials = [['operator', CONF.object_storage.operator_role],
+ ['operator_alt', CONF.object_storage.operator_role]]
containers = []
@classmethod
def setup_credentials(cls):
super(AccountTest, cls).setup_credentials()
- cls.os_operator = cls.get_client_manager(
- roles=[CONF.object_storage.operator_role], force_new=True)
+ cls.os = cls.os_roles_operator
+ cls.os_operator = cls.os_roles_operator_alt
@classmethod
def resource_setup(cls):
diff --git a/tempest/api/object_storage/test_account_services_negative.py b/tempest/api/object_storage/test_account_services_negative.py
index dfc5dfa..998c2bd 100644
--- a/tempest/api/object_storage/test_account_services_negative.py
+++ b/tempest/api/object_storage/test_account_services_negative.py
@@ -23,11 +23,14 @@
class AccountNegativeTest(base.BaseObjectTest):
+ credentials = [['operator', CONF.object_storage.operator_role],
+ ['operator_alt', CONF.object_storage.operator_role]]
+
@classmethod
def setup_credentials(cls):
super(AccountNegativeTest, cls).setup_credentials()
- cls.os_operator = cls.get_client_manager(
- roles=[CONF.object_storage.operator_role], force_new=True)
+ cls.os = cls.os_roles_operator
+ cls.os_operator = cls.os_roles_operator_alt
@test.attr(type=['negative'])
@test.idempotent_id('070e6aca-6152-4867-868d-1118d68fb38c')
diff --git a/tempest/api/object_storage/test_container_acl.py b/tempest/api/object_storage/test_container_acl.py
index 25dac6b..4df813d 100644
--- a/tempest/api/object_storage/test_container_acl.py
+++ b/tempest/api/object_storage/test_container_acl.py
@@ -24,11 +24,14 @@
class ObjectTestACLs(base.BaseObjectTest):
+ credentials = [['operator', CONF.object_storage.operator_role],
+ ['operator_alt', CONF.object_storage.operator_role]]
+
@classmethod
def setup_credentials(cls):
super(ObjectTestACLs, cls).setup_credentials()
- cls.os_operator = cls.get_client_manager(
- roles=[CONF.object_storage.operator_role], force_new=True)
+ cls.os = cls.os_roles_operator
+ cls.os_operator = cls.os_roles_operator_alt
@classmethod
def resource_setup(cls):
diff --git a/tempest/api/object_storage/test_container_acl_negative.py b/tempest/api/object_storage/test_container_acl_negative.py
index 31c301a..1c42e97 100644
--- a/tempest/api/object_storage/test_container_acl_negative.py
+++ b/tempest/api/object_storage/test_container_acl_negative.py
@@ -24,11 +24,14 @@
class ObjectACLsNegativeTest(base.BaseObjectTest):
+ credentials = [['operator', CONF.object_storage.operator_role],
+ ['operator_alt', CONF.object_storage.operator_role]]
+
@classmethod
def setup_credentials(cls):
super(ObjectACLsNegativeTest, cls).setup_credentials()
- cls.os_operator = cls.get_client_manager(
- roles=[CONF.object_storage.operator_role], force_new=True)
+ cls.os = cls.os_roles_operator
+ cls.os_operator = cls.os_roles_operator_alt
@classmethod
def resource_setup(cls):
diff --git a/tempest/api/object_storage/test_container_sync.py b/tempest/api/object_storage/test_container_sync.py
index 4c0723d..06e700b 100644
--- a/tempest/api/object_storage/test_container_sync.py
+++ b/tempest/api/object_storage/test_container_sync.py
@@ -37,11 +37,14 @@
class ContainerSyncTest(base.BaseObjectTest):
clients = {}
+ credentials = [['operator', CONF.object_storage.operator_role],
+ ['operator_alt', CONF.object_storage.operator_role]]
+
@classmethod
def setup_credentials(cls):
super(ContainerSyncTest, cls).setup_credentials()
- cls.os_alt = cls.get_client_manager(
- roles=[CONF.object_storage.operator_role], force_new=True)
+ cls.os = cls.os_roles_operator
+ cls.os_alt = cls.os_roles_operator_alt
@classmethod
def setup_clients(cls):
diff --git a/tempest/api/object_storage/test_object_services.py b/tempest/api/object_storage/test_object_services.py
index b02f178..627895e 100644
--- a/tempest/api/object_storage/test_object_services.py
+++ b/tempest/api/object_storage/test_object_services.py
@@ -989,11 +989,14 @@
class PublicObjectTest(base.BaseObjectTest):
+ credentials = [['operator', CONF.object_storage.operator_role],
+ ['operator_alt', CONF.object_storage.operator_role]]
+
@classmethod
def setup_credentials(cls):
super(PublicObjectTest, cls).setup_credentials()
- cls.os_alt = cls.get_client_manager(
- roles=[CONF.object_storage.operator_role], force_new=True)
+ cls.os = cls.os_roles_operator
+ cls.os_alt = cls.os_roles_operator_alt
@classmethod
def setup_clients(cls):
diff --git a/tempest/api/orchestration/stacks/test_swift_resources.py b/tempest/api/orchestration/stacks/test_swift_resources.py
index d4fd3f9..dadabfa 100644
--- a/tempest/api/orchestration/stacks/test_swift_resources.py
+++ b/tempest/api/orchestration/stacks/test_swift_resources.py
@@ -31,6 +31,14 @@
raise cls.skipException("Swift support is required")
@classmethod
+ def setup_credentials(cls):
+ super(SwiftResourcesTestJSON, cls).setup_credentials()
+ stack_owner_role = CONF.orchestration.stack_owner_role
+ operator_role = CONF.object_storage.operator_role
+ cls.os = cls.get_client_manager(
+ roles=[stack_owner_role, operator_role])
+
+ @classmethod
def setup_clients(cls):
super(SwiftResourcesTestJSON, cls).setup_clients()
cls.account_client = cls.os.account_client
diff --git a/tempest/api/volume/test_volumes_actions.py b/tempest/api/volume/test_volumes_actions.py
index f571f2d..375d34a 100644
--- a/tempest/api/volume/test_volumes_actions.py
+++ b/tempest/api/volume/test_volumes_actions.py
@@ -18,6 +18,7 @@
from tempest.api.volume import base
from tempest import config
from tempest import test
+import testtools
CONF = config.CONF
@@ -69,6 +70,18 @@
self.client.detach_volume(self.volume['id'])
self.client.wait_for_volume_status(self.volume['id'], 'available')
+ @test.idempotent_id('63e21b4c-0a0c-41f6-bfc3-7c2816815599')
+ @testtools.skipUnless(CONF.volume_feature_enabled.bootable,
+ 'Update bootable status of a volume is not enabled.')
+ def test_volume_bootable(self):
+ # Verify that a volume bootable flag is retrieved
+ for bool_bootable in [True, False]:
+ self.client.set_bootable_volume(self.volume['id'], bool_bootable)
+ fetched_volume = self.client.show_volume(self.volume['id'])
+ # Get Volume information
+ bool_flag = self._is_true(fetched_volume['bootable'])
+ self.assertEqual(bool_bootable, bool_flag)
+
@test.idempotent_id('9516a2c8-9135-488c-8dd6-5677a7e5f371')
@test.stresstest(class_setup_per='process')
@test.services('compute')
diff --git a/tempest/api_schema/response/compute/baremetal_nodes.py b/tempest/api_schema/response/compute/v2_1/baremetal_nodes.py
similarity index 100%
rename from tempest/api_schema/response/compute/baremetal_nodes.py
rename to tempest/api_schema/response/compute/v2_1/baremetal_nodes.py
diff --git a/tempest/api_schema/response/compute/v2_1/flavors.py b/tempest/api_schema/response/compute/v2_1/flavors.py
index 725d17a..26760ac 100644
--- a/tempest/api_schema/response/compute/v2_1/flavors.py
+++ b/tempest/api_schema/response/compute/v2_1/flavors.py
@@ -12,7 +12,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest.api_schema.response.compute import parameter_types
+from tempest.api_schema.response.compute.v2_1 import parameter_types
list_flavors = {
'status_code': [200],
diff --git a/tempest/api_schema/response/compute/flavors_access.py b/tempest/api_schema/response/compute/v2_1/flavors_access.py
similarity index 100%
rename from tempest/api_schema/response/compute/flavors_access.py
rename to tempest/api_schema/response/compute/v2_1/flavors_access.py
diff --git a/tempest/api_schema/response/compute/flavors_extra_specs.py b/tempest/api_schema/response/compute/v2_1/flavors_extra_specs.py
similarity index 94%
rename from tempest/api_schema/response/compute/flavors_extra_specs.py
rename to tempest/api_schema/response/compute/v2_1/flavors_extra_specs.py
index 4003d36..faa25d0 100644
--- a/tempest/api_schema/response/compute/flavors_extra_specs.py
+++ b/tempest/api_schema/response/compute/v2_1/flavors_extra_specs.py
@@ -12,7 +12,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-flavor_extra_specs = {
+set_get_flavor_extra_specs = {
'status_code': [200],
'response_body': {
'type': 'object',
@@ -28,7 +28,7 @@
}
}
-flavor_extra_specs_key = {
+set_get_flavor_extra_specs_key = {
'status_code': [200],
'response_body': {
'type': 'object',
diff --git a/tempest/api_schema/response/compute/v2_1/floating_ips.py b/tempest/api_schema/response/compute/v2_1/floating_ips.py
index 7369bec..ad1c531 100644
--- a/tempest/api_schema/response/compute/v2_1/floating_ips.py
+++ b/tempest/api_schema/response/compute/v2_1/floating_ips.py
@@ -48,7 +48,7 @@
}
}
-floating_ip = {
+create_get_floating_ip = {
'status_code': [200],
'response_body': {
'type': 'object',
@@ -59,7 +59,7 @@
}
}
-floating_ip_pools = {
+list_floating_ip_pools = {
'status_code': [200],
'response_body': {
'type': 'object',
diff --git a/tempest/api_schema/response/compute/v2_1/images.py b/tempest/api_schema/response/compute/v2_1/images.py
index 3c0b80e..e6f8db6 100644
--- a/tempest/api_schema/response/compute/v2_1/images.py
+++ b/tempest/api_schema/response/compute/v2_1/images.py
@@ -14,7 +14,7 @@
import copy
-from tempest.api_schema.response.compute import parameter_types
+from tempest.api_schema.response.compute.v2_1 import parameter_types
image_links = copy.deepcopy(parameter_types.links)
image_links['items']['properties'].update({'type': {'type': 'string'}})
diff --git a/tempest/api_schema/response/compute/v2_1/interfaces.py b/tempest/api_schema/response/compute/v2_1/interfaces.py
index 4de3309..033f816 100644
--- a/tempest/api_schema/response/compute/v2_1/interfaces.py
+++ b/tempest/api_schema/response/compute/v2_1/interfaces.py
@@ -12,7 +12,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest.api_schema.response.compute import parameter_types
+from tempest.api_schema.response.compute.v2_1 import parameter_types
interface_common_info = {
'type': 'object',
diff --git a/tempest/api_schema/response/compute/migrations.py b/tempest/api_schema/response/compute/v2_1/migrations.py
similarity index 100%
rename from tempest/api_schema/response/compute/migrations.py
rename to tempest/api_schema/response/compute/v2_1/migrations.py
diff --git a/tempest/api_schema/response/compute/parameter_types.py b/tempest/api_schema/response/compute/v2_1/parameter_types.py
similarity index 100%
rename from tempest/api_schema/response/compute/parameter_types.py
rename to tempest/api_schema/response/compute/v2_1/parameter_types.py
diff --git a/tempest/api_schema/response/compute/v2_1/servers.py b/tempest/api_schema/response/compute/v2_1/servers.py
index 726f9b1..875f607 100644
--- a/tempest/api_schema/response/compute/v2_1/servers.py
+++ b/tempest/api_schema/response/compute/v2_1/servers.py
@@ -14,7 +14,7 @@
import copy
-from tempest.api_schema.response.compute import parameter_types
+from tempest.api_schema.response.compute.v2_1 import parameter_types
create_server = {
'status_code': [202],
diff --git a/tempest/api_schema/response/compute/services.py b/tempest/api_schema/response/compute/v2_1/services.py
similarity index 100%
rename from tempest/api_schema/response/compute/services.py
rename to tempest/api_schema/response/compute/v2_1/services.py
diff --git a/tempest/api_schema/response/compute/v2_1/tenant_usages.py b/tempest/api_schema/response/compute/v2_1/tenant_usages.py
index 0b824a1..d51ef12 100644
--- a/tempest/api_schema/response/compute/v2_1/tenant_usages.py
+++ b/tempest/api_schema/response/compute/v2_1/tenant_usages.py
@@ -66,7 +66,7 @@
'total_hours', 'total_local_gb_usage',
'total_memory_mb_usage', 'total_vcpus_usage']
-list_tenant = {
+list_tenant_usage = {
'status_code': [200],
'response_body': {
'type': 'object',
@@ -80,7 +80,7 @@
}
}
-get_tenant = {
+get_tenant_usage = {
'status_code': [200],
'response_body': {
'type': 'object',
diff --git a/tempest/api_schema/response/compute/version.py b/tempest/api_schema/response/compute/v2_1/version.py
similarity index 100%
rename from tempest/api_schema/response/compute/version.py
rename to tempest/api_schema/response/compute/v2_1/version.py
diff --git a/tempest/clients.py b/tempest/clients.py
index a0c9471..9f6a9bb 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -22,6 +22,7 @@
from tempest.common import cred_provider
from tempest.common import negative_rest_client
from tempest import config
+from tempest import exceptions
from tempest import manager
from tempest.services.baremetal.v1.json.baremetal_client import \
BaremetalClientJSON
@@ -343,11 +344,22 @@
self.credentials_client = CredentialsClientJSON(self.auth_provider,
**params)
# Token clients do not use the catalog. They only need default_params.
- self.token_client = TokenClientJSON(CONF.identity.uri,
- **self.default_params)
+ # They read auth_url, so they should only be set if the corresponding
+ # API version is marked as enabled
+ if CONF.identity_feature_enabled.api_v2:
+ if CONF.identity.uri:
+ self.token_client = TokenClientJSON(
+ CONF.identity.uri, **self.default_params)
+ else:
+ msg = 'Identity v2 API enabled, but no identity.uri set'
+ raise exceptions.InvalidConfiguration(msg)
if CONF.identity_feature_enabled.api_v3:
- self.token_v3_client = V3TokenClientJSON(CONF.identity.uri_v3,
- **self.default_params)
+ if CONF.identity.uri_v3:
+ self.token_v3_client = V3TokenClientJSON(
+ CONF.identity.uri_v3, **self.default_params)
+ else:
+ msg = 'Identity v3 API enabled, but no identity.uri_v3 set'
+ raise exceptions.InvalidConfiguration(msg)
def _set_volume_clients(self):
params = {
diff --git a/tempest/cmd/account_generator.py b/tempest/cmd/account_generator.py
new file mode 100755
index 0000000..0a48b8d
--- /dev/null
+++ b/tempest/cmd/account_generator.py
@@ -0,0 +1,266 @@
+#!/usr/bin/env python
+
+# Copyright 2015 Mirantis, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import argparse
+import os
+
+from oslo_log import log as logging
+import yaml
+
+from tempest import config
+from tempest import exceptions
+from tempest.services.identity.v2.json import identity_client
+import tempest_lib.auth
+from tempest_lib.common.utils import data_utils
+import tempest_lib.exceptions
+
+LOG = None
+CONF = config.CONF
+
+
+def setup_logging():
+ global LOG
+ logging.setup(CONF, __name__)
+ LOG = logging.getLogger(__name__)
+
+
+def keystone_admin(opts):
+ _creds = tempest_lib.auth.KeystoneV2Credentials(
+ username=opts.os_username,
+ password=opts.os_password,
+ tenant_name=opts.os_tenant_name)
+ auth_params = {
+ 'disable_ssl_certificate_validation':
+ CONF.identity.disable_ssl_certificate_validation,
+ 'ca_certs': CONF.identity.ca_certificates_file,
+ 'trace_requests': CONF.debug.trace_requests
+ }
+ _auth = tempest_lib.auth.KeystoneV2AuthProvider(
+ _creds, CONF.identity.uri, **auth_params)
+ params = {
+ 'disable_ssl_certificate_validation':
+ CONF.identity.disable_ssl_certificate_validation,
+ 'ca_certs': CONF.identity.ca_certificates_file,
+ 'trace_requests': CONF.debug.trace_requests,
+ 'build_interval': CONF.compute.build_interval,
+ 'build_timeout': CONF.compute.build_timeout
+ }
+ return identity_client.IdentityClientJSON(
+ _auth,
+ CONF.identity.catalog_type,
+ CONF.identity.region,
+ endpoint_type='adminURL',
+ **params
+ )
+
+
+def create_resources(opts, resources):
+ admin = keystone_admin(opts)
+ roles = admin.list_roles()
+ for u in resources['users']:
+ u['role_ids'] = []
+ for r in u.get('roles', ()):
+ try:
+ role = filter(lambda r_: r_['name'] == r, roles)[0]
+ u['role_ids'] += [role['id']]
+ except IndexError:
+ raise exceptions.TempestException(
+ "Role: %s - doesn't exist" % r
+ )
+ existing = [x['name'] for x in admin.list_tenants()]
+ for tenant in resources['tenants']:
+ if tenant not in existing:
+ admin.create_tenant(tenant)
+ else:
+ LOG.warn("Tenant '%s' already exists in this environment" % tenant)
+ LOG.info('Tenants created')
+ for u in resources['users']:
+ try:
+ tenant = admin.get_tenant_by_name(u['tenant'])
+ except tempest_lib.exceptions.NotFound:
+ LOG.error("Tenant: %s - not found" % u['tenant'])
+ continue
+ while True:
+ try:
+ admin.get_user_by_username(tenant['id'], u['name'])
+ except tempest_lib.exceptions.NotFound:
+ admin.create_user(
+ u['name'], u['pass'], tenant['id'],
+ "%s@%s" % (u['name'], tenant['id']),
+ enabled=True)
+ break
+ else:
+ LOG.warn("User '%s' already exists in this environment. "
+ "New name generated" % u['name'])
+ u['name'] = random_user_name(opts.tag, u['prefix'])
+
+ LOG.info('Users created')
+ for u in resources['users']:
+ try:
+ tenant = admin.get_tenant_by_name(u['tenant'])
+ except tempest_lib.exceptions.NotFound:
+ LOG.error("Tenant: %s - not found" % u['tenant'])
+ continue
+ try:
+ user = admin.get_user_by_username(tenant['id'],
+ u['name'])
+ except tempest_lib.exceptions.NotFound:
+ LOG.error("User: %s - not found" % u['user'])
+ continue
+ for r in u['role_ids']:
+ try:
+ admin.assign_user_role(tenant['id'], user['id'], r)
+ except tempest_lib.exceptions.Conflict:
+ # don't care if it's already assigned
+ pass
+ LOG.info('Roles assigned')
+ LOG.info('Resources deployed successfully!')
+
+
+def random_user_name(tag, prefix):
+ if tag:
+ return data_utils.rand_name('-'.join((tag, prefix)))
+ else:
+ return data_utils.rand_name(prefix)
+
+
+def generate_resources(opts):
+ spec = [{'number': 1,
+ 'prefix': 'primary',
+ 'roles': (CONF.auth.tempest_roles +
+ [CONF.object_storage.operator_role])},
+ {'number': 1,
+ 'prefix': 'alt',
+ 'roles': (CONF.auth.tempest_roles +
+ [CONF.object_storage.operator_role])},
+ {'number': 1,
+ 'prefix': 'swift_admin',
+ 'roles': (CONF.auth.tempest_roles +
+ [CONF.object_storage.operator_role,
+ CONF.object_storage.reseller_admin_role])},
+ {'number': 1,
+ 'prefix': 'stack_owner',
+ 'roles': (CONF.auth.tempest_roles +
+ [CONF.orchestration.stack_owner_role])},
+ ]
+ if opts.admin:
+ spec.append({
+ 'number': 1,
+ 'prefix': 'admin',
+ 'roles': (CONF.auth.tempest_roles +
+ [CONF.identity.admin_role])
+ })
+ resources = {'tenants': [],
+ 'users': []}
+ for count in range(opts.concurrency):
+ for user_group in spec:
+ users = [random_user_name(opts.tag, user_group['prefix'])
+ for _ in range(user_group['number'])]
+ for user in users:
+ tenant = '-'.join((user, 'tenant'))
+ resources['tenants'].append(tenant)
+ resources['users'].append({
+ 'tenant': tenant,
+ 'name': user,
+ 'pass': data_utils.rand_name(),
+ 'prefix': user_group['prefix'],
+ 'roles': user_group['roles']
+ })
+ return resources
+
+
+def dump_accounts(opts, resources):
+ accounts = []
+ for user in resources['users']:
+ accounts.append({
+ 'username': user['name'],
+ 'tenant_name': user['tenant'],
+ 'password': user['pass'],
+ 'roles': user['roles']
+ })
+ if os.path.exists(opts.accounts):
+ os.rename(opts.accounts, '.'.join((opts.accounts, 'bak')))
+ with open(opts.accounts, 'w') as f:
+ yaml.dump(accounts, f, default_flow_style=False)
+ LOG.info('%s generated successfully!' % opts.accounts)
+
+
+def get_options():
+ usage_string = ('account_generator [-h] <ARG> ...\n\n'
+ 'To see help on specific argument, do:\n'
+ 'account_generator <ARG> -h')
+ parser = argparse.ArgumentParser(
+ description='Create accounts.yaml file for concurrent test runs. '
+ 'One primary user, one alt user, '
+ 'one swift admin, one stack owner '
+ 'and one admin (optionally) will be created '
+ 'for each concurrent thread.',
+ formatter_class=argparse.ArgumentDefaultsHelpFormatter,
+ usage=usage_string
+ )
+
+ parser.add_argument('-c', '--config-file',
+ metavar='/etc/tempest.conf',
+ help='path to tempest config file')
+ parser.add_argument('--os-username',
+ metavar='<auth-user-name>',
+ default=os.environ.get('OS_USERNAME'),
+ help='User should have permitions '
+ 'to create new user accounts and '
+ 'tenants. Defaults to env[OS_USERNAME].')
+ parser.add_argument('--os-password',
+ metavar='<auth-password>',
+ default=os.environ.get('OS_PASSWORD'),
+ help='Defaults to env[OS_PASSWORD].')
+ parser.add_argument('--os-tenant-name',
+ metavar='<auth-tenant-name>',
+ default=os.environ.get('OS_TENANT_NAME'),
+ help='Defaults to env[OS_TENANT_NAME].')
+ parser.add_argument('--tag',
+ default='',
+ required=False,
+ dest='tag',
+ help='Resources tag')
+ parser.add_argument('-r', '--concurrency',
+ default=1,
+ type=int,
+ required=True,
+ dest='concurrency',
+ help='Concurrency count')
+ parser.add_argument('--with-admin',
+ action='store_true',
+ dest='admin',
+ help='Create admin in every tenant')
+ parser.add_argument('accounts',
+ metavar='accounts_file.yaml',
+ help='Output accounts yaml file')
+
+ opts = parser.parse_args()
+ if opts.config_file:
+ config.CONF.set_config_path(opts.config_file)
+ return opts
+
+
+def main(opts=None):
+ if not opts:
+ opts = get_options()
+ setup_logging()
+ resources = generate_resources(opts)
+ create_resources(opts, resources)
+ dump_accounts(opts, resources)
+
+if __name__ == "__main__":
+ main()
diff --git a/tempest/cmd/cleanup_service.py b/tempest/cmd/cleanup_service.py
index 1ad12eb..eb6f143 100644
--- a/tempest/cmd/cleanup_service.py
+++ b/tempest/cmd/cleanup_service.py
@@ -343,6 +343,44 @@
self.data['volumes'] = vols
+class VolumeQuotaService(BaseService):
+ def __init__(self, manager, **kwargs):
+ super(VolumeQuotaService, self).__init__(kwargs)
+ self.client = manager.volume_quotas_client
+
+ def delete(self):
+ client = self.client
+ try:
+ client.delete_quota_set(self.tenant_id)
+ except Exception as e:
+ LOG.exception("Delete Volume Quotas exception: %s" % e)
+ pass
+
+ def dry_run(self):
+ quotas = self.client.show_quota_usage(self.tenant_id)
+ self.data['volume_quotas'] = quotas
+
+
+class NovaQuotaService(BaseService):
+ def __init__(self, manager, **kwargs):
+ super(NovaQuotaService, self).__init__(kwargs)
+ self.client = manager.quotas_client
+ self.limits_client = manager.limits_client
+
+ def delete(self):
+ client = self.client
+ try:
+ client.delete_quota_set(self.tenant_id)
+ except Exception as e:
+ LOG.exception("Delete Quotas exception: %s" % e)
+ pass
+
+ def dry_run(self):
+ client = self.limits_client
+ quotas = client.get_absolute_limits()
+ self.data['compute_quotas'] = quotas
+
+
# Begin network service classes
class NetworkService(BaseService):
def __init__(self, manager, **kwargs):
@@ -667,7 +705,7 @@
self.data['pools'] = pools
-class NetworMeteringLabelRuleService(NetworkService):
+class NetworkMeteringLabelRuleService(NetworkService):
def list(self):
client = self.client
@@ -692,7 +730,7 @@
self.data['rules'] = rules
-class NetworMeteringLabelService(NetworkService):
+class NetworkMeteringLabelService(NetworkService):
def list(self):
client = self.client
@@ -1052,6 +1090,7 @@
tenant_services.append(ServerGroupService)
if not IS_NEUTRON:
tenant_services.append(FloatingIpService)
+ tenant_services.append(NovaQuotaService)
if IS_HEAT:
tenant_services.append(StackService)
if IS_NEUTRON:
@@ -1068,8 +1107,8 @@
tenant_services.append(NetworkVipService)
tenant_services.append(NetworkPoolService)
if test.is_extension_enabled('metering', 'network'):
- tenant_services.append(NetworMeteringLabelRuleService)
- tenant_services.append(NetworMeteringLabelService)
+ tenant_services.append(NetworkMeteringLabelRuleService)
+ tenant_services.append(NetworkMeteringLabelService)
tenant_services.append(NetworkRouterService)
tenant_services.append(NetworkFloatingIpService)
tenant_services.append(NetworkPortService)
@@ -1078,6 +1117,7 @@
if IS_CINDER:
tenant_services.append(SnapshotService)
tenant_services.append(VolumeService)
+ tenant_services.append(VolumeQuotaService)
return tenant_services
diff --git a/tempest/cmd/javelin.py b/tempest/cmd/javelin.py
index 4e2af76..d3426c6 100755
--- a/tempest/cmd/javelin.py
+++ b/tempest/cmd/javelin.py
@@ -617,7 +617,7 @@
def _get_image_by_name(client, name):
- body = client.images.image_list()
+ body = client.images.list_images()
for image in body:
if name == image['name']:
return image
diff --git a/tempest/config.py b/tempest/config.py
index 3f3e7e7..e365b2b 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -142,6 +142,9 @@
cfg.StrOpt('admin_domain_name',
help="Admin domain name for authentication (Keystone V3)."
"The same domain applies to user and project"),
+ cfg.StrOpt('default_domain_id',
+ default='default',
+ help="ID of the default domain"),
]
identity_feature_group = cfg.OptGroup(name='identity-feature-enabled',
@@ -657,6 +660,10 @@
cfg.BoolOpt('api_v2',
default=True,
help="Is the v2 volume API enabled"),
+ cfg.BoolOpt('bootable',
+ default=False,
+ help='Update bootable status of a volume '
+ 'Not implemented on icehouse ')
]
@@ -943,6 +950,8 @@
cfg.StrOpt('img_container_format',
default='bare',
help='Image container format'),
+ cfg.DictOpt('img_properties', help='Glance image properties. '
+ 'Use for custom images which require them'),
cfg.StrOpt('ami_img_file',
default='cirros-0.3.1-x86_64-blank.img',
help='AMI image file name'),
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
index 849da01..6ccd4d9 100644
--- a/tempest/scenario/manager.py
+++ b/tempest/scenario/manager.py
@@ -20,6 +20,7 @@
from oslo_log import log
import six
from tempest_lib.common.utils import data_utils
+from tempest_lib.common.utils import misc as misc_utils
from tempest_lib import exceptions as lib_exc
from tempest.common import fixed_network
@@ -307,8 +308,13 @@
password=password)
try:
linux_client.validate_authentication()
- except Exception:
- LOG.exception('Initializing SSH connection to %s failed' % ip)
+ except Exception as e:
+ message = ('Initializing SSH connection to %(ip)s failed. '
+ 'Error: %(error)s' % {'ip': ip, 'error': e})
+ caller = misc_utils.find_test_caller()
+ if caller:
+ message = '(%s) %s' % (caller, message)
+ LOG.exception(message)
# If we don't explicitly set for which servers we want to
# log the console output then all the servers will be logged.
# See the definition of _log_console_output()
@@ -317,7 +323,8 @@
return linux_client
- def _image_create(self, name, fmt, path, properties=None):
+ def _image_create(self, name, fmt, path,
+ disk_format=None, properties=None):
if properties is None:
properties = {}
name = data_utils.rand_name('%s-' % name)
@@ -326,10 +333,10 @@
params = {
'name': name,
'container_format': fmt,
- 'disk_format': fmt,
+ 'disk_format': disk_format or fmt,
'is_public': 'False',
}
- params.update(properties)
+ params['properties'] = properties
image = self.image_client.create_image(**params)
self.addCleanup(self.image_client.delete_image, image['id'])
self.assertEqual("queued", image['status'])
@@ -343,23 +350,22 @@
ami_img_path = CONF.scenario.img_dir + "/" + CONF.scenario.ami_img_file
img_container_format = CONF.scenario.img_container_format
img_disk_format = CONF.scenario.img_disk_format
+ img_properties = CONF.scenario.img_properties
LOG.debug("paths: img: %s, container_fomat: %s, disk_format: %s, "
- "ami: %s, ari: %s, aki: %s" %
+ "properties: %s, ami: %s, ari: %s, aki: %s" %
(img_path, img_container_format, img_disk_format,
- ami_img_path, ari_img_path, aki_img_path))
+ img_properties, ami_img_path, ari_img_path, aki_img_path))
try:
self.image = self._image_create('scenario-img',
img_container_format,
img_path,
- properties={'disk_format':
- img_disk_format})
+ disk_format=img_disk_format,
+ properties=img_properties)
except IOError:
LOG.debug("A qcow2 image was not found. Try to get a uec image.")
kernel = self._image_create('scenario-aki', 'aki', aki_img_path)
ramdisk = self._image_create('scenario-ari', 'ari', ari_img_path)
- properties = {
- 'properties': {'kernel_id': kernel, 'ramdisk_id': ramdisk}
- }
+ properties = {'kernel_id': kernel, 'ramdisk_id': ramdisk}
self.image = self._image_create('scenario-ami', 'ami',
path=ami_img_path,
properties=properties)
diff --git a/tempest/scenario/test_server_basic_ops.py b/tempest/scenario/test_server_basic_ops.py
index b795775..d9918f3 100644
--- a/tempest/scenario/test_server_basic_ops.py
+++ b/tempest/scenario/test_server_basic_ops.py
@@ -48,7 +48,7 @@
self.image_ref = CONF.compute.image_ref
if not hasattr(self, 'flavor_ref'):
self.flavor_ref = CONF.compute.flavor_ref
- self.image_utils = test_utils.ImageUtils()
+ self.image_utils = test_utils.ImageUtils(self.manager)
if not self.image_utils.is_flavor_enough(self.flavor_ref,
self.image_ref):
raise self.skipException(
diff --git a/tempest/scenario/test_volume_boot_pattern.py b/tempest/scenario/test_volume_boot_pattern.py
index 177697b..1731c48 100644
--- a/tempest/scenario/test_volume_boot_pattern.py
+++ b/tempest/scenario/test_volume_boot_pattern.py
@@ -41,8 +41,6 @@
super(TestVolumeBootPattern, cls).skip_checks()
if not CONF.volume_feature_enabled.snapshot:
raise cls.skipException("Cinder volume snapshots are disabled")
- if CONF.volume.storage_protocol == 'ceph':
- raise cls.skipException('Skip until bug 1439371 is fixed.')
def _create_volume_from_image(self):
img_uuid = CONF.compute.image_ref
diff --git a/tempest/scenario/utils.py b/tempest/scenario/utils.py
index e5613d6..b4f2466 100644
--- a/tempest/scenario/utils.py
+++ b/tempest/scenario/utils.py
@@ -30,28 +30,16 @@
CONF = config.CONF
-@misc.singleton
class ImageUtils(object):
default_ssh_user = 'root'
- def __init__(self):
+ def __init__(self, os):
# Load configuration items
self.ssh_users = json.loads(CONF.input_scenario.ssh_user_regex)
self.non_ssh_image_pattern = \
CONF.input_scenario.non_ssh_image_regex
# Setup clients
- network_resources = {
- 'network': False,
- 'router': False,
- 'subnet': False,
- 'dhcp': False,
- }
- self.isolated_creds = credentials.get_isolated_credentials(
- name='ScenarioImageUtils',
- identity_version=CONF.identity.auth_version,
- network_resources=network_resources)
- os = clients.Manager(self.isolated_creds.get_primary_creds())
self.images_client = os.images_client
self.flavors_client = os.flavors_client
@@ -131,6 +119,9 @@
nname = ''.join(c for c in nname if c in self.validchars)
return nname
+ def clear_creds(self):
+ self.isolated_creds.clear_isolated_creds()
+
@property
def scenario_images(self):
"""
@@ -177,12 +168,19 @@
loader, standard_tests, pattern = args
else:
standard_tests, module, loader = args
+ output = None
+ scenario_utils = None
try:
scenario_utils = InputScenarioUtils()
scenario_flavor = scenario_utils.scenario_flavors
scenario_image = scenario_utils.scenario_images
except (exceptions.InvalidConfiguration, TypeError):
- return standard_tests
+ output = standard_tests
+ finally:
+ if scenario_utils:
+ scenario_utils.clear_creds()
+ if output is not None:
+ return output
for test in testtools.iterate_tests(standard_tests):
setattr(test, 'scenarios', testscenarios.multiply_scenarios(
scenario_image,
diff --git a/tempest/services/compute/json/baremetal_nodes_client.py b/tempest/services/compute/json/baremetal_nodes_client.py
index fa2d7f4..d8f13c4 100644
--- a/tempest/services/compute/json/baremetal_nodes_client.py
+++ b/tempest/services/compute/json/baremetal_nodes_client.py
@@ -16,7 +16,8 @@
from six.moves.urllib import parse as urllib
-from tempest.api_schema.response.compute import baremetal_nodes as schema
+from tempest.api_schema.response.compute.v2_1 import baremetal_nodes \
+ as schema
from tempest.common import service_client
diff --git a/tempest/services/compute/json/flavors_client.py b/tempest/services/compute/json/flavors_client.py
index 80cbe4d..7938d8e 100644
--- a/tempest/services/compute/json/flavors_client.py
+++ b/tempest/services/compute/json/flavors_client.py
@@ -17,10 +17,11 @@
from six.moves.urllib import parse as urllib
-from tempest.api_schema.response.compute import flavors_access as schema_access
-from tempest.api_schema.response.compute import flavors_extra_specs \
- as schema_extra_specs
from tempest.api_schema.response.compute.v2_1 import flavors as schema
+from tempest.api_schema.response.compute.v2_1 import flavors_access \
+ as schema_access
+from tempest.api_schema.response.compute.v2_1 import flavors_extra_specs \
+ as schema_extra_specs
from tempest.common import service_client
@@ -103,7 +104,7 @@
resp, body = self.post('flavors/%s/os-extra_specs' % flavor_id,
post_body)
body = json.loads(body)
- self.validate_response(schema_extra_specs.flavor_extra_specs,
+ self.validate_response(schema_extra_specs.set_get_flavor_extra_specs,
resp, body)
return service_client.ResponseBody(resp, body['extra_specs'])
@@ -111,7 +112,7 @@
"""Gets extra Specs details of the mentioned flavor."""
resp, body = self.get('flavors/%s/os-extra_specs' % flavor_id)
body = json.loads(body)
- self.validate_response(schema_extra_specs.flavor_extra_specs,
+ self.validate_response(schema_extra_specs.set_get_flavor_extra_specs,
resp, body)
return service_client.ResponseBody(resp, body['extra_specs'])
@@ -120,8 +121,9 @@
resp, body = self.get('flavors/%s/os-extra_specs/%s' % (str(flavor_id),
key))
body = json.loads(body)
- self.validate_response(schema_extra_specs.flavor_extra_specs_key,
- resp, body)
+ self.validate_response(
+ schema_extra_specs.set_get_flavor_extra_specs_key,
+ resp, body)
return service_client.ResponseBody(resp, body)
def update_flavor_extra_spec(self, flavor_id, key, **kwargs):
@@ -129,8 +131,9 @@
resp, body = self.put('flavors/%s/os-extra_specs/%s' %
(flavor_id, key), json.dumps(kwargs))
body = json.loads(body)
- self.validate_response(schema_extra_specs.flavor_extra_specs_key,
- resp, body)
+ self.validate_response(
+ schema_extra_specs.set_get_flavor_extra_specs_key,
+ resp, body)
return service_client.ResponseBody(resp, body)
def unset_flavor_extra_spec(self, flavor_id, key):
diff --git a/tempest/services/compute/json/floating_ips_client.py b/tempest/services/compute/json/floating_ips_client.py
index 9568a5e..f30bfdb 100644
--- a/tempest/services/compute/json/floating_ips_client.py
+++ b/tempest/services/compute/json/floating_ips_client.py
@@ -40,7 +40,7 @@
url = "os-floating-ips/%s" % str(floating_ip_id)
resp, body = self.get(url)
body = json.loads(body)
- self.validate_response(schema.floating_ip, resp, body)
+ self.validate_response(schema.create_get_floating_ip, resp, body)
return service_client.ResponseBody(resp, body['floating_ip'])
def create_floating_ip(self, pool_name=None):
@@ -50,7 +50,7 @@
post_body = json.dumps(post_body)
resp, body = self.post(url, post_body)
body = json.loads(body)
- self.validate_response(schema.floating_ip, resp, body)
+ self.validate_response(schema.create_get_floating_ip, resp, body)
return service_client.ResponseBody(resp, body['floating_ip'])
def delete_floating_ip(self, floating_ip_id):
@@ -108,7 +108,7 @@
resp, body = self.get(url)
body = json.loads(body)
- self.validate_response(schema.floating_ip_pools, resp, body)
+ self.validate_response(schema.list_floating_ip_pools, resp, body)
return service_client.ResponseBodyList(resp, body['floating_ip_pools'])
def create_floating_ips_bulk(self, ip_range, pool, interface):
diff --git a/tempest/services/compute/json/migrations_client.py b/tempest/services/compute/json/migrations_client.py
index 009992c..f708a07 100644
--- a/tempest/services/compute/json/migrations_client.py
+++ b/tempest/services/compute/json/migrations_client.py
@@ -16,7 +16,7 @@
from six.moves.urllib import parse as urllib
-from tempest.api_schema.response.compute import migrations as schema
+from tempest.api_schema.response.compute.v2_1 import migrations as schema
from tempest.common import service_client
diff --git a/tempest/services/compute/json/services_client.py b/tempest/services/compute/json/services_client.py
index e2d959b..156ad8d 100644
--- a/tempest/services/compute/json/services_client.py
+++ b/tempest/services/compute/json/services_client.py
@@ -18,7 +18,7 @@
from six.moves.urllib import parse as urllib
-from tempest.api_schema.response.compute import services as schema
+from tempest.api_schema.response.compute.v2_1 import services as schema
from tempest.common import service_client
diff --git a/tempest/services/compute/json/tenant_usages_client.py b/tempest/services/compute/json/tenant_usages_client.py
index b7e2b2a..52f46e2 100644
--- a/tempest/services/compute/json/tenant_usages_client.py
+++ b/tempest/services/compute/json/tenant_usages_client.py
@@ -30,7 +30,7 @@
resp, body = self.get(url)
body = json.loads(body)
- self.validate_response(schema.list_tenant, resp, body)
+ self.validate_response(schema.list_tenant_usage, resp, body)
return service_client.ResponseBodyList(resp, body['tenant_usages'][0])
def get_tenant_usage(self, tenant_id, params=None):
@@ -40,5 +40,5 @@
resp, body = self.get(url)
body = json.loads(body)
- self.validate_response(schema.get_tenant, resp, body)
+ self.validate_response(schema.get_tenant_usage, resp, body)
return service_client.ResponseBodyList(resp, body['tenant_usage'])
diff --git a/tempest/services/image/v1/json/image_client.py b/tempest/services/image/v1/json/image_client.py
index 5e442fa..0761d3e 100644
--- a/tempest/services/image/v1/json/image_client.py
+++ b/tempest/services/image/v1/json/image_client.py
@@ -201,7 +201,7 @@
self.expected_success(200, resp.status)
return service_client.ResponseBody(resp, body)
- def image_list(self, **kwargs):
+ def list_images(self, **kwargs):
url = 'v1/images'
if len(kwargs) > 0:
diff --git a/tempest/services/image/v2/json/image_client.py b/tempest/services/image/v2/json/image_client.py
index aff8e85..70ed0db 100644
--- a/tempest/services/image/v2/json/image_client.py
+++ b/tempest/services/image/v2/json/image_client.py
@@ -102,7 +102,7 @@
self.expected_success(204, resp.status)
return service_client.ResponseBody(resp)
- def image_list(self, params=None):
+ def list_images(self, params=None):
url = 'v2/images'
if params:
diff --git a/tempest/services/volume/json/volumes_client.py b/tempest/services/volume/json/volumes_client.py
index 9a08bbd..65aa0f4 100644
--- a/tempest/services/volume/json/volumes_client.py
+++ b/tempest/services/volume/json/volumes_client.py
@@ -123,6 +123,15 @@
self.expected_success(202, resp.status)
return service_client.ResponseBody(resp, body)
+ def set_bootable_volume(self, volume_id, bootable):
+ """set a bootable flag for a volume - true or false."""
+ post_body = {"bootable": bootable}
+ post_body = json.dumps({'os-set_bootable': post_body})
+ url = 'volumes/%s/action' % (volume_id)
+ resp, body = self.post(url, post_body)
+ self.expected_success(200, resp.status)
+ return service_client.ResponseBody(resp, body)
+
def detach_volume(self, volume_id):
"""Detaches a volume from an instance."""
post_body = {}
diff --git a/tempest/test.py b/tempest/test.py
index d80e478..0d709f6c 100644
--- a/tempest/test.py
+++ b/tempest/test.py
@@ -230,7 +230,9 @@
_service = None
# NOTE(andreaf) credentials holds a list of the credentials to be allocated
- # at class setup time. Credential types can be 'primary', 'alt' or 'admin'
+ # at class setup time. Credential types can be 'primary', 'alt', 'admin' or
+ # a list of roles - the first element of the list being a label, and the
+ # rest the actual roles
credentials = []
# Resources required to validate a server using ssh
validation_resources = {}
@@ -322,9 +324,16 @@
if 'admin' in cls.credentials and not credentials.is_admin_available():
msg = "Missing Identity Admin API credentials in configuration."
raise cls.skipException(msg)
- if 'alt' is cls.credentials and not credentials.is_alt_available():
+ if 'alt' in cls.credentials and not credentials.is_alt_available():
msg = "Missing a 2nd set of API credentials in configuration."
raise cls.skipException(msg)
+ if hasattr(cls, 'identity_version'):
+ if cls.identity_version == 'v2':
+ if not CONF.identity_feature_enabled.api_v2:
+ raise cls.skipException("Identity api v2 is not enabled")
+ elif cls.identity_version == 'v3':
+ if not CONF.identity_feature_enabled.api_v3:
+ raise cls.skipException("Identity api v3 is not enabled")
@classmethod
def setup_credentials(cls):
@@ -337,19 +346,24 @@
# This may raise an exception in case credentials are not available
# In that case we want to let the exception through and the test
# fail accordingly
- manager = cls.get_client_manager(
- credential_type=credentials_type)
- setattr(cls, 'os_%s' % credentials_type, manager)
- # Setup some common aliases
- # TODO(andreaf) The aliases below are a temporary hack
- # to avoid changing too much code in one patch. They should
- # be removed eventually
- if credentials_type == 'primary':
- cls.os = cls.manager = cls.os_primary
- if credentials_type == 'admin':
- cls.os_adm = cls.admin_manager = cls.os_admin
- if credentials_type == 'alt':
- cls.alt_manager = cls.os_alt
+ if isinstance(credentials_type, six.string_types):
+ manager = cls.get_client_manager(
+ credential_type=credentials_type)
+ setattr(cls, 'os_%s' % credentials_type, manager)
+ # Setup some common aliases
+ # TODO(andreaf) The aliases below are a temporary hack
+ # to avoid changing too much code in one patch. They should
+ # be removed eventually
+ if credentials_type == 'primary':
+ cls.os = cls.manager = cls.os_primary
+ if credentials_type == 'admin':
+ cls.os_adm = cls.admin_manager = cls.os_admin
+ if credentials_type == 'alt':
+ cls.alt_manager = cls.os_alt
+ elif isinstance(credentials_type, list):
+ manager = cls.get_client_manager(roles=credentials_type[1:],
+ force_new=True)
+ setattr(cls, 'os_roles_%s' % credentials_type[0], manager)
@classmethod
def setup_clients(cls):
@@ -439,14 +453,13 @@
return cls._creds_provider
@classmethod
- def get_client_manager(cls, identity_version=None,
- credential_type=None, roles=None, force_new=None):
+ def get_client_manager(cls, credential_type=None, roles=None,
+ force_new=None):
"""Returns an OpenStack client manager
Returns an OpenStack client manager based on either credential_type
or a list of roles. If neither is specified, it defaults to
credential_type 'primary'
- :param identity_version: string - v2 or v3
:param credential_type: string - primary, alt or admin
:param roles: list of roles
@@ -458,7 +471,6 @@
raise ValueError(msg)
if not any([roles, credential_type]):
credential_type = 'primary'
- cls.identity_version = identity_version
cred_provider = cls._get_credentials_provider()
if roles:
for role in roles:
@@ -486,7 +498,7 @@
"""
Clears isolated creds if set
"""
- if hasattr(cls, '_cred_provider'):
+ if hasattr(cls, '_creds_provider'):
cls._creds_provider.clear_isolated_creds()
@classmethod
diff --git a/tempest/tests/cmd/test_javelin.py b/tempest/tests/cmd/test_javelin.py
new file mode 100644
index 0000000..860599b
--- /dev/null
+++ b/tempest/tests/cmd/test_javelin.py
@@ -0,0 +1,233 @@
+#!/usr/bin/env python
+#
+# 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 mock
+from oslotest import mockpatch
+from tempest_lib import exceptions as lib_exc
+
+from tempest.cmd import javelin
+from tempest.tests import base
+
+
+class JavelinUnitTest(base.TestCase):
+
+ def setUp(self):
+ super(JavelinUnitTest, self).setUp()
+ javelin.setup_logging()
+ self.fake_client = mock.MagicMock()
+ self.fake_object = mock.MagicMock()
+
+
+class TestCreateResources(JavelinUnitTest):
+ def test_create_tenants(self):
+
+ self.fake_client.identity.list_tenants.return_value = []
+ self.useFixture(mockpatch.PatchObject(javelin, "keystone_admin",
+ return_value=self.fake_client))
+
+ javelin.create_tenants([self.fake_object['name']])
+
+ mocked_function = self.fake_client.identity.create_tenant
+ mocked_function.assert_called_once_with(self.fake_object['name'])
+
+ def test_create_duplicate_tenant(self):
+ self.fake_client.identity.list_tenants.return_value = [
+ {'name': self.fake_object['name']}]
+ self.useFixture(mockpatch.PatchObject(javelin, "keystone_admin",
+ return_value=self.fake_client))
+
+ javelin.create_tenants([self.fake_object['name']])
+
+ mocked_function = self.fake_client.identity.create_tenant
+ self.assertFalse(mocked_function.called)
+
+ def test_create_users(self):
+ self.fake_client.identity.get_tenant_by_name.return_value = \
+ self.fake_object['tenant']
+ self.fake_client.identity.get_user_by_username.side_effect = \
+ lib_exc.NotFound()
+ self.useFixture(mockpatch.PatchObject(javelin, "keystone_admin",
+ return_value=self.fake_client))
+
+ javelin.create_users([self.fake_object])
+
+ fake_tenant_id = self.fake_object['tenant']['id']
+ fake_email = "%s@%s" % (self.fake_object['user'], fake_tenant_id)
+ mocked_function = self.fake_client.identity.create_user
+ mocked_function.assert_called_once_with(self.fake_object['name'],
+ self.fake_object['password'],
+ fake_tenant_id,
+ fake_email,
+ enabled=True)
+
+ def test_create_user_missing_tenant(self):
+ self.fake_client.identity.get_tenant_by_name.side_effect = \
+ lib_exc.NotFound()
+ self.useFixture(mockpatch.PatchObject(javelin, "keystone_admin",
+ return_value=self.fake_client))
+
+ javelin.create_users([self.fake_object])
+
+ mocked_function = self.fake_client.identity.create_user
+ self.assertFalse(mocked_function.called)
+
+ def test_create_objects(self):
+
+ self.useFixture(mockpatch.PatchObject(javelin, "client_for_user",
+ return_value=self.fake_client))
+ self.useFixture(mockpatch.PatchObject(javelin, "_assign_swift_role"))
+ self.useFixture(mockpatch.PatchObject(javelin, "_file_contents",
+ return_value=self.fake_object.content))
+
+ javelin.create_objects([self.fake_object])
+
+ mocked_function = self.fake_client.containers.create_container
+ mocked_function.assert_called_once_with(self.fake_object['container'])
+ mocked_function = self.fake_client.objects.create_object
+ mocked_function.assert_called_once_with(self.fake_object['container'],
+ self.fake_object['name'],
+ self.fake_object.content)
+
+ def test_create_images(self):
+ self.fake_client.images.create_image.return_value = \
+ self.fake_object['body']
+
+ self.useFixture(mockpatch.PatchObject(javelin, "client_for_user",
+ return_value=self.fake_client))
+ self.useFixture(mockpatch.PatchObject(javelin, "_get_image_by_name",
+ return_value=[]))
+ self.useFixture(mockpatch.PatchObject(javelin, "_resolve_image",
+ return_value=(None, None)))
+
+ with mock.patch('six.moves.builtins.open', mock.mock_open(),
+ create=True) as open_mock:
+ javelin.create_images([self.fake_object])
+
+ mocked_function = self.fake_client.images.create_image
+ mocked_function.assert_called_once_with(self.fake_object['name'],
+ self.fake_object['format'],
+ self.fake_object['format'])
+
+ mocked_function = self.fake_client.images.store_image
+ fake_image_id = self.fake_object['body'].get('id')
+ mocked_function.assert_called_once_with(fake_image_id, open_mock())
+
+ def test_create_networks(self):
+ self.fake_client.networks.list_networks.return_value = {
+ 'networks': []}
+
+ self.useFixture(mockpatch.PatchObject(javelin, "client_for_user",
+ return_value=self.fake_client))
+
+ javelin.create_networks([self.fake_object])
+
+ mocked_function = self.fake_client.networks.create_network
+ mocked_function.assert_called_once_with(name=self.fake_object['name'])
+
+ def test_create_subnet(self):
+
+ fake_network = self.fake_object['network']
+
+ self.useFixture(mockpatch.PatchObject(javelin, "client_for_user",
+ return_value=self.fake_client))
+ self.useFixture(mockpatch.PatchObject(javelin, "_get_resource_by_name",
+ return_value=fake_network))
+
+ fake_netaddr = mock.MagicMock()
+ self.useFixture(mockpatch.PatchObject(javelin, "netaddr",
+ return_value=fake_netaddr))
+ fake_version = javelin.netaddr.IPNetwork().version
+
+ javelin.create_subnets([self.fake_object])
+
+ mocked_function = self.fake_client.networks.create_subnet
+ mocked_function.assert_called_once_with(network_id=fake_network['id'],
+ cidr=self.fake_object['range'],
+ name=self.fake_object['name'],
+ ip_version=fake_version)
+
+
+class TestDestroyResources(JavelinUnitTest):
+
+ def test_destroy_tenants(self):
+
+ fake_tenant = self.fake_object['tenant']
+
+ fake_auth = mock.MagicMock()
+ fake_auth.identity.get_tenant_by_name.return_value = fake_tenant
+
+ self.useFixture(mockpatch.PatchObject(javelin, "keystone_admin",
+ return_value=fake_auth))
+ javelin.destroy_tenants([fake_tenant])
+
+ mocked_function = fake_auth.identity.delete_tenant
+ mocked_function.assert_called_once_with(fake_tenant['id'])
+
+ def test_destroy_users(self):
+
+ fake_user = self.fake_object['user']
+ fake_tenant = self.fake_object['tenant']
+
+ fake_auth = mock.MagicMock()
+ fake_auth.identity.get_tenant_by_name.return_value = fake_tenant
+ fake_auth.identity.get_user_by_username.return_value = fake_user
+
+ self.useFixture(mockpatch.PatchObject(javelin, "keystone_admin",
+ return_value=fake_auth))
+
+ javelin.destroy_users([fake_user])
+
+ mocked_function = fake_auth.identity.delete_user
+ mocked_function.assert_called_once_with(fake_user['id'])
+
+ def test_destroy_objects(self):
+
+ fake_client = mock.MagicMock()
+ fake_client.objects.delete_object.return_value = {'status': "200"}, ""
+ self.useFixture(mockpatch.PatchObject(javelin, "client_for_user",
+ return_value=fake_client))
+ javelin.destroy_objects([self.fake_object])
+
+ mocked_function = fake_client.objects.delete_object
+ mocked_function.asswert_called_once(self.fake_object['container'],
+ self.fake_object['name'])
+
+ def test_destroy_images(self):
+
+ fake_client = mock.MagicMock()
+ self.useFixture(mockpatch.PatchObject(javelin, "client_for_user",
+ return_value=fake_client))
+ self.useFixture(mockpatch.PatchObject(javelin, "_get_image_by_name",
+ return_value=self.fake_object['image']))
+
+ javelin.destroy_images([self.fake_object])
+
+ mocked_function = fake_client.images.delete_image
+ mocked_function.assert_called_once_with(
+ self.fake_object['image']['id'])
+
+ def test_destroy_networks(self):
+
+ fake_client = mock.MagicMock()
+ self.useFixture(mockpatch.PatchObject(javelin, "client_for_user",
+ return_value=fake_client))
+ self.useFixture(mockpatch.PatchObject(
+ javelin, "_get_resource_by_name",
+ return_value=self.fake_object['resource']))
+
+ javelin.destroy_networks([self.fake_object])
+
+ mocked_function = fake_client.networks.delete_network
+ mocked_function.assert_called_once_with(
+ self.fake_object['resource']['id'])
diff --git a/tox.ini b/tox.ini
index 88d1302..2d2ed38 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,5 +1,5 @@
[tox]
-envlist = pep8,py27
+envlist = pep8,py27,py34
minversion = 1.6
skipsdist = True