a5a1929149aef12e22f064ad2da0db85d51cf15e
3 # Copyright 2016 RIFT.IO Inc
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
9 # http://www.apache.org/licenses/LICENSE-2.0
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
22 import keystoneclient
.v3
.client
as keystone_client
23 from keystoneauth1
import (
24 identity
as keystone_identity
,
25 session
as keystone_session
28 from gi
.repository
import RwcalYang
30 logger
= logging
.getLogger(name
=__name__
)
33 class OpenstackImageError(Exception):
37 class OpenstackNonUniqueImageError(OpenstackImageError
):
41 class OpenstackImageCreateError(Exception):
45 class OpenstackImageDeleteError(Exception):
49 class InvalidImageError(Exception):
53 class OpenstackAccount(object):
54 def __init__(self
, auth_url
, tenant
, username
, password
):
55 self
.auth_url
= auth_url
57 self
.username
= username
58 self
.password
= password
61 class OpenstackImage(object):
62 """ This value class encapsultes the RIFT-relevent glance image fields """
64 FIELDS
= ["id", "name", "checksum", "disk_format",
65 "container_format", "size", "properties", "status"]
66 OPTIONAL_FIELDS
= ["id", "checksum", "location"]
68 def __init__(self
, name
, disk_format
, container_format
, size
,
69 properties
=None, id=None, checksum
=None, status
="saving",
72 self
.disk_format
= disk_format
73 self
.container_format
= container_format
75 self
.properties
= properties
if properties
is not None else {}
79 self
.checksum
= checksum
82 def from_image_response(cls
, image
):
83 """ Convert a image response from glance into a OpenstackImage
86 image - A glance image object (from glance_client.images.list() for example)
89 An instance of OpenstackImage
92 OpenstackImageError - Could not convert the response into a OpenstackImage object
94 missing_fields
= [field
for field
in cls
.FIELDS
95 if field
not in cls
.OPTIONAL_FIELDS
and not hasattr(image
, field
)]
97 raise OpenstackImageError(
98 "Openstack image is missing required fields: %s" % missing_fields
101 kwargs
= {field
: getattr(image
, field
) for field
in cls
.FIELDS
}
106 class OpenstackKeystoneClient(object):
107 """ This class wraps the Keystone Client """
108 def __init__(self
, ks_client
):
109 self
._ks
_client
= ks_client
112 def auth_token(self
):
113 return self
._ks
_client
.auth_token
116 def from_openstack_account(cls
, os_account
):
117 ks_client
= keystone_client
.Client(
119 auth_url
=os_account
.auth_url
,
120 username
=os_account
.username
,
121 password
=os_account
.password
,
122 tenant_name
=os_account
.tenant
125 return cls(ks_client
)
128 def glance_endpoint(self
):
129 """ Return the glance endpoint from the keystone service """
130 glance_ep
= self
._ks
_client
.service_catalog
.url_for(
131 service_type
='image',
132 endpoint_type
='publicURL'
138 class OpenstackGlanceClient(object):
139 def __init__(self
, log
, glance_client
):
141 self
._client
= glance_client
144 def from_ks_client(cls
, log
, ks_client
):
145 """ Create a OpenstackGlanceClient from a keystone client instance
148 log - logger instance
149 ks_client - A keystone client instance
152 glance_ep
= ks_client
.glance_endpoint
153 glance_client
= glanceclient
.Client(
156 token
=ks_client
.auth_token
,
159 return cls(log
, glance_client
)
162 def from_token(cls
, log
, host
, port
, token
):
163 """ Create a OpenstackGlanceClient instance using a keystone auth token
166 log - logger instance
167 host - the glance host
168 port - the glance port
169 token - the keystone token
172 A OpenstackGlanceClient instance
174 endpoint
= "http://{}:{}".format(host
, port
)
175 glance_client
= glanceclient
.Client("1", endpoint
, token
=token
)
176 return cls(log
, glance_client
)
178 def get_image_list(self
):
179 """ Return the list of images from the Glance server
182 A list of OpenstackImage instances
185 for image
in itertools
.chain(
186 self
._client
.images
.list(is_public
=False),
187 self
._client
.images
.list(is_public
=True)
189 images
.append(OpenstackImage
.from_image_response(image
))
193 def get_image_data(self
, image_id
):
194 """ Return a image bytes generator from a image id
197 image_id - An image id that exists on the glance server
200 An generator which produces the image data bytestrings
203 OpenstackImageError - Could not find the image id
207 self
._client
.images
.get(image_id
)
208 except Exception as e
:
209 msg
= "Failed to find image from image: %s" % image_id
210 self
._log
.exception(msg
)
211 raise OpenstackImageError(msg
) from e
213 img_data
= self
._client
.images
.data(image_id
)
216 def find_active_image(self
, id=None, name
=None, checksum
=None):
217 """ Find an active images on the glance server
220 id - the image id to match
221 name - the image name to match
222 checksum - the image checksum to match
225 A OpenstackImage instance
228 OpenstackImageError - could not find a matching image
229 with matching image name and checksum
231 if id is None and name
is None:
232 raise ValueError("image id or image name must be provided")
234 self
._log
.debug("attempting to find active image with id %s name %s and checksum %s",
239 image_list
= self
.get_image_list()
240 self
._log
.debug("got image list from openstack: %s", image_list
)
241 for image
in self
.get_image_list():
242 self
._log
.debug(image
)
243 if image
.status
!= "active":
251 if image
.name
!= name
:
254 if checksum
is not None:
255 if image
.checksum
!= checksum
:
258 if found_image
is not None:
259 raise OpenstackNonUniqueImageError(
260 "Found multiple images that matched the criteria. Use image id to disambiguate."
265 if found_image
is None:
266 raise OpenstackImageError(
267 "could not find an active image with id %s name %s and checksum %s" %
268 (id, name
, checksum
))
270 return OpenstackImage
.from_image_response(found_image
)
272 def create_image_from_hdl(self
, image
, file_hdl
):
273 """ Create an image on the glance server a file handle
276 image - An OpenstackImage instance
277 file_hdl - An open image file handle
280 OpenstackImageCreateError - Could not upload the image
283 self
._client
.images
.create(
286 disk_format
=image
.disk_format
,
287 container_format
=image
.container_format
,
290 except Exception as e
:
291 msg
= "Failed to Openstack upload image"
292 self
._log
.exception(msg
)
293 raise OpenstackImageCreateError(msg
) from e
295 def create_image_from_url(self
, image_url
, image_name
, image_checksum
=None,
296 disk_format
=None, container_format
=None):
297 """ Create an image on the glance server from a image url
300 image_url - An HTTP image url
301 image_name - An openstack image name (filename with proper extension)
302 image checksum - The image md5 checksum
305 OpenstackImageCreateError - Could not create the image
307 def disk_format_from_image_name(image_name
):
308 _
, image_ext
= os
.path
.splitext(image_name
)
310 raise InvalidImageError("image name must have an extension")
313 image_ext
= image_ext
[1:]
315 if not hasattr(RwcalYang
.DiskFormat
, image_ext
.upper()):
316 raise InvalidImageError("unknown image extension for disk format: %s", image_ext
)
318 disk_format
= image_ext
.lower()
321 # If the disk format was not provided, attempt to match via the file
323 if disk_format
is None:
324 disk_format
= disk_format_from_image_name(image_name
)
326 if container_format
is None:
327 container_format
= "bare"
333 disk_format
=disk_format
,
334 container_format
=container_format
,
337 if image_checksum
is not None:
338 create_args
["checksum"] = image_checksum
341 self
._log
.debug("creating an image from url: %s", create_args
)
342 image
= self
._client
.images
.create(**create_args
)
343 except Exception as e
:
344 msg
= "Failed to create image from url in openstack"
345 self
._log
.exception(msg
)
346 raise OpenstackImageCreateError(msg
) from e
348 return OpenstackImage
.from_image_response(image
)
350 def delete_image_from_id(self
, image_id
):
351 self
._log
.info("Deleting image from catalog: %s", image_id
)
353 image
= self
._client
.images
.delete(image_id
)
354 except Exception as e
:
355 msg
= "Failed to delete image %s in openstack" % image_id
356 self
._log
.exception(msg
)
357 raise OpenstackImageDeleteError(msg
)