From ad66ca895f47617198fab39f8186f3671a644eef Mon Sep 17 00:00:00 2001 From: Olga Kopylova Date: Fri, 25 Apr 2014 18:55:00 +0300 Subject: [PATCH] Unit tests for TempestConf Add unit tests for TempestConf class. Small refactoring of fake resources and TempestConf. Change-Id: Iae6b1322b372bebc086043d8bc0b5454f829222b --- .../verification/verifiers/tempest/config.py | 20 +- tests/fakes.py | 25 +- tests/verification/verifiers/fakes.py | 68 ---- tests/verification/verifiers/test_config.py | 296 ++++++++++++++++++ 4 files changed, 317 insertions(+), 92 deletions(-) create mode 100644 tests/verification/verifiers/test_config.py diff --git a/rally/verification/verifiers/tempest/config.py b/rally/verification/verifiers/tempest/config.py index d15f408a6b..bc80ce7cd8 100644 --- a/rally/verification/verifiers/tempest/config.py +++ b/rally/verification/verifiers/tempest/config.py @@ -111,8 +111,8 @@ class TempestConf(object): def _set_compute_images(self): glanceclient = self.clients.glance() image_list = [img for img in glanceclient.images.list() - if img.status == 'active' and img.name is not None - and 'cirros' in img.name] + if img.status.lower() == 'active' and + img.name is not None and 'cirros' in img.name] # Upload new images if there are no # necessary images in the cloud (cirros) while len(image_list) < 2: @@ -124,9 +124,10 @@ class TempestConf(object): container_format='bare') image.update(data=open(self.img_path, 'rb')) image_list.append(image) - except Exception: + except Exception as e: msg = _('There are no desired images (cirros) or only one and ' - 'new image could not be created') + 'new image could not be created.\n' + 'Reason: %s') % e.message raise exceptions.TempestConfigCreationFailure(message=msg) self.conf.set('compute', 'image_ref', image_list[0].id) self.conf.set('compute', 'image_ref_alt', image_list[1].id) @@ -142,9 +143,10 @@ class TempestConf(object): try: flv = novaclient.flavors.create("m1.tiny_%s" % now, 512, 1, 1) flavor_list.append(flv) - except Exception: + except Exception as e: msg = _('There are no desired flavors or only one and ' - 'new flavor could not be created') + 'new flavor could not be created.\n' + 'Reason: %s') % e.message raise exceptions.TempestConfigCreationFailure(message=msg) self.conf.set('compute', 'flavor_ref', flavor_list[0].id) self.conf.set('compute', 'flavor_ref_alt', flavor_list[1].id) @@ -199,12 +201,14 @@ class TempestConf(object): 'cinder', 'nova', 'glance'] for service in services: self.conf.set('service_available', service, - service in self.available_services) + str(service in self.available_services)) horizon_url = ('http://' + urlparse.urlparse(self.endpoint['auth_url']).hostname) answer_code = urllib2.urlopen(horizon_url).getcode() + # convert boolean to string because ConfigParser fails + # on attempt to get option with boolean value self.conf.set('service_available', 'horizon', - answer_code == httplib.OK) + str(answer_code == httplib.OK)) def generate(self): self._set_default() diff --git a/tests/fakes.py b/tests/fakes.py index 66804eab73..6d63771d8f 100644 --- a/tests/fakes.py +++ b/tests/fakes.py @@ -53,9 +53,6 @@ class FakeResource(object): class FakeServer(FakeResource): - def __init__(self, manager=None): - super(FakeServer, self).__init__(manager) - def suspend(self): self.status = "SUSPENDED" @@ -63,26 +60,24 @@ class FakeServer(FakeResource): class FakeFailedServer(FakeResource): def __init__(self, manager=None): - super(FakeFailedServer, self).__init__(manager) - self.status = "ERROR" + super(FakeFailedServer, self).__init__(manager, status="ERROR") class FakeImage(FakeResource): - def __init__(self, manager=None, id="image-id-0", - min_ram=0, size=0, min_disk=0): - super(FakeImage, self).__init__(manager) - self.id = id + def __init__(self, manager=None, id="image-id-0", min_ram=0, + size=0, min_disk=0, name=None): + super(FakeImage, self).__init__(manager, id=id, name=name) self.min_ram = min_ram self.size = size self.min_disk = min_disk + self.update = mock.MagicMock() class FakeFailedImage(FakeResource): def __init__(self, manager=None): - super(FakeFailedImage, self).__init__(manager) - self.status = "error" + super(FakeFailedImage, self).__init__(manager, status="error") class FakeFloatingIP(FakeResource): @@ -92,8 +87,7 @@ class FakeFloatingIP(FakeResource): class FakeTenant(FakeResource): def __init__(self, manager, name): - super(FakeTenant, self).__init__(manager) - self.name = name + super(FakeTenant, self).__init__(manager, name=name) class FakeUser(FakeResource): @@ -106,8 +100,8 @@ class FakeNetwork(FakeResource): class FakeFlavor(FakeResource): - def __init__(self, manager=None, ram=0, disk=0): - super(FakeFlavor, self).__init__(manager) + def __init__(self, id="flavor-id-0", manager=None, ram=0, disk=0): + super(FakeFlavor, self).__init__(manager, id=id) self.ram = ram self.disk = disk @@ -124,7 +118,6 @@ class FakeSecurityGroup(FakeResource): def __init__(self, manager=None, rule_manager=None): super(FakeSecurityGroup, self).__init__(manager) - self.manager = manager self.rule_manager = rule_manager @property diff --git a/tests/verification/verifiers/fakes.py b/tests/verification/verifiers/fakes.py index f8486de43e..5aa3362f21 100644 --- a/tests/verification/verifiers/fakes.py +++ b/tests/verification/verifiers/fakes.py @@ -14,74 +14,6 @@ # under the License. -FAKE_CONFIG = { - 'DEFAULT': [ - ('lock_path', 'fake_lock_path'), - ('debug', 'True'), - ('log_file', 'tempest.log'), - ('use_stderr', 'False')], - 'boto': [ - ('http_socket_timeout', '30'), - ('build_timeout', '196'), - ('build_interval', '1'), - ('instance_type', 'm1.nano'), - ('ssh_user', 'cirros')], - 'compute': [ - ('allow_tenant_isolation', 'False'), - ('ip_version_for_ssh', '4'), - ('image_ssh_user', 'cirros'), - ('ssh_connect_method', 'floating'), - ('flavor_ref', 'fake_flavor_ref'), - ('image_ref_alt', 'fake_image_ref_alt'), - ('build_interval', '1'), - ('change_password_available', 'False'), - ('use_block_migration_for_live_migration', 'False'), - ('live_migration_available', 'False'), - ('image_alt_ssh_user', 'cirros'), - ('build_timeout', '196'), - ('network_for_ssh', 'private'), - ('ssh_user', 'cirros'), - ('ssh_timeout', '196'), - ('image_ref', 'fake_image_ref'), - ('flavor_ref_alt', 'fake_flavor_ref_alt')], - 'cli': [('cli_dir', '/usr/local/bin')], - 'scenario': [('large_ops_number', '0')], - 'compute-admin': [('password', 'fake_password')], - 'volume': [ - ('build_interval', '1'), - ('build_timeout', '196')], - 'network-feature-enabled': [('api_extensions', 'all')], - 'service_available': [ - ('heat', 'True'), - ('ironic', 'False'), - ('marconi', 'False'), - ('swift', 'False'), - ('glance', 'True'), - ('ceilometer', 'False'), - ('trove', 'False'), - ('nova', 'True'), - ('horizon', 'True'), - ('debug', 'True'), - ('cinder', 'True'), - ('log_file', 'tempest.log'), - ('neutron', 'True'), - ('savanna', 'False')], - 'identity': [ - ('username', 'fake_username'), - ('password', 'fake_password'), - ('tenant_name', 'fake_tenant_name'), - ('admin_tenant_name', 'fake_tenant_name'), - ('uri', 'fake_uri'), - ('uri_v3', 'fake_uri'), - ('admin_username', 'fake_username'), - ('admin_password', 'fake_password')], - 'network': [ - ('tenant_networks_reachable', 'false'), - ('default_network', '10.0.0.0/24'), - ('api_version', '2.0')] -} - - def get_fake_test_case(): return { 'total': { diff --git a/tests/verification/verifiers/test_config.py b/tests/verification/verifiers/test_config.py new file mode 100644 index 0000000000..966cf9f7c1 --- /dev/null +++ b/tests/verification/verifiers/test_config.py @@ -0,0 +1,296 @@ +# Copyright 2014: Mirantis 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. + +import httplib +import mock +import os +from oslo.config import cfg + +from rally import exceptions +from rally.verification.verifiers.tempest import config +from tests import fakes +from tests import test + +CONF = cfg.CONF + + +class ConfigTestCase(test.TestCase): + + @mock.patch("rally.objects.deploy.db.deployment_get") + @mock.patch("rally.osclients.Clients.verified_keystone") + @mock.patch("rally.verification.verifiers.tempest.config.os.path.isfile") + def setUp(self, mock_isfile, mock_verified_keystone, mock_get): + super(ConfigTestCase, self).setUp() + self.endpoint = {"username": "test", + "tenant_name": "test", + "password": "test", + "auth_url": "http://test/v2.0", + "permission": "admin"} + mock_get.return_value = {"endpoints": [self.endpoint]} + mock_isfile.return_value = True + self.deploy_id = "fake_deploy_id" + self.conf_generator = config.TempestConf(self.deploy_id) + + def _remove_default_section(self, items): + # getting items from configparser by specified section name + # retruns also values from DEFAULT section + defaults = (("log_file", "tempest.log"), ("debug", "True"), + ("use_stderr", "False")) + return [item for item in items if item not in defaults] + + @mock.patch("rally.verification.verifiers.tempest.config.urllib2.urlopen") + @mock.patch("six.moves.builtins.open") + def test__load_img_success(self, mock_open, mock_urlopen): + mock_result = mock.MagicMock() + mock_result.getcode.return_value = httplib.OK + mock_urlopen.return_value = mock_result + mock_file = mock.MagicMock() + mock_open.return_value = mock_file + self.conf_generator._load_img() + cirros_url = ("http://download.cirros-cloud.net/%s/%s" % + (CONF.image.cirros_version, + CONF.image.cirros_image)) + mock_urlopen.assert_called_once_with(cirros_url) + mock_file.__enter__().write.assert_called_once_with(mock_result.read()) + mock_file.__exit__.assert_called_once_with(None, None, None) + + @mock.patch("rally.verification.verifiers.tempest.config.urllib2.urlopen") + def test__load_img_notfound(self, mock_urlopen): + mock_result = mock.MagicMock() + mock_result.getcode.return_value = httplib.NOT_FOUND + mock_urlopen.return_value = mock_result + self.assertRaises(exceptions.TempestConfigCreationFailure, + self.conf_generator._load_img) + + def test__get_url(self): + service = "test_service" + url = "test_url" + # mocked at setUp + self.conf_generator.keystoneclient.auth_ref = {"serviceCatalog": + [{"name": service, + "endpoints": + [{"publicURL": url}]}]} + self.assertEqual(self.conf_generator._get_url(service), url) + + @mock.patch("rally.verification.verifiers.tempest.config.TempestConf" + "._get_url") + def test__set_boto(self, mock_get_url): + url = "test_url" + mock_get_url.return_value = url + self.conf_generator._set_boto() + expected = (("ec2_url", url), + ("s3_url", url), + ("build_interval", "1"), + ("build_timeout", "196"), + ("http_socket_timeout", "30"), + ("instance_type", "m1.nano"), + ("ssh_user", "cirros"), + ("s3_materials_path", + os.path.join(self.conf_generator.data_path, + "s3matherials"))) + results = self._remove_default_section( + self.conf_generator.conf.items("boto")) + self.assertEqual(sorted(expected), sorted(results)) + + def test__set_compute_admin(self): + self.conf_generator._set_compute_admin() + expected = [("username", self.endpoint["username"]), + ("password", self.endpoint["password"]), + ("tenant_name", self.endpoint["tenant_name"])] + results = self._remove_default_section( + self.conf_generator.conf.items("compute-admin")) + self.assertEqual(sorted(expected), sorted(results)) + + @mock.patch("rally.osclients.nova") + def test__set_compute_flavors(self, mock_nova): + mock_novaclient = mock.MagicMock() + mock_novaclient.flavors.list.return_value = [ + fakes.FakeFlavor(id="id1"), fakes.FakeFlavor(id="id2")] + mock_nova.Client.return_value = mock_novaclient + self.conf_generator._set_compute_flavors() + expected = ("id1", "id2") + results = (self.conf_generator.conf.get("compute", "flavor_ref"), + self.conf_generator.conf.get("compute", "flavor_ref_alt")) + self.assertEqual(sorted(expected), sorted(results)) + + @mock.patch("rally.osclients.nova") + def test__set_compute_flavors_create(self, mock_nova): + mock_novaclient = mock.MagicMock() + mock_novaclient.flavors.list.return_value = [] + mock_novaclient.flavors.create.side_effect = [ + fakes.FakeFlavor(id="id1"), fakes.FakeFlavor(id="id2")] + mock_nova.Client.return_value = mock_novaclient + self.conf_generator._set_compute_flavors() + self.assertEqual(mock_novaclient.flavors.create.call_count, 2) + expected = ("id1", "id2") + results = (self.conf_generator.conf.get("compute", "flavor_ref"), + self.conf_generator.conf.get("compute", "flavor_ref_alt")) + self.assertEqual(sorted(expected), sorted(results)) + + @mock.patch("rally.osclients.nova") + def test__set_compute_flavors_create_fails(self, mock_nova): + mock_novaclient = mock.MagicMock() + mock_novaclient.flavors.list.return_value = [] + mock_novaclient.flavors.create.side_effect = Exception() + mock_nova.Client.return_value = mock_novaclient + self.assertRaises(exceptions.TempestConfigCreationFailure, + self.conf_generator._set_compute_flavors) + + @mock.patch("rally.osclients.glance") + @mock.patch("rally.osclients.keystone") + def test__set_compute_images(self, mock_keystone, mock_glance): + mock_glanceclient = mock.MagicMock() + mock_glanceclient.images.list.return_value = [ + fakes.FakeImage(id="id1", name="cirros1"), + fakes.FakeImage(id="id2", name="cirros2")] + mock_glance.Client.return_value = mock_glanceclient + self.conf_generator._set_compute_images() + expected = ("id1", "id2") + results = (self.conf_generator.conf.get("compute", "image_ref"), + self.conf_generator.conf.get("compute", "image_ref_alt")) + self.assertEqual(sorted(expected), sorted(results)) + + @mock.patch("rally.osclients.glance") + @mock.patch("rally.osclients.keystone") + @mock.patch("six.moves.builtins.open") + def test__set_compute_images_create(self, mock_open, mock_keystone, + mock_glance): + mock_glanceclient = mock.MagicMock() + mock_glanceclient.images.list.return_value = [] + mock_glanceclient.images.create.side_effect = [ + fakes.FakeImage(id="id1"), fakes.FakeImage(id="id2")] + mock_glance.Client.return_value = mock_glanceclient + self.conf_generator._set_compute_images() + self.assertEqual(mock_glanceclient.images.create.call_count, 2) + expected = ("id1", "id2") + results = (self.conf_generator.conf.get("compute", "image_ref"), + self.conf_generator.conf.get("compute", "image_ref_alt")) + self.assertEqual(sorted(expected), sorted(results)) + + @mock.patch("rally.osclients.glance") + @mock.patch("rally.osclients.keystone") + def test__set_compute_images_create_fails(self, mock_keystone, + mock_glance): + mock_glanceclient = mock.MagicMock() + mock_glanceclient.images.list.return_value = [] + mock_glanceclient.images.create.side_effect = Exception() + mock_glance.Client.return_value = mock_glanceclient + self.assertRaises(exceptions.TempestConfigCreationFailure, + self.conf_generator._set_compute_images) + + def test__set_compute_ssh_connect_method_if_neutron(self): + self.conf_generator._set_compute_ssh_connect_method() + self.assertEqual("fixed", + self.conf_generator.conf.get("compute", + "ssh_connect_method")) + # if neutron is available + self.conf_generator.available_services = ["neutron"] + self.conf_generator._set_compute_ssh_connect_method() + self.assertEqual("floating", + self.conf_generator.conf.get("compute", + "ssh_connect_method")) + + @mock.patch("rally.verification.verifiers.tempest.config.os.path.exists") + @mock.patch("rally.verification.verifiers.tempest.config.os.makedirs") + def test__set_default(self, mock_makedirs, mock_exists): + mock_exists.return_value = False + self.conf_generator._set_default() + lock_path = os.path.join(self.conf_generator.data_path, "lock_files_%s" + % self.deploy_id) + mock_makedirs.assert_called_once_with(lock_path) + expected = (("debug", "True"), ("log_file", "tempest.log"), + ("use_stderr", "False"), + ("lock_path", lock_path)) + results = self.conf_generator.conf.items("DEFAULT") + self.assertEqual(sorted(expected), sorted(results)) + + def test__set_identity(self): + self.conf_generator._set_identity() + expected = (("username", self.endpoint["username"]), + ("password", self.endpoint["password"]), + ("tenant_name", self.endpoint["tenant_name"]), + ("admin_username", self.endpoint["username"]), + ("admin_password", self.endpoint["password"]), + ("admin_tenant_name", self.endpoint["username"]), + ("uri", self.endpoint["auth_url"]), + ("uri_v3", self.endpoint["auth_url"].replace("/v2.0", + "/v3"))) + results = self._remove_default_section( + self.conf_generator.conf.items("identity")) + self.assertEqual(sorted(expected), sorted(results)) + + @mock.patch("rally.osclients.neutron") + def test__set_network_if_neutron(self, mock_neutron): + fake_neutronclient = mock.MagicMock() + fake_neutronclient.list_networks.return_value = {"networks": [ + {"status": "ACTIVE", + "id": "test_id", + "router:external": + True}]} + fake_neutronclient.list_routers.return_value = {"routers": [ + {"id": "test_router"}]} + fake_neutronclient.list_subnets.return_value = {"subnets": [ + {"cidr": + "10.0.0.0/24"}]} + mock_neutron.Client.return_value = fake_neutronclient + self.conf_generator.available_services = ["neutron"] + self.conf_generator._set_network() + expected = (("default_network", "10.0.0.0/24"), + ("tenant_networks_reachable", "false"), + ("api_version", "2.0"), + ("public_network_id", "test_id"), + ("public_router_id", "test_router")) + results = self._remove_default_section( + self.conf_generator.conf.items("network")) + self.assertEqual(sorted(expected), sorted(results)) + + @mock.patch("rally.osclients.nova") + def test__set_network_if_nova(self, mock_nova): + network = "10.0.0.0/24" + mock_novaclient = mock.MagicMock() + mock_network = mock.MagicMock() + mock_network.cidr = network + mock_novaclient.networks.list.return_value = [mock_network] + mock_nova.Client.return_value = mock_novaclient + self.conf_generator._set_network() + self.assertEqual(network, + self.conf_generator.conf.get("network", + "default_network")) + + @mock.patch("rally.verification.verifiers.tempest.config.urllib2.urlopen") + def test__set_service_available(self, mock_urlopen): + mock_result = mock.MagicMock() + mock_result.getcode.return_value = httplib.NOT_FOUND + mock_urlopen.return_value = mock_result + available_services = ("nova", "cinder", "glance") + self.conf_generator.available_services = available_services + self.conf_generator._set_service_available() + expected = (("neutron", "False"), ("heat", "False"), + ("ceilometer", "False"), ("swift", "False"), + ("cinder", "True"), ("nova", "True"), + ("glance", "True"), ("horizon", "False")) + options = self._remove_default_section( + self.conf_generator.conf.items("service_available")) + self.assertEqual(sorted(expected), sorted(options)) + + @mock.patch("rally.verification.verifiers.tempest.config.urllib2.urlopen") + def test__set_service_available_horizon(self, mock_urlopen): + mock_result = mock.MagicMock() + mock_result.getcode.return_value = httplib.OK + mock_urlopen.return_value = mock_result + self.conf_generator._set_service_available() + self.assertEqual(self.conf_generator.conf.get("service_available", + "horizon"), + "True")