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
48 self.__glance = glance_utils.glance_client(os_creds)
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.__image = glance_utils.get_image(self.__glance, self.image_settings.name)
58 logger.info('Found image with name - ' + self.image_settings.name)
61 extra_properties = self.image_settings.extra_properties or dict()
63 if self.image_settings.kernel_image_settings:
64 self.__kernel_image = glance_utils.get_image(
65 self.__glance, self.image_settings.kernel_image_settings.name)
67 if not self.__kernel_image and not cleanup:
68 logger.info('Creating associated kernel image')
69 self.__kernel_image = glance_utils.create_image(
70 self.__glance, self.image_settings.kernel_image_settings)
71 extra_properties['kernel_id'] = self.__kernel_image.id
72 if self.image_settings.ramdisk_image_settings:
73 self.__ramdisk_image = glance_utils.get_image(
74 self.__glance, self.image_settings.ramdisk_image_settings.name)
76 if not self.__ramdisk_image and not cleanup:
77 logger.info('Creating associated ramdisk image')
78 self.__ramdisk_image = glance_utils.create_image(
79 self.__glance, self.image_settings.ramdisk_image_settings)
80 extra_properties['ramdisk_id'] = self.__ramdisk_image.id
82 self.image_settings.extra_properties = extra_properties
83 self.__image = glance_utils.create_image(self.__glance, self.image_settings)
84 logger.info('Creating image')
85 if self.__image and self.image_active(block=True):
86 logger.info('Image is now active with name - ' + self.image_settings.name)
89 raise Exception('Image was not created or activated in the alloted amount of time')
91 logger.info('Did not create image due to cleanup mode')
97 Cleanse environment of all artifacts
100 for image in [self.__image, self.__kernel_image, self.__ramdisk_image]:
103 glance_utils.delete_image(self.__glance, image)
108 self.__kernel_image = None
109 self.__ramdisk_image = None
113 Returns the domain Image object as it was populated when create() was called
118 def get_kernel_image(self):
120 Returns the OpenStack kernel image object as it was populated when create() was called
123 return self.__kernel_image
125 def get_ramdisk_image(self):
127 Returns the OpenStack ramdisk image object as it was populated when create() was called
130 return self.__ramdisk_image
132 def image_active(self, block=False, timeout=IMAGE_ACTIVE_TIMEOUT, poll_interval=POLL_INTERVAL):
134 Returns true when the image status returns the value of expected_status_code
135 :param block: When true, thread will block until active or timeout value in seconds has been exceeded (False)
136 :param timeout: The timeout value
137 :param poll_interval: The polling interval in seconds
140 return self._image_status_check(STATUS_ACTIVE, block, timeout, poll_interval)
142 def _image_status_check(self, expected_status_code, block, timeout, poll_interval):
144 Returns true when the image status returns the value of expected_status_code
145 :param expected_status_code: instance status evaluated with this string value
146 :param block: When true, thread will block until active or timeout value in seconds has been exceeded (False)
147 :param timeout: The timeout value
148 :param poll_interval: The polling interval in seconds
151 # sleep and wait for image status change
155 start = time.time() - timeout
157 while timeout > time.time() - start:
158 status = self._status(expected_status_code)
160 logger.info('Image is active with name - ' + self.image_settings.name)
163 logger.debug('Retry querying image status in ' + str(poll_interval) + ' seconds')
164 time.sleep(poll_interval)
165 logger.debug('Image status query timeout in ' + str(timeout - (time.time() - start)))
167 logger.error('Timeout checking for image status for ' + expected_status_code)
170 def _status(self, expected_status_code):
172 Returns True when active else False
173 :param expected_status_code: instance status evaluated with this string value
176 # TODO - Place this API call into glance_utils.
177 status = glance_utils.get_image_status(self.__glance, self.__image)
179 logger.warn('Cannot image status for image with ID - ' + self.__image.id)
182 if status == 'ERROR':
183 raise Exception('Instance had an error during deployment')
184 logger.debug('Instance status is - ' + status)
185 return status == expected_status_code
189 def __init__(self, config=None, name=None, image_user=None, img_format=None, url=None, image_file=None,
190 extra_properties=None, nic_config_pb_loc=None, kernel_image_settings=None,
191 ramdisk_image_settings=None):
194 :param config: dict() object containing the configuration settings using the attribute names below as each
195 member's the key and overrides any of the other parameters.
196 :param name: the image's name (required)
197 :param image_user: the image's default sudo user (required)
198 :param img_format: the image type (required)
199 :param url: the image download location (requires url or img_file)
200 :param image_file: the image file location (requires url or img_file)
201 :param extra_properties: dict() object containing extra parameters to pass when loading the image;
202 can be ids of kernel and initramfs images for a 3-part image
203 :param nic_config_pb_loc: the file location to the Ansible Playbook that can configure multiple NICs
204 :param kernel_image_settings: the settings for a kernel image
205 :param ramdisk_image_settings: the settings for a kernel image
209 self.name = config.get('name')
210 self.image_user = config.get('image_user')
211 self.format = config.get('format')
212 self.url = config.get('download_url')
213 self.image_file = config.get('image_file')
214 self.extra_properties = config.get('extra_properties')
215 self.nic_config_pb_loc = config.get('nic_config_pb_loc')
216 if config.get('kernel_image_settings'):
217 self.kernel_image_settings = ImageSettings(config=config['kernel_image_settings'])
219 self.kernel_image_settings = None
221 if config.get('ramdisk_image_settings'):
222 self.ramdisk_image_settings = ImageSettings(config=config['ramdisk_image_settings'])
224 self.ramdisk_image_settings = None
228 self.image_user = image_user
229 self.format = img_format
231 self.image_file = image_file
232 self.extra_properties = extra_properties
233 self.nic_config_pb_loc = nic_config_pb_loc
234 self.kernel_image_settings = kernel_image_settings
235 self.ramdisk_image_settings = ramdisk_image_settings
237 if not self.name or not self.image_user or not self.format:
238 raise Exception("The attributes name, image_user, format, and url are required for ImageSettings")
240 if not self.url and not self.image_file:
241 raise Exception('URL or image file must be set')
243 if self.url and self.image_file:
244 raise Exception('Please set either URL or image file, not both')