blob: 6664b7e3d90df6060040eeee2e561dfe879fbdc0 [file] [log] [blame]
# -*- coding: utf-8 -*-
'''
Managing Images in OpenStack Glance
===================================
'''
# Import python libs
import logging
import time
# Import OpenStack libs
def __virtual__():
return 'glancev2' if 'glancev2.image_list' in __salt__ else False
log = logging.getLogger(__name__)
def _glancev2_call(fname, *args, **kwargs):
return __salt__['glancev2.{}'.format(fname)](*args, **kwargs)
def _cinderv3_call(fname, *args, **kwargs):
return __salt__['cinderv3.{}'.format(fname)](*args, **kwargs)
def image_present(name, cloud_name, location=None, image_properties=None,
import_from_format='raw', timeout=30,
sleep_time=5, checksum=None,
volume_name=None, volume_kwargs=None):
"""
Creates a task to import an image
This state checks if an image is present and, if not, creates a task
with import_type that would download an image from a remote location and
upload it to Glance.
After the task is created, its status is monitored. On success the state
would check that an image is present and return its ID.
Also, this state can update(add and replace) image properties,
but !!It can't delete properties, that are already in state
:param name: name of the image
:param cloud_name: name of the cloud in cloud.yaml
:param location: url link describing where to obtain image
:param image_properties: Dict that contains params needed
to create or update image.
:param container_format: Format of the container
:param disk_format: Format of the disk
:param protected: If true, image will not be deletable.
:param tags: List of strings related to the image
:param visibility: Scope of image accessibility.
Valid values: public, private, community, shared
:param import_from_format: (optional) Format to import the image from
:param timeout: (optional) Time for task to download image or wait
until it becomes active if it is already present
:param sleep_time: (optional) Timer countdown
:param checksum: (optional) checksum of the image to verify it
:param volume_name: (optional) name of the volume if specified the command
works like openstack image create --volume
:param volume_kwargs: (optional) if volume_name is specified, this will be
used as arguments to image_upload_volume
"""
try:
exact_image = _glancev2_call(
'image_get_details', name=name, cloud_name=cloud_name
)
except Exception as e:
if 'ResourceNotFound' in repr(e):
if volume_name:
try:
_cinderv3_call(
'volume_get_details', volume_name,
cloud_name=cloud_name
)
except Exception as e:
if 'ResourceNotFound' in repr(e):
return _failed('none', volume_name, 'volume')
elif 'MultipleResourcesFound' in repr(e):
return _failed('find', volume_name, 'volume')
else:
raise
if not volume_kwargs:
volume_kwargs = {}
try:
resp = _cinderv3_call(
'image_upload_volume', volume_name, image_name=name,
cloud_name=cloud_name, **volume_kwargs)
except Exception as e:
log.error(
'Glance create image with '
'volume failed with {}'.format(e))
return _failed('create', name, 'image')
image_status = resp['os-volume_upload_image']['status']
while timeout > 0 and image_status != 'active':
try:
image = _glancev2_call('image_get_details', name=name,
cloud_name=cloud_name)
except Exception as e:
if 'ResourceNotFound' in repr(e):
timeout -= sleep_time
time.sleep(sleep_time)
continue
else:
raise
image_status = image['status']
if image_status != 'active':
log.error(
'Glance upload image to volume failed '
'for given amount of time'
)
return _failed('create', name, 'image')
return _succeeded('create', name, 'image', image)
else:
if not (location and image_properties):
return _failed('create', name, 'image')
image_properties['name'] = name
task_params = {"import_from": location,
"import_from_format": import_from_format,
"image_properties": image_properties
}
# Try create task
try:
task = _glancev2_call(
'task_create', task_type='import',
task_input=task_params, cloud_name=cloud_name)
except Exception as e:
log.error(
'Glance image create failed on '
'create task with {}'.format(
e)
)
return _failed('create', name, 'image_task')
while timeout > 0:
if task['status'] == 'success':
break
elif task['status'] == 'failure':
log.error('Glance task failed to complete')
return _failed('create', name, 'image')
else:
timeout -= sleep_time
time.sleep(sleep_time)
# Check task status again
try:
task = _glancev2_call(
'task_show', task_id=task['id'],
cloud_name=cloud_name
)
except Exception as e:
log.error(
'Glance failed to check '
'task status with {}'.format(e)
)
return _failed('create', name, 'image_task')
if timeout <= 0 and task['status'] != 'success':
log.error(
'Glance task failed to import '
'image for given amount of time'
)
return _failed('create', name, 'image')
# Task successfully finished
# and now check that is created the image
images = _glancev2_call(
'image_list', name=name, cloud_name=cloud_name
)['images']
if not len(images):
return _failed('create', name, 'image')
image = images[0]
resp = _succeeded('create', name, 'image', image)
if checksum:
if image['status'] == 'active':
if 'checksum' not in image:
log.error(
'Glance image. No checksum for image.'
'Image status is active'
)
return _failed('create', name, 'image')
if image['checksum'] != checksum:
log.error(
'Glance image create failed since '
'image_checksum should be '
'{} but it is {}'.format(checksum,
image['checksum'])
)
return _failed('create', name, 'image')
elif image['status'] in ['saving', 'queued']:
resp['comment'] += (" checksum couldn't be verified, "
"since status is not active")
return resp
elif 'MultipleResourcesFound' in repr(e):
return _failed('find', name, 'image')
else:
raise
# NOTE(pas-ha) fail the salt state if existing image is not in active state
image_status = exact_image['status'].copy()
while timeout > 0 and image_status != 'active':
log.warning("Image {name} is not ACTIVE in state, waiting..".format(
name=exact_image['name']))
timeout -= sleep_time
time.sleep(sleep_time)
try:
image = _glancev2_call('image_get_details', name=name,
cloud_name=cloud_name)
except Exception:
raise
image_status = image['status']
if image_status != 'active':
log.error("Image {name} failed to reach ACTIVE in state".format(
name=exact_image['name']))
return _failed('present', name, 'image')
to_change = []
for prop in image_properties:
path = prop.replace('~', '~0').replace('/', '~1')
if prop in exact_image:
if exact_image[prop] != image_properties[prop]:
to_change.append({
'op': 'replace',
'path': '/{}'.format(path),
'value': image_properties[prop]
})
else:
to_change.append({
'op': 'add',
'path': '/{}'.format(path),
'value': image_properties[prop]
})
if to_change:
try:
resp = _glancev2_call(
'image_update', name=name,
properties=to_change, cloud_name=cloud_name,
)
except Exception as e:
log.error('Glance image update failed with {}'.format(e))
return _failed('update', name, 'image')
return _succeeded('update', name, 'image', resp)
return _succeeded('no_changes', name, 'image')
def image_absent(name, cloud_name):
try:
_glancev2_call('image_get_details', name=name, cloud_name=cloud_name)
except Exception as e:
if 'ResourceNotFound' in repr(e):
return _succeeded('absent', name, 'image')
if 'MultipleResourcesFound' in repr(e):
return _failed('find', name, 'image')
try:
_glancev2_call('image_delete', name=name, cloud_name=cloud_name)
except Exception as e:
log.error('Glance image delete failed with {}'.format(e))
return _failed('delete', name, 'image')
return _succeeded('delete', name, 'image')
def _succeeded(op, name, resource, changes=None):
msg_map = {
'create': '{0} {1} created',
'delete': '{0} {1} removed',
'update': '{0} {1} updated',
'no_changes': '{0} {1} is in desired state',
'absent': '{0} {1} not present',
'resources_moved': '{1} resources were moved from {0}',
}
changes_dict = {
'name': name,
'result': True,
'comment': msg_map[op].format(resource, name),
'changes': changes or {},
}
return changes_dict
def _failed(op, name, resource):
msg_map = {
'create': '{0} {1} failed to create',
'delete': '{0} {1} failed to delete',
'update': '{0} {1} failed to update',
'find': '{0} {1} found multiple {0}',
'none': '{0} {1} found no {0}',
'resources_moved': 'failed to move {1} from {0}',
}
changes_dict = {
'name': name,
'result': False,
'comment': msg_map[op].format(resource, name),
'changes': {},
}
return changes_dict