[Verify] Fixing issue with downloading images for the tests

Scenario tests in Tempest require an image file and Rally always
downloads the image file by the link specified in CONF.tempest.img_url
regardless of CONF.tempest.img_name_regex is set or not.

If CONF.tempest.img_name_regex is set, we should find an image matching
to the regex in Glance and download it for the tests.
If CONF.tempest.img_name_regex is not set (or we didn't find the image
matching to CONF.tempest.img_name_regex), we should download the image
by the link specified in CONF.tempest.img_url.

Also, this patch changes Tempest plugin URL in the rally_verify.py file from

  https://github.com/MBonell/hello-world-tempest-plugin

to

  https://git.openstack.org/openstack/ceilometer

to resolve jenkins gate issues with rally verify jobs.

Tempest plugin is inside the ceilometer project [1].

[1] https://github.com/openstack/ceilometer/tree/master/ceilometer/tests/tempest

Change-Id: I3210e04bb53e77acab3a6bec172bb8d035bb8af2
This commit is contained in:
Yaroslav Lobankov 2016-09-19 20:10:34 +03:00
parent e1bf4505c9
commit c5daa0a3e5
4 changed files with 201 additions and 114 deletions

View File

@ -43,6 +43,7 @@ ipv6 = True
instance_type = instance_type =
[scenario] [scenario]
img_file =
[service_available] [service_available]

View File

@ -116,6 +116,41 @@ def _create_or_get_data_dir():
return data_dir return data_dir
def _download_image(image_path, image=None):
if image:
LOG.debug("Downloading image '%s' "
"from Glance to %s" % (image.name, image_path))
with open(image_path, "wb") as image_file:
for chunk in image.data():
image_file.write(chunk)
else:
LOG.debug("Downloading image from %s "
"to %s" % (CONF.tempest.img_url, image_path))
try:
response = requests.get(CONF.tempest.img_url, stream=True)
except requests.ConnectionError as err:
msg = _("Failed to download image. "
"Possibly there is no connection to Internet. "
"Error: %s.") % (str(err) or "unknown")
raise exceptions.TempestConfigCreationFailure(msg)
if response.status_code == 200:
with open(image_path, "wb") as image_file:
for chunk in response.iter_content(chunk_size=1024):
if chunk: # filter out keep-alive new chunks
image_file.write(chunk)
image_file.flush()
else:
if response.status_code == 404:
msg = _("Failed to download image. Image was not found.")
else:
msg = _("Failed to download image. "
"HTTP error code %d.") % response.status_code
raise exceptions.TempestConfigCreationFailure(msg)
LOG.debug("The image has been successfully downloaded!")
def _write_config(conf_path, conf_data): def _write_config(conf_path, conf_data):
with open(conf_path, "w+") as conf_file: with open(conf_path, "w+") as conf_file:
conf_data.write(conf_file) conf_data.write(conf_file)
@ -137,39 +172,6 @@ class TempestConfig(utils.RandomNameGeneratorMixin):
self.conf = configparser.ConfigParser() self.conf = configparser.ConfigParser()
self.conf.read(os.path.join(os.path.dirname(__file__), "config.ini")) self.conf.read(os.path.join(os.path.dirname(__file__), "config.ini"))
self.image_name = parse.urlparse(
CONF.tempest.img_url).path.split("/")[-1]
self._download_image()
def _download_image(self):
img_path = os.path.join(self.data_dir, self.image_name)
if os.path.isfile(img_path):
return
try:
response = requests.get(CONF.tempest.img_url, stream=True)
except requests.ConnectionError as err:
msg = _("Failed to download image. "
"Possibly there is no connection to Internet. "
"Error: %s.") % (str(err) or "unknown")
raise exceptions.TempestConfigCreationFailure(msg)
if response.status_code == 200:
with open(img_path + ".tmp", "wb") as img_file:
for chunk in response.iter_content(chunk_size=1024):
if chunk: # filter out keep-alive new chunks
img_file.write(chunk)
img_file.flush()
os.rename(img_path + ".tmp", img_path)
else:
if response.status_code == 404:
msg = _("Failed to download image. "
"Image was not found.")
else:
msg = _("Failed to download image. "
"HTTP error code %d.") % response.status_code
raise exceptions.TempestConfigCreationFailure(msg)
def _get_service_url(self, service_name): def _get_service_url(self, service_name):
s_type = self._get_service_type_by_service_name(service_name) s_type = self._get_service_type_by_service_name(service_name)
available_endpoints = self.keystone.service_catalog.get_endpoints() available_endpoints = self.keystone.service_catalog.get_endpoints()
@ -284,7 +286,6 @@ class TempestConfig(utils.RandomNameGeneratorMixin):
def _configure_scenario(self, section_name="scenario"): def _configure_scenario(self, section_name="scenario"):
self.conf.set(section_name, "img_dir", self.data_dir) self.conf.set(section_name, "img_dir", self.data_dir)
self.conf.set(section_name, "img_file", self.image_name)
def _configure_service_available(self, section_name="service_available"): def _configure_service_available(self, section_name="service_available"):
services = ["cinder", "glance", "heat", "ironic", "neutron", "nova", services = ["cinder", "glance", "heat", "ironic", "neutron", "nova",
@ -338,8 +339,8 @@ class TempestResourcesContext(utils.RandomNameGeneratorMixin):
self.conf = configparser.ConfigParser() self.conf = configparser.ConfigParser()
self.conf.read(conf_path) self.conf.read(conf_path)
self.image_name = parse.urlparse( self.data_dir = _create_or_get_data_dir()
CONF.tempest.img_url).path.split("/")[-1] self.image_name = "tempest-image"
self._created_roles = [] self._created_roles = []
self._created_images = [] self._created_images = []
@ -348,16 +349,19 @@ class TempestResourcesContext(utils.RandomNameGeneratorMixin):
def __enter__(self): def __enter__(self):
self._create_tempest_roles() self._create_tempest_roles()
self._configure_option("scenario", "img_file", self.image_name,
helper_method=self._download_image)
self._configure_option("compute", "image_ref", self._configure_option("compute", "image_ref",
self._discover_or_create_image) helper_method=self._discover_or_create_image)
self._configure_option("compute", "image_ref_alt", self._configure_option("compute", "image_ref_alt",
self._discover_or_create_image) helper_method=self._discover_or_create_image)
self._configure_option("compute", "flavor_ref", self._configure_option("compute", "flavor_ref",
self._discover_or_create_flavor, helper_method=self._discover_or_create_flavor,
CONF.tempest.flavor_ref_ram) flv_ram=CONF.tempest.flavor_ref_ram)
self._configure_option("compute", "flavor_ref_alt", self._configure_option("compute", "flavor_ref_alt",
self._discover_or_create_flavor, helper_method=self._discover_or_create_flavor,
CONF.tempest.flavor_ref_alt_ram) flv_ram=CONF.tempest.flavor_ref_alt_ram)
if "neutron" in self.available_services: if "neutron" in self.available_services:
neutronclient = self.clients.neutron() neutronclient = self.clients.neutron()
if neutronclient.list_networks(shared=True)["networks"]: if neutronclient.list_networks(shared=True)["networks"]:
@ -370,12 +374,14 @@ class TempestResourcesContext(utils.RandomNameGeneratorMixin):
# resources. # resources.
LOG.debug("Shared networks found. " LOG.debug("Shared networks found. "
"'fixed_network_name' option should be configured") "'fixed_network_name' option should be configured")
self._configure_option("compute", "fixed_network_name", self._configure_option(
self._create_network_resources) "compute", "fixed_network_name",
helper_method=self._create_network_resources)
if "heat" in self.available_services: if "heat" in self.available_services:
self._configure_option("orchestration", "instance_type", self._configure_option(
self._discover_or_create_flavor, "orchestration", "instance_type",
CONF.tempest.heat_instance_type_ram) helper_method=self._discover_or_create_flavor,
flv_ram=CONF.tempest.heat_instance_type_ram)
_write_config(self.conf_path, self.conf) _write_config(self.conf_path, self.conf)
@ -406,14 +412,46 @@ class TempestResourcesContext(utils.RandomNameGeneratorMixin):
LOG.debug("Creating role '%s'" % role) LOG.debug("Creating role '%s'" % role)
self._created_roles.append(keystoneclient.roles.create(role)) self._created_roles.append(keystoneclient.roles.create(role))
def _configure_option(self, section, option, def _discover_image(self):
create_method, *args, **kwargs): LOG.debug("Trying to discover a public image with name matching "
"regular expression '%s'. Note that case insensitive "
"matching is performed." % CONF.tempest.img_name_regex)
glance_wrapper = glance.wrap(self.clients.glance, self)
images = glance_wrapper.list_images(status="active",
visibility="public")
for image in images:
if image.name and re.match(CONF.tempest.img_name_regex,
image.name, re.IGNORECASE):
LOG.debug("The following public "
"image discovered: '%s'" % image.name)
return image
LOG.debug("There is no public image with name matching "
"regular expression '%s'" % CONF.tempest.img_name_regex)
def _download_image(self):
image_path = os.path.join(self.data_dir, self.image_name)
if os.path.isfile(image_path):
LOG.debug("Image is already downloaded to %s" % image_path)
return
if CONF.tempest.img_name_regex:
image = self._discover_image()
if image:
return _download_image(image_path, image)
_download_image(image_path)
def _configure_option(self, section, option, value=None,
helper_method=None, *args, **kwargs):
option_value = self.conf.get(section, option) option_value = self.conf.get(section, option)
if not option_value: if not option_value:
LOG.debug("Option '%s' from '%s' section " LOG.debug("Option '%s' from '%s' section "
"is not configured" % (option, section)) "is not configured" % (option, section))
resource = create_method(*args, **kwargs) if helper_method:
value = resource["name"] if "network" in option else resource.id res = helper_method(*args, **kwargs)
if res:
value = res["name"] if "network" in option else res.id
LOG.debug("Setting value '%s' for option '%s'" % (value, option)) LOG.debug("Setting value '%s' for option '%s'" % (value, option))
self.conf.set(section, option, value) self.conf.set(section, option, value)
LOG.debug("Option '{opt}' is configured. " LOG.debug("Option '{opt}' is configured. "
@ -424,35 +462,25 @@ class TempestResourcesContext(utils.RandomNameGeneratorMixin):
.format(opt=option, opt_val=option_value)) .format(opt=option, opt_val=option_value))
def _discover_or_create_image(self): def _discover_or_create_image(self):
glance_wrapper = glance.wrap(self.clients.glance, self)
if CONF.tempest.img_name_regex: if CONF.tempest.img_name_regex:
LOG.debug("Trying to discover a public image with name matching " image = self._discover_image()
"regular expression '%s'. Note that case insensitive " if image:
"matching is performed" % CONF.tempest.img_name_regex) LOG.debug("Using image '%s' (ID = %s) "
images = glance_wrapper.list_images(status="active", "for the tests" % (image.name, image.id))
visibility="public") return image
for img in images:
if img.name and re.match(CONF.tempest.img_name_regex,
img.name, re.IGNORECASE):
LOG.debug(
"The following public image discovered: '{0}'. "
"Using image '{0}' for the tests".format(img.name))
return img
LOG.debug("There is no public image with name matching "
"regular expression '%s'" % CONF.tempest.img_name_regex)
params = { params = {
"name": self.generate_random_name(), "name": self.generate_random_name(),
"disk_format": CONF.tempest.img_disk_format, "disk_format": CONF.tempest.img_disk_format,
"container_format": CONF.tempest.img_container_format, "container_format": CONF.tempest.img_container_format,
"image_location": os.path.join(_create_or_get_data_dir(), "image_location": os.path.join(self.data_dir, self.image_name),
self.image_name),
"visibility": "public" "visibility": "public"
} }
LOG.debug("Creating image '%s'" % params["name"]) LOG.debug("Creating image '%s'" % params["name"])
glance_wrapper = glance.wrap(self.clients.glance, self)
image = glance_wrapper.create_image(**params) image = glance_wrapper.create_image(**params)
LOG.debug("Image '%s' (ID = %s) has been "
"successfully created!" % (image.name, image.id))
self._created_images.append(image) self._created_images.append(image)
return image return image
@ -463,10 +491,11 @@ class TempestResourcesContext(utils.RandomNameGeneratorMixin):
LOG.debug("Trying to discover a flavor with the following " LOG.debug("Trying to discover a flavor with the following "
"properties: RAM = %dMB, VCPUs = 1, disk = 0GB" % flv_ram) "properties: RAM = %dMB, VCPUs = 1, disk = 0GB" % flv_ram)
for flavor in novaclient.flavors.list(): for flavor in novaclient.flavors.list():
if (flavor.ram == flv_ram if (flavor.ram == flv_ram and
and flavor.vcpus == 1 and flavor.disk == 0): flavor.vcpus == 1 and flavor.disk == 0):
LOG.debug("The following flavor discovered: '{0}'. Using " LOG.debug("The following flavor discovered: '{0}'. "
"flavor '{0}' for the tests".format(flavor.name)) "Using flavor '{0}' (ID = {1}) for the tests"
.format(flavor.name, flavor.id))
return flavor return flavor
LOG.debug("There is no flavor with the mentioned properties") LOG.debug("There is no flavor with the mentioned properties")
@ -480,6 +509,8 @@ class TempestResourcesContext(utils.RandomNameGeneratorMixin):
LOG.debug("Creating flavor '%s' with the following properties: RAM " LOG.debug("Creating flavor '%s' with the following properties: RAM "
"= %dMB, VCPUs = 1, disk = 0GB" % (params["name"], flv_ram)) "= %dMB, VCPUs = 1, disk = 0GB" % (params["name"], flv_ram))
flavor = novaclient.flavors.create(**params) flavor = novaclient.flavors.create(**params)
LOG.debug("Flavor '%s' (ID = %s) has been "
"successfully created!" % (flavor.name, flavor.id))
self._created_flavors.append(flavor) self._created_flavors.append(flavor)
return flavor return flavor
@ -491,6 +522,7 @@ class TempestResourcesContext(utils.RandomNameGeneratorMixin):
net = neutron_wrapper.create_network( net = neutron_wrapper.create_network(
tenant_id, subnets_num=1, add_router=True, tenant_id, subnets_num=1, add_router=True,
network_create_args={"shared": True}) network_create_args={"shared": True})
LOG.debug("Network resources have been successfully created!")
self._created_networks.append(net) self._created_networks.append(net)
return net return net
@ -500,6 +532,7 @@ class TempestResourcesContext(utils.RandomNameGeneratorMixin):
for role in self._created_roles: for role in self._created_roles:
LOG.debug("Deleting role '%s'" % role.name) LOG.debug("Deleting role '%s'" % role.name)
keystoneclient.roles.delete(role.id) keystoneclient.roles.delete(role.id)
LOG.debug("Role '%s' has been deleted" % role.name)
def _cleanup_images(self): def _cleanup_images(self):
glance_wrapper = glance.wrap(self.clients.glance, self) glance_wrapper = glance.wrap(self.clients.glance, self)
@ -513,6 +546,7 @@ class TempestResourcesContext(utils.RandomNameGeneratorMixin):
timeout=CONF.benchmark.glance_image_delete_timeout, timeout=CONF.benchmark.glance_image_delete_timeout,
check_interval=CONF.benchmark. check_interval=CONF.benchmark.
glance_image_delete_poll_interval) glance_image_delete_poll_interval)
LOG.debug("Image '%s' has been deleted" % image.name)
self._remove_opt_value_from_config("compute", image.id) self._remove_opt_value_from_config("compute", image.id)
def _cleanup_flavors(self): def _cleanup_flavors(self):
@ -520,6 +554,7 @@ class TempestResourcesContext(utils.RandomNameGeneratorMixin):
for flavor in self._created_flavors: for flavor in self._created_flavors:
LOG.debug("Deleting flavor '%s'" % flavor.name) LOG.debug("Deleting flavor '%s'" % flavor.name)
novaclient.flavors.delete(flavor.id) novaclient.flavors.delete(flavor.id)
LOG.debug("Flavor '%s' has been deleted" % flavor.name)
self._remove_opt_value_from_config("compute", flavor.id) self._remove_opt_value_from_config("compute", flavor.id)
self._remove_opt_value_from_config("orchestration", flavor.id) self._remove_opt_value_from_config("orchestration", flavor.id)
@ -529,6 +564,7 @@ class TempestResourcesContext(utils.RandomNameGeneratorMixin):
LOG.debug("Deleting network resources: router, subnet, network") LOG.debug("Deleting network resources: router, subnet, network")
neutron_wrapper.delete_network(net) neutron_wrapper.delete_network(net)
self._remove_opt_value_from_config("compute", net["name"]) self._remove_opt_value_from_config("compute", net["name"])
LOG.debug("Network resources have been deleted")
def _remove_opt_value_from_config(self, section, opt_value): def _remove_opt_value_from_config(self, section, opt_value):
for option, value in self.conf.items(section): for option, value in self.conf.items(section):
@ -536,3 +572,4 @@ class TempestResourcesContext(utils.RandomNameGeneratorMixin):
LOG.debug("Removing value '%s' for option '%s' " LOG.debug("Removing value '%s' for option '%s' "
"from Tempest config file" % (opt_value, option)) "from Tempest config file" % (opt_value, option))
self.conf.set(section, option, "") self.conf.set(section, option, "")
LOG.debug("Value '%s' has been removed" % opt_value)

View File

@ -46,7 +46,7 @@ EXPECTED_FAILURES = {
"This test fails because 'novnc' console type is unavailable." "This test fails because 'novnc' console type is unavailable."
} }
TEMPEST_PLUGIN = "https://github.com/MBonell/hello-world-tempest-plugin" TEMPEST_PLUGIN = "https://git.openstack.org/openstack/ceilometer"
# NOTE(andreykurilin): this variable is used to generate output file names # NOTE(andreykurilin): this variable is used to generate output file names
# with prefix ${CALL_COUNT}_ . # with prefix ${CALL_COUNT}_ .
@ -249,7 +249,7 @@ def main():
render_vars["reinstall"] = call_rally( render_vars["reinstall"] = call_rally(
"verify reinstall --version %s" % tempest_commit_id) "verify reinstall --version %s" % tempest_commit_id)
# Install a simple Tempest plugin # Install a Tempest plugin
render_vars["installplugin"] = call_rally( render_vars["installplugin"] = call_rally(
"verify installplugin --source %s" % TEMPEST_PLUGIN) "verify installplugin --source %s" % TEMPEST_PLUGIN)

View File

@ -20,7 +20,6 @@ import mock
from oslo_config import cfg from oslo_config import cfg
import requests import requests
import six import six
from six.moves.urllib import parse
from rally import exceptions from rally import exceptions
from rally.verification.tempest import config from rally.verification.tempest import config
@ -53,35 +52,9 @@ class TempestConfigTestCase(test.TestCase):
mock.patch("rally.common.objects.deploy.db.deployment_get", mock.patch("rally.common.objects.deploy.db.deployment_get",
return_value=CREDS).start() return_value=CREDS).start()
mock.patch("rally.osclients.Clients").start() mock.patch("rally.osclients.Clients").start()
self.mock_isfile = mock.patch("os.path.isfile",
return_value=True).start()
self.tempest_conf = config.TempestConfig("fake_deployment") self.tempest_conf = config.TempestConfig("fake_deployment")
@mock.patch("os.rename")
@mock.patch("six.moves.builtins.open", side_effect=mock.mock_open())
@mock.patch("requests.get", return_value=mock.MagicMock(status_code=200))
def test__download_image_success(self, mock_get,
mock_open, mock_rename):
self.mock_isfile.return_value = False
self.tempest_conf._download_image()
mock_get.assert_called_once_with(
CONF.tempest.img_url, stream=True)
@mock.patch("requests.get")
@ddt.data(404, 500)
def test__download_image_failure(self, status_code, mock_get):
self.mock_isfile.return_value = False
mock_get.return_value = mock.MagicMock(status_code=status_code)
self.assertRaises(exceptions.TempestConfigCreationFailure,
self.tempest_conf._download_image)
@mock.patch("requests.get", side_effect=requests.ConnectionError())
def test__download_image_connection_error(self, mock_requests_get):
self.mock_isfile.return_value = False
self.assertRaises(exceptions.TempestConfigCreationFailure,
self.tempest_conf._download_image)
@ddt.data({"publicURL": "test_url"}, @ddt.data({"publicURL": "test_url"},
{"interface": "public", "url": "test_url"}) {"interface": "public", "url": "test_url"})
def test__get_service_url(self, endpoint): def test__get_service_url(self, endpoint):
@ -223,10 +196,7 @@ class TempestConfigTestCase(test.TestCase):
def test__configure_scenario(self): def test__configure_scenario(self):
self.tempest_conf._configure_scenario() self.tempest_conf._configure_scenario()
image_name = parse.urlparse( expected = (("img_dir", self.tempest_conf.data_dir),)
config.CONF.tempest.img_url).path.split("/")[-1]
expected = (("img_dir", self.tempest_conf.data_dir),
("img_file", image_name))
result = self.tempest_conf.conf.items("scenario") result = self.tempest_conf.conf.items("scenario")
for item in expected: for item in expected:
self.assertIn(item, result) self.assertIn(item, result)
@ -284,6 +254,7 @@ class TempestConfigTestCase(test.TestCase):
conf_data.write.assert_called_once_with(mock_open.side_effect()) conf_data.write.assert_called_once_with(mock_open.side_effect())
@ddt.ddt
class TempestResourcesContextTestCase(test.TestCase): class TempestResourcesContextTestCase(test.TestCase):
def setUp(self): def setUp(self):
@ -292,6 +263,8 @@ class TempestResourcesContextTestCase(test.TestCase):
mock.patch("rally.common.objects.deploy.db.deployment_get", mock.patch("rally.common.objects.deploy.db.deployment_get",
return_value=CREDS).start() return_value=CREDS).start()
mock.patch("rally.osclients.Clients").start() mock.patch("rally.osclients.Clients").start()
self.mock_isfile = mock.patch("os.path.isfile",
return_value=True).start()
fake_verification = {"uuid": "uuid"} fake_verification = {"uuid": "uuid"}
self.context = config.TempestResourcesContext("fake_deployment", self.context = config.TempestResourcesContext("fake_deployment",
@ -299,6 +272,54 @@ class TempestResourcesContextTestCase(test.TestCase):
"/fake/path/to/config") "/fake/path/to/config")
self.context.conf.add_section("compute") self.context.conf.add_section("compute")
self.context.conf.add_section("orchestration") self.context.conf.add_section("orchestration")
self.context.conf.add_section("scenario")
@mock.patch("six.moves.builtins.open", side_effect=mock.mock_open(),
create=True)
def test__download_image_from_glance(self, mock_open):
self.mock_isfile.return_value = False
img_path = os.path.join(self.context.data_dir, "foo")
img = mock.MagicMock()
img.data.return_value = "data"
config._download_image(img_path, img)
mock_open.assert_called_once_with(img_path, "wb")
mock_open().write.assert_has_calls([mock.call("d"),
mock.call("a"),
mock.call("t"),
mock.call("a")])
@mock.patch("six.moves.builtins.open", side_effect=mock.mock_open())
@mock.patch("requests.get", return_value=mock.MagicMock(status_code=200))
def test__download_image_from_url_success(self, mock_get, mock_open):
self.mock_isfile.return_value = False
img_path = os.path.join(self.context.data_dir, "foo")
mock_get.return_value.iter_content.return_value = "data"
config._download_image(img_path)
mock_get.assert_called_once_with(CONF.tempest.img_url, stream=True)
mock_open.assert_called_once_with(img_path, "wb")
mock_open().write.assert_has_calls([mock.call("d"),
mock.call("a"),
mock.call("t"),
mock.call("a")])
@mock.patch("requests.get")
@ddt.data(404, 500)
def test__download_image_from_url_failure(self, status_code, mock_get):
self.mock_isfile.return_value = False
mock_get.return_value = mock.MagicMock(status_code=status_code)
self.assertRaises(
exceptions.TempestConfigCreationFailure, config._download_image,
os.path.join(self.context.data_dir, "foo"))
@mock.patch("requests.get", side_effect=requests.ConnectionError())
def test__download_image_from_url_connection_error(
self, mock_requests_get):
self.mock_isfile.return_value = False
self.assertRaises(
exceptions.TempestConfigCreationFailure, config._download_image,
os.path.join(self.context.data_dir, "foo"))
@mock.patch("rally.plugins.openstack.wrappers." @mock.patch("rally.plugins.openstack.wrappers."
"network.NeutronWrapper.create_network") "network.NeutronWrapper.create_network")
@ -313,6 +334,7 @@ class TempestResourcesContextTestCase(test.TestCase):
self.context.conf.set("compute", "flavor_ref_alt", "id4") self.context.conf.set("compute", "flavor_ref_alt", "id4")
self.context.conf.set("compute", "fixed_network_name", "name1") self.context.conf.set("compute", "fixed_network_name", "name1")
self.context.conf.set("orchestration", "instance_type", "id5") self.context.conf.set("orchestration", "instance_type", "id5")
self.context.conf.set("scenario", "img_file", "id6")
self.context.__enter__() self.context.__enter__()
@ -342,16 +364,45 @@ class TempestResourcesContextTestCase(test.TestCase):
self.assertIn(role3, created_roles) self.assertIn(role3, created_roles)
self.assertIn(role4, created_roles) self.assertIn(role4, created_roles)
@mock.patch("rally.plugins.openstack.wrappers.glance.wrap")
def test__discover_image(self, mock_wrap):
client = mock_wrap.return_value
client.list_images.return_value = [fakes.FakeImage(name="Foo"),
fakes.FakeImage(name="CirrOS")]
image = self.context._discover_image()
self.assertEqual("CirrOS", image.name)
@mock.patch("six.moves.builtins.open", side_effect=mock.mock_open(),
create=True)
@mock.patch("rally.plugins.openstack.wrappers.glance.wrap")
@mock.patch("os.path.isfile", return_value=False)
def test__download_image(self, mock_isfile, mock_wrap, mock_open):
img_1 = mock.MagicMock()
img_1.name = "Foo"
img_2 = mock.MagicMock()
img_2.name = "CirrOS"
img_2.data.return_value = "data"
mock_wrap.return_value.list_images.return_value = [img_1, img_2]
self.context._download_image()
img_path = os.path.join(self.context.data_dir, self.context.image_name)
mock_open.assert_called_once_with(img_path, "wb")
mock_open().write.assert_has_calls([mock.call("d"),
mock.call("a"),
mock.call("t"),
mock.call("a")])
# We can choose any option to test the '_configure_option' method. So let's # We can choose any option to test the '_configure_option' method. So let's
# configure the 'flavor_ref' option. # configure the 'flavor_ref' option.
def test__configure_option(self): def test__configure_option(self):
create_method = mock.MagicMock() helper_method = mock.MagicMock()
create_method.side_effect = [fakes.FakeFlavor(id="id1")] helper_method.side_effect = [fakes.FakeFlavor(id="id1")]
self.context.conf.set("compute", "flavor_ref", "") self.context.conf.set("compute", "flavor_ref", "")
self.context._configure_option("compute", self.context._configure_option("compute", "flavor_ref",
"flavor_ref", create_method, 64) helper_method=helper_method, flv_ram=64)
self.assertEqual(create_method.call_count, 1) self.assertEqual(helper_method.call_count, 1)
result = self.context.conf.get("compute", "flavor_ref") result = self.context.conf.get("compute", "flavor_ref")
self.assertEqual("id1", result) self.assertEqual("id1", result)
@ -374,8 +425,6 @@ class TempestResourcesContextTestCase(test.TestCase):
self.assertEqual(image, client.create_image.return_value) self.assertEqual(image, client.create_image.return_value)
self.assertEqual(self.context._created_images[0], self.assertEqual(self.context._created_images[0],
client.create_image.return_value) client.create_image.return_value)
mock_wrap.assert_called_once_with(self.context.clients.glance,
self.context)
client.create_image.assert_called_once_with( client.create_image.assert_called_once_with(
container_format=CONF.tempest.img_container_format, container_format=CONF.tempest.img_container_format,
image_location=mock.ANY, image_location=mock.ANY,