# Copyright 2016 NTT DATA # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ Handles all requests to Nova. """ import functools import sys from keystoneauth1 import exceptions as keystone_exception import keystoneauth1.loading import keystoneauth1.session from novaclient import api_versions from novaclient import client as nova_client from novaclient import exceptions as nova_exception from oslo_log import log as logging from oslo_utils import encodeutils from requests import exceptions as request_exceptions import six from masakari import conf from masakari import context as ctx from masakari import exception CONF = conf.CONF CONF.import_group('keystone_authtoken', 'keystonemiddleware.auth_token') LOG = logging.getLogger(__name__) NOVA_API_VERSION = "2.14" nova_extensions = [ext for ext in nova_client.discover_extensions(NOVA_API_VERSION) if ext.name in ("list_extensions",)] def _reraise(desired_exc): six.reraise(type(desired_exc), desired_exc, sys.exc_info()[2]) def translate_nova_exception(method): """Transforms a cinder exception but keeps its traceback intact.""" @functools.wraps(method) def wrapper(self, ctx, *args, **kwargs): try: res = method(self, ctx, *args, **kwargs) except (request_exceptions.Timeout, nova_exception.CommandError, keystone_exception.ConnectionError) as exc: err_msg = encodeutils.exception_to_unicode(exc) _reraise(exception.MasakariException(reason=err_msg)) except (keystone_exception.BadRequest, nova_exception.BadRequest) as exc: err_msg = encodeutils.exception_to_unicode(exc) _reraise(exception.InvalidInput(reason=err_msg)) except (keystone_exception.Forbidden, nova_exception.Forbidden) as exc: err_msg = encodeutils.exception_to_unicode(exc) _reraise(exception.Forbidden(err_msg)) except (nova_exception.EndpointNotFound, nova_exception.NotFound) as exc: err_msg = encodeutils.exception_to_unicode(exc) _reraise(exception.NotFound(reason=err_msg)) except nova_exception.Conflict as exc: err_msg = encodeutils.exception_to_unicode(exc) _reraise(exception.Conflict(reason=err_msg)) return res return wrapper def novaclient(context, timeout=None): """Returns a Nova client @param timeout: Number of seconds to wait for an answer before raising a Timeout exception (None to disable) """ nova_catalog_info = CONF.nova_catalog_admin_info service_type, service_name, endpoint_type = nova_catalog_info.split(':') context = ctx.RequestContext( CONF.os_privileged_user_name, None, auth_token=CONF.os_privileged_user_password, project_name=CONF.os_privileged_user_tenant, service_catalog=context.service_catalog, global_request_id=context.global_id) # User needs to authenticate to Keystone before querying Nova, so we set # auth_url to the identity service endpoint url = CONF.os_privileged_user_auth_url LOG.debug('Creating a Nova client using "%s" user', CONF.os_privileged_user_name) # Now that we have the correct auth_url, username, password and # project_name, let's build a Keystone session. loader = keystoneauth1.loading.get_plugin_loader( CONF.keystone_authtoken.auth_type) auth = loader.load_from_options( auth_url=url, username=context.user_id, password=context.auth_token, project_name=context.project_name, user_domain_name=CONF.os_user_domain_name, project_domain_name=CONF.os_project_domain_name) keystone_session = keystoneauth1.session.Session(auth=auth) client_obj = nova_client.Client( api_versions.APIVersion(NOVA_API_VERSION), session=keystone_session, insecure=CONF.nova_api_insecure, timeout=timeout, global_request_id=context.global_id, region_name=CONF.os_region_name, endpoint_type=endpoint_type, service_type=service_type, service_name=service_name, cacert=CONF.nova_ca_certificates_file, extensions=nova_extensions) return client_obj class API(object): """API for interacting with novaclient.""" @translate_nova_exception def get_servers(self, context, host): """Get a list of servers running on a specified host.""" opts = { 'host': host, 'all_tenants': True } nova = novaclient(context) LOG.info('Fetch Server list on %s', host) return nova.servers.list(detailed=True, search_opts=opts) @translate_nova_exception def enable_disable_service(self, context, host_name, enable=False, reason=None): """Enable or disable the service specified by hostname and binary.""" nova = novaclient(context) if not enable: LOG.info('Disable nova-compute on %s', host_name) if reason: nova.services.disable_log_reason(host_name, 'nova-compute', reason) else: nova.services.disable(host_name, 'nova-compute') else: LOG.info('Enable nova-compute on %s', host_name) nova.services.enable(host_name, 'nova-compute') @translate_nova_exception def is_service_down(self, context, host_name, binary): """Check whether service is up or down on given host.""" nova = novaclient(context) service = nova.services.list(host=host_name, binary=binary)[0] return service.status == 'disabled' @translate_nova_exception def evacuate_instance(self, context, uuid, target=None): """Evacuate an instance from failed host to specified host.""" msg = ('Call evacuate command for instance %(uuid)s on host ' '%(target)s') LOG.info(msg, {'uuid': uuid, 'target': target}) nova = novaclient(context) nova.servers.evacuate(uuid, host=target) @translate_nova_exception def reset_instance_state(self, context, uuid, status='error'): """Reset the state of an instance to active or error.""" msg = ('Call reset state command on instance %(uuid)s to ' 'status: %(status)s.') LOG.info(msg, {'uuid': uuid, 'status': status}) nova = novaclient(context) nova.servers.reset_state(uuid, status) @translate_nova_exception def get_server(self, context, uuid): """Get a server.""" nova = novaclient(context) msg = ('Call get server command for instance %(uuid)s') LOG.info(msg, {'uuid': uuid}) return nova.servers.get(uuid) @translate_nova_exception def stop_server(self, context, uuid): """Stop a server.""" nova = novaclient(context) msg = ('Call stop server command for instance %(uuid)s') LOG.info(msg, {'uuid': uuid}) return nova.servers.stop(uuid) @translate_nova_exception def start_server(self, context, uuid): """Start a server.""" nova = novaclient(context) msg = ('Call start server command for instance %(uuid)s') LOG.info(msg, {'uuid': uuid}) return nova.servers.start(uuid) @translate_nova_exception def get_aggregate_list(self, context): """Get all aggregate list.""" nova = novaclient(context) LOG.info('Call aggregate-list command to get list of all aggregates.') return nova.aggregates.list() @translate_nova_exception def add_host_to_aggregate(self, context, host, aggregate): """Add host to given aggregate.""" nova = novaclient(context) msg = ("Call add_host command to add host '%(host_name)s' to " "aggregate '%(aggregate_name)s'.") LOG.info(msg, {'host_name': host, 'aggregate_name': aggregate.name}) return nova.aggregates.add_host(aggregate.id, host) @translate_nova_exception def lock_server(self, context, uuid): """Lock a server.""" nova = novaclient(context) msg = ('Call lock server command for instance %(uuid)s') LOG.info(msg, {'uuid': uuid}) return nova.servers.lock(uuid) @translate_nova_exception def unlock_server(self, context, uuid): """Unlock a server.""" nova = novaclient(context) msg = ('Call unlock server command for instance %(uuid)s') LOG.info(msg, {'uuid': uuid}) return nova.servers.unlock(uuid)