Merge pull request #25087 from 0xf10e/fix_glance_2015.5

Fix execution module for glance - now based on 2015.5!
This commit is contained in:
Thomas S Hatch 2015-07-02 13:36:27 -06:00
commit c80990ba4f

View File

@ -6,30 +6,27 @@ Module for handling openstack glance calls.
:configuration: This module is not usable until the following are specified
either in a pillar or in the minion's config file::
keystone.user: admin
keystone.password: verybadpass
keystone.tenant: admin
keystone.tenant_id: f80919baedab48ec8931f200c65a50df
keystone.insecure: False #(optional)
keystone.auth_url: 'http://127.0.0.1:5000/v2.0/'
glance.user: admin
glance.password: verybadpass
glance.tenant: admin
glance.insecure: False #(optional)
glance.auth_url: 'http://127.0.0.1:5000/v2.0/'
If configuration for multiple openstack accounts is required, they can be
set up as different configuration profiles:
For example::
openstack1:
keystone.user: admin
keystone.password: verybadpass
keystone.tenant: admin
keystone.tenant_id: f80919baedab48ec8931f200c65a50df
keystone.auth_url: 'http://127.0.0.1:5000/v2.0/'
glance.user: admin
glance.password: verybadpass
glance.tenant: admin
glance.auth_url: 'http://127.0.0.1:5000/v2.0/'
openstack2:
keystone.user: admin
keystone.password: verybadpass
keystone.tenant: admin
keystone.tenant_id: f80919baedab48ec8931f200c65a50df
keystone.auth_url: 'http://127.0.0.2:5000/v2.0/'
glance.user: admin
glance.password: verybadpass
glance.tenant: admin
glance.auth_url: 'http://127.0.0.2:5000/v2.0/'
With this configuration in place, any of the keystone functions can make use
of a configuration profile by declaring it explicitly.
@ -39,11 +36,30 @@ Module for handling openstack glance calls.
'''
# Import third party libs
#import salt.ext.six as six
from salt.exceptions import (
#CommandExecutionError,
SaltInvocationError
)
# pylint: disable=import-error
HAS_GLANCE = False
try:
from glanceclient import client
import glanceclient.v1.images
from glanceclient import exc
HAS_GLANCE = True
import logging
logging.basicConfig(level=logging.DEBUG)
log = logging.getLogger(__name__)
import pprint
except ImportError:
pass
# Workaround, as the Glance API v2 requires you to
# already have a keystone token
HAS_KEYSTONE = False
try:
from keystoneclient.v2_0 import client as kstone
HAS_KEYSTONE = True
except ImportError:
pass
@ -60,21 +76,86 @@ def __virtual__():
__opts__ = {}
def _auth(profile=None):
def _auth(profile=None, api_version=2, **connection_args):
'''
Set up keystone credentials
Set up glance credentials, returns
`glanceclient.client.Client`. Optional parameter
"api_version" defaults to 2.
Only intended to be used within glance-enabled modules
'''
kstone = __salt__['keystone.auth'](profile)
token = kstone.auth_token
endpoint = kstone.service_catalog.url_for(
service_type='image',
endpoint_type='publicURL',
)
return client.Client('1', endpoint, token=token)
if profile:
prefix = profile + ":glance."
else:
prefix = "glance."
# look in connection_args first, then default to config file
def get(key, default=None):
'''
TODO: Add docstring.
'''
return connection_args.get('connection_' + key,
__salt__['config.get'](prefix + key, default))
user = get('user', 'admin')
password = get('password', 'ADMIN')
tenant = get('tenant', 'admin')
tenant_id = get('tenant_id')
auth_url = get('auth_url', 'http://127.0.0.1:35357/v2.0/')
insecure = get('insecure', False)
token = get('token')
region = get('region')
endpoint = get('endpoint', 'http://127.0.0.1:9292/')
if token:
kwargs = {'token': token,
'username': user,
'endpoint_url': endpoint,
'auth_url': auth_url,
'region_name': region,
'tenant_name': tenant}
else:
kwargs = {'username': user,
'password': password,
'tenant_id': tenant_id,
'auth_url': auth_url,
'region_name': region,
'tenant_name': tenant}
# 'insecure' keyword not supported by all v2.0 keystone clients
# this ensures it's only passed in when defined
if insecure:
kwargs['insecure'] = True
if token:
log.debug('Calling glanceclient.client.Client(' +
'{0}, {1}, **{2})'.format(api_version, endpoint, kwargs))
try:
return client.Client(api_version, endpoint, **kwargs)
except exc.HTTPUnauthorized:
kwargs.pop('token')
kwargs['password'] = password
log.warn('Supplied token is invalid, trying to ' +
'get a new one using username and password.')
if HAS_KEYSTONE:
# TODO: redact kwargs['password']
log.debug('Calling keystoneclient.v2_0.client.Client(' +
'{0}, **{1})'.format(endpoint, kwargs))
keystone = kstone.Client(**kwargs)
log.debug(help(keystone.get_token))
kwargs['token'] = keystone.get_token(keystone.session)
kwargs.pop('password')
log.debug('Calling glanceclient.client.Client(' +
'{0}, {1}, **{2})'.format(api_version, endpoint, kwargs))
return client.Client(api_version, endpoint, **kwargs)
else:
raise NotImplementedError(
"Can't retrieve a auth_token without keystone")
def image_create(profile=None, **kwargs):
def image_create(name, location, profile=None, visibility='public',
container_format='bare', disk_format='raw'):
'''
Create an image (glance image-create)
@ -82,23 +163,35 @@ def image_create(profile=None, **kwargs):
.. code-block:: bash
salt '*' glance.image_create name=f16-jeos is_public=true \\
salt '*' glance.image_create name=f16-jeos visibility=public \\
disk_format=qcow2 container_format=ovf \\
copy_from=http://berrange.fedorapeople.org/images/2012-02-29/f16-x86_64-openstack-sda.qcow2
copy_from=http://berrange.fedorapeople.org/\
images/2012-02-29/f16-x86_64-openstack-sda.qcow2
For all possible values, run ``glance help image-create`` on the minion.
'''
nt_ks = _auth(profile)
fields = dict(
filter(
lambda x: x[0] in glanceclient.v1.images.CREATE_PARAMS,
kwargs.items()
)
)
image = nt_ks.images.create(**fields)
newimage = image_list(str(image.id), profile)
return {newimage['name']: newimage}
# valid options for "visibility":
v_list = ['public', 'private']
# valid options for "container_format":
cf_list = ['ami', 'ari', 'aki', 'bare', 'ovf']
# valid options for "disk_format":
df_list = ['ami', 'ari', 'aki', 'vhd', 'vmdk',
'raw', 'qcow2', 'vdi', 'iso']
if visibility not in v_list:
raise SaltInvocationError('"visibility" needs to be one ' +
'of the following: {0}'.format(', '.join(v_list)))
if container_format not in cf_list:
raise SaltInvocationError('"container_format" needs to be ' +
'one of the following: {0}'.format(', '.join(cf_list)))
if disk_format not in df_list:
raise SaltInvocationError('"disk_format" needs to be one ' +
'of the following: {0}'.format(', '.join(df_list)))
# Icehouse's glanceclient doesn't have add_location() and
# glanceclient.v2 doesn't implement Client.images.create()
# in a usable fashion. Thus we have to use v1 for now.
g_client = _auth(profile, api_version=1)
image = g_client.images.create(name=name, copy_from=location)
return image_show(image.id)
def image_delete(id=None, name=None, profile=None): # pylint: disable=C0103
@ -113,15 +206,15 @@ def image_delete(id=None, name=None, profile=None): # pylint: disable=C0103
salt '*' glance.image_delete id=c2eb2eb0-53e1-4a80-b990-8ec887eae7df
salt '*' glance.image_delete name=f16-jeos
'''
nt_ks = _auth(profile)
g_client = _auth(profile)
if name:
for image in nt_ks.images.list():
for image in g_client.images.list():
if image.name == name:
id = image.id # pylint: disable=C0103
continue
if not id:
return {'Error': 'Unable to resolve image id'}
nt_ks.images.delete(id)
g_client.images.delete(id)
ret = 'Deleted image with ID {0}'.format(id)
if name:
ret += ' ({0})'.format(name)
@ -136,35 +229,29 @@ def image_show(id=None, name=None, profile=None): # pylint: disable=C0103
.. code-block:: bash
salt '*' glance.image_get
salt '*' glance.image_show
'''
nt_ks = _auth(profile)
g_client = _auth(profile)
ret = {}
if name:
for image in nt_ks.images.list():
for image in g_client.images.list():
if image.name == name:
id = image.id # pylint: disable=C0103
continue
if not id:
return {'Error': 'Unable to resolve image id'}
image = nt_ks.images.get(id)
ret[image.name] = {
'id': image.id,
'name': image.name,
'checksum': image.checksum,
'container_format': image.container_format,
'created_at': image.created_at,
'deleted': image.deleted,
'disk_format': image.disk_format,
'is_public': image.is_public,
'min_disk': image.min_disk,
'min_ram': image.min_ram,
'owner': image.owner,
'protected': image.protected,
'size': image.size,
'status': image.status,
'updated_at': image.updated_at,
}
image = g_client.images.get(id)
pformat = pprint.PrettyPrinter(indent=4).pformat
log.debug('Properties of image {0}:\n{1}'.format(
image.name, pformat(image)))
# TODO: Get rid of the wrapping dict, see #24568
ret[image.name] = {}
schema = image_schema(profile=profile)
if len(schema.keys()) == 1:
schema = schema['image']
for key in schema.keys():
if key in image:
ret[image.name][key] = image[key]
return ret
@ -178,31 +265,59 @@ def image_list(id=None, profile=None): # pylint: disable=C0103
salt '*' glance.image_list
'''
nt_ks = _auth(profile)
g_client = _auth(profile)
ret = {}
for image in nt_ks.images.list():
# TODO: Get rid of the wrapping dict, see #24568
for image in g_client.images.list():
ret[image.name] = {
'id': image.id,
'name': image.name,
'checksum': image.checksum,
'container_format': image.container_format,
'created_at': image.created_at,
'deleted': image.deleted,
'disk_format': image.disk_format,
'is_public': image.is_public,
'file': image.file,
'min_disk': image.min_disk,
'min_ram': image.min_ram,
'owner': image.owner,
'protected': image.protected,
'size': image.size,
'status': image.status,
'tags': image.tags,
'updated_at': image.updated_at,
'visibility': image.visibility,
}
# Those cause AttributeErrors in Icehouse' glanceclient
for attr in ['container_format', 'disk_format', 'size']:
if attr in image:
ret[image.name][attr] = image[attr]
if id == image.id:
return ret[image.name]
return ret
def image_schema(profile=None):
'''
Returns names and descriptions of the schema "image"'s
properties for this profile's instance of glance
'''
return schema_get('image', profile)
def schema_get(name, profile=None):
'''
Known valid names of schemas are:
- image
- images
- member
- members
'''
g_client = _auth(profile)
pformat = pprint.PrettyPrinter(indent=4).pformat
schema_props = {}
for prop in g_client.schemas.get(name).properties:
schema_props[prop.name] = prop.description
log.debug('Properties of schema {0}:\n{1}'.format(
name, pformat(schema_props)))
return {name: schema_props}
def _item_list(profile=None):
'''
Template for writing list functions
@ -214,15 +329,16 @@ def _item_list(profile=None):
salt '*' glance.item_list
'''
nt_ks = _auth(profile)
g_client = _auth(profile)
ret = []
for item in nt_ks.items.list():
for item in g_client.items.list():
ret.append(item.__dict__)
#ret[item.name] = {
# 'name': item.name,
# }
return ret
#The following is a list of functions that need to be incorporated in the
#glance module. This list should be updated as functions are added.