blob: e59dcbd8c931430cc506f305ac31ae5f7cbcd5e2 [file] [log] [blame]
# -*- coding: utf-8 -*-
'''
Managing Images in OpenStack Glance
===================================
'''
# Import python libs
from __future__ import absolute_import
import logging
import time
# Import OpenStack libs
try:
from keystoneclient.exceptions import \
Unauthorized as kstone_Unauthorized
HAS_KEYSTONE = True
except ImportError:
try:
from keystoneclient.apiclient.exceptions import \
Unauthorized as kstone_Unauthorized
HAS_KEYSTONE = True
except ImportError:
HAS_KEYSTONE = False
try:
from glanceclient.exc import \
HTTPUnauthorized as glance_Unauthorized
HAS_GLANCE = True
except ImportError:
HAS_GLANCE = False
log = logging.getLogger(__name__)
def __virtual__():
'''
Only load if dependencies are loaded
'''
return HAS_KEYSTONE and HAS_GLANCE
def _find_image(name, profile=None):
'''
Tries to find image with given name, returns
- image, 'Found image <name>'
- None, 'No such image found'
- False, 'Found more than one image with given name'
'''
try:
images = __salt__['glanceng.image_list'](name=name, profile=profile)
except kstone_Unauthorized:
return False, 'keystoneclient: Unauthorized'
except glance_Unauthorized:
return False, 'glanceclient: Unauthorized'
log.debug('Got images: {0}'.format(images))
if type(images) is dict and len(images) == 1 and 'images' in images:
images = images['images']
images_list = list(images.values()) if type(images) is dict else images
if len(images_list) == 0:
return None, 'No image with name "{0}"'.format(name)
elif len(images_list) == 1:
return images_list[0], 'Found image {0}'.format(name)
elif len(images_list) > 1:
return False, 'Found more than one image with given name'
else:
raise NotImplementedError
def image_present(name, profile=None, visibility='public', protected=None,
checksum=None, location=None, disk_format='raw', wait_for=None,
timeout=30):
'''
Checks if given image is present with properties
set as specified.
An image should got through the stages 'queued', 'saving'
before becoming 'active'. The attribute 'checksum' can
only be checked once the image is active.
If you don't specify 'wait_for' but 'checksum' the function
will wait for the image to become active before comparing
checksums. If you don't specify checksum either the function
will return when the image reached 'saving'.
The default timeout for both is 30 seconds.
Supported properties:
- profile (string)
- visibility ('public' or 'private')
- protected (bool)
- checksum (string, md5sum)
- location (URL, to copy from)
- disk_format ('raw' (default), 'vhd', 'vhdx', 'vmdk', 'vdi', 'iso',
'qcow2', 'aki', 'ari' or 'ami')
'''
ret = {'name': name,
'changes': {},
'result': True,
'comment': '',
}
acceptable = ['queued', 'saving', 'active']
if wait_for is None and checksum is None:
wait_for = 'saving'
elif wait_for is None and checksum is not None:
wait_for = 'active'
# Just pop states until we reach the
# first acceptable one:
while len(acceptable) > 1:
if acceptable[0] == wait_for:
break
else:
acceptable.pop(0)
image, msg = _find_image(name, profile)
if image is False:
if __opts__['test']:
ret['result'] = None
else:
ret['result'] = False
ret['comment'] = msg
return ret
log.debug(msg)
# No image yet and we know where to get one
if image is None and location is not None:
if __opts__['test']:
ret['result'] = None
ret['comment'] = 'glance.image_present would ' \
'create an image from {0}'.format(location)
return ret
image = __salt__['glanceng.image_create'](name=name, profile=profile,
protected=protected, visibility=visibility,
location=location, disk_format=disk_format)
log.debug('Created new image:\n{0}'.format(image))
ret['changes'] = {
name:
{
'new':
{
'id': image['id']
},
'old': None
}
}
timer = timeout
# Kinda busy-loopy but I don't think the Glance
# API has events we can listen for
while timer > 0:
if 'status' in image and \
image['status'] in acceptable:
log.debug('Image {0} has reached status {1}'.format(
image['name'], image['status']))
break
else:
timer -= 5
time.sleep(5)
image, msg = _find_image(name, profile)
if not image:
ret['result'] = False
ret['comment'] += 'Created image {0} '.format(
name) + ' vanished:\n' + msg
return ret
if timer <= 0 and image['status'] not in acceptable:
ret['result'] = False
ret['comment'] += 'Image didn\'t reach an acceptable '+\
'state ({0}) before timeout:\n'.format(acceptable)+\
'\tLast status was "{0}".\n'.format(image['status'])
# There's no image but where would I get one??
elif location is None:
if __opts__['test']:
ret['result'] = None
ret['comment'] = 'No location to copy image from specified,\n' +\
'glance.image_present would not create one'
else:
ret['result'] = False
ret['comment'] = 'No location to copy image from specified,\n' +\
'not creating a new image.'
return ret
# If we've created a new image also return its last status:
if name in ret['changes']:
ret['changes'][name]['new']['status'] = image['status']
if visibility:
if image['visibility'] != visibility:
old_value = image['visibility']
if not __opts__['test']:
image = __salt__['glanceng.image_update'](
id=image['id'], visibility=visibility)
# Check if image_update() worked:
if image['visibility'] != visibility:
if not __opts__['test']:
ret['result'] = False
elif __opts__['test']:
ret['result'] = None
ret['comment'] += '"visibility" is {0}, '\
'should be {1}.\n'.format(image['visibility'],
visibility)
else:
if 'new' in ret['changes']:
ret['changes']['new']['visibility'] = visibility
else:
ret['changes']['new'] = {'visibility': visibility}
if 'old' in ret['changes']:
ret['changes']['old']['visibility'] = old_value
else:
ret['changes']['old'] = {'visibility': old_value}
else:
ret['comment'] += '"visibility" is correct ({0}).\n'.format(
visibility)
if protected is not None:
if not isinstance(protected, bool) or image['protected'] ^ protected:
if not __opts__['test']:
ret['result'] = False
else:
ret['result'] = None
ret['comment'] += '"protected" is {0}, should be {1}.\n'.format(
image['protected'], protected)
else:
ret['comment'] += '"protected" is correct ({0}).\n'.format(
protected)
if 'status' in image and checksum:
if image['status'] == 'active':
if 'checksum' not in image:
# Refresh our info about the image
image = __salt__['glanceng.image_show'](image['id'])
if 'checksum' not in image:
if not __opts__['test']:
ret['result'] = False
else:
ret['result'] = None
ret['comment'] += 'No checksum available for this image:\n' +\
'\tImage has status "{0}".'.format(image['status'])
elif image['checksum'] != checksum:
if not __opts__['test']:
ret['result'] = False
else:
ret['result'] = None
ret['comment'] += '"checksum" is {0}, should be {1}.\n'.format(
image['checksum'], checksum)
else:
ret['comment'] += '"checksum" is correct ({0}).\n'.format(
checksum)
elif image['status'] in ['saving', 'queued']:
ret['comment'] += 'Checksum won\'t be verified as image ' +\
'hasn\'t reached\n\t "status=active" yet.\n'
log.debug('glance.image_present will return: {0}'.format(ret))
return ret
def image_import(name, profile=None, visibility='public', protected=False,
location=None, import_from_format='raw', disk_format='raw',
container_format='bare', tags=None,
checksum=None, timeout=30):
"""
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.
*Important*: This state is supposed to work only with Glance V2 API as
opposed to the image_present state that is compatible only
with Glance V1.
:param name: Name of an image
:param profile: Authentication profile
:param visibility: Scope of image accessibility.
Valid values: public, private, community, shared
:param protected: If true, image will not be deletable.
:param location: a URL where Glance can get the image data
:param import_from_format: Format to import the image from
:param disk_format: Format of the disk
:param container_format: Format of the container
:param tags: List of strings related to the image
:param checksum: Checksum of the image to import, it would be used to
validate the checksum of a newly created image
:param timeout: Time to wait for an import task to succeed
"""
ret = {'name': name,
'changes': {},
'result': True,
'comment': 'Image "{0}" already exists'.format(name)}
tags = tags or []
image, msg = _find_image(name, profile)
log.debug(msg)
if image:
return ret
elif image is False:
if __opts__['test']:
ret['result'] = None
else:
ret['result'] = False
ret['comment'] = msg
return ret
else:
if __opts__['test']:
ret['result'] = None
ret['comment'] = ("glanceng.image_import would create an image "
"from {0}".format(location))
return ret
image_properties = {"container_format": container_format,
"disk_format": disk_format,
"name": name,
"protected": protected,
"tags": tags,
"visibility": visibility
}
task_params = {"import_from": location,
"import_from_format": import_from_format,
"image_properties": image_properties
}
task = __salt__['glanceng.task_create'](
task_type='import', profile=profile,
input_params=task_params)
task_id = task['id']
log.debug('Created new task:\n{0}'.format(task))
ret['changes'] = {
name:
{
'new':
{
'task_id': task_id
},
'old': None
}
}
# Wait for the task to complete
timer = timeout
while timer > 0:
if 'status' in task and task['status'] == 'success':
log.debug('Task {0} has successfully completed'.format(
task_id))
break
elif 'status' in task and task['status'] == 'failure':
msg = "Task {0} has failed".format(task_id)
ret['result'] = False
ret['comment'] = msg
return ret
else:
timer -= 5
time.sleep(5)
existing_tasks = __salt__['glanceng.task_list'](profile)
if task_id not in existing_tasks:
ret['result'] = False
ret['comment'] += 'Created task {0} '.format(
task_id) + ' vanished:\n' + msg
return ret
else:
task = existing_tasks[task_id]
if timer <= 0 and task['status'] != 'success':
ret['result'] = False
ret['comment'] = ('Task {0} did not reach state success before '
'the timeout:\nLast status was '
'"{1}".\n'.format(task_id, task['status']))
return ret
# The import task has successfully completed. Now, let's check that it
# created the image.
image, msg = _find_image(name, profile)
if not image:
ret['result'] = False
ret['comment'] = msg
else:
ret['changes'][name]['new']['image_id'] = image['id']
ret['changes'][name]['new']['image_status'] = image['status']
ret['comment'] = ("Image {0} was successfully created by task "
"{1}".format(image['id'], task_id))
if checksum:
if image['status'] == 'active':
if 'checksum' not in image:
# Refresh our info about the image
image = __salt__['glanceng.image_show'](image['id'])
if 'checksum' not in image:
if not __opts__['test']:
ret['result'] = False
else:
ret['result'] = None
ret['comment'] += (
"No checksum available for this image:\n"
"Image has status '{0}'.".format(image['status']))
elif image['checksum'] != checksum:
if not __opts__['test']:
ret['result'] = False
else:
ret['result'] = None
ret['comment'] += ("'checksum' is {0}, should be "
"{1}.\n".format(image['checksum'],
checksum))
else:
ret['comment'] += (
"'checksum' is correct ({0}).\n".format(checksum))
elif image['status'] in ['saving', 'queued']:
ret['comment'] += (
"Checksum will not be verified as image has not "
"reached 'status=active' yet.\n")
log.debug('glance.image_present will return: {0}'.format(ret))
return ret