Merge "[Verify] Fixing issue with downloading images for the tests"

This commit is contained in:
Jenkins 2016-10-06 11:02:33 +00:00 committed by Gerrit Code Review
commit 538916bb1c
4 changed files with 201 additions and 114 deletions

View File

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

View File

@ -116,6 +116,41 @@ def _create_or_get_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):
with open(conf_path, "w+") as conf_file:
conf_data.write(conf_file)
@ -137,39 +172,6 @@ class TempestConfig(utils.RandomNameGeneratorMixin):
self.conf = configparser.ConfigParser()
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):
s_type = self._get_service_type_by_service_name(service_name)
available_endpoints = self.keystone.service_catalog.get_endpoints()
@ -284,7 +286,6 @@ class TempestConfig(utils.RandomNameGeneratorMixin):
def _configure_scenario(self, section_name="scenario"):
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"):
services = ["cinder", "glance", "heat", "ironic", "neutron", "nova",
@ -338,8 +339,8 @@ class TempestResourcesContext(utils.RandomNameGeneratorMixin):
self.conf = configparser.ConfigParser()
self.conf.read(conf_path)
self.image_name = parse.urlparse(
CONF.tempest.img_url).path.split("/")[-1]
self.data_dir = _create_or_get_data_dir()
self.image_name = "tempest-image"
self._created_roles = []
self._created_images = []
@ -348,16 +349,19 @@ class TempestResourcesContext(utils.RandomNameGeneratorMixin):
def __enter__(self):
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._discover_or_create_image)
helper_method=self._discover_or_create_image)
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._discover_or_create_flavor,
CONF.tempest.flavor_ref_ram)
helper_method=self._discover_or_create_flavor,
flv_ram=CONF.tempest.flavor_ref_ram)
self._configure_option("compute", "flavor_ref_alt",
self._discover_or_create_flavor,
CONF.tempest.flavor_ref_alt_ram)
helper_method=self._discover_or_create_flavor,
flv_ram=CONF.tempest.flavor_ref_alt_ram)
if "neutron" in self.available_services:
neutronclient = self.clients.neutron()
if neutronclient.list_networks(shared=True)["networks"]:
@ -370,12 +374,14 @@ class TempestResourcesContext(utils.RandomNameGeneratorMixin):
# resources.
LOG.debug("Shared networks found. "
"'fixed_network_name' option should be configured")
self._configure_option("compute", "fixed_network_name",
self._create_network_resources)
self._configure_option(
"compute", "fixed_network_name",
helper_method=self._create_network_resources)
if "heat" in self.available_services:
self._configure_option("orchestration", "instance_type",
self._discover_or_create_flavor,
CONF.tempest.heat_instance_type_ram)
self._configure_option(
"orchestration", "instance_type",
helper_method=self._discover_or_create_flavor,
flv_ram=CONF.tempest.heat_instance_type_ram)
_write_config(self.conf_path, self.conf)
@ -406,14 +412,46 @@ class TempestResourcesContext(utils.RandomNameGeneratorMixin):
LOG.debug("Creating role '%s'" % role)
self._created_roles.append(keystoneclient.roles.create(role))
def _configure_option(self, section, option,
create_method, *args, **kwargs):
def _discover_image(self):
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)
if not option_value:
LOG.debug("Option '%s' from '%s' section "
"is not configured" % (option, section))
resource = create_method(*args, **kwargs)
value = resource["name"] if "network" in option else resource.id
if helper_method:
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))
self.conf.set(section, option, value)
LOG.debug("Option '{opt}' is configured. "
@ -424,35 +462,25 @@ class TempestResourcesContext(utils.RandomNameGeneratorMixin):
.format(opt=option, opt_val=option_value))
def _discover_or_create_image(self):
glance_wrapper = glance.wrap(self.clients.glance, self)
if CONF.tempest.img_name_regex:
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)
images = glance_wrapper.list_images(status="active",
visibility="public")
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)
image = self._discover_image()
if image:
LOG.debug("Using image '%s' (ID = %s) "
"for the tests" % (image.name, image.id))
return image
params = {
"name": self.generate_random_name(),
"disk_format": CONF.tempest.img_disk_format,
"container_format": CONF.tempest.img_container_format,
"image_location": os.path.join(_create_or_get_data_dir(),
self.image_name),
"image_location": os.path.join(self.data_dir, self.image_name),
"visibility": "public"
}
LOG.debug("Creating image '%s'" % params["name"])
glance_wrapper = glance.wrap(self.clients.glance, self)
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)
return image
@ -463,10 +491,11 @@ class TempestResourcesContext(utils.RandomNameGeneratorMixin):
LOG.debug("Trying to discover a flavor with the following "
"properties: RAM = %dMB, VCPUs = 1, disk = 0GB" % flv_ram)
for flavor in novaclient.flavors.list():
if (flavor.ram == flv_ram
and flavor.vcpus == 1 and flavor.disk == 0):
LOG.debug("The following flavor discovered: '{0}'. Using "
"flavor '{0}' for the tests".format(flavor.name))
if (flavor.ram == flv_ram and
flavor.vcpus == 1 and flavor.disk == 0):
LOG.debug("The following flavor discovered: '{0}'. "
"Using flavor '{0}' (ID = {1}) for the tests"
.format(flavor.name, flavor.id))
return flavor
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 "
"= %dMB, VCPUs = 1, disk = 0GB" % (params["name"], flv_ram))
flavor = novaclient.flavors.create(**params)
LOG.debug("Flavor '%s' (ID = %s) has been "
"successfully created!" % (flavor.name, flavor.id))
self._created_flavors.append(flavor)
return flavor
@ -491,6 +522,7 @@ class TempestResourcesContext(utils.RandomNameGeneratorMixin):
net = neutron_wrapper.create_network(
tenant_id, subnets_num=1, add_router=True,
network_create_args={"shared": True})
LOG.debug("Network resources have been successfully created!")
self._created_networks.append(net)
return net
@ -500,6 +532,7 @@ class TempestResourcesContext(utils.RandomNameGeneratorMixin):
for role in self._created_roles:
LOG.debug("Deleting role '%s'" % role.name)
keystoneclient.roles.delete(role.id)
LOG.debug("Role '%s' has been deleted" % role.name)
def _cleanup_images(self):
glance_wrapper = glance.wrap(self.clients.glance, self)
@ -513,6 +546,7 @@ class TempestResourcesContext(utils.RandomNameGeneratorMixin):
timeout=CONF.benchmark.glance_image_delete_timeout,
check_interval=CONF.benchmark.
glance_image_delete_poll_interval)
LOG.debug("Image '%s' has been deleted" % image.name)
self._remove_opt_value_from_config("compute", image.id)
def _cleanup_flavors(self):
@ -520,6 +554,7 @@ class TempestResourcesContext(utils.RandomNameGeneratorMixin):
for flavor in self._created_flavors:
LOG.debug("Deleting flavor '%s'" % flavor.name)
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("orchestration", flavor.id)
@ -529,6 +564,7 @@ class TempestResourcesContext(utils.RandomNameGeneratorMixin):
LOG.debug("Deleting network resources: router, subnet, network")
neutron_wrapper.delete_network(net)
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):
for option, value in self.conf.items(section):
@ -536,3 +572,4 @@ class TempestResourcesContext(utils.RandomNameGeneratorMixin):
LOG.debug("Removing value '%s' for option '%s' "
"from Tempest config file" % (opt_value, 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."
}
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
# with prefix ${CALL_COUNT}_ .
@ -249,7 +249,7 @@ def main():
render_vars["reinstall"] = call_rally(
"verify reinstall --version %s" % tempest_commit_id)
# Install a simple Tempest plugin
# Install a Tempest plugin
render_vars["installplugin"] = call_rally(
"verify installplugin --source %s" % TEMPEST_PLUGIN)

View File

@ -20,7 +20,6 @@ import mock
from oslo_config import cfg
import requests
import six
from six.moves.urllib import parse
from rally import exceptions
from rally.verification.tempest import config
@ -53,35 +52,9 @@ class TempestConfigTestCase(test.TestCase):
mock.patch("rally.common.objects.deploy.db.deployment_get",
return_value=CREDS).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")
@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"},
{"interface": "public", "url": "test_url"})
def test__get_service_url(self, endpoint):
@ -223,10 +196,7 @@ class TempestConfigTestCase(test.TestCase):
def test__configure_scenario(self):
self.tempest_conf._configure_scenario()
image_name = parse.urlparse(
config.CONF.tempest.img_url).path.split("/")[-1]
expected = (("img_dir", self.tempest_conf.data_dir),
("img_file", image_name))
expected = (("img_dir", self.tempest_conf.data_dir),)
result = self.tempest_conf.conf.items("scenario")
for item in expected:
self.assertIn(item, result)
@ -284,6 +254,7 @@ class TempestConfigTestCase(test.TestCase):
conf_data.write.assert_called_once_with(mock_open.side_effect())
@ddt.ddt
class TempestResourcesContextTestCase(test.TestCase):
def setUp(self):
@ -292,6 +263,8 @@ class TempestResourcesContextTestCase(test.TestCase):
mock.patch("rally.common.objects.deploy.db.deployment_get",
return_value=CREDS).start()
mock.patch("rally.osclients.Clients").start()
self.mock_isfile = mock.patch("os.path.isfile",
return_value=True).start()
fake_verification = {"uuid": "uuid"}
self.context = config.TempestResourcesContext("fake_deployment",
@ -299,6 +272,54 @@ class TempestResourcesContextTestCase(test.TestCase):
"/fake/path/to/config")
self.context.conf.add_section("compute")
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."
"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", "fixed_network_name", "name1")
self.context.conf.set("orchestration", "instance_type", "id5")
self.context.conf.set("scenario", "img_file", "id6")
self.context.__enter__()
@ -342,16 +364,45 @@ class TempestResourcesContextTestCase(test.TestCase):
self.assertIn(role3, 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
# configure the 'flavor_ref' option.
def test__configure_option(self):
create_method = mock.MagicMock()
create_method.side_effect = [fakes.FakeFlavor(id="id1")]
helper_method = mock.MagicMock()
helper_method.side_effect = [fakes.FakeFlavor(id="id1")]
self.context.conf.set("compute", "flavor_ref", "")
self.context._configure_option("compute",
"flavor_ref", create_method, 64)
self.assertEqual(create_method.call_count, 1)
self.context._configure_option("compute", "flavor_ref",
helper_method=helper_method, flv_ram=64)
self.assertEqual(helper_method.call_count, 1)
result = self.context.conf.get("compute", "flavor_ref")
self.assertEqual("id1", result)
@ -374,8 +425,6 @@ class TempestResourcesContextTestCase(test.TestCase):
self.assertEqual(image, client.create_image.return_value)
self.assertEqual(self.context._created_images[0],
client.create_image.return_value)
mock_wrap.assert_called_once_with(self.context.clients.glance,
self.context)
client.create_image.assert_called_once_with(
container_format=CONF.tempest.img_container_format,
image_location=mock.ANY,