1# Licensed under the Apache License, Version 2.0 (the "License"); 2# you may not use this file except in compliance with the License. 3# You may obtain a copy of the License at 4# 5# http://www.apache.org/licenses/LICENSE-2.0 6# 7# Unless required by applicable law or agreed to in writing, software 8# distributed under the License is distributed on an "AS IS" BASIS, 9# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10# See the License for the specific language governing permissions and 11# limitations under the License. 12 13# import types so that we can reference ListType in sphinx param declarations. 14# We can't just use list, because sphinx gets confused by 15# openstack.resource.Resource.list and openstack.resource2.Resource.list 16import types # noqa 17 18from openstack.cloud import _normalize 19from openstack.cloud import _utils 20from openstack.cloud import exc 21from openstack import utils 22 23 24def _no_pending_images(images): 25 """If there are any images not in a steady state, don't cache""" 26 for image in images: 27 if image.status not in ('active', 'deleted', 'killed'): 28 return False 29 return True 30 31 32class ImageCloudMixin(_normalize.Normalizer): 33 34 def __init__(self): 35 self.image_api_use_tasks = self.config.config['image_api_use_tasks'] 36 37 @property 38 def _raw_image_client(self): 39 if 'raw-image' not in self._raw_clients: 40 image_client = self._get_raw_client('image') 41 self._raw_clients['raw-image'] = image_client 42 return self._raw_clients['raw-image'] 43 44 @property 45 def _image_client(self): 46 if 'image' not in self._raw_clients: 47 self._raw_clients['image'] = self._get_versioned_client( 48 'image', min_version=1, max_version='2.latest') 49 return self._raw_clients['image'] 50 51 def search_images(self, name_or_id=None, filters=None): 52 images = self.list_images() 53 return _utils._filter_list(images, name_or_id, filters) 54 55 @_utils.cache_on_arguments(should_cache_fn=_no_pending_images) 56 def list_images(self, filter_deleted=True, show_all=False): 57 """Get available images. 58 59 :param filter_deleted: Control whether deleted images are returned. 60 :param show_all: Show all images, including images that are shared 61 but not accepted. (By default in glance v2 shared image that 62 have not been accepted are not shown) show_all will override the 63 value of filter_deleted to False. 64 :returns: A list of glance images. 65 """ 66 if show_all: 67 filter_deleted = False 68 # First, try to actually get images from glance, it's more efficient 69 images = [] 70 params = {} 71 image_list = [] 72 if self._is_client_version('image', 2): 73 if show_all: 74 params['member_status'] = 'all' 75 image_list = list(self.image.images(**params)) 76 77 for image in image_list: 78 # The cloud might return DELETED for invalid images. 79 # While that's cute and all, that's an implementation detail. 80 if not filter_deleted: 81 images.append(image) 82 elif image.status.lower() != 'deleted': 83 images.append(image) 84 return self._normalize_images(images) 85 86 def get_image(self, name_or_id, filters=None): 87 """Get an image by name or ID. 88 89 :param name_or_id: Name or ID of the image. 90 :param filters: 91 A dictionary of meta data to use for further filtering. Elements 92 of this dictionary may, themselves, be dictionaries. Example:: 93 94 { 95 'last_name': 'Smith', 96 'other': { 97 'gender': 'Female' 98 } 99 } 100 101 OR 102 A string containing a jmespath expression for further filtering. 103 Example:: "[?last_name==`Smith`] | [?other.gender]==`Female`]" 104 105 :returns: An image ``munch.Munch`` or None if no matching image 106 is found 107 108 """ 109 return _utils._get_entity(self, 'image', name_or_id, filters) 110 111 def get_image_by_id(self, id): 112 """ Get a image by ID 113 114 :param id: ID of the image. 115 :returns: An image ``munch.Munch``. 116 """ 117 image = self._normalize_image( 118 self.image.get_image(image={'id': id})) 119 120 return image 121 122 def download_image( 123 self, name_or_id, output_path=None, output_file=None, 124 chunk_size=1024): 125 """Download an image by name or ID 126 127 :param str name_or_id: Name or ID of the image. 128 :param output_path: the output path to write the image to. Either this 129 or output_file must be specified 130 :param output_file: a file object (or file-like object) to write the 131 image data to. Only write() will be called on this object. Either 132 this or output_path must be specified 133 :param int chunk_size: size in bytes to read from the wire and buffer 134 at one time. Defaults to 1024 135 136 :raises: OpenStackCloudException in the event download_image is called 137 without exactly one of either output_path or output_file 138 :raises: OpenStackCloudResourceNotFound if no images are found matching 139 the name or ID provided 140 """ 141 if output_path is None and output_file is None: 142 raise exc.OpenStackCloudException( 143 'No output specified, an output path or file object' 144 ' is necessary to write the image data to') 145 elif output_path is not None and output_file is not None: 146 raise exc.OpenStackCloudException( 147 'Both an output path and file object were provided,' 148 ' however only one can be used at once') 149 150 image = self.image.find_image(name_or_id) 151 if not image: 152 raise exc.OpenStackCloudResourceNotFound( 153 "No images with name or ID %s were found" % name_or_id, None) 154 155 return self.image.download_image( 156 image, output=output_file or output_path, 157 chunk_size=chunk_size) 158 159 def get_image_exclude(self, name_or_id, exclude): 160 for image in self.search_images(name_or_id): 161 if exclude: 162 if exclude not in image.name: 163 return image 164 else: 165 return image 166 return None 167 168 def get_image_name(self, image_id, exclude=None): 169 image = self.get_image_exclude(image_id, exclude) 170 if image: 171 return image.name 172 return None 173 174 def get_image_id(self, image_name, exclude=None): 175 image = self.get_image_exclude(image_name, exclude) 176 if image: 177 return image.id 178 return None 179 180 def wait_for_image(self, image, timeout=3600): 181 image_id = image['id'] 182 for count in utils.iterate_timeout( 183 timeout, "Timeout waiting for image to snapshot"): 184 self.list_images.invalidate(self) 185 image = self.get_image(image_id) 186 if not image: 187 continue 188 if image['status'] == 'active': 189 return image 190 elif image['status'] == 'error': 191 raise exc.OpenStackCloudException( 192 'Image {image} hit error state'.format(image=image_id)) 193 194 def delete_image( 195 self, name_or_id, wait=False, timeout=3600, delete_objects=True): 196 """Delete an existing image. 197 198 :param name_or_id: Name of the image to be deleted. 199 :param wait: If True, waits for image to be deleted. 200 :param timeout: Seconds to wait for image deletion. None is forever. 201 :param delete_objects: If True, also deletes uploaded swift objects. 202 203 :returns: True if delete succeeded, False otherwise. 204 205 :raises: OpenStackCloudException if there are problems deleting. 206 """ 207 image = self.get_image(name_or_id) 208 if not image: 209 return False 210 self.image.delete_image(image) 211 self.list_images.invalidate(self) 212 213 # Task API means an image was uploaded to swift 214 # TODO(gtema) does it make sense to move this into proxy? 215 if self.image_api_use_tasks and ( 216 self.image._IMAGE_OBJECT_KEY in image 217 or self.image._SHADE_IMAGE_OBJECT_KEY in image): 218 (container, objname) = image.get( 219 self.image._IMAGE_OBJECT_KEY, image.get( 220 self.image._SHADE_IMAGE_OBJECT_KEY)).split('/', 1) 221 self.delete_object(container=container, name=objname) 222 223 if wait: 224 for count in utils.iterate_timeout( 225 timeout, 226 "Timeout waiting for the image to be deleted."): 227 self._get_cache(None).invalidate() 228 if self.get_image(image.id) is None: 229 break 230 return True 231 232 def create_image( 233 self, name, filename=None, 234 container=None, 235 md5=None, sha256=None, 236 disk_format=None, container_format=None, 237 disable_vendor_agent=True, 238 wait=False, timeout=3600, tags=None, 239 allow_duplicates=False, meta=None, volume=None, **kwargs): 240 """Upload an image. 241 242 :param str name: Name of the image to create. If it is a pathname 243 of an image, the name will be constructed from the 244 extensionless basename of the path. 245 :param str filename: The path to the file to upload, if needed. 246 (optional, defaults to None) 247 :param str container: Name of the container in swift where images 248 should be uploaded for import if the cloud 249 requires such a thing. (optiona, defaults to 250 'images') 251 :param str md5: md5 sum of the image file. If not given, an md5 will 252 be calculated. 253 :param str sha256: sha256 sum of the image file. If not given, an md5 254 will be calculated. 255 :param str disk_format: The disk format the image is in. (optional, 256 defaults to the os-client-config config value 257 for this cloud) 258 :param str container_format: The container format the image is in. 259 (optional, defaults to the 260 os-client-config config value for this 261 cloud) 262 :param list tags: List of tags for this image. Each tag is a string 263 of at most 255 chars. 264 :param bool disable_vendor_agent: Whether or not to append metadata 265 flags to the image to inform the 266 cloud in question to not expect a 267 vendor agent to be runing. 268 (optional, defaults to True) 269 :param bool wait: If true, waits for image to be created. Defaults to 270 true - however, be aware that one of the upload 271 methods is always synchronous. 272 :param timeout: Seconds to wait for image creation. None is forever. 273 :param allow_duplicates: If true, skips checks that enforce unique 274 image name. (optional, defaults to False) 275 :param meta: A dict of key/value pairs to use for metadata that 276 bypasses automatic type conversion. 277 :param volume: Name or ID or volume object of a volume to create an 278 image from. Mutually exclusive with (optional, defaults 279 to None) 280 281 Additional kwargs will be passed to the image creation as additional 282 metadata for the image and will have all values converted to string 283 except for min_disk, min_ram, size and virtual_size which will be 284 converted to int. 285 286 If you are sure you have all of your data types correct or have an 287 advanced need to be explicit, use meta. If you are just a normal 288 consumer, using kwargs is likely the right choice. 289 290 If a value is in meta and kwargs, meta wins. 291 292 :returns: A ``munch.Munch`` of the Image object 293 294 :raises: OpenStackCloudException if there are problems uploading 295 """ 296 if volume: 297 image = self.block_storage.create_image( 298 name=name, volume=volume, 299 allow_duplicates=allow_duplicates, 300 container_format=container_format, disk_format=disk_format, 301 wait=wait, timeout=timeout) 302 else: 303 image = self.image.create_image( 304 name, filename=filename, 305 container=container, 306 md5=md5, sha256=sha256, 307 disk_format=disk_format, container_format=container_format, 308 disable_vendor_agent=disable_vendor_agent, 309 wait=wait, timeout=timeout, tags=tags, 310 allow_duplicates=allow_duplicates, meta=meta, **kwargs) 311 312 self._get_cache(None).invalidate() 313 if not wait: 314 return image 315 try: 316 for count in utils.iterate_timeout( 317 timeout, 318 "Timeout waiting for the image to finish."): 319 image_obj = self.get_image(image.id) 320 if image_obj and image_obj.status not in ('queued', 'saving'): 321 return image_obj 322 except exc.OpenStackCloudTimeout: 323 self.log.debug( 324 "Timeout waiting for image to become ready. Deleting.") 325 self.delete_image(image.id, wait=True) 326 raise 327 328 def update_image_properties( 329 self, image=None, name_or_id=None, meta=None, **properties): 330 image = image or name_or_id 331 return self.image.update_image_properties( 332 image=image, meta=meta, **properties) 333