-# Copyright (c) 2016 Cable Television Laboratories, Inc. ("CableLabs")
+# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs")
# and others. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# See the License for the specific language governing permissions and
# limitations under the License.
import logging
+import os
+import uuid
from snaps import file_utils
from glanceclient.client import Client
+
+from snaps.domain.image import Image
from snaps.openstack.utils import keystone_utils
__author__ = 'spisarski'
logger = logging.getLogger('glance_utils')
+VERSION_1 = 1.0
+VERSION_2 = 2.0
+
"""
Utilities for basic neutron API calls
"""
-def glance_client(os_creds):
+def glance_client(os_creds, session=None):
"""
Creates and returns a glance client object
+ :param os_creds: the credentials for connecting to the OpenStack remote API
+ :param session: the keystone session object (optional)
:return: the glance client
"""
- return Client(version=os_creds.image_api_version, session=keystone_utils.keystone_session(os_creds))
+ if not session:
+ session = keystone_utils.keystone_session(os_creds)
+ return Client(version=os_creds.image_api_version,
+ session=session,
+ region_name=os_creds.region_name)
-def get_image(nova, glance, image_name):
+
+def get_image(glance, image_name=None, image_settings=None):
"""
Returns an OpenStack image object for a given name
- :param nova: the Nova client
:param glance: the Glance client
:param image_name: the image name to lookup
+ :param image_settings: the image settings used for lookups
:return: the image object or None
"""
- try:
- image_dict = nova.images.find(name=image_name)
- if image_dict:
- return glance.images.get(image_dict.id)
- except:
- pass
- return None
+ img_filter = dict()
+ if image_settings:
+ if image_settings.exists:
+ img_filter = {'name': image_settings.name}
+ else:
+ img_filter = {'name': image_settings.name,
+ 'disk_format': image_settings.format}
+ elif image_name:
+ img_filter = {'name': image_name}
+
+ images = glance.images.list(**{'filters': img_filter})
+ for image in images:
+ if glance.version == VERSION_1:
+ image = glance.images.get(image.id)
+ return Image(name=image.name, image_id=image.id,
+ size=image.size, properties=image.properties)
+ elif glance.version == VERSION_2:
+ return Image(
+ name=image['name'], image_id=image['id'],
+ size=image['size'], properties=image.get('properties'))
+
+
+def get_image_by_id(glance, image_id):
+ """
+ Returns an OpenStack image object for a given name
+ :param glance: the Glance client
+ :param image_id: the image ID to lookup
+ :return: the SNAPS-OO Domain Image object or None
+ """
+ image = glance.images.get(image_id)
+ return Image(
+ name=image['name'], image_id=image['id'],
+ size=image['size'], properties=image.get('properties'))
+
+
+def get_image_status(glance, image):
+ """
+ Returns a new OpenStack Image object for a given OpenStack image object
+ :param glance: the Glance client
+ :param image: the domain Image object
+ :return: the OpenStack Image object
+ """
+ if glance.version == VERSION_1:
+ os_image = glance.images.get(image.id)
+ return os_image.status
+ elif glance.version == VERSION_2:
+ os_image = glance.images.get(image.id)
+ return os_image['status']
+ else:
+ raise GlanceException('Unsupported glance client version')
def create_image(glance, image_settings):
:return: the OpenStack image object
:raise Exception if using a file and it cannot be found
"""
- if image_settings.url:
- return glance.images.create(name=image_settings.name, disk_format=image_settings.format,
- container_format="bare", location=image_settings.url)
- elif image_settings.image_file:
- image_file = file_utils.get_file(image_settings.image_file)
- return glance.images.create(name=image_settings.name, disk_format=image_settings.format,
- container_format="bare", data=image_file)
+ if glance.version == VERSION_1:
+ return __create_image_v1(glance, image_settings)
+ elif glance.version == VERSION_2:
+ return __create_image_v2(glance, image_settings)
+ else:
+ raise GlanceException('Unsupported glance client version')
+
+
+def __create_image_v1(glance, image_settings):
+ """
+ Creates and returns OpenStack image object with an external URL
+ :param glance: the glance client
+ :param image_settings: the image settings object
+ :return: the OpenStack image object
+ :raise exceptions from the Glance client or IOError when opening a file
+ """
+ kwargs = {
+ 'name': image_settings.name, 'disk_format': image_settings.format,
+ 'container_format': 'bare', 'is_public': image_settings.public}
+
+ image_file = None
+
+ try:
+ if image_settings.extra_properties:
+ kwargs['properties'] = image_settings.extra_properties
+
+ if image_settings.url:
+ kwargs['location'] = image_settings.url
+ elif image_settings.image_file:
+ image_file = open(image_settings.image_file, 'rb')
+ kwargs['data'] = image_file
+ else:
+ logger.warn(
+ 'Unable to create image with name - %s. No file or URL',
+ image_settings.name)
+ return None
+
+ created_image = glance.images.create(**kwargs)
+ return Image(name=image_settings.name, image_id=created_image.id,
+ size=created_image.size,
+ properties=created_image.properties)
+ finally:
+ if image_file:
+ image_file.close()
+
+
+def __create_image_v2(glance, image_settings):
+ """
+ Creates and returns OpenStack image object with an external URL
+ :param glance: the glance client v2
+ :param image_settings: the image settings object
+ :return: the OpenStack image object
+ :raise GlanceException or IOException or URLError
+ """
+ cleanup_temp_file = False
+ image_file = None
+ if image_settings.image_file is not None:
+ image_filename = image_settings.image_file
+ elif image_settings.url:
+ file_name = str(uuid.uuid4())
+ try:
+ image_file = file_utils.download(
+ image_settings.url, './tmp', file_name)
+ image_filename = image_file.name
+ except:
+ if image_file:
+ os.remove(image_file.name)
+ raise
+
+ cleanup_temp_file = True
+ else:
+ raise GlanceException('Filename or URL of image not configured')
+
+ os_image = None
+ try:
+ kwargs = dict()
+ kwargs['name'] = image_settings.name
+ kwargs['disk_format'] = image_settings.format
+ kwargs['container_format'] = 'bare'
+
+ if image_settings.public:
+ kwargs['visibility'] = 'public'
+
+ if image_settings.extra_properties:
+ kwargs.update(image_settings.extra_properties)
+
+ os_image = glance.images.create(**kwargs)
+ image_file = open(os.path.expanduser(image_filename), 'rb')
+ glance.images.upload(os_image['id'], image_file)
+ except:
+ logger.error('Unexpected exception creating image. Rolling back')
+ if os_image:
+ delete_image(glance, Image(
+ name=os_image['name'], image_id=os_image['id'],
+ size=os_image['size'], properties=os_image.get('properties')))
+ raise
+ finally:
+ if image_file:
+ logger.debug('Closing file %s', image_file.name)
+ image_file.close()
+ if cleanup_temp_file:
+ logger.info('Removing file %s', image_file.name)
+ os.remove(image_filename)
+
+ return get_image_by_id(glance, os_image['id'])
def delete_image(glance, image):
:param glance: the glance client
:param image: the image to delete
"""
- glance.images.delete(image)
+ logger.info('Deleting image named - %s', image.name)
+ glance.images.delete(image.id)
+
+
+class GlanceException(Exception):
+ """
+ Exception when calls to the Glance client cannot be served properly
+ """