# Copyright 2016: 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 abc import os import time from glanceclient import exc as glance_exc import requests import six from rally.common import cfg from rally.common import logging from rally.common import utils as rutils from rally import exceptions from rally.task import utils LOG = logging.getLogger(__name__) CONF = cfg.CONF @six.add_metaclass(abc.ABCMeta) class GlanceWrapper(object): def __init__(self, client, owner): self.owner = owner self.client = client def get_image(self, image): """Gets image. This serves to fetch the latest data on the image for the various wait_for_*() functions. Must raise rally.exceptions.GetResourceNotFound if the resource is not found or deleted. """ # NOTE(stpierre): This function actually has a single # implementation that works for both Glance v1 and Glance v2, # but since we need to use this function in both wrappers, it # gets implemented here. try: return self.client.images.get(image.id) except glance_exc.HTTPNotFound: raise exceptions.GetResourceNotFound(resource=image) @abc.abstractmethod def create_image(self, container_format, image_location, disk_format): """Creates new image. Accepts all Glance v2 parameters. """ @abc.abstractmethod def set_visibility(self, image, visibility="public"): """Set an existing image to public or private.""" @abc.abstractmethod def list_images(self, **filters): """List images. Accepts all Glance v2 filters. """ class GlanceV1Wrapper(GlanceWrapper): def create_image(self, container_format, image_location, disk_format, **kwargs): kw = { "container_format": container_format, "disk_format": disk_format, } kw.update(kwargs) if "name" not in kw: kw["name"] = self.owner.generate_random_name() if "visibility" in kw: kw["is_public"] = kw.pop("visibility") == "public" image_location = os.path.expanduser(image_location) try: if os.path.isfile(image_location): kw["data"] = open(image_location) else: kw["copy_from"] = image_location image = self.client.images.create(**kw) rutils.interruptable_sleep(CONF.openstack. glance_image_create_prepoll_delay) image = utils.wait_for_status( image, ["active"], update_resource=self.get_image, timeout=CONF.openstack.glance_image_create_timeout, check_interval=CONF.openstack. glance_image_create_poll_interval) finally: if "data" in kw: kw["data"].close() return image def set_visibility(self, image, visibility="public"): self.client.images.update(image.id, is_public=(visibility == "public")) def list_images(self, **filters): kwargs = {"filters": filters} if "owner" in filters: # NOTE(stpierre): in glance v1, "owner" is not a filter, # so we need to handle it separately. kwargs["owner"] = kwargs["filters"].pop("owner") visibility = kwargs["filters"].pop("visibility", None) images = self.client.images.list(**kwargs) # NOTE(stpierre): Glance v1 isn't smart enough to filter on # public/private images, so we have to do it manually. if visibility is not None: is_public = visibility == "public" return [i for i in images if i.is_public is is_public] return images class GlanceV2Wrapper(GlanceWrapper): def create_image(self, container_format, image_location, disk_format, **kwargs): kw = { "container_format": container_format, "disk_format": disk_format, } 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) image = self.client.images.create(**kw) rutils.interruptable_sleep(CONF.openstack. glance_image_create_prepoll_delay) start = time.time() image = utils.wait_for_status( image, ["queued"], update_resource=self.get_image, timeout=CONF.openstack.glance_image_create_timeout, check_interval=CONF.openstack. glance_image_create_poll_interval) timeout = time.time() - start image_data = None response = None try: if os.path.isfile(image_location): image_data = open(image_location) else: response = requests.get(image_location, stream=True) image_data = response.raw self.client.images.upload(image.id, image_data) finally: if image_data is not None: image_data.close() if response is not None: response.close() return utils.wait_for_status( image, ["active"], update_resource=self.get_image, timeout=timeout, check_interval=CONF.openstack. glance_image_create_poll_interval) def set_visibility(self, image, visibility="public"): self.client.images.update(image.id, visibility=visibility) def list_images(self, **filters): return self.client.images.list(filters=filters) def wrap(client, owner): """Returns glanceclient wrapper based on glance client version.""" LOG.warning("Method wrap from %s and whole Glance wrappers are " "deprecated since Rally 0.10.0 and will be removed soon. Use " "rally.plugins.openstack.services.image.image.Image " "instead." % __file__) version = client.choose_version() if version == "1": return GlanceV1Wrapper(client(), owner) elif version == "2": return GlanceV2Wrapper(client(), owner) else: msg = "Version %s of the glance API could not be identified." % version LOG.warning(msg) raise exceptions.InvalidArgumentsException(msg)