Repository URL to install this package:
Version:
5.0.6.dev10 ▾
|
# 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