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
23 __author__ = 'spisarski'
25 logger = logging.getLogger('create_image')
27 IMAGE_ACTIVE_TIMEOUT = 600
29 STATUS_ACTIVE = 'active'
32 class OpenStackImage(OpenStackCloudObject):
34 Class responsible for managing an image in OpenStack
37 def __init__(self, os_creds, image_settings):
40 :param os_creds: The OpenStack connection credentials
41 :param image_settings: The image settings
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 image in [self.__image, self.__kernel_image, self.__ramdisk_image]:
138 glance_utils.delete_image(self.__glance, image)
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
240 def __init__(self, **kwargs):
243 :param name: the image's name (required)
244 :param image_user: the image's default sudo user (required)
245 :param format or img_format: the image type (required)
246 :param url or download_url: the image download location (requires url
248 :param image_file: the image file location (requires url or img_file)
249 :param extra_properties: dict() object containing extra parameters to
250 pass when loading the image;
251 can be ids of kernel and initramfs images for
253 :param nic_config_pb_loc: the file location to the Ansible Playbook
254 that can configure multiple NICs
255 :param kernel_image_settings: the settings for a kernel image
256 :param ramdisk_image_settings: the settings for a kernel image
257 :param exists: When True, an image with the given name must exist
258 :param public: When True, an image will be created with public
262 self.name = kwargs.get('name')
263 self.image_user = kwargs.get('image_user')
264 self.format = kwargs.get('format')
266 self.format = kwargs.get('img_format')
268 self.url = kwargs.get('url')
270 self.url = kwargs.get('download_url')
271 if self.url == 'None':
274 self.image_file = kwargs.get('image_file')
275 if self.image_file == 'None':
276 self.image_file = None
278 self.extra_properties = kwargs.get('extra_properties')
279 self.nic_config_pb_loc = kwargs.get('nic_config_pb_loc')
281 kernel_image_settings = kwargs.get('kernel_image_settings')
282 if kernel_image_settings:
283 if isinstance(kernel_image_settings, dict):
284 self.kernel_image_settings = ImageSettings(
285 **kernel_image_settings)
287 self.kernel_image_settings = kernel_image_settings
289 self.kernel_image_settings = None
291 ramdisk_image_settings = kwargs.get('ramdisk_image_settings')
292 if ramdisk_image_settings:
293 if isinstance(ramdisk_image_settings, dict):
294 self.ramdisk_image_settings = ImageSettings(
295 **ramdisk_image_settings)
297 self.ramdisk_image_settings = ramdisk_image_settings
299 self.ramdisk_image_settings = None
301 if 'exists' in kwargs and kwargs['exists'] is True:
306 if 'public' in kwargs and kwargs['public'] is True:
312 raise ImageSettingsError("The attribute name is required")
314 if not (self.url or self.image_file) and not self.exists:
315 raise ImageSettingsError(
316 'URL or image file must be set or image must already exist')
318 if not self.image_user:
319 raise ImageSettingsError('Image user is required')
321 if not self.format and not self.exists:
322 raise ImageSettingsError(
323 'Format is required when the image should not already exist')
326 class ImageSettingsError(Exception):
328 Exception to be thrown when an image settings are incorrect
331 def __init__(self, message):
332 Exception.__init__(self, message)
335 class ImageCreationError(Exception):
337 Exception to be thrown when an image cannot be created
340 def __init__(self, message):
341 Exception.__init__(self, message)