blob: fb41fb1fa4f3e5fbe8146adff2bb4287e096c272 [file] [log] [blame]
Richard Felkl4143a0e2017-02-01 23:24:13 +01001# -*- coding: utf-8 -*-
2'''
3Managing Images in OpenStack Glance
4===================================
5'''
6# Import python libs
7from __future__ import absolute_import
8import logging
9import time
10
11# Import salt libs
12
13# Import OpenStack libs
14try:
15 from keystoneclient.exceptions import \
16 Unauthorized as kstone_Unauthorized
17 HAS_KEYSTONE = True
18except ImportError:
19 try:
20 from keystoneclient.apiclient.exceptions import \
21 Unauthorized as kstone_Unauthorized
22 HAS_KEYSTONE = True
23 except ImportError:
24 HAS_KEYSTONE = False
25
26try:
27 from glanceclient.exc import \
28 HTTPUnauthorized as glance_Unauthorized
29 HAS_GLANCE = True
30except ImportError:
31 HAS_GLANCE = False
32
33log = logging.getLogger(__name__)
34
35
36def __virtual__():
37 '''
38 Only load if dependencies are loaded
39 '''
40 return HAS_KEYSTONE and HAS_GLANCE
41
42
43def _find_image(name, profile=None):
44 '''
45 Tries to find image with given name, returns
46 - image, 'Found image <name>'
47 - None, 'No such image found'
48 - False, 'Found more than one image with given name'
49 '''
50 try:
51 images = __salt__['glance.image_list'](name=name, profile=profile)
52 except kstone_Unauthorized:
53 return False, 'keystoneclient: Unauthorized'
54 except glance_Unauthorized:
55 return False, 'glanceclient: Unauthorized'
56 log.debug('Got images: {0}'.format(images))
57
58 if type(images) is dict and len(images) == 1 and 'images' in images:
59 images = images['images']
60
61 images_list = images.values() if type(images) is dict else images
62
63 if len(images_list) == 0:
64 return None, 'No image with name "{0}"'.format(name)
65 elif len(images_list) == 1:
66 return images_list[0], 'Found image {0}'.format(name)
67 elif len(images_list) > 1:
68 return False, 'Found more than one image with given name'
69 else:
70 raise NotImplementedError
71
72
73def image_present(name, profile=None, visibility='public', protected=None,
74 checksum=None, location=None, disk_format='raw', wait_for=None,
75 timeout=30):
76 '''
77 Checks if given image is present with properties
78 set as specified.
79 An image should got through the stages 'queued', 'saving'
80 before becoming 'active'. The attribute 'checksum' can
81 only be checked once the image is active.
82 If you don't specify 'wait_for' but 'checksum' the function
83 will wait for the image to become active before comparing
84 checksums. If you don't specify checksum either the function
85 will return when the image reached 'saving'.
86 The default timeout for both is 30 seconds.
87 Supported properties:
88 - profile (string)
89 - visibility ('public' or 'private')
90 - protected (bool)
91 - checksum (string, md5sum)
92 - location (URL, to copy from)
93 - disk_format ('raw' (default), 'vhd', 'vhdx', 'vmdk', 'vdi', 'iso',
94 'qcow2', 'aki', 'ari' or 'ami')
95 '''
96 ret = {'name': name,
97 'changes': {},
98 'result': True,
99 'comment': '',
100 }
101 acceptable = ['queued', 'saving', 'active']
102 if wait_for is None and checksum is None:
103 wait_for = 'saving'
104 elif wait_for is None and checksum is not None:
105 wait_for = 'active'
106
107 # Just pop states until we reach the
108 # first acceptable one:
109 while len(acceptable) > 1:
110 if acceptable[0] == wait_for:
111 break
112 else:
113 acceptable.pop(0)
114
115 image, msg = _find_image(name, profile)
116 if image is False:
117 if __opts__['test']:
118 ret['result'] = None
119 else:
120 ret['result'] = False
121 ret['comment'] = msg
122 return ret
123 log.debug(msg)
124 # No image yet and we know where to get one
125 if image is None and location is not None:
126 if __opts__['test']:
127 ret['result'] = None
128 ret['comment'] = 'glance.image_present would ' \
129 'create an image from {0}'.format(location)
130 return ret
131 image = __salt__['glance.image_create'](name=name, profile=profile,
132 protected=protected, visibility=visibility,
133 location=location, disk_format=disk_format)
134 log.debug('Created new image:\n{0}'.format(image))
135 ret['changes'] = {
136 name:
137 {
138 'new':
139 {
140 'id': image['id']
141 },
142 'old': None
143 }
144 }
145 timer = timeout
146 # Kinda busy-loopy but I don't think the Glance
147 # API has events we can listen for
148 while timer > 0:
149 if 'status' in image and \
150 image['status'] in acceptable:
151 log.debug('Image {0} has reached status {1}'.format(
152 image['name'], image['status']))
153 break
154 else:
155 timer -= 5
156 time.sleep(5)
157 image, msg = _find_image(name, profile)
158 if not image:
159 ret['result'] = False
160 ret['comment'] += 'Created image {0} '.format(
161 name) + ' vanished:\n' + msg
162 return ret
163 if timer <= 0 and image['status'] not in acceptable:
164 ret['result'] = False
165 ret['comment'] += 'Image didn\'t reach an acceptable '+\
166 'state ({0}) before timeout:\n'.format(acceptable)+\
167 '\tLast status was "{0}".\n'.format(image['status'])
168
169 # There's no image but where would I get one??
170 elif location is None:
171 if __opts__['test']:
172 ret['result'] = None
173 ret['comment'] = 'No location to copy image from specified,\n' +\
174 'glance.image_present would not create one'
175 else:
176 ret['result'] = False
177 ret['comment'] = 'No location to copy image from specified,\n' +\
178 'not creating a new image.'
179 return ret
180
181 # If we've created a new image also return its last status:
182 if name in ret['changes']:
183 ret['changes'][name]['new']['status'] = image['status']
184
185 if visibility:
186 if image['visibility'] != visibility:
187 old_value = image['visibility']
188 if not __opts__['test']:
189 image = __salt__['glance.image_update'](
190 id=image['id'], visibility=visibility)
191 # Check if image_update() worked:
192 if image['visibility'] != visibility:
193 if not __opts__['test']:
194 ret['result'] = False
195 elif __opts__['test']:
196 ret['result'] = None
197 ret['comment'] += '"visibility" is {0}, '\
198 'should be {1}.\n'.format(image['visibility'],
199 visibility)
200 else:
201 if 'new' in ret['changes']:
202 ret['changes']['new']['visibility'] = visibility
203 else:
204 ret['changes']['new'] = {'visibility': visibility}
205 if 'old' in ret['changes']:
206 ret['changes']['old']['visibility'] = old_value
207 else:
208 ret['changes']['old'] = {'visibility': old_value}
209 else:
210 ret['comment'] += '"visibility" is correct ({0}).\n'.format(
211 visibility)
212 if protected is not None:
213 if not isinstance(protected, bool) or image['protected'] ^ protected:
214 if not __opts__['test']:
215 ret['result'] = False
216 else:
217 ret['result'] = None
218 ret['comment'] += '"protected" is {0}, should be {1}.\n'.format(
219 image['protected'], protected)
220 else:
221 ret['comment'] += '"protected" is correct ({0}).\n'.format(
222 protected)
223 if 'status' in image and checksum:
224 if image['status'] == 'active':
225 if 'checksum' not in image:
226 # Refresh our info about the image
227 image = __salt__['glance.image_show'](image['id'])
228 if 'checksum' not in image:
229 if not __opts__['test']:
230 ret['result'] = False
231 else:
232 ret['result'] = None
233 ret['comment'] += 'No checksum available for this image:\n' +\
234 '\tImage has status "{0}".'.format(image['status'])
235 elif image['checksum'] != checksum:
236 if not __opts__['test']:
237 ret['result'] = False
238 else:
239 ret['result'] = None
240 ret['comment'] += '"checksum" is {0}, should be {1}.\n'.format(
241 image['checksum'], checksum)
242 else:
243 ret['comment'] += '"checksum" is correct ({0}).\n'.format(
244 checksum)
245 elif image['status'] in ['saving', 'queued']:
246 ret['comment'] += 'Checksum won\'t be verified as image ' +\
247 'hasn\'t reached\n\t "status=active" yet.\n'
248 log.debug('glance.image_present will return: {0}'.format(ret))
249 return ret