49bfe958624d86b70994672aa2ccfe8a54e6473c
[snaps.git] / snaps / openstack / utils / glance_utils.py
1 # Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs")
2 #                    and others.  All rights reserved.
3 #
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:
7 #
8 #     http://www.apache.org/licenses/LICENSE-2.0
9 #
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.
15 import logging
16 import os
17 import uuid
18
19 from snaps import file_utils
20 from glanceclient.client import Client
21
22 from snaps.domain.image import Image
23 from snaps.openstack.utils import keystone_utils
24
25 __author__ = 'spisarski'
26
27 logger = logging.getLogger('glance_utils')
28
29 VERSION_1 = 1.0
30 VERSION_2 = 2.0
31
32 """
33 Utilities for basic neutron API calls
34 """
35
36
37 def glance_client(os_creds):
38     """
39     Creates and returns a glance client object
40     :return: the glance client
41     """
42     return Client(version=os_creds.image_api_version,
43                   session=keystone_utils.keystone_session(os_creds),
44                   region_name=os_creds.region_name)
45
46
47 def get_image(glance, image_name=None):
48     """
49     Returns an OpenStack image object for a given name
50     :param glance: the Glance client
51     :param image_name: the image name to lookup
52     :return: the image object or None
53     """
54     images = glance.images.list()
55     for image in images:
56         if glance.version == VERSION_1:
57             if image.name == image_name:
58                 image = glance.images.get(image.id)
59                 return Image(name=image.name, image_id=image.id,
60                              size=image.size, properties=image.properties)
61         elif glance.version == VERSION_2:
62             if image['name'] == image_name:
63                 return Image(
64                     name=image['name'], image_id=image['id'],
65                     size=image['size'], properties=image.get('properties'))
66     return None
67
68
69 def get_image_by_id(glance, image_id):
70     """
71     Returns an OpenStack image object for a given name
72     :param glance: the Glance client
73     :param image_id: the image ID to lookup
74     :return: the SNAPS-OO Domain Image object or None
75     """
76     image = glance.images.get(image_id)
77     return Image(
78         name=image['name'], image_id=image['id'],
79         size=image['size'], properties=image.get('properties'))
80
81
82 def get_image_status(glance, image):
83     """
84     Returns a new OpenStack Image object for a given OpenStack image object
85     :param glance: the Glance client
86     :param image: the domain Image object
87     :return: the OpenStack Image object
88     """
89     if glance.version == VERSION_1:
90         os_image = glance.images.get(image.id)
91         return os_image.status
92     elif glance.version == VERSION_2:
93         os_image = glance.images.get(image.id)
94         return os_image['status']
95     else:
96         raise GlanceException('Unsupported glance client version')
97
98
99 def create_image(glance, image_settings):
100     """
101     Creates and returns OpenStack image object with an external URL
102     :param glance: the glance client
103     :param image_settings: the image settings object
104     :return: the OpenStack image object
105     :raise Exception if using a file and it cannot be found
106     """
107     if glance.version == VERSION_1:
108         return __create_image_v1(glance, image_settings)
109     elif glance.version == VERSION_2:
110         return __create_image_v2(glance, image_settings)
111     else:
112         raise GlanceException('Unsupported glance client version')
113
114
115 def __create_image_v1(glance, image_settings):
116     """
117     Creates and returns OpenStack image object with an external URL
118     :param glance: the glance client
119     :param image_settings: the image settings object
120     :return: the OpenStack image object
121     :raise exceptions from the Glance client or IOError when opening a file
122     """
123     kwargs = {
124         'name': image_settings.name, 'disk_format': image_settings.format,
125         'container_format': 'bare', 'is_public': image_settings.public}
126
127     if image_settings.extra_properties:
128         kwargs['properties'] = image_settings.extra_properties
129
130     if image_settings.url:
131         kwargs['location'] = image_settings.url
132     elif image_settings.image_file:
133         image_file = open(image_settings.image_file, 'rb')
134         kwargs['data'] = image_file
135     else:
136         logger.warn('Unable to create image with name - %s. No file or URL',
137                     image_settings.name)
138         return None
139
140     created_image = glance.images.create(**kwargs)
141     return Image(name=image_settings.name, image_id=created_image.id,
142                  size=created_image.size, properties=created_image.properties)
143
144
145 def __create_image_v2(glance, image_settings):
146     """
147     Creates and returns OpenStack image object with an external URL
148     :param glance: the glance client v2
149     :param image_settings: the image settings object
150     :return: the OpenStack image object
151     :raise GlanceException or IOException or URLError
152     """
153     cleanup_temp_file = False
154     image_file = None
155     if image_settings.image_file:
156         image_filename = image_settings.image_file
157     elif image_settings.url:
158         file_name = str(uuid.uuid4())
159         try:
160             image_file = file_utils.download(
161                 image_settings.url, './tmp', file_name)
162             image_filename = image_file.name
163         except:
164             if image_file:
165                 os.remove(image_file.name)
166             raise
167
168         cleanup_temp_file = True
169     else:
170         raise GlanceException('Filename or URL of image not configured')
171
172     os_image = None
173     try:
174         kwargs = dict()
175         kwargs['name'] = image_settings.name
176         kwargs['disk_format'] = image_settings.format
177         kwargs['container_format'] = 'bare'
178
179         if image_settings.public:
180             kwargs['visibility'] = 'public'
181
182         if image_settings.extra_properties:
183             kwargs.update(image_settings.extra_properties)
184
185         os_image = glance.images.create(**kwargs)
186         image_file = open(image_filename, 'rb')
187         glance.images.upload(os_image['id'], image_file)
188     except:
189         logger.error('Unexpected exception creating image. Rolling back')
190         if os_image:
191             delete_image(glance, Image(
192                 name=os_image['name'], image_id=os_image['id'],
193                 size=os_image['size'], properties=os_image.get('properties')))
194         raise
195     finally:
196         if image_file:
197             logger.debug('Closing file %s', image_file.name)
198             image_file.close()
199         if cleanup_temp_file:
200             logger.info('Removing file %s', image_file.name)
201             os.remove(image_filename)
202
203     return get_image_by_id(glance, os_image['id'])
204
205
206 def delete_image(glance, image):
207     """
208     Deletes an image from OpenStack
209     :param glance: the glance client
210     :param image: the image to delete
211     """
212     logger.info('Deleting image named - %s', image.name)
213     glance.images.delete(image.id)
214
215
216 class GlanceException(Exception):
217     """
218     Exception when calls to the Glance client cannot be served properly
219     """