blob: 2250e25a6807ba22fa785195f0cdf487666ccf11 [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
Oleh Hryhorov6de49c92018-05-21 12:53:59 +0000138 elif 'MultipleResourcesFound' in repr(e):
Oleksiy Petrenko2652ac12018-04-16 17:40:13 +0300139 return _find_failed(name, 'image')
Oleh Hryhorov6de49c92018-05-21 12:53:59 +0000140 else:
141 raise
142
Oleksiy Petrenko2652ac12018-04-16 17:40:13 +0300143 to_change = []
144 for prop in image_properties:
145 path = prop.replace('~', '~0').replace('/', '~1')
146 if prop in exact_image:
147 if exact_image[prop] != image_properties[prop]:
148 to_change.append({
149 'op': 'replace',
150 'path': '/{}'.format(path),
151 'value': image_properties[prop]
152 })
153 else:
154 to_change.append({
155 'op': 'add',
156 'path': '/{}'.format(path),
157 'value': image_properties[prop]
158 })
159 if to_change:
160 try:
161 resp = _glancev2_call(
162 'image_update', name=name,
163 properties=to_change, cloud_name=cloud_name,
164 )
165 except Exception as e:
166 log.error('Glance image update failed with {}'.format(e))
167 return _update_failed(name, 'image')
168 return _updated(name, 'image', resp)
169 return _no_changes(name, 'image')
170
171
172def image_absent(name, cloud_name):
173 try:
174 image = _glancev2_call(
175 'image_get_details', name=name, cloud_name=cloud_name
176 )
177 except Exception as e:
178 if 'ResourceNotFound' in repr(e):
179 return _absent(name, 'image')
180 if 'MultipleResourcesFound' in repr(e):
181 return _find_failed(name, 'image')
182 try:
183 _glancev2_call(
184 'image_delete', name=name, cloud_name=cloud_name
185 )
186 except Exception as e:
187 log.error('Glance image delete failed with {}'.format(e))
188 return _delete_failed(name, 'image')
189 return _deleted(name, 'image')
190
191
192def _created(name, resource, resource_definition):
193 changes_dict = {
194 'name': name,
195 'changes': resource_definition,
196 'result': True,
197 'comment': '{}{} created'.format(resource, name)
198 }
199 return changes_dict
200
201
202def _updated(name, resource, resource_definition):
203 changes_dict = {
204 'name': name,
205 'changes': resource_definition,
206 'result': True,
207 'comment': '{}{} updated'.format(resource, name)
208 }
209 return changes_dict
210
211
212def _no_changes(name, resource):
213 changes_dict = {
214 'name': name,
215 'changes': {},
216 'result': True,
217 'comment': '{}{} is in desired state'.format(resource, name)
218 }
219 return changes_dict
220
221
222def _deleted(name, resource):
223 changes_dict = {
224 'name': name,
225 'changes': {},
226 'result': True,
227 'comment': '{}{} removed'.format(resource, name)
228 }
229 return changes_dict
230
231
232def _absent(name, resource):
233 changes_dict = {'name': name,
234 'changes': {},
235 'comment': '{0} {1} not present'.format(resource, name),
236 'result': True}
237 return changes_dict
238
239
240def _delete_failed(name, resource):
241 changes_dict = {'name': name,
242 'changes': {},
243 'comment': '{0} {1} failed to delete'.format(resource,
244 name),
245 'result': False}
246 return changes_dict
247
248
249def _create_failed(name, resource):
250 changes_dict = {'name': name,
251 'changes': {},
252 'comment': '{0} {1} failed to create'.format(resource,
253 name),
254 'result': False}
255 return changes_dict
256
257
258def _update_failed(name, resource):
259 changes_dict = {'name': name,
260 'changes': {},
261 'comment': '{0} {1} failed to update'.format(resource,
262 name),
263 'result': False}
264 return changes_dict
265
266
267def _find_failed(name, resource):
268 changes_dict = {
269 'name': name,
270 'changes': {},
271 'comment': '{0} {1} found multiple {0}'.format(resource, name),
272 'result': False,
273 }
274 return changes_dict