blob: 5b149f26782a55e4143994d14c42a837020f0daf [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
55 :param timeout: (optional) Time for task to download image
56 :param sleep_time: (optional) Timer countdown
57 :param checksum: (optional) checksum of the image to verify it
Oleksiy Petrenkoed088d02018-12-07 14:04:14 +020058 :param volume_name: (optional) name of the volume if specified the command
59 works like openstack image create --volume
60 :param volume_kwargs: (optional) if volume_name is specified, this will be
61 used as arguments to image_upload_volume
Oleksiy Petrenko2652ac12018-04-16 17:40:13 +030062 """
63 try:
64 exact_image = _glancev2_call(
65 'image_get_details', name=name, cloud_name=cloud_name
66 )
67 except Exception as e:
68 if 'ResourceNotFound' in repr(e):
Oleksiy Petrenkoed088d02018-12-07 14:04:14 +020069 if volume_name:
70 try:
71 _cinderv3_call(
72 'volume_get_details', volume_name,
73 cloud_name=cloud_name
74 )
75 except Exception as e:
76 if 'ResourceNotFound' in repr(e):
77 return _failed('none', volume_name, 'volume')
78 elif 'MultipleResourcesFound' in repr(e):
79 return _failed('find', volume_name, 'volume')
80 else:
81 raise
82 if not volume_kwargs:
83 volume_kwargs = {}
84 try:
85 resp = _cinderv3_call(
86 'image_upload_volume', volume_name, image_name=name,
87 cloud_name=cloud_name, **volume_kwargs)
88 except Exception as e:
89 log.error(
90 'Glance create image with '
91 'volume failed with {}'.format(e))
92 return _failed('create', name, 'image')
93 image_status = resp['os-volume_upload_image']['status']
94 while timeout > 0 and image_status != 'active':
Oleksiy Petrenko2652ac12018-04-16 17:40:13 +030095 try:
Oleksiy Petrenkoed088d02018-12-07 14:04:14 +020096 image = _glancev2_call('image_get_details', name=name,
97 cloud_name=cloud_name)
Oleksiy Petrenko2652ac12018-04-16 17:40:13 +030098 except Exception as e:
Oleksiy Petrenkoed088d02018-12-07 14:04:14 +020099 if 'ResourceNotFound' in repr(e):
100 timeout -= sleep_time
101 time.sleep(sleep_time)
102 continue
103 else:
104 raise
105 image_status = image['status']
106 if image_status != 'active':
107 log.error(
108 'Glance upload image to volume failed '
109 'for given amount of time'
110 )
111 return _failed('create', name, 'image')
112 return _succeeded('create', name, 'image', image)
Oleksiy Petrenko2652ac12018-04-16 17:40:13 +0300113
Oleksiy Petrenkoed088d02018-12-07 14:04:14 +0200114 else:
115 if not (location and image_properties):
116 return _failed('create', name, 'image')
117 image_properties['name'] = name
118 task_params = {"import_from": location,
119 "import_from_format": import_from_format,
120 "image_properties": image_properties
121 }
122 # Try create task
123 try:
124 task = _glancev2_call(
125 'task_create', task_type='import',
126 task_input=task_params, cloud_name=cloud_name)
127 except Exception as e:
128 log.error(
129 'Glance image create failed on '
130 'create task with {}'.format(
131 e)
132 )
133 return _failed('create', name, 'image_task')
134 while timeout > 0:
135 if task['status'] == 'success':
136 break
137 elif task['status'] == 'failure':
138 log.error('Glance task failed to complete')
139 return _failed('create', name, 'image')
140 else:
141 timeout -= sleep_time
142 time.sleep(sleep_time)
143 # Check task status again
144 try:
145 task = _glancev2_call(
146 'task_show', task_id=task['id'],
147 cloud_name=cloud_name
148 )
149 except Exception as e:
150 log.error(
151 'Glance failed to check '
152 'task status with {}'.format(e)
153 )
154 return _failed('create', name, 'image_task')
155 if timeout <= 0 and task['status'] != 'success':
156 log.error(
157 'Glance task failed to import '
158 'image for given amount of time'
159 )
160 return _failed('create', name, 'image')
161 # Task successfully finished
162 # and now check that is created the image
Oleksiy Petrenko2652ac12018-04-16 17:40:13 +0300163
Oleksiy Petrenkoed088d02018-12-07 14:04:14 +0200164 images = _glancev2_call(
165 'image_list', name=name, cloud_name=cloud_name
166 )['images']
Oleksiy Petrenko2652ac12018-04-16 17:40:13 +0300167
Oleksiy Petrenkoed088d02018-12-07 14:04:14 +0200168 if not len(images):
169 return _failed('create', name, 'image')
170 image = images[0]
171 resp = _succeeded('create', name, 'image', image)
Oleksiy Petrenko2652ac12018-04-16 17:40:13 +0300172
Oleksiy Petrenkoed088d02018-12-07 14:04:14 +0200173 if checksum:
174 if image['status'] == 'active':
175 if 'checksum' not in image:
176 log.error(
177 'Glance image. No checksum for image.'
178 'Image status is active'
179 )
180 return _failed('create', name, 'image')
181 if image['checksum'] != checksum:
182 log.error(
183 'Glance image create failed since '
184 'image_checksum should be '
185 '{} but it is {}'.format(checksum,
186 image['checksum'])
187 )
188 return _failed('create', name, 'image')
Oleksiy Petrenko2652ac12018-04-16 17:40:13 +0300189
Oleksiy Petrenkoed088d02018-12-07 14:04:14 +0200190 elif image['status'] in ['saving', 'queued']:
191 resp['comment'] = resp['comment'] \
192 + " checksum couldn't be verified, " \
193 "since status is not active"
194 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
Oleksiy Petrenko2652ac12018-04-16 17:40:13 +0300200 to_change = []
201 for prop in image_properties:
202 path = prop.replace('~', '~0').replace('/', '~1')
203 if prop in exact_image:
204 if exact_image[prop] != image_properties[prop]:
205 to_change.append({
206 'op': 'replace',
207 'path': '/{}'.format(path),
208 'value': image_properties[prop]
209 })
210 else:
211 to_change.append({
212 'op': 'add',
213 'path': '/{}'.format(path),
214 'value': image_properties[prop]
215 })
216 if to_change:
217 try:
218 resp = _glancev2_call(
219 'image_update', name=name,
220 properties=to_change, cloud_name=cloud_name,
221 )
222 except Exception as e:
223 log.error('Glance image update failed with {}'.format(e))
Oleksiy Petrenkoed088d02018-12-07 14:04:14 +0200224 return _failed('update', name, 'image')
225 return _succeeded('update', name, 'image', resp)
226 return _succeeded('no_changes', name, 'image')
Oleksiy Petrenko2652ac12018-04-16 17:40:13 +0300227
228
229def image_absent(name, cloud_name):
230 try:
Oleksiy Petrenkoed088d02018-12-07 14:04:14 +0200231 _glancev2_call('image_get_details', name=name, cloud_name=cloud_name)
Oleksiy Petrenko2652ac12018-04-16 17:40:13 +0300232 except Exception as e:
233 if 'ResourceNotFound' in repr(e):
Oleksiy Petrenkoed088d02018-12-07 14:04:14 +0200234 return _succeeded('absent', name, 'image')
Oleksiy Petrenko2652ac12018-04-16 17:40:13 +0300235 if 'MultipleResourcesFound' in repr(e):
Oleksiy Petrenkoed088d02018-12-07 14:04:14 +0200236 return _failed('find', name, 'image')
Oleksiy Petrenko2652ac12018-04-16 17:40:13 +0300237 try:
Oleksiy Petrenkoed088d02018-12-07 14:04:14 +0200238 _glancev2_call('image_delete', name=name, cloud_name=cloud_name)
Oleksiy Petrenko2652ac12018-04-16 17:40:13 +0300239 except Exception as e:
240 log.error('Glance image delete failed with {}'.format(e))
Oleksiy Petrenkoed088d02018-12-07 14:04:14 +0200241 return _failed('delete', name, 'image')
242 return _succeeded('delete', name, 'image')
Oleksiy Petrenko2652ac12018-04-16 17:40:13 +0300243
244
Oleksiy Petrenkoed088d02018-12-07 14:04:14 +0200245def _succeeded(op, name, resource, changes=None):
246 msg_map = {
247 'create': '{0} {1} created',
248 'delete': '{0} {1} removed',
249 'update': '{0} {1} updated',
250 'no_changes': '{0} {1} is in desired state',
251 'absent': '{0} {1} not present',
252 'resources_moved': '{1} resources were moved from {0}',
253 }
Oleksiy Petrenko2652ac12018-04-16 17:40:13 +0300254 changes_dict = {
255 'name': name,
Oleksiy Petrenko2652ac12018-04-16 17:40:13 +0300256 'result': True,
Oleksiy Petrenkoed088d02018-12-07 14:04:14 +0200257 'comment': msg_map[op].format(resource, name),
258 'changes': changes or {},
Oleksiy Petrenko2652ac12018-04-16 17:40:13 +0300259 }
260 return changes_dict
261
Oleksiy Petrenkoed088d02018-12-07 14:04:14 +0200262def _failed(op, name, resource):
263 msg_map = {
264 'create': '{0} {1} failed to create',
265 'delete': '{0} {1} failed to delete',
266 'update': '{0} {1} failed to update',
267 'find': '{0} {1} found multiple {0}',
268 'none': '{0} {1} found no {0}',
269 'resources_moved': 'failed to move {1} from {0}',
Oleksiy Petrenko2652ac12018-04-16 17:40:13 +0300270 }
Oleksiy Petrenko2652ac12018-04-16 17:40:13 +0300271 changes_dict = {
272 'name': name,
Oleksiy Petrenko2652ac12018-04-16 17:40:13 +0300273 'result': False,
Oleksiy Petrenkoed088d02018-12-07 14:04:14 +0200274 'comment': msg_map[op].format(resource, name),
275 'changes': {},
Oleksiy Petrenko2652ac12018-04-16 17:40:13 +0300276 }
Oleksiy Petrenkoed088d02018-12-07 14:04:14 +0200277 return changes_dict