Ensure library and tests close all necessary resources.
[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     image_file = None
128
129     try:
130         if image_settings.extra_properties:
131             kwargs['properties'] = image_settings.extra_properties
132
133         if image_settings.url:
134             kwargs['location'] = image_settings.url
135         elif image_settings.image_file:
136             image_file = open(image_settings.image_file, 'rb')
137             kwargs['data'] = image_file
138         else:
139             logger.warn(
140                 'Unable to create image with name - %s. No file or URL',
141                 image_settings.name)
142             return None
143
144         created_image = glance.images.create(**kwargs)
145         return Image(name=image_settings.name, image_id=created_image.id,
146                      size=created_image.size,
147                      properties=created_image.properties)
148     finally:
149         if image_file:
150             image_file.close()
151
152
153 def __create_image_v2(glance, image_settings):
154     """
155     Creates and returns OpenStack image object with an external URL
156     :param glance: the glance client v2
157     :param image_settings: the image settings object
158     :return: the OpenStack image object
159     :raise GlanceException or IOException or URLError
160     """
161     cleanup_temp_file = False
162     image_file = None
163     if image_settings.image_file:
164         image_filename = image_settings.image_file
165     elif image_settings.url:
166         file_name = str(uuid.uuid4())
167         try:
168             image_file = file_utils.download(
169                 image_settings.url, './tmp', file_name)
170             image_filename = image_file.name
171         except:
172             if image_file:
173                 os.remove(image_file.name)
174             raise
175
176         cleanup_temp_file = True
177     else:
178         raise GlanceException('Filename or URL of image not configured')
179
180     os_image = None
181     try:
182         kwargs = dict()
183         kwargs['name'] = image_settings.name
184         kwargs['disk_format'] = image_settings.format
185         kwargs['container_format'] = 'bare'
186
187         if image_settings.public:
188             kwargs['visibility'] = 'public'
189
190         if image_settings.extra_properties:
191             kwargs.update(image_settings.extra_properties)
192
193         os_image = glance.images.create(**kwargs)
194         image_file = open(image_filename, 'rb')
195         glance.images.upload(os_image['id'], image_file)
196     except:
197         logger.error('Unexpected exception creating image. Rolling back')
198         if os_image:
199             delete_image(glance, Image(
200                 name=os_image['name'], image_id=os_image['id'],
201                 size=os_image['size'], properties=os_image.get('properties')))
202         raise
203     finally:
204         if image_file:
205             logger.debug('Closing file %s', image_file.name)
206             image_file.close()
207         if cleanup_temp_file:
208             logger.info('Removing file %s', image_file.name)
209             os.remove(image_filename)
210
211     return get_image_by_id(glance, os_image['id'])
212
213
214 def delete_image(glance, image):
215     """
216     Deletes an image from OpenStack
217     :param glance: the glance client
218     :param image: the image to delete
219     """
220     logger.info('Deleting image named - %s', image.name)
221     glance.images.delete(image.id)
222
223
224 class GlanceException(Exception):
225     """
226     Exception when calls to the Glance client cannot be served properly
227     """