%PDF- %PDF-
Direktori : /lib/python2.7/site-packages/salt/modules/ |
Current File : //lib/python2.7/site-packages/salt/modules/glance.py |
# -*- coding: utf-8 -*- ''' Module for handling openstack glance calls. :optdepends: - glanceclient Python adapter :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.insecure: False #(optional) keystone.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.auth_url: 'http://127.0.0.1:5000/v2.0/' openstack2: keystone.user: admin keystone.password: verybadpass keystone.tenant: admin keystone.auth_url: 'http://127.0.0.2:5000/v2.0/' With this configuration in place, any of the glance functions can make use of a configuration profile by declaring it explicitly. For example:: salt '*' glance.image_list profile=openstack1 ''' # Import Python libs from __future__ import absolute_import, print_function, unicode_literals import re # Import salt libs from salt.exceptions import ( SaltInvocationError ) from salt.version import ( __version__, SaltStackVersion ) from salt.ext import six # is there not SaltStackVersion.current() to get # the version of the salt running this code?? _version_ary = __version__.split('.') CUR_VER = SaltStackVersion(_version_ary[0], _version_ary[1]) BORON = SaltStackVersion.from_name('Boron') # pylint: disable=import-error HAS_GLANCE = False try: from glanceclient import client from glanceclient import exc HAS_GLANCE = True except ImportError: pass # Workaround, as the Glance API v2 requires you to # already have a keystone session token HAS_KEYSTONE = False try: from keystoneclient.v2_0 import client as kstone #import keystoneclient.apiclient.exceptions as kstone_exc HAS_KEYSTONE = True except ImportError: pass import logging logging.basicConfig(level=logging.DEBUG) log = logging.getLogger(__name__) import pprint def __virtual__(): ''' Only load this module if glance is installed on this minion. ''' if HAS_GLANCE: return 'glance' return (False, 'The glance execution module cannot be loaded: the glanceclient python library is not available.') __opts__ = {} def _auth(profile=None, api_version=2, **connection_args): ''' Set up glance credentials, returns `glanceclient.client.Client`. Optional parameter "api_version" defaults to 2. Only intended to be used within glance-enabled modules ''' __utils__['versions.warn_until']( 'Neon', ( 'The glance module has been deprecated and will be removed in {version}. ' 'Please update to using the glanceng module' ), ) if profile: prefix = profile + ":keystone." else: prefix = "keystone." def get(key, default=None): ''' Checks connection_args, then salt-minion config, falls back to specified default value. ''' return connection_args.get('connection_' + key, __salt__['config.get'](prefix + key, default)) user = get('user', 'admin') password = get('password', None) 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) admin_token = get('token') region = get('region') ks_endpoint = get('endpoint', 'http://127.0.0.1:9292/') g_endpoint_url = __salt__['keystone.endpoint_get']('glance', profile) # The trailing 'v2' causes URLs like thise one: # http://127.0.0.1:9292/v2/v1/images g_endpoint_url = re.sub('/v2', '', g_endpoint_url['internalurl']) if admin_token and api_version != 1 and not password: # If we had a password we could just # ignore the admin-token and move on... raise SaltInvocationError('Only can use keystone admin token ' + 'with Glance API v1') elif password: # Can't use the admin-token anyway kwargs = {'username': user, 'password': password, 'tenant_id': tenant_id, 'auth_url': auth_url, 'endpoint_url': g_endpoint_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 elif api_version == 1 and admin_token: kwargs = {'token': admin_token, 'auth_url': auth_url, 'endpoint_url': g_endpoint_url} else: raise SaltInvocationError('No credentials to authenticate with.') if HAS_KEYSTONE: log.debug('Calling keystoneclient.v2_0.client.Client(' + '{0}, **{1})'.format(ks_endpoint, kwargs)) keystone = kstone.Client(**kwargs) kwargs['token'] = keystone.get_token(keystone.session) # This doesn't realy prevent the password to show up # in the minion log as keystoneclient.session is # logging it anyway when in debug-mode kwargs.pop('password') log.debug('Calling glanceclient.client.Client(' + '{0}, {1}, **{2})'.format(api_version, g_endpoint_url, kwargs)) # may raise exc.HTTPUnauthorized, exc.HTTPNotFound # but we deal with those elsewhere return client.Client(api_version, g_endpoint_url, **kwargs) else: raise NotImplementedError( "Can't retrieve a auth_token without keystone") def _add_image(collection, image): ''' Add image to given dictionary ''' image_prep = { 'id': image.id, 'name': image.name, 'created_at': image.created_at, 'file': image.file, 'min_disk': image.min_disk, 'min_ram': image.min_ram, 'owner': image.owner, 'protected': image.protected, '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: image_prep[attr] = image[attr] if type(collection) is dict: collection[image.name] = image_prep elif type(collection) is list: collection.append(image_prep) else: msg = '"collection" is {0}'.format(type(collection)) +\ 'instead of dict or list.' log.error(msg) raise TypeError(msg) return collection def image_create(name, location=None, profile=None, visibility=None, container_format='bare', disk_format='raw', protected=None,): ''' Create an image (glance image-create) CLI Example, old format: .. code-block:: bash salt '*' glance.image_create name=f16-jeos \\ disk_format=qcow2 container_format=ovf CLI Example, new format resembling Glance API v2: .. code-block:: bash salt '*' glance.image_create name=f16-jeos visibility=public \\ disk_format=qcow2 container_format=ovf The parameter 'visibility' defaults to 'public' if not specified. ''' kwargs = {} # 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'] kwargs['copy_from'] = location if visibility is not None: if visibility not in v_list: raise SaltInvocationError('"visibility" needs to be one ' + 'of the following: {0}'.format(', '.join(v_list))) elif visibility == 'public': kwargs['is_public'] = True else: kwargs['is_public'] = False else: kwargs['is_public'] = True if container_format not in cf_list: raise SaltInvocationError('"container_format" needs to be ' + 'one of the following: {0}'.format(', '.join(cf_list))) else: kwargs['container_format'] = container_format if disk_format not in df_list: raise SaltInvocationError('"disk_format" needs to be one ' + 'of the following: {0}'.format(', '.join(df_list))) else: kwargs['disk_format'] = disk_format if protected is not None: kwargs['protected'] = protected # 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, **kwargs) return image_show(image.id, profile=profile) def image_delete(id=None, name=None, profile=None): # pylint: disable=C0103 ''' Delete an image (glance image-delete) CLI Examples: .. code-block:: bash salt '*' glance.image_delete c2eb2eb0-53e1-4a80-b990-8ec887eae7df salt '*' glance.image_delete id=c2eb2eb0-53e1-4a80-b990-8ec887eae7df salt '*' glance.image_delete name=f16-jeos ''' g_client = _auth(profile) image = {'id': False, 'name': None} if name: for image in g_client.images.list(): if image.name == name: id = image.id # pylint: disable=C0103 continue if not id: return { 'result': False, 'comment': 'Unable to resolve image id ' 'for name {0}'.format(name) } elif not name: name = image['name'] try: g_client.images.delete(id) except exc.HTTPNotFound: return { 'result': False, 'comment': 'No image with ID {0}'.format(id) } except exc.HTTPForbidden as forbidden: log.error(six.text_type(forbidden)) return { 'result': False, 'comment': six.text_type(forbidden) } return { 'result': True, 'comment': 'Deleted image \'{0}\' ({1}).'.format(name, id), } def image_show(id=None, name=None, profile=None): # pylint: disable=C0103 ''' Return details about a specific image (glance image-show) CLI Example: .. code-block:: bash salt '*' glance.image_show ''' g_client = _auth(profile) ret = {} if name: for image in g_client.images.list(): if image.name == name: id = image.id # pylint: disable=C0103 continue if not id: return { 'result': False, 'comment': 'Unable to resolve image ID ' 'for name \'{0}\''.format(name) } try: image = g_client.images.get(id) except exc.HTTPNotFound: return { 'result': False, 'comment': 'No image with ID {0}'.format(id) } pformat = pprint.PrettyPrinter(indent=4).pformat log.debug('Properties of image {0}:\n{1}'.format( image.name, pformat(image))) schema = image_schema(profile=profile) if len(schema.keys()) == 1: schema = schema['image'] for key in schema: if key in image: ret[key] = image[key] return ret def image_list(id=None, profile=None, name=None): # pylint: disable=C0103 ''' Return a list of available images (glance image-list) CLI Example: .. code-block:: bash salt '*' glance.image_list ''' g_client = _auth(profile) ret = [] for image in g_client.images.list(): if id is None and name is None: _add_image(ret, image) else: if id is not None and id == image.id: _add_image(ret, image) return ret if name == image.name: if name in ret and CUR_VER < BORON: # Not really worth an exception return { 'result': False, 'comment': 'More than one image with ' 'name "{0}"'.format(name) } _add_image(ret, image) log.debug('Returning images: {0}'.format(ret)) return ret def image_schema(profile=None): ''' Returns names and descriptions of the schema "image"'s properties for this profile's instance of glance CLI Example: .. code-block:: bash salt '*' glance.image_schema ''' return schema_get('image', profile) def image_update(id=None, name=None, profile=None, **kwargs): # pylint: disable=C0103 ''' Update properties of given image. Known to work for: - min_ram (in MB) - protected (bool) - visibility ('public' or 'private') CLI Example: .. code-block:: bash salt '*' glance.image_update id=c2eb2eb0-53e1-4a80-b990-8ec887eae7df salt '*' glance.image_update name=f16-jeos ''' if id: image = image_show(id=id, profile=profile) if 'result' in image and not image['result']: return image elif len(image) == 1: image = image.values()[0] elif name: img_list = image_list(name=name, profile=profile) if img_list is dict and 'result' in img_list: return img_list elif len(img_list) == 0: return { 'result': False, 'comment': 'No image with name \'{0}\' ' 'found.'.format(name) } elif len(img_list) == 1: try: image = img_list[0] except KeyError: image = img_list[name] else: raise SaltInvocationError log.debug('Found image:\n{0}'.format(image)) to_update = {} for key, value in kwargs.items(): if key.startswith('_'): continue if key not in image or image[key] != value: log.debug('add <{0}={1}> to to_update'.format(key, value)) to_update[key] = value g_client = _auth(profile) updated = g_client.images.update(image['id'], **to_update) return updated def schema_get(name, profile=None): ''' Known valid names of schemas are: - image - images - member - members CLI Example: .. code-block:: bash salt '*' glance.schema_get name=f16-jeos ''' 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 Return a list of available items (glance items-list) CLI Example: .. code-block:: bash salt '*' glance.item_list ''' g_client = _auth(profile) ret = [] 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. # image-download Download a specific image. # member-create Share a specific image with a tenant. # member-delete Remove a shared image from a tenant. # member-list Describe sharing permissions by image or tenant.