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 super(self.__class__, self).initialize()
59 self.__glance = glance_utils.glance_client(
60 self._os_creds, self._os_session)
61 self.__image = glance_utils.get_image(
62 self.__glance, image_settings=self.image_settings)
65 logger.info('Found image with name - ' + self.image_settings.name)
67 elif (self.image_settings.exists and not self.image_settings.url
68 and not self.image_settings.image_file):
69 raise ImageCreationError(
70 'Image with does not exist with name - ' +
71 self.image_settings.name)
73 if self.image_settings.kernel_image_settings:
74 self.__kernel_image = glance_utils.get_image(
76 image_settings=self.image_settings.kernel_image_settings)
78 if self.image_settings.ramdisk_image_settings:
79 self.__ramdisk_image = glance_utils.get_image(
81 image_settings=self.image_settings.ramdisk_image_settings)
87 Creates the image in OpenStack if it does not already exist and returns
88 the domain Image object
89 :return: The Image domain object or None
94 extra_properties = self.image_settings.extra_properties or dict()
96 if self.image_settings.kernel_image_settings:
97 if not self.__kernel_image:
99 'Creating associated kernel image with name - %s',
100 self.image_settings.kernel_image_settings.name)
101 self.__kernel_image = glance_utils.create_image(
103 self.image_settings.kernel_image_settings)
104 extra_properties['kernel_id'] = self.__kernel_image.id
106 if self.image_settings.ramdisk_image_settings:
107 if not self.__ramdisk_image:
109 'Creating associated ramdisk image with name - %s',
110 self.image_settings.ramdisk_image_settings.name)
111 self.__ramdisk_image = glance_utils.create_image(
113 self.image_settings.ramdisk_image_settings)
114 extra_properties['ramdisk_id'] = self.__ramdisk_image.id
116 self.image_settings.extra_properties = extra_properties
117 self.__image = glance_utils.create_image(self.__glance,
121 'Created image with name - %s', self.image_settings.name)
123 if self.__image and self.image_active(block=True):
125 'Image is now active with name - %s',
126 self.image_settings.name)
129 raise ImageCreationError(
130 'Image was not created or activated in the alloted amount'
137 Cleanse environment of all artifacts
140 for img in [self.__image, self.__kernel_image, self.__ramdisk_image]:
143 glance_utils.delete_image(self.__glance, img)
148 self.__kernel_image = None
149 self.__ramdisk_image = None
152 self.__glance.http_client.session.session.close()
154 super(self.__class__, self).clean()
158 Returns the domain Image object as it was populated when create() was
164 def get_kernel_image(self):
166 Returns the OpenStack kernel image object as it was populated when
170 return self.__kernel_image
172 def get_ramdisk_image(self):
174 Returns the OpenStack ramdisk image object as it was populated when
178 return self.__ramdisk_image
180 def image_active(self, block=False, timeout=IMAGE_ACTIVE_TIMEOUT,
181 poll_interval=POLL_INTERVAL):
183 Returns true when the image status returns the value of
185 :param block: When true, thread will block until active or timeout
186 value in seconds has been exceeded (False)
187 :param timeout: The timeout value
188 :param poll_interval: The polling interval in seconds
191 return self._image_status_check(STATUS_ACTIVE, block, timeout,
194 def _image_status_check(self, expected_status_code, block, timeout,
197 Returns true when the image status returns the value of
199 :param expected_status_code: instance status evaluated with this string
201 :param block: When true, thread will block until active or timeout
202 value in seconds has been exceeded (False)
203 :param timeout: The timeout value
204 :param poll_interval: The polling interval in seconds
207 # sleep and wait for image status change
211 start = time.time() - timeout
213 while timeout > time.time() - start:
214 status = self._status(expected_status_code)
217 'Image is active with name - ' + self.image_settings.name)
220 logger.debug('Retry querying image status in ' + str(
221 poll_interval) + ' seconds')
222 time.sleep(poll_interval)
223 logger.debug('Image status query timeout in ' + str(
224 timeout - (time.time() - start)))
227 'Timeout checking for image status for ' + expected_status_code)
230 def _status(self, expected_status_code):
232 Returns True when active else False
233 :param expected_status_code: instance status evaluated with this string
237 status = glance_utils.get_image_status(self.__glance, self.__image)
240 'Cannot image status for image with ID - ' + self.__image.id)
243 if status == 'ERROR':
244 raise ImageCreationError('Instance had an error during deployment')
245 logger.debug('Instance status is - ' + status)
246 return status == expected_status_code
249 class ImageSettings(image.ImageConfig):
251 Deprecated, use snaps.config.image.ImageSettings instead
253 def __init__(self, **kwargs):
254 from warnings import warn
255 warn('Use snaps.config.image.ImageConfig instead', DeprecationWarning)
256 super(ImageSettings, self).__init__(**kwargs)
259 class ImageCreationError(Exception):
261 Exception to be thrown when an image cannot be created
264 def __init__(self, message):
265 Exception.__init__(self, message)