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 the domain Image object
53 :param cleanup: Denotes whether or not this is being called for cleanup or not
54 :return: The OpenStack Image object
56 self.__glance = glance_utils.glance_client(self.__os_creds)
57 self.__image = glance_utils.get_image(self.__glance, self.image_settings.name)
59 logger.info('Found image with name - ' + self.image_settings.name)
61 elif self.image_settings.exists and not self.image_settings.url and not self.image_settings.image_file:
62 raise ImageCreationError('Image with does not exist with name - ' + self.image_settings.name)
64 extra_properties = self.image_settings.extra_properties or dict()
66 if self.image_settings.kernel_image_settings:
67 self.__kernel_image = glance_utils.get_image(
68 self.__glance, self.image_settings.kernel_image_settings.name)
70 if not self.__kernel_image and not cleanup:
71 logger.info('Creating associated kernel image with name - '
72 + self.image_settings.kernel_image_settings.name)
73 self.__kernel_image = glance_utils.create_image(
74 self.__glance, self.image_settings.kernel_image_settings)
75 extra_properties['kernel_id'] = self.__kernel_image.id
76 if self.image_settings.ramdisk_image_settings:
77 self.__ramdisk_image = glance_utils.get_image(
78 self.__glance, self.image_settings.ramdisk_image_settings.name)
80 if not self.__ramdisk_image and not cleanup:
81 logger.info('Creating associated ramdisk image with name - '
82 + self.image_settings.ramdisk_image_settings.name)
83 self.__ramdisk_image = glance_utils.create_image(
84 self.__glance, self.image_settings.ramdisk_image_settings)
85 extra_properties['ramdisk_id'] = self.__ramdisk_image.id
87 self.image_settings.extra_properties = extra_properties
88 self.__image = glance_utils.create_image(self.__glance, self.image_settings)
90 logger.info('Created image with name - ' + self.image_settings.name)
91 if self.__image and self.image_active(block=True):
92 logger.info('Image is now active with name - ' + self.image_settings.name)
95 raise ImageCreationError('Image was not created or activated in the alloted amount of time')
97 logger.info('Did not create image due to cleanup mode')
103 Cleanse environment of all artifacts
106 for image in [self.__image, self.__kernel_image, self.__ramdisk_image]:
109 glance_utils.delete_image(self.__glance, image)
114 self.__kernel_image = None
115 self.__ramdisk_image = None
119 Returns the domain Image object as it was populated when create() was called
124 def get_kernel_image(self):
126 Returns the OpenStack kernel image object as it was populated when create() was called
129 return self.__kernel_image
131 def get_ramdisk_image(self):
133 Returns the OpenStack ramdisk image object as it was populated when create() was called
136 return self.__ramdisk_image
138 def image_active(self, block=False, timeout=IMAGE_ACTIVE_TIMEOUT, poll_interval=POLL_INTERVAL):
140 Returns true when the image status returns the value of expected_status_code
141 :param block: When true, thread will block until active or timeout value in seconds has been exceeded (False)
142 :param timeout: The timeout value
143 :param poll_interval: The polling interval in seconds
146 return self._image_status_check(STATUS_ACTIVE, block, timeout, poll_interval)
148 def _image_status_check(self, expected_status_code, block, timeout, poll_interval):
150 Returns true when the image status returns the value of expected_status_code
151 :param expected_status_code: instance status evaluated with this string value
152 :param block: When true, thread will block until active or timeout value in seconds has been exceeded (False)
153 :param timeout: The timeout value
154 :param poll_interval: The polling interval in seconds
157 # sleep and wait for image status change
161 start = time.time() - timeout
163 while timeout > time.time() - start:
164 status = self._status(expected_status_code)
166 logger.debug('Image is active with name - ' + self.image_settings.name)
169 logger.debug('Retry querying image status in ' + str(poll_interval) + ' seconds')
170 time.sleep(poll_interval)
171 logger.debug('Image status query timeout in ' + str(timeout - (time.time() - start)))
173 logger.error('Timeout checking for image status for ' + expected_status_code)
176 def _status(self, expected_status_code):
178 Returns True when active else False
179 :param expected_status_code: instance status evaluated with this string value
182 # TODO - Place this API call into glance_utils.
183 status = glance_utils.get_image_status(self.__glance, self.__image)
185 logger.warning('Cannot image status for image with ID - ' + self.__image.id)
188 if status == 'ERROR':
189 raise ImageCreationError('Instance had an error during deployment')
190 logger.debug('Instance status is - ' + status)
191 return status == expected_status_code
195 def __init__(self, config=None, name=None, image_user=None, img_format=None, url=None, image_file=None,
196 extra_properties=None, nic_config_pb_loc=None, kernel_image_settings=None,
197 ramdisk_image_settings=None, exists=False, public=False):
200 :param config: dict() object containing the configuration settings using the attribute names below as each
201 member's the key and overrides any of the other parameters.
202 :param name: the image's name (required)
203 :param image_user: the image's default sudo user (required)
204 :param img_format: the image type (required)
205 :param url: the image download location (requires url or img_file)
206 :param image_file: the image file location (requires url or img_file)
207 :param extra_properties: dict() object containing extra parameters to pass when loading the image;
208 can be ids of kernel and initramfs images for a 3-part image
209 :param nic_config_pb_loc: the file location to the Ansible Playbook that can configure multiple NICs
210 :param kernel_image_settings: the settings for a kernel image
211 :param ramdisk_image_settings: the settings for a kernel image
212 :param exists: When True, an image with the given name must exist
213 :param public: When True, an image will be created with public visibility
217 self.name = config.get('name')
218 self.image_user = config.get('image_user')
219 self.format = config.get('format')
220 self.url = config.get('download_url')
221 self.image_file = config.get('image_file')
222 self.extra_properties = config.get('extra_properties')
223 self.nic_config_pb_loc = config.get('nic_config_pb_loc')
224 if config.get('kernel_image_settings'):
225 self.kernel_image_settings = ImageSettings(config=config['kernel_image_settings'])
227 self.kernel_image_settings = None
229 if config.get('ramdisk_image_settings'):
230 self.ramdisk_image_settings = ImageSettings(config=config['ramdisk_image_settings'])
232 self.ramdisk_image_settings = None
234 if 'exists' in config and config['exists'] is True:
239 if 'public' in config and config['public'] is True:
245 self.image_user = image_user
246 self.format = img_format
248 self.image_file = image_file
249 self.extra_properties = extra_properties
250 self.nic_config_pb_loc = nic_config_pb_loc
251 self.kernel_image_settings = kernel_image_settings
252 self.ramdisk_image_settings = ramdisk_image_settings
257 raise ImageSettingsError("The attribute name is required")
259 if not (self.url or self.image_file) and not self.exists:
260 raise ImageSettingsError('URL or image file must be set or image must already exist')
262 if self.url and self.image_file:
263 raise ImageSettingsError('Please set either URL or image file, not both')
265 if not self.image_user:
266 raise ImageSettingsError('Image user is required')
268 if not self.format and not self.exists:
269 raise ImageSettingsError('Format is required when the image should not already exist')
272 class ImageSettingsError(Exception):
274 Exception to be thrown when an image settings are incorrect
276 def __init__(self, message):
277 Exception.__init__(self, message)
280 class ImageCreationError(Exception):
282 Exception to be thrown when an image cannot be created
284 def __init__(self, message):
285 Exception.__init__(self, message)