Why Gemfury? Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Debian packages RPM packages NuGet packages

Repository URL to install this package:

Details    
Size: Mime:
# vim: tabstop=4 shiftwidth=4 softtabstop=4

# Copyright (c) 2013 TrilioData, Inc.
# All Rights Reserved.

"""
Handles all requests relating to compute + contego.
"""

import glob
import itertools
import pkgutil
import os
import imp
import pkg_resources
from threading import Lock
from functools import wraps

from oslo_config import cfg

from novaclient import client
from novaclient import extension as nova_extension

from workloadmgr.db import base
from workloadmgr.db.workloadmgrdb import WorkloadMgrDB
from workloadmgr.common import context as wlm_context
from workloadmgr import exception
from workloadmgr.openstack.common.gettextutils import _
from workloadmgr.openstack.common import excutils
from workloadmgr.openstack.common import log as logging
from workloadmgr.common import clients
from workloadmgr.compute import nova

from workloadmgr import autolog
from workloadmgr.decorators import retry

LOG = logging.getLogger(__name__)
Logger = autolog.Logger(LOG)


contego_opts = [
    cfg.StrOpt('contego_admin_auth_url',
               default='http://localhost:5000/v2.0',
               help='auth url for connecting to contego in admin context'),
    cfg.StrOpt('contego_admin_username',
               default='admin',
               help='tenant name for connecting to contego in admin context'),
    cfg.StrOpt('contego_admin_password',
               default='password',
               help='password for connecting to contego in admin context',
               secret=True),
    cfg.StrOpt('contego_admin_tenant_name',
               default='admin',
               help='tenant name for connecting to contego in admin context'),
    cfg.StrOpt('contego_production_endpoint_template',
               default='http://localhost:8282/v2/%(project_id)s',
               help='contego production endpoint '
                    'e.g. http://localhost:8774/v2/%(project_id)s'),
    cfg.StrOpt('contego_tvault_endpoint_template',
               default='http://localhost:8282/v2/%(project_id)s',
               help='contego tvault endpoint e.g. '
                    'http://localhost:8774/v2/%(project_id)s'),
    cfg.StrOpt('contego_production_region_name',
               default=None,
               help='region name for connecting to contego in admin context'),
    cfg.StrOpt('contego_tvault_region_name',
               default=None,
               help='region name for connecting to contego in admin context'),
    cfg.BoolOpt('contego_api_insecure',
                default=True,
                help='if set, ignore any SSL validation issues'),
    cfg.StrOpt('contego_auth_system',
               default='keystone',
               help='auth system for connecting to '
               'contego in admin context'),
    cfg.IntOpt('contego_url_timeout',
               default=600,
               help='timeout value for connecting to contego in seconds'),
]

CONF = cfg.CONF
CONF.register_opts(contego_opts)

LOG = logging.getLogger(__name__)

contegolock = Lock()


class ObjectDummy(object):
    pass


def synchronized(lock):
    """ Synchronization decorator. """
    def wrap(f):
        def new_function(*args, **kw):
            lock.acquire()
            try:
                return f(*args, **kw)
            finally:
                lock.release()
        return new_function
    return wrap


def _discover_extensions(version):
    extensions = []
    for name, module in itertools.chain(
        _discover_via_python_path(),
            _discover_via_contrib_path(version),
            _discover_via_entry_points()):

        extension = nova_extension.Extension(name, module)
        extensions.append(extension)
    return extensions


def _discover_via_python_path():
    for (module_loader, name, _ispkg) in pkgutil.iter_modules():
        if name.endswith('_python_novaclient_ext'):
            if not hasattr(module_loader, 'load_module'):
                # Python 2.6 compat: actually get an ImpImporter obj
                module_loader = module_loader.find_module(name)

            module = module_loader.load_module(name)
            if hasattr(module, 'extension_name'):
                name = module.extension_name
            yield name, module


def _discover_via_contrib_path(version):
    module_path = os.path.dirname(os.path.abspath(__file__))
    version_str = "v%s" % version.replace('.', '_')
    ext_path = os.path.join(module_path, version_str, 'contrib')
    ext_glob = os.path.join(ext_path, "*.py")

    for ext_path in glob.iglob(ext_glob):
        name = os.path.basename(ext_path)[:-3]

        if name == "__init__":
            continue

        module = imp.load_source(name, ext_path)
        yield name, module


def _discover_via_entry_points():
    for ep in pkg_resources.iter_entry_points('novaclient.extension'):
        name = ep.name
        module = ep.load()
        yield name, module


try:
    # load keystone_authtoken by importing keystonemiddleware
    # if it is already loaded, just ignore the exception
    cfg.CONF.import_group('keystone_authtoken',
                          'keystonemiddleware.auth_token')
except BaseException:
    pass


def _get_trusts(user_id, tenant_id):

    db = WorkloadMgrDB().db
    context = wlm_context.RequestContext(
        user_id=user_id,
        project_id=tenant_id)

    settings = db.setting_get_all_by_project(
        context, context.project_id)

    trust = [t for t in settings if t.type == "trust_id" and
             t.project_id == context.project_id and
             t.user_id == context.user_id]
    return trust


def _get_tenant_context(context):
    from workloadmgr import workloads as workloadAPI
    if isinstance(context, dict):
        user_id = context['user_id']
        tenant_id = context['project_id']
        user = context.get('user', None)
        tenant = context.get('tenant', None)
        if 'user_domain_id' in context:
            user_domain_id = context['user_domain_id']
        else:
            user_domain_id = 'default'
    else:
        if hasattr(context, 'user_id'):
            user_id = context.user_id
        elif hasattr(context, 'user'):
            user_id = context.user

        if hasattr(context, 'tenant_id'):
            tenant_id = context.tenant_id
        elif hasattr(context, 'project_id'):
            tenant_id = context.project_id
        elif hasattr(context, 'tenant'):
            tenant_id = context.tenant

        if hasattr(context, 'user_domain_id'):
            if context.user_domain_id is None:
                user_domain_id = 'default'
            else:
                user_domain_id = context.user_domain_id
        elif hasattr(context, 'user_domain'):
            if context.user_domain is None:
                user_domain_id = 'default'
            else:
                user_domain_id = context.user_domain
        else:
            user_domain_id = 'default'

        user = getattr(context, 'user', 'NA')
        tenant = getattr(context, 'tenant', 'NA')

    trust = _get_trusts(user_id, tenant_id)
    if len(trust):
        try:
            trust_id = trust[0].value
            context = wlm_context.RequestContext(
                username=CONF.keystone_authtoken.admin_user,
                password=CONF.keystone_authtoken.admin_password,
                trust_id=trust_id,
                tenant_id=tenant_id,
                trustor_user_id=user_id,
                user_domain_id=CONF.triliovault_user_domain_id,
                is_admin=False)

            clients.initialise()
            client_plugin = clients.Clients(context)
            kclient = client_plugin.client("keystone")
            context.auth_token = kclient.auth_token
            context.user_id = user_id
            if user != 'NA' and getattr(context, 'user', None) is None:
                context.user = user
            if tenant != 'NA' and getattr(context, 'tenant', None) is None:
                context.tenant = tenant
        except Exception:
            with excutils.save_and_reraise_exception():
                msg = _("Assign valid trustee role to tenant %s") % tenant_id
                cntx = ObjectDummy()
                cntx.user_id = user_id
                cntx.project_id = tenant_id
                workloadAPI.api.AUDITLOG.log(cntx, msg, None)
                LOG.info(msg)
                LOG.exception(_("token cannot be created using saved "
                                "trust id for user %s, tenant %s") %
                              (user_id, tenant_id))
    else:
        LOG.info(_("Could not find any saved trust ids. Trying "
                   "admin credentials to generate token"))
        try:
            httpclient = client.HTTPClient(
                user=CONF.contego_admin_username,
                password=CONF.contego_admin_password,
                tenant_id=tenant_id,
                service_type='compute',
                endpoint_type=CONF.clients.endpoint_type,
                region_name=CONF.contego_production_region_name,
                auth_url=CONF.contego_admin_auth_url,
                domain_name=user_domain_id,
                timeout=CONF.contego_url_timeout,
                auth_system=CONF.contego_auth_system,
                insecure=CONF.contego_api_insecure)
            httpclient.authenticate()
            context = wlm_context.RequestContext(
                user_id=user_id, project_id=tenant_id,
                is_admin=True, auth_token=httpclient.auth_token)
            if user != 'NA' and getattr(context, 'user', None) is None:
                context.user = user
            if tenant != 'NA' and getattr(context, 'tenant', None) is None:
                context.tenant = tenant
        except Exception:
            with excutils.save_and_reraise_exception():
                LOG.exception(_("_get_auth_token() with admin credentials "
                                "failed. Perhaps admin is not member of "
                                "tenant %s") % tenant_id)
                cntx = ObjectDummy()
                cntx.user_id = user_id
                cntx.project_id = tenant_id
                msg = _("Assign valid trustee role to tenant %s") % tenant_id
                workloadAPI.api.AUDITLOG.log(cntx, msg, None)
                LOG.info(msg)

    return context


def contegoclient(context, production=True,
                  refresh_token=False, extensions=None):
    trust = _get_trusts(context.user_id, context.project_id)
    if hasattr(context, 'user_domain_id'):
        if context.user_domain_id is None:
            user_domain_id = 'default'
        else:
            user_domain_id = context.user_domain_id
    elif hasattr(context, 'user_domain'):
        if context.user_domain is None:
            user_domain_id = 'default'
        else:
            user_domain_id = context.user_domain
    else:
        user_domain_id = 'default'
    # pick the first trust. Usually it should not be more than one trust
    assert len(trust)
    trust_id = trust[0].value

    if refresh_token:
        context = wlm_context.RequestContext(
            username=CONF.keystone_authtoken.admin_user,
            password=CONF.keystone_authtoken.admin_password,
            trust_id=trust_id,
            tenant_id=context.project_id,
            trustor_user_id=context.user_id,
            user_domain_id=CONF.triliovault_user_domain_id,
            is_admin=False)
    else:
        context = wlm_context.RequestContext(
            trustor_user_id=context.user_id,
            project_id=context.project_id,
            auth_token=context.auth_token,
            trust_id=trust_id,
            user_domain_id=user_domain_id,
            is_admin=False)

    clients.initialise()
    contego_plugin = clients.Clients(context)
    contegoclient = contego_plugin.client("contego")

    contegoclient.client_plugin = contegoclient

    return contegoclient


def exception_handler(ignore_exception=False,
                      refresh_token=True, contego=False):
    def exception_handler_decorator(func):
        @wraps(func)
        def func_wrapper(*args, **argv):
            try:
                try:
                    extensions = None
                    if contego is True:
                        extensions = _discover_extensions('1.1')
                    client = contegoclient(args[1], args[0]._production,
                                           refresh_token=False,
                                           extensions=extensions)
                    argv.update({'client': client})
                    return func(*args, **argv)
                except Exception as unauth_ex:
                    LOG.exception(unauth_ex)
                    if "unauthorized" in str(unauth_ex).lower() and \
                            refresh_token is True:
                        argv.pop('client')
                        client = contegoclient(args[1], args[0]._production,
                                               refresh_token=True,
                                               extensions=extensions)
                        argv.update({'client': client})
                        return func(*args, **argv)
                    else:
                        LOG.exception(unauth_ex)
                        raise unauth_ex
            except Exception as ex:
                if ignore_exception is True:
                    LOG.exception(ex)
                    return
                elif contego is True:
                    LOG.exception(ex)
                    msg = "Unable to call %s; Please check contego " \
                        "logs for more details" % func.__name__
                    if hasattr(ex, 'code') and ex.code == 413:
                        msg = str(ex)
                    raise exception.ErrorOccurred(reason=msg)
                else:
                    LOG.exception(ex)
                    raise ex

        return func_wrapper
    return exception_handler_decorator


def update_params_with_server(context, server, params):
    """ fetch server host value and add it to params. """
    if 'server_obj' not in params:
        compute_service = nova.API(production=True)
        context = nova._get_tenant_context(context)
        server_obj = compute_service.get_server(context, server)
        if server_obj:
            params.update({'server_obj': server_obj.to_dict()})


class API(base.Base):
    """API for interacting with the volume manager."""

    def __init__(self, production=True):
        self._production = production

    @synchronized(contegolock)
    @exception_handler(ignore_exception=False, contego=True)
    def map_snapshot_files(self, context, server, params, **kwargs):
        """
        Map snapshot volume images to file manager instance
        :param server: The :class:`Server` (or its ID) to query.
        """
        client = kwargs['client']
        return client.contego.map_snapshot_files(server=server, params=params)

    @synchronized(contegolock)
    @exception_handler(ignore_exception=False, contego=True)
    def vast_prepare(self, context, server, params, **kwargs):
        """
        PREPARE to VAST an instance
        :param server: The :class:`Server` (or its ID) to prepare.
        """
        update_params_with_server(context, server, params)
        try:
            ctx = context.values
        except:
            ctx = {}
        client = kwargs['client']
        return client.contego.vast_prepare(ctx, server=server, params=params)

    @synchronized(contegolock)
    @exception_handler(ignore_exception=True, contego=True)
    def vast_freeze(self, context, server, params, **kwargs):
        """
        FREEZE an instance
        :param server: The :class:`Server` (or its ID) to freeze.
        """
        update_params_with_server(context, server, params)
        client = kwargs['client']
        return client.contego.vast_freeze(server=server, params=params)

    @synchronized(contegolock)
    @exception_handler(ignore_exception=True, contego=True)
    def vast_thaw(self, context, server, params, **kwargs):
        """
        Thaw an instance
        :param server: The :class:`Server` (or its ID) to thaw.
        """
        update_params_with_server(context, server, params)
        client = kwargs['client']
        return client.contego.vast_thaw(server=server, params=params)

    @synchronized(contegolock)
    @exception_handler(ignore_exception=False, contego=True)
    def vast_instance(self, context, server, params, **kwargs):
        """
        VAST an instance
        :param server: The :class:`Server` (or its ID) to query.
        """
        update_params_with_server(context, server, params)
        client = kwargs['client']
        return client.contego.vast_instance(server=server, params=params)


    @synchronized(contegolock)
    @exception_handler(ignore_exception=False, contego=True)
    @retry(Exception, tries=3, delay=1, logger=LOG)
    def vast_get_info(self, context, server, params, **kwargs):
        """
        Get components of a VASTed instance
        :param server: The :class:`Server` (or its ID) to query.
        """
        update_params_with_server(context, server, params)
        client = kwargs['client']
        return client.contego.vast_get_info(server=server, params=params)

    @synchronized(contegolock)
    @exception_handler(ignore_exception=False, contego=True)
    def vast_data_transfer(self, context, server, params, **kwargs):
        """
        Transfer a component of a VASTed instance to backup store
        :param server: The :class:`Server` (or its ID) to query.
        """
        update_params_with_server(context, server, params)
        client = kwargs['client']
        return client.contego.vast_data_transfer(
            server=server, params=params, do_checksum=True)

    @synchronized(contegolock)
    @exception_handler(ignore_exception=False, contego=True)
    def vast_check_prev_snapshot(self, context, server, params, **kwargs):
        """
        Check if the previous snapshot is valid
        :param server: The :class:`Server` (or its ID) to query.
        """
        update_params_with_server(context, server, params)
        client = kwargs['client']
        return client.contego.vast_check_prev_snapshot(
            server=server, params=params, do_checksum=True)

    @synchronized(contegolock)
    @exception_handler(ignore_exception=False, contego=True)
    def copy_backup_image_to_volume(self, context, server, params, **kwargs):
        """
        Transfer the backup image to volume
        :param server: The :class:`Server` (or its ID) to query.
        """
        update_params_with_server(context, server, params)
        client = kwargs['client']
        return client.contego.copy_backup_image_to_volume(
            server=server, params=params, do_checksum=True)

    @synchronized(contegolock)
    @exception_handler(ignore_exception=False, contego=True)
    def vast_async_task_status(self, context, server, params, **kwargs):
        """
        Get data transfer status of VASTed instance component
        :param server: The :class:`Server` (or its ID) to query.
        """
        update_params_with_server(context, server, params)
        client = kwargs['client']
        return client.contego.vast_async_task_status(
            server=server, params=params, do_checksum=True)

    @synchronized(contegolock)
    @exception_handler(ignore_exception=True, contego=True)
    @autolog.log_method(logger=Logger)
    def vast_finalize(self, context, server, params, **kwargs):
        """
        Finalize the VAST
        :param server: The :class:`Server` (or its ID) to query.
        """
        update_params_with_server(context, server, params)
        client = kwargs['client']
        return client.contego.vast_finalize(server=server, params=params)

    @synchronized(contegolock)
    @exception_handler(ignore_exception=True, contego=True)
    @autolog.log_method(logger=Logger)
    def vast_reset(self, context, server, params, **kwargs):
        """
        Reset the VAST snapshot
        :param server: The :class:`Server` (or its ID) to query.
        """
        update_params_with_server(context, server, params)
        client = kwargs['client']
        return client.contego.vast_reset(server=server, params=params)

    @synchronized(contegolock)
    @exception_handler(ignore_exception=False, contego=True)
    def testbubble_attach_volume(self, context, server, params, **kwargs):
        """
        Attach a volume to testbubble instance
        :param server: The :class:`Server` (or its ID) to query.
        """
        update_params_with_server(context, server, params)
        client = kwargs['client']
        return client.contego.testbubble_attach_volume(
            server=server, params=params)

    @synchronized(contegolock)
    @exception_handler(ignore_exception=False, contego=True)
    def testbubble_reboot_instance(self, context, server, params, **kwargs):
        """
        Simple reboot of a testbubble instance
        :param server: The :class:`Server` (or its ID) to query.
        """
        update_params_with_server(context, server, params)
        client = kwargs['client']
        return client.contego.testbubble_reboot_instance(
            server=server, params=params)

    @synchronized(contegolock)
    @exception_handler(ignore_exception=False, contego=True)
    def vast_commit_image(self, context, server, params, **kwargs):
        """
        Commit snapshot image for instance.
        :param server: The :class:`Server` (or its ID) to query.
        """
        update_params_with_server(context, server, params)
        client = kwargs['client']
        return client.contego.vast_commit_image(server=server, params=params)

    @synchronized(contegolock)
    @exception_handler(ignore_exception=False, contego=True)
    def vast_config_backup(self, context, backup_id, params, **kwargs):
        """
        Backup OpenStack config files.
        """
        client = kwargs['client']
        return client.contego.vast_config_backup(backup_id, params)

    @synchronized(contegolock)
    @exception_handler(ignore_exception=False, contego=True)
    def validate_database_creds(self, context, params, **kwargs):
        """
        Validate database credentials.
        :param : database credentials which need to be validate.
        """
        client = kwargs['client']
        return client.contego.validate_database_creds(
            CONF.cloud_unique_id, params)

    @synchronized(contegolock)
    @exception_handler(ignore_exception=False, contego=True)
    def validate_trusted_user_and_key(self, context, params, **kwargs):
        """
        validate trusted user and private key for connecting
        with controller node.
        :param : trusted_user and priivate_key.
        """
        client = kwargs['client']
        return client.contego.validate_trusted_user_and_key(
            CONF.cloud_unique_id, params)

    @synchronized(contegolock)
    @exception_handler(ignore_exception=False, contego=True)
    def get_controller_nodes(self, context, **kwargs):
        """
        Get list of controller nodes.
        """
        client = kwargs['client']
        return client.contego.get_controller_nodes(CONF.cloud_unique_id)

    @synchronized(contegolock)
    @exception_handler(ignore_exception=False, contego=True)
    def vast_disk_check(self, context, server, params, **kwargs):
        """
        Get disk integrity check of a VASTed instance
        :param server: The :class:`Server` (or its ID) to query.
        """
        update_params_with_server(context, server, params)
        client = kwargs['client']
        return client.contego.vast_disk_check(server=server, params=params)

    @synchronized(contegolock)
    @exception_handler(ignore_exception=False, contego=True)
    def vast_clean_nbd_devices(self, context, server, params, **kwargs):
        """
        Clean nbd deices mounted for snapshot mount operation
        """
        update_params_with_server(context, server, params)
        client = kwargs['client']
        is_cleaned = client.contego.vast_clean_nbd_devices(context, server=server, params=params)
        if is_cleaned and isinstance(is_cleaned, dict) and is_cleaned.get("result", False):
            return True
        return False