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.utils import glance_utils
22 __author__ = 'spisarski'
24 logger = logging.getLogger('create_image')
26 IMAGE_ACTIVE_TIMEOUT = 600
28 STATUS_ACTIVE = 'active'
33 Class responsible for creating an image in OpenStack
36 def __init__(self, os_creds, image_settings):
39 :param os_creds: The OpenStack connection credentials
40 :param image_settings: The image settings
43 self.__os_creds = os_creds
44 self.image_settings = image_settings
46 self.__kernel_image = None
47 self.__ramdisk_image = None
50 def create(self, cleanup=False):
52 Creates the image in OpenStack if it does not already exist and returns
53 the domain Image object
54 :param cleanup: Denotes whether or not this is being called for cleanup
56 :return: The OpenStack Image object
58 self.__glance = glance_utils.glance_client(self.__os_creds)
59 self.__image = glance_utils.get_image(self.__glance,
60 self.image_settings.name)
62 logger.info('Found image with name - ' + self.image_settings.name)
64 elif self.image_settings.exists and not self.image_settings.url \
65 and not self.image_settings.image_file:
66 raise ImageCreationError(
67 'Image with does not exist with name - ' +
68 self.image_settings.name)
70 extra_properties = self.image_settings.extra_properties or dict()
72 if self.image_settings.kernel_image_settings:
73 self.__kernel_image = glance_utils.get_image(
75 self.image_settings.kernel_image_settings.name)
77 if not self.__kernel_image and not cleanup:
79 'Creating associated kernel image with name - %s',
80 self.image_settings.kernel_image_settings.name)
81 self.__kernel_image = glance_utils.create_image(
83 self.image_settings.kernel_image_settings)
84 extra_properties['kernel_id'] = self.__kernel_image.id
85 if self.image_settings.ramdisk_image_settings:
86 self.__ramdisk_image = glance_utils.get_image(
88 self.image_settings.ramdisk_image_settings.name)
90 if not self.__ramdisk_image and not cleanup:
92 'Creating associated ramdisk image with name - %s',
93 self.image_settings.ramdisk_image_settings.name)
94 self.__ramdisk_image = glance_utils.create_image(
96 self.image_settings.ramdisk_image_settings)
97 extra_properties['ramdisk_id'] = self.__ramdisk_image.id
99 self.image_settings.extra_properties = extra_properties
100 self.__image = glance_utils.create_image(self.__glance,
104 'Created image with name - %s', self.image_settings.name)
105 if self.__image and self.image_active(block=True):
107 'Image is now active with name - %s',
108 self.image_settings.name)
111 raise ImageCreationError(
112 'Image was not created or activated in the alloted amount'
115 logger.info('Did not create image due to cleanup mode')
121 Cleanse environment of all artifacts
124 for image in [self.__image, self.__kernel_image, self.__ramdisk_image]:
127 glance_utils.delete_image(self.__glance, image)
132 self.__kernel_image = None
133 self.__ramdisk_image = None
137 Returns the domain Image object as it was populated when create() was
143 def get_kernel_image(self):
145 Returns the OpenStack kernel image object as it was populated when
149 return self.__kernel_image
151 def get_ramdisk_image(self):
153 Returns the OpenStack ramdisk image object as it was populated when
157 return self.__ramdisk_image
159 def image_active(self, block=False, timeout=IMAGE_ACTIVE_TIMEOUT,
160 poll_interval=POLL_INTERVAL):
162 Returns true when the image status returns the value of
164 :param block: When true, thread will block until active or timeout
165 value in seconds has been exceeded (False)
166 :param timeout: The timeout value
167 :param poll_interval: The polling interval in seconds
170 return self._image_status_check(STATUS_ACTIVE, block, timeout,
173 def _image_status_check(self, expected_status_code, block, timeout,
176 Returns true when the image status returns the value of
178 :param expected_status_code: instance status evaluated with this string
180 :param block: When true, thread will block until active or timeout
181 value in seconds has been exceeded (False)
182 :param timeout: The timeout value
183 :param poll_interval: The polling interval in seconds
186 # sleep and wait for image status change
190 start = time.time() - timeout
192 while timeout > time.time() - start:
193 status = self._status(expected_status_code)
196 'Image is active with name - ' + self.image_settings.name)
199 logger.debug('Retry querying image status in ' + str(
200 poll_interval) + ' seconds')
201 time.sleep(poll_interval)
202 logger.debug('Image status query timeout in ' + str(
203 timeout - (time.time() - start)))
206 'Timeout checking for image status for ' + expected_status_code)
209 def _status(self, expected_status_code):
211 Returns True when active else False
212 :param expected_status_code: instance status evaluated with this string
216 status = glance_utils.get_image_status(self.__glance, self.__image)
219 'Cannot image status for image with ID - ' + self.__image.id)
222 if status == 'ERROR':
223 raise ImageCreationError('Instance had an error during deployment')
224 logger.debug('Instance status is - ' + status)
225 return status == expected_status_code
229 def __init__(self, **kwargs):
232 :param name: the image's name (required)
233 :param image_user: the image's default sudo user (required)
234 :param format or img_format: the image type (required)
235 :param url or download_url: the image download location (requires url
237 :param image_file: the image file location (requires url or img_file)
238 :param extra_properties: dict() object containing extra parameters to
239 pass when loading the image;
240 can be ids of kernel and initramfs images for
242 :param nic_config_pb_loc: the file location to the Ansible Playbook
243 that can configure multiple NICs
244 :param kernel_image_settings: the settings for a kernel image
245 :param ramdisk_image_settings: the settings for a kernel image
246 :param exists: When True, an image with the given name must exist
247 :param public: When True, an image will be created with public
251 self.name = kwargs.get('name')
252 self.image_user = kwargs.get('image_user')
253 self.format = kwargs.get('format')
255 self.format = kwargs.get('img_format')
257 self.url = kwargs.get('url')
259 self.url = kwargs.get('download_url')
261 self.image_file = kwargs.get('image_file')
262 self.extra_properties = kwargs.get('extra_properties')
263 self.nic_config_pb_loc = kwargs.get('nic_config_pb_loc')
265 kernel_image_settings = kwargs.get('kernel_image_settings')
266 if kernel_image_settings:
267 if isinstance(kernel_image_settings, dict):
268 self.kernel_image_settings = ImageSettings(
269 **kernel_image_settings)
271 self.kernel_image_settings = kernel_image_settings
273 self.kernel_image_settings = None
275 ramdisk_image_settings = kwargs.get('ramdisk_image_settings')
276 if ramdisk_image_settings:
277 if isinstance(ramdisk_image_settings, dict):
278 self.ramdisk_image_settings = ImageSettings(
279 **ramdisk_image_settings)
281 self.ramdisk_image_settings = ramdisk_image_settings
283 self.ramdisk_image_settings = None
285 if 'exists' in kwargs and kwargs['exists'] is True:
290 if 'public' in kwargs and kwargs['public'] is True:
296 raise ImageSettingsError("The attribute name is required")
298 if not (self.url or self.image_file) and not self.exists:
299 raise ImageSettingsError(
300 'URL or image file must be set or image must already exist')
302 if self.url and self.image_file:
303 raise ImageSettingsError(
304 'Please set either URL or image file, not both')
306 if not self.image_user:
307 raise ImageSettingsError('Image user is required')
309 if not self.format and not self.exists:
310 raise ImageSettingsError(
311 'Format is required when the image should not already exist')
314 class ImageSettingsError(Exception):
316 Exception to be thrown when an image settings are incorrect
319 def __init__(self, message):
320 Exception.__init__(self, message)
323 class ImageCreationError(Exception):
325 Exception to be thrown when an image cannot be created
328 def __init__(self, message):
329 Exception.__init__(self, message)