Make glance v2 the default

This adds some compatibility to ensure that both Glance wrappers
support both is_public and visibility kwargs.

Co-Authored-By: Andrey Kurilin <andr.kurilin@gmail.com>

Change-Id: If27fffe83b7f610c802fcb29c7442801ac6ca908
This commit is contained in:
Chris St. Pierre
2016-04-18 11:41:03 -05:00
committed by Li Yingjun
parent 75983b70a7
commit 51254b7811
11 changed files with 120 additions and 53 deletions

View File

@@ -123,7 +123,7 @@
images_per_tenant: 1
image_name: "image-context-test"
image_args:
is_public: True
visibility: "public"
sla:
failure_rate:
max: 0

View File

@@ -680,7 +680,7 @@
images_per_tenant: 1
image_name: "rally-named-image-from-context"
image_args:
is_public: True
visibility: "public"
sla:
failure_rate:
max: 0

View File

@@ -880,6 +880,9 @@
users:
tenants: 2
users_per_tenant: 3
api_versions:
glance:
version: 1
sla:
failure_rate:
max: 0

View File

@@ -102,6 +102,10 @@ class ImageGenerator(context.Context):
LOG.warning("The 'min_disk' argument is deprecated; specify "
"arbitrary arguments with 'image_args' instead")
kwargs["min_disk"] = self.config["min_disk"]
if "is_public" in kwargs:
LOG.warning("The 'is_public' argument is deprecated since "
"Rally 0.8.0; specify visibility arguments "
"instead")
for i in range(images_per_tenant):
if image_name and i > 0:

View File

@@ -26,7 +26,8 @@ LOG = logging.getLogger(__name__)
"""Scenarios for Glance images."""
@types.convert(image_location={"type": "path_or_url"})
@types.convert(image_location={"type": "path_or_url"},
kwargs={"type": "glance_image_args"})
@validation.required_services(consts.Service.GLANCE)
@validation.required_openstack(users=True)
@scenario.configure(context={"cleanup": ["glance"]},
@@ -79,6 +80,8 @@ class ListImages(utils.GlanceScenario, nova_utils.NovaScenario):
self._list_images()
@types.convert(image_location={"type": "path_or_url"},
kwargs={"type": "glance_image_args"})
@validation.required_services(consts.Service.GLANCE)
@validation.required_openstack(users=True)
@scenario.configure(context={"cleanup": ["glance"]},
@@ -102,7 +105,9 @@ class CreateAndDeleteImage(utils.GlanceScenario, nova_utils.NovaScenario):
self._delete_image(image)
@types.convert(flavor={"type": "nova_flavor"})
@types.convert(flavor={"type": "nova_flavor"},
image_location={"type": "path_or_url"},
kwargs={"type": "glance_image_args"})
@validation.flavor_exists("flavor")
@validation.required_services(consts.Service.GLANCE, consts.Service.NOVA)
@validation.required_openstack(users=True)

View File

@@ -12,6 +12,8 @@
# License for the specific language governing permissions and limitations
# under the License.
import copy
from rally.common.plugin import plugin
from rally import exceptions
from rally.task import types
@@ -87,6 +89,29 @@ class GlanceImage(types.ResourceType):
return resource_id
@plugin.configure(name="glance_image_args")
class GlanceImageArguments(types.ResourceType):
@classmethod
def transform(cls, clients, resource_config):
"""Transform the resource config to id.
:param clients: openstack admin client handles
:param resource_config: scenario config with `id`, `name` or `regex`
:returns: id matching resource
"""
resource_config = copy.deepcopy(resource_config)
if "is_public" in resource_config:
if "visibility" in resource_config:
resource_config.pop("is_public")
else:
visibility = ("public" if resource_config.pop("is_public")
else "private")
resource_config["visibility"] = visibility
return resource_config
@plugin.configure(name="ec2_image")
class EC2Image(types.ResourceType):
@@ -213,4 +238,4 @@ class WatcherGoal(types.ResourceType):
resource_config.get("name"))],
typename="goal",
id_attr="uuid")
return resource_id
return resource_id

View File

@@ -116,41 +116,6 @@ 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_configfile(path, conf_object):
with open(path, "w") as configfile:
conf_object.write(configfile)
@@ -448,6 +413,40 @@ class TempestResourcesContext(context.VerifierContext):
LOG.debug("There is no public image with name matching "
"regular expression '%s'" % CONF.tempest.img_name_regex)
def _do_download_image(self, 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 self.clients.glance().images.data(image.id):
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 _download_image(self):
image_path = os.path.join(self.data_dir, self.image_name)
if os.path.isfile(image_path):
@@ -457,9 +456,9 @@ class TempestResourcesContext(context.VerifierContext):
if CONF.tempest.img_name_regex:
image = self._discover_image()
if image:
return _download_image(image_path, image)
return self._do_download_image(image_path, image)
_download_image(image_path)
self._do_download_image(image_path)
def _configure_option(self, section, option, value=None,
helper_method=None, *args, **kwargs):

View File

@@ -158,6 +158,10 @@ class GlanceV2Wrapper(GlanceWrapper):
kw.update(kwargs)
if "name" not in kw:
kw["name"] = self.owner.generate_random_name()
if "is_public" in kw:
LOG.warning("is_public is not supported by Glance v2, and is "
"deprecated in Rally v0.8.0")
kw["visibility"] = "public" if kw.pop("is_public") else "private"
image_location = os.path.expanduser(image_location)

View File

@@ -177,6 +177,23 @@ class GlanceImageTestCase(test.TestCase):
resource_config)
class GlanceImageArgsTestCase(test.TestCase):
def test_transform(self):
self.assertEqual({}, types.GlanceImageArguments.transform(
clients=None, resource_config={}))
self.assertEqual(
{"visibility": "public"}, types.GlanceImageArguments.transform(
clients=None, resource_config={"visibility": "public"}))
self.assertEqual(
{"visibility": "public"}, types.GlanceImageArguments.transform(
clients=None, resource_config={"visibility": "public",
"is_public": False}))
self.assertEqual(
{"visibility": "private"}, types.GlanceImageArguments.transform(
clients=None, resource_config={"is_public": False}))
class EC2ImageTestCase(test.TestCase):
def setUp(self):

View File

@@ -270,10 +270,12 @@ class TempestResourcesContextTestCase(test.TestCase):
self.mock_isfile.return_value = False
img_path = os.path.join(self.context.data_dir, "foo")
img = mock.MagicMock()
img.data.return_value = "data"
glanceclient = self.context.clients.glance()
glanceclient.images.data.return_value = "data"
config._download_image(img_path, img)
self.context._do_download_image(img_path, img)
mock_open.assert_called_once_with(img_path, "wb")
glanceclient.images.data.assert_called_once_with(img.id)
mock_open().write.assert_has_calls([mock.call("d"),
mock.call("a"),
mock.call("t"),
@@ -286,7 +288,7 @@ class TempestResourcesContextTestCase(test.TestCase):
img_path = os.path.join(self.context.data_dir, "foo")
mock_get.return_value.iter_content.return_value = "data"
config._download_image(img_path)
self.context._do_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"),
@@ -300,7 +302,8 @@ class TempestResourcesContextTestCase(test.TestCase):
self.mock_isfile.return_value = False
mock_get.return_value = mock.MagicMock(status_code=status_code)
self.assertRaises(
exceptions.TempestConfigCreationFailure, config._download_image,
exceptions.TempestConfigCreationFailure,
self.context._do_download_image,
os.path.join(self.context.data_dir, "foo"))
@mock.patch("requests.get", side_effect=requests.ConnectionError())
@@ -308,7 +311,8 @@ class TempestResourcesContextTestCase(test.TestCase):
self, mock_requests_get):
self.mock_isfile.return_value = False
self.assertRaises(
exceptions.TempestConfigCreationFailure, config._download_image,
exceptions.TempestConfigCreationFailure,
self.context._do_download_image,
os.path.join(self.context.data_dir, "foo"))
@mock.patch("rally.plugins.openstack.wrappers."
@@ -372,11 +376,15 @@ class TempestResourcesContextTestCase(test.TestCase):
img_1.name = "Foo"
img_2 = mock.MagicMock()
img_2.name = "CirrOS"
img_2.data.return_value = "data"
glanceclient = self.context.clients.glance()
glanceclient.images.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_wrap.return_value.list_images.assert_called_once_with(
status="active", visibility="public")
glanceclient.images.data.assert_called_once_with(img_2.id)
mock_open.assert_called_once_with(img_path, "wb")
mock_open().write.assert_has_calls([mock.call("d"),
mock.call("a"),

View File

@@ -197,12 +197,14 @@ class GlanceV2WrapperTestCase(test.ScenarioTestCase):
{"location": "image_location", "visibility": "private"},
{"location": "image_location", "fakearg": "fake"},
{"location": "image_location", "name": "image_name"},
{"location": _tempfile.name, "visibility": "public"})
{"location": _tempfile.name, "visibility": "public"},
{"location": "image_location",
"expected_kwargs": {"visibility": "public"}, "is_public": True})
@ddt.unpack
@mock.patch("six.moves.builtins.open")
@mock.patch("requests.get")
def test_create_image(self, mock_requests_get, mock_open, location,
**kwargs):
expected_kwargs=None, **kwargs):
self.wrapped_client.get_image = mock.Mock()
created_image = mock.Mock()
uploaded_image = mock.Mock()
@@ -213,11 +215,11 @@ class GlanceV2WrapperTestCase(test.ScenarioTestCase):
location,
"disk_format",
**kwargs)
create_args = kwargs
create_args = expected_kwargs or kwargs
create_args["container_format"] = "container_format"
create_args["disk_format"] = "disk_format"
if "name" not in kwargs:
create_args["name"] = self.owner.generate_random_name.return_value
create_args.setdefault("name",
self.owner.generate_random_name.return_value)
self.client().images.create.assert_called_once_with(**create_args)