blob: 6664b7e3d90df6060040eeee2e561dfe879fbdc0 [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
Oleksiy Petrenkoed088d02018-12-07 14:04:14 +020023def _cinderv3_call(fname, *args, **kwargs):
24 return __salt__['cinderv3.{}'.format(fname)](*args, **kwargs)
25
26
27def image_present(name, cloud_name, location=None, image_properties=None,
Oleksiy Petrenko2652ac12018-04-16 17:40:13 +030028 import_from_format='raw', timeout=30,
Oleksiy Petrenkoed088d02018-12-07 14:04:14 +020029 sleep_time=5, checksum=None,
30 volume_name=None, volume_kwargs=None):
Oleksiy Petrenko2652ac12018-04-16 17:40:13 +030031 """
32 Creates a task to import an image
33
34 This state checks if an image is present and, if not, creates a task
35 with import_type that would download an image from a remote location and
36 upload it to Glance.
37 After the task is created, its status is monitored. On success the state
38 would check that an image is present and return its ID.
39
40 Also, this state can update(add and replace) image properties,
41 but !!It can't delete properties, that are already in state
42
43 :param name: name of the image
44 :param cloud_name: name of the cloud in cloud.yaml
45 :param location: url link describing where to obtain image
46 :param image_properties: Dict that contains params needed
47 to create or update image.
48 :param container_format: Format of the container
49 :param disk_format: Format of the disk
50 :param protected: If true, image will not be deletable.
51 :param tags: List of strings related to the image
52 :param visibility: Scope of image accessibility.
53 Valid values: public, private, community, shared
54 :param import_from_format: (optional) Format to import the image from
Pavlo Shchelokovskyybab584d2019-06-10 16:59:50 +030055 :param timeout: (optional) Time for task to download image or wait
56 until it becomes active if it is already present
Oleksiy Petrenko2652ac12018-04-16 17:40:13 +030057 :param sleep_time: (optional) Timer countdown
58 :param checksum: (optional) checksum of the image to verify it
Oleksiy Petrenkoed088d02018-12-07 14:04:14 +020059 :param volume_name: (optional) name of the volume if specified the command
60 works like openstack image create --volume
61 :param volume_kwargs: (optional) if volume_name is specified, this will be
62 used as arguments to image_upload_volume
Oleksiy Petrenko2652ac12018-04-16 17:40:13 +030063 """
64 try:
65 exact_image = _glancev2_call(
66 'image_get_details', name=name, cloud_name=cloud_name
67 )
68 except Exception as e:
69 if 'ResourceNotFound' in repr(e):
Oleksiy Petrenkoed088d02018-12-07 14:04:14 +020070 if volume_name:
71 try:
72 _cinderv3_call(
73 'volume_get_details', volume_name,
74 cloud_name=cloud_name
75 )
76 except Exception as e:
77 if 'ResourceNotFound' in repr(e):
78 return _failed('none', volume_name, 'volume')
79 elif 'MultipleResourcesFound' in repr(e):
80 return _failed('find', volume_name, 'volume')
81 else:
82 raise
83 if not volume_kwargs:
84 volume_kwargs = {}
85 try:
86 resp = _cinderv3_call(
87 'image_upload_volume', volume_name, image_name=name,
88 cloud_name=cloud_name, **volume_kwargs)
89 except Exception as e:
90 log.error(
91 'Glance create image with '
92 'volume failed with {}'.format(e))
93 return _failed('create', name, 'image')
94 image_status = resp['os-volume_upload_image']['status']
95 while timeout > 0 and image_status != 'active':
Oleksiy Petrenko2652ac12018-04-16 17:40:13 +030096 try:
Oleksiy Petrenkoed088d02018-12-07 14:04:14 +020097 image = _glancev2_call('image_get_details', name=name,
Pavlo Shchelokovskyybab584d2019-06-10 16:59:50 +030098 cloud_name=cloud_name)
Oleksiy Petrenko2652ac12018-04-16 17:40:13 +030099 except Exception as e:
Oleksiy Petrenkoed088d02018-12-07 14:04:14 +0200100 if 'ResourceNotFound' in repr(e):
101 timeout -= sleep_time
102 time.sleep(sleep_time)
103 continue
104 else:
105 raise
106 image_status = image['status']
107 if image_status != 'active':
108 log.error(
109 'Glance upload image to volume failed '
110 'for given amount of time'
111 )
112 return _failed('create', name, 'image')
113 return _succeeded('create', name, 'image', image)
Oleksiy Petrenko2652ac12018-04-16 17:40:13 +0300114
Oleksiy Petrenkoed088d02018-12-07 14:04:14 +0200115 else:
116 if not (location and image_properties):
117 return _failed('create', name, 'image')
118 image_properties['name'] = name
119 task_params = {"import_from": location,
120 "import_from_format": import_from_format,
121 "image_properties": image_properties
122 }
123 # Try create task
124 try:
125 task = _glancev2_call(
126 'task_create', task_type='import',
127 task_input=task_params, cloud_name=cloud_name)
128 except Exception as e:
129 log.error(
130 'Glance image create failed on '
131 'create task with {}'.format(
132 e)
133 )
134 return _failed('create', name, 'image_task')
135 while timeout > 0:
136 if task['status'] == 'success':
137 break
138 elif task['status'] == 'failure':
139 log.error('Glance task failed to complete')
140 return _failed('create', name, 'image')
141 else:
142 timeout -= sleep_time
143 time.sleep(sleep_time)
144 # Check task status again
145 try:
146 task = _glancev2_call(
147 'task_show', task_id=task['id'],
148 cloud_name=cloud_name
149 )
150 except Exception as e:
151 log.error(
152 'Glance failed to check '
153 'task status with {}'.format(e)
154 )
155 return _failed('create', name, 'image_task')
156 if timeout <= 0 and task['status'] != 'success':
157 log.error(
158 'Glance task failed to import '
159 'image for given amount of time'
160 )
161 return _failed('create', name, 'image')
162 # Task successfully finished
163 # and now check that is created the image
Oleksiy Petrenko2652ac12018-04-16 17:40:13 +0300164
Oleksiy Petrenkoed088d02018-12-07 14:04:14 +0200165 images = _glancev2_call(
166 'image_list', name=name, cloud_name=cloud_name
167 )['images']
Oleksiy Petrenko2652ac12018-04-16 17:40:13 +0300168
Oleksiy Petrenkoed088d02018-12-07 14:04:14 +0200169 if not len(images):
170 return _failed('create', name, 'image')
171 image = images[0]
172 resp = _succeeded('create', name, 'image', image)
Oleksiy Petrenko2652ac12018-04-16 17:40:13 +0300173
Oleksiy Petrenkoed088d02018-12-07 14:04:14 +0200174 if checksum:
175 if image['status'] == 'active':
176 if 'checksum' not in image:
177 log.error(
178 'Glance image. No checksum for image.'
179 'Image status is active'
180 )
181 return _failed('create', name, 'image')
182 if image['checksum'] != checksum:
183 log.error(
184 'Glance image create failed since '
185 'image_checksum should be '
186 '{} but it is {}'.format(checksum,
187 image['checksum'])
188 )
189 return _failed('create', name, 'image')
Oleksiy Petrenko2652ac12018-04-16 17:40:13 +0300190
Oleksiy Petrenkoed088d02018-12-07 14:04:14 +0200191 elif image['status'] in ['saving', 'queued']:
Pavlo Shchelokovskyybab584d2019-06-10 16:59:50 +0300192 resp['comment'] += (" checksum couldn't be verified, "
193 "since status is not active")
Oleksiy Petrenkoed088d02018-12-07 14:04:14 +0200194 return resp
Oleh Hryhorov6de49c92018-05-21 12:53:59 +0000195 elif 'MultipleResourcesFound' in repr(e):
Oleksiy Petrenkoed088d02018-12-07 14:04:14 +0200196 return _failed('find', name, 'image')
Oleh Hryhorov6de49c92018-05-21 12:53:59 +0000197 else:
198 raise
199
Pavlo Shchelokovskyybab584d2019-06-10 16:59:50 +0300200 # NOTE(pas-ha) fail the salt state if existing image is not in active state
201 image_status = exact_image['status'].copy()
202 while timeout > 0 and image_status != 'active':
203 log.warning("Image {name} is not ACTIVE in state, waiting..".format(
204 name=exact_image['name']))
205 timeout -= sleep_time
206 time.sleep(sleep_time)
207 try:
208 image = _glancev2_call('image_get_details', name=name,
209 cloud_name=cloud_name)
210 except Exception:
211 raise
212 image_status = image['status']
213 if image_status != 'active':
214 log.error("Image {name} failed to reach ACTIVE in state".format(
215 name=exact_image['name']))
216 return _failed('present', name, 'image')
217
Oleksiy Petrenko2652ac12018-04-16 17:40:13 +0300218 to_change = []
219 for prop in image_properties:
220 path = prop.replace('~', '~0').replace('/', '~1')
221 if prop in exact_image:
222 if exact_image[prop] != image_properties[prop]:
223 to_change.append({
224 'op': 'replace',
225 'path': '/{}'.format(path),
226 'value': image_properties[prop]
227 })
228 else:
229 to_change.append({
230 'op': 'add',
231 'path': '/{}'.format(path),
232 'value': image_properties[prop]
233 })
234 if to_change:
235 try:
236 resp = _glancev2_call(
237 'image_update', name=name,
238 properties=to_change, cloud_name=cloud_name,
239 )
240 except Exception as e:
241 log.error('Glance image update failed with {}'.format(e))
Oleksiy Petrenkoed088d02018-12-07 14:04:14 +0200242 return _failed('update', name, 'image')
243 return _succeeded('update', name, 'image', resp)
244 return _succeeded('no_changes', name, 'image')
Oleksiy Petrenko2652ac12018-04-16 17:40:13 +0300245
246
247def image_absent(name, cloud_name):
248 try:
Oleksiy Petrenkoed088d02018-12-07 14:04:14 +0200249 _glancev2_call('image_get_details', name=name, cloud_name=cloud_name)
Oleksiy Petrenko2652ac12018-04-16 17:40:13 +0300250 except Exception as e:
251 if 'ResourceNotFound' in repr(e):
Oleksiy Petrenkoed088d02018-12-07 14:04:14 +0200252 return _succeeded('absent', name, 'image')
Oleksiy Petrenko2652ac12018-04-16 17:40:13 +0300253 if 'MultipleResourcesFound' in repr(e):
Oleksiy Petrenkoed088d02018-12-07 14:04:14 +0200254 return _failed('find', name, 'image')
Oleksiy Petrenko2652ac12018-04-16 17:40:13 +0300255 try:
Oleksiy Petrenkoed088d02018-12-07 14:04:14 +0200256 _glancev2_call('image_delete', name=name, cloud_name=cloud_name)
Oleksiy Petrenko2652ac12018-04-16 17:40:13 +0300257 except Exception as e:
258 log.error('Glance image delete failed with {}'.format(e))
Oleksiy Petrenkoed088d02018-12-07 14:04:14 +0200259 return _failed('delete', name, 'image')
260 return _succeeded('delete', name, 'image')
Oleksiy Petrenko2652ac12018-04-16 17:40:13 +0300261
262
Oleksiy Petrenkoed088d02018-12-07 14:04:14 +0200263def _succeeded(op, name, resource, changes=None):
264 msg_map = {
265 'create': '{0} {1} created',
266 'delete': '{0} {1} removed',
267 'update': '{0} {1} updated',
268 'no_changes': '{0} {1} is in desired state',
269 'absent': '{0} {1} not present',
270 'resources_moved': '{1} resources were moved from {0}',
271 }
Oleksiy Petrenko2652ac12018-04-16 17:40:13 +0300272 changes_dict = {
273 'name': name,
Oleksiy Petrenko2652ac12018-04-16 17:40:13 +0300274 'result': True,
Oleksiy Petrenkoed088d02018-12-07 14:04:14 +0200275 'comment': msg_map[op].format(resource, name),
276 'changes': changes or {},
Oleksiy Petrenko2652ac12018-04-16 17:40:13 +0300277 }
278 return changes_dict
279
Pavlo Shchelokovskyybab584d2019-06-10 16:59:50 +0300280
Oleksiy Petrenkoed088d02018-12-07 14:04:14 +0200281def _failed(op, name, resource):
282 msg_map = {
283 'create': '{0} {1} failed to create',
284 'delete': '{0} {1} failed to delete',
285 'update': '{0} {1} failed to update',
286 'find': '{0} {1} found multiple {0}',
287 'none': '{0} {1} found no {0}',
288 'resources_moved': 'failed to move {1} from {0}',
Oleksiy Petrenko2652ac12018-04-16 17:40:13 +0300289 }
Oleksiy Petrenko2652ac12018-04-16 17:40:13 +0300290 changes_dict = {
291 'name': name,
Oleksiy Petrenko2652ac12018-04-16 17:40:13 +0300292 'result': False,
Oleksiy Petrenkoed088d02018-12-07 14:04:14 +0200293 'comment': msg_map[op].format(resource, name),
294 'changes': {},
Oleksiy Petrenko2652ac12018-04-16 17:40:13 +0300295 }
Pavlo Shchelokovskyybab584d2019-06-10 16:59:50 +0300296 return changes_dict