blob: 2c5a84fc0622801fdb570072185c42d7816ef427 [file] [log] [blame]
Oleksiy Petrenko2652ac12018-04-16 17:40:13 +03001# -*- coding: utf-8 -*-
2'''
3Managing Images in OpenStack Glance
4===================================
5'''
6# Import python libs
7import logging
8import time
9
10
11# Import OpenStack libs
12def __virtual__():
13 return 'glancev2' if 'glancev2.image_list' in __salt__ else False
14
15
16log = logging.getLogger(__name__)
17
18
19def _glancev2_call(fname, *args, **kwargs):
20 return __salt__['glancev2.{}'.format(fname)](*args, **kwargs)
21
22
23def image_present(name, cloud_name, location, image_properties,
24 import_from_format='raw', timeout=30,
25 sleep_time=5, checksum=None):
26 """
27 Creates a task to import an image
28
29 This state checks if an image is present and, if not, creates a task
30 with import_type that would download an image from a remote location and
31 upload it to Glance.
32 After the task is created, its status is monitored. On success the state
33 would check that an image is present and return its ID.
34
35 Also, this state can update(add and replace) image properties,
36 but !!It can't delete properties, that are already in state
37
38 :param name: name of the image
39 :param cloud_name: name of the cloud in cloud.yaml
40 :param location: url link describing where to obtain image
41 :param image_properties: Dict that contains params needed
42 to create or update image.
43 :param container_format: Format of the container
44 :param disk_format: Format of the disk
45 :param protected: If true, image will not be deletable.
46 :param tags: List of strings related to the image
47 :param visibility: Scope of image accessibility.
48 Valid values: public, private, community, shared
49 :param import_from_format: (optional) Format to import the image from
50 :param timeout: (optional) Time for task to download image
51 :param sleep_time: (optional) Timer countdown
52 :param checksum: (optional) checksum of the image to verify it
53 """
54 try:
55 exact_image = _glancev2_call(
56 'image_get_details', name=name, cloud_name=cloud_name
57 )
58 except Exception as e:
59 if 'ResourceNotFound' in repr(e):
60 image_properties['name'] = name
61 task_params = {"import_from": location,
62 "import_from_format": import_from_format,
63 "image_properties": image_properties
64 }
65 # Try create task
66 try:
67 task = _glancev2_call(
68 'task_create', task_type='import', task_input=task_params,
69 cloud_name=cloud_name
70 )
71 except Exception as e:
72 log.error(
73 'Glance image create failed on create task with {}'.format(
74 e)
75 )
76 return _create_failed(name, 'image_task')
77 while timeout > 0:
78 if task['status'] == 'success':
79 break
80 elif task['status'] == 'failure':
81 log.error('Glance task failed to complete')
82 return _create_failed(name, 'image')
83 else:
84 timeout -= sleep_time
85 time.sleep(sleep_time)
86 # Check task status again
87 try:
88 task = _glancev2_call(
89 'task_show', task_id=task['id'],
90 cloud_name=cloud_name
91 )
92 except Exception as e:
93 log.error(
94 'Glance failed to check '
95 'task status with {}'.format(e)
96 )
97 return _create_failed(name, 'image_task')
98 if timeout <= 0 and task['status'] != 'success':
99 log.error(
100 'Glance task failed to import '
101 'image for given amount of time'
102 )
103 return _create_failed(name, 'image')
104 # Task successfully finished
105 # and now check that is created the image
106
107 image = _glancev2_call(
108 'image_list', name=name, cloud_name=cloud_name
109 )['images'][0]
110
111 if not image:
112 return _create_failed(name, 'image')
113
114 resp = _created(name, 'image', image)
115
116 if checksum:
117 if image['status'] == 'active':
118 if 'checksum' not in image:
119 log.error(
120 'Glance image. No checksum for image.'
121 'Image status is active'
122 )
123 return _create_failed(name, 'image')
124 if image['checksum'] != checksum:
125 log.error(
126 'Glance image create failed since '
127 'image_checksum should be '
128 '{} but it is {}'.format(checksum,
129 image['checksum'])
130 )
131 return _create_failed(name, 'image')
132
133 elif image['status'] in ['saving', 'queued']:
134 resp['comment'] = resp['comment'] \
135 + " checksum couldn't be verified, " \
136 "since status is not active"
137 return resp
138 if 'MultipleResourcesFound' in repr(e):
139 return _find_failed(name, 'image')
140 to_change = []
141 for prop in image_properties:
142 path = prop.replace('~', '~0').replace('/', '~1')
143 if prop in exact_image:
144 if exact_image[prop] != image_properties[prop]:
145 to_change.append({
146 'op': 'replace',
147 'path': '/{}'.format(path),
148 'value': image_properties[prop]
149 })
150 else:
151 to_change.append({
152 'op': 'add',
153 'path': '/{}'.format(path),
154 'value': image_properties[prop]
155 })
156 if to_change:
157 try:
158 resp = _glancev2_call(
159 'image_update', name=name,
160 properties=to_change, cloud_name=cloud_name,
161 )
162 except Exception as e:
163 log.error('Glance image update failed with {}'.format(e))
164 return _update_failed(name, 'image')
165 return _updated(name, 'image', resp)
166 return _no_changes(name, 'image')
167
168
169def image_absent(name, cloud_name):
170 try:
171 image = _glancev2_call(
172 'image_get_details', name=name, cloud_name=cloud_name
173 )
174 except Exception as e:
175 if 'ResourceNotFound' in repr(e):
176 return _absent(name, 'image')
177 if 'MultipleResourcesFound' in repr(e):
178 return _find_failed(name, 'image')
179 try:
180 _glancev2_call(
181 'image_delete', name=name, cloud_name=cloud_name
182 )
183 except Exception as e:
184 log.error('Glance image delete failed with {}'.format(e))
185 return _delete_failed(name, 'image')
186 return _deleted(name, 'image')
187
188
189def _created(name, resource, resource_definition):
190 changes_dict = {
191 'name': name,
192 'changes': resource_definition,
193 'result': True,
194 'comment': '{}{} created'.format(resource, name)
195 }
196 return changes_dict
197
198
199def _updated(name, resource, resource_definition):
200 changes_dict = {
201 'name': name,
202 'changes': resource_definition,
203 'result': True,
204 'comment': '{}{} updated'.format(resource, name)
205 }
206 return changes_dict
207
208
209def _no_changes(name, resource):
210 changes_dict = {
211 'name': name,
212 'changes': {},
213 'result': True,
214 'comment': '{}{} is in desired state'.format(resource, name)
215 }
216 return changes_dict
217
218
219def _deleted(name, resource):
220 changes_dict = {
221 'name': name,
222 'changes': {},
223 'result': True,
224 'comment': '{}{} removed'.format(resource, name)
225 }
226 return changes_dict
227
228
229def _absent(name, resource):
230 changes_dict = {'name': name,
231 'changes': {},
232 'comment': '{0} {1} not present'.format(resource, name),
233 'result': True}
234 return changes_dict
235
236
237def _delete_failed(name, resource):
238 changes_dict = {'name': name,
239 'changes': {},
240 'comment': '{0} {1} failed to delete'.format(resource,
241 name),
242 'result': False}
243 return changes_dict
244
245
246def _create_failed(name, resource):
247 changes_dict = {'name': name,
248 'changes': {},
249 'comment': '{0} {1} failed to create'.format(resource,
250 name),
251 'result': False}
252 return changes_dict
253
254
255def _update_failed(name, resource):
256 changes_dict = {'name': name,
257 'changes': {},
258 'comment': '{0} {1} failed to update'.format(resource,
259 name),
260 'result': False}
261 return changes_dict
262
263
264def _find_failed(name, resource):
265 changes_dict = {
266 'name': name,
267 'changes': {},
268 'comment': '{0} {1} found multiple {0}'.format(resource, name),
269 'result': False,
270 }
271 return changes_dict