1 # Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs")
2 # and others. All rights reserved.
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at:
8 # http://www.apache.org/licenses/LICENSE-2.0
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
16 from glanceclient.exc import HTTPNotFound
20 from snaps.openstack.openstack_creator import OpenStackCloudObject
21 from snaps.openstack.utils import glance_utils
22 from snaps.config import image
24 __author__ = 'spisarski'
26 logger = logging.getLogger('create_image')
28 IMAGE_ACTIVE_TIMEOUT = 600
30 STATUS_ACTIVE = 'active'
33 class OpenStackImage(OpenStackCloudObject):
35 Class responsible for managing an image in OpenStack
38 def __init__(self, os_creds, image_settings):
41 :param os_creds: The OpenStack connection credentials
42 :param image_settings: An snaps.config.image.ImageConfig object
44 super(self.__class__, self).__init__(os_creds)
46 self.image_settings = image_settings
48 self.__kernel_image = None
49 self.__ramdisk_image = None
54 Loads the existing Image
55 :return: The Image domain object or None
57 self.__glance = glance_utils.glance_client(self._os_creds)
58 self.__image = glance_utils.get_image(
59 self.__glance, image_settings=self.image_settings)
61 logger.info('Found image with name - ' + self.image_settings.name)
63 elif (self.image_settings.exists and not self.image_settings.url
64 and not self.image_settings.image_file):
65 raise ImageCreationError(
66 'Image with does not exist with name - ' +
67 self.image_settings.name)
69 if self.image_settings.kernel_image_settings:
70 self.__kernel_image = glance_utils.get_image(
72 image_settings=self.image_settings.kernel_image_settings)
73 if self.image_settings.ramdisk_image_settings:
74 self.__ramdisk_image = glance_utils.get_image(
76 image_settings=self.image_settings.ramdisk_image_settings)
82 Creates the image in OpenStack if it does not already exist and returns
83 the domain Image object
84 :return: The Image domain object or None
89 extra_properties = self.image_settings.extra_properties or dict()
91 if self.image_settings.kernel_image_settings:
92 if not self.__kernel_image:
94 'Creating associated kernel image with name - %s',
95 self.image_settings.kernel_image_settings.name)
96 self.__kernel_image = glance_utils.create_image(
98 self.image_settings.kernel_image_settings)
99 extra_properties['kernel_id'] = self.__kernel_image.id
100 if self.image_settings.ramdisk_image_settings:
101 if not self.__ramdisk_image:
103 'Creating associated ramdisk image with name - %s',
104 self.image_settings.ramdisk_image_settings.name)
105 self.__ramdisk_image = glance_utils.create_image(
107 self.image_settings.ramdisk_image_settings)
108 extra_properties['ramdisk_id'] = self.__ramdisk_image.id
110 self.image_settings.extra_properties = extra_properties
111 self.__image = glance_utils.create_image(self.__glance,
115 'Created image with name - %s', self.image_settings.name)
116 if self.__image and self.image_active(block=True):
118 'Image is now active with name - %s',
119 self.image_settings.name)
122 raise ImageCreationError(
123 'Image was not created or activated in the alloted amount'
126 logger.info('Did not create image due to cleanup mode')
132 Cleanse environment of all artifacts
135 for img in [self.__image, self.__kernel_image, self.__ramdisk_image]:
138 glance_utils.delete_image(self.__glance, img)
143 self.__kernel_image = None
144 self.__ramdisk_image = None
148 Returns the domain Image object as it was populated when create() was
154 def get_kernel_image(self):
156 Returns the OpenStack kernel image object as it was populated when
160 return self.__kernel_image
162 def get_ramdisk_image(self):
164 Returns the OpenStack ramdisk image object as it was populated when
168 return self.__ramdisk_image
170 def image_active(self, block=False, timeout=IMAGE_ACTIVE_TIMEOUT,
171 poll_interval=POLL_INTERVAL):
173 Returns true when the image status returns the value of
175 :param block: When true, thread will block until active or timeout
176 value in seconds has been exceeded (False)
177 :param timeout: The timeout value
178 :param poll_interval: The polling interval in seconds
181 return self._image_status_check(STATUS_ACTIVE, block, timeout,
184 def _image_status_check(self, expected_status_code, block, timeout,
187 Returns true when the image status returns the value of
189 :param expected_status_code: instance status evaluated with this string
191 :param block: When true, thread will block until active or timeout
192 value in seconds has been exceeded (False)
193 :param timeout: The timeout value
194 :param poll_interval: The polling interval in seconds
197 # sleep and wait for image status change
201 start = time.time() - timeout
203 while timeout > time.time() - start:
204 status = self._status(expected_status_code)
207 'Image is active with name - ' + self.image_settings.name)
210 logger.debug('Retry querying image status in ' + str(
211 poll_interval) + ' seconds')
212 time.sleep(poll_interval)
213 logger.debug('Image status query timeout in ' + str(
214 timeout - (time.time() - start)))
217 'Timeout checking for image status for ' + expected_status_code)
220 def _status(self, expected_status_code):
222 Returns True when active else False
223 :param expected_status_code: instance status evaluated with this string
227 status = glance_utils.get_image_status(self.__glance, self.__image)
230 'Cannot image status for image with ID - ' + self.__image.id)
233 if status == 'ERROR':
234 raise ImageCreationError('Instance had an error during deployment')
235 logger.debug('Instance status is - ' + status)
236 return status == expected_status_code
239 class ImageSettings(image.ImageConfig):
241 Deprecated, use snaps.config.image.ImageSettings instead
243 def __init__(self, **kwargs):
244 from warnings import warn
245 warn('Use snaps.config.image.ImageConfig instead', DeprecationWarning)
246 super(ImageSettings, self).__init__(**kwargs)
249 class ImageCreationError(Exception):
251 Exception to be thrown when an image cannot be created
254 def __init__(self, message):
255 Exception.__init__(self, message)