Merge "Auto Generated INFO.yaml file"
[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, session=None):
38     """
39     Creates and returns a glance client object
40     :param os_creds: the credentials for connecting to the OpenStack remote API
41     :param session: the keystone session object (optional)
42     :return: the glance client
43     """
44     if not session:
45         session = keystone_utils.keystone_session(os_creds)
46
47     return Client(version=os_creds.image_api_version,
48                   session=session,
49                   region_name=os_creds.region_name)
50
51
52 def get_image(glance, image_name=None, image_settings=None):
53     """
54     Returns an OpenStack image object for a given name
55     :param glance: the Glance client
56     :param image_name: the image name to lookup
57     :param image_settings: the image settings used for lookups
58     :return: the image object or None
59     """
60     img_filter = dict()
61     if image_settings:
62         if image_settings.exists:
63             img_filter = {'name': image_settings.name}
64         else:
65             img_filter = {'name': image_settings.name,
66                           'disk_format': image_settings.format}
67     elif image_name:
68         img_filter = {'name': image_name}
69
70     images = glance.images.list(**{'filters': img_filter})
71     for image in images:
72         if glance.version == VERSION_1:
73             image = glance.images.get(image.id)
74             return Image(name=image.name, image_id=image.id,
75                          size=image.size, properties=image.properties)
76         elif glance.version == VERSION_2:
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_by_id(glance, image_id):
83     """
84     Returns an OpenStack image object for a given name
85     :param glance: the Glance client
86     :param image_id: the image ID to lookup
87     :return: the SNAPS-OO Domain Image object or None
88     """
89     image = glance.images.get(image_id)
90     return Image(
91         name=image['name'], image_id=image['id'],
92         size=image['size'], properties=image.get('properties'))
93
94
95 def get_image_status(glance, image):
96     """
97     Returns a new OpenStack Image object for a given OpenStack image object
98     :param glance: the Glance client
99     :param image: the domain Image object
100     :return: the OpenStack Image object
101     """
102     if glance.version == VERSION_1:
103         os_image = glance.images.get(image.id)
104         return os_image.status
105     elif glance.version == VERSION_2:
106         os_image = glance.images.get(image.id)
107         return os_image['status']
108     else:
109         raise GlanceException('Unsupported glance client version')
110
111
112 def create_image(glance, image_settings):
113     """
114     Creates and returns OpenStack image object with an external URL
115     :param glance: the glance client
116     :param image_settings: the image settings object
117     :return: the OpenStack image object
118     :raise Exception if using a file and it cannot be found
119     """
120     if glance.version == VERSION_1:
121         return __create_image_v1(glance, image_settings)
122     elif glance.version == VERSION_2:
123         return __create_image_v2(glance, image_settings)
124     else:
125         raise GlanceException('Unsupported glance client version')
126
127
128 def __create_image_v1(glance, image_settings):
129     """
130     Creates and returns OpenStack image object with an external URL
131     :param glance: the glance client
132     :param image_settings: the image settings object
133     :return: the OpenStack image object
134     :raise exceptions from the Glance client or IOError when opening a file
135     """
136     kwargs = {
137         'name': image_settings.name, 'disk_format': image_settings.format,
138         'container_format': 'bare', 'is_public': image_settings.public}
139
140     image_file = None
141
142     try:
143         if image_settings.extra_properties:
144             kwargs['properties'] = image_settings.extra_properties
145
146         if image_settings.url:
147             kwargs['location'] = image_settings.url
148         elif image_settings.image_file:
149             image_file = open(image_settings.image_file, 'rb')
150             kwargs['data'] = image_file
151         else:
152             logger.warn(
153                 'Unable to create image with name - %s. No file or URL',
154                 image_settings.name)
155             return None
156
157         created_image = glance.images.create(**kwargs)
158         return Image(name=image_settings.name, image_id=created_image.id,
159                      size=created_image.size,
160                      properties=created_image.properties)
161     finally:
162         if image_file:
163             image_file.close()
164
165
166 def __create_image_v2(glance, image_settings):
167     """
168     Creates and returns OpenStack image object with an external URL
169     :param glance: the glance client v2
170     :param image_settings: the image settings object
171     :return: the OpenStack image object
172     :raise GlanceException or IOException or URLError
173     """
174     cleanup_temp_file = False
175     image_file = None
176     if image_settings.image_file is not None:
177         image_filename = image_settings.image_file
178     elif image_settings.url:
179         file_name = str(uuid.uuid4())
180         try:
181             image_file = file_utils.download(
182                 image_settings.url, './tmp', file_name)
183             image_filename = image_file.name
184         except:
185             if image_file:
186                 os.remove(image_file.name)
187             raise
188
189         cleanup_temp_file = True
190     else:
191         raise GlanceException('Filename or URL of image not configured')
192
193     os_image = None
194     try:
195         kwargs = dict()
196         kwargs['name'] = image_settings.name
197         kwargs['disk_format'] = image_settings.format
198         kwargs['container_format'] = 'bare'
199
200         if image_settings.public:
201             kwargs['visibility'] = 'public'
202
203         if image_settings.extra_properties:
204             kwargs.update(image_settings.extra_properties)
205
206         os_image = glance.images.create(**kwargs)
207         image_file = open(os.path.expanduser(image_filename), 'rb')
208         glance.images.upload(os_image['id'], image_file)
209     except:
210         logger.error('Unexpected exception creating image. Rolling back')
211         if os_image:
212             delete_image(glance, Image(
213                 name=os_image['name'], image_id=os_image['id'],
214                 size=os_image['size'], properties=os_image.get('properties')))
215         raise
216     finally:
217         if image_file:
218             logger.debug('Closing file %s', image_file.name)
219             image_file.close()
220         if cleanup_temp_file:
221             logger.info('Removing file %s', image_file.name)
222             os.remove(image_filename)
223
224     return get_image_by_id(glance, os_image['id'])
225
226
227 def delete_image(glance, image):
228     """
229     Deletes an image from OpenStack
230     :param glance: the glance client
231     :param image: the image to delete
232     """
233     logger.info('Deleting image named - %s', image.name)
234     glance.images.delete(image.id)
235
236
237 class GlanceException(Exception):
238     """
239     Exception when calls to the Glance client cannot be served properly
240     """