Add nova support in masakari

This patch allows masakari to communicate with nova using
novaclient.

Change-Id: Ia7a2c8e91811500301d5cc44dfebc9f7ca2026e9
This commit is contained in:
dineshbhor 2016-10-14 14:08:17 +05:30
parent c3a58e4988
commit 484da30293
8 changed files with 350 additions and 1 deletions

View File

@ -113,6 +113,12 @@ function configure_masakari {
configure_auth_token_middleware $MASAKARI_CONF masakari $MASAKARI_AUTH_CACHE_DIR configure_auth_token_middleware $MASAKARI_CONF masakari $MASAKARI_AUTH_CACHE_DIR
fi fi
# Set os_privileged_user credentials (used for connecting nova service)
iniset $MASAKARI_CONF DEFAULT os_privileged_user_name nova
iniset $MASAKARI_CONF DEFAULT os_privileged_user_password "$SERVICE_PASSWORD"
iniset $MASAKARI_CONF DEFAULT os_privileged_user_tenant "$SERVICE_PROJECT_NAME"
iniset $MASAKARI_CONF DEFAULT graceful_shutdown_timeout "$SERVICE_GRACEFUL_SHUTDOWN_TIMEOUT"
} }
# install_masakari() - Collect source and prepare # install_masakari() - Collect source and prepare

View File

@ -0,0 +1,21 @@
# Copyright 2016 NTT DATA
# All Rights Reserved.
#
# 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.
from oslo_utils import importutils
def API():
cls = importutils.import_class("masakari.compute.nova.API")
return cls()

239
masakari/compute/nova.py Normal file
View File

@ -0,0 +1,239 @@
# 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 novaclient import service_catalog
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
from masakari.i18n import _LI
CONF = conf.CONF
CONF.import_group('keystone_authtoken', 'keystonemiddleware.auth_token')
LOG = logging.getLogger(__name__)
NOVA_API_VERSION = "2.1"
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))
return res
return wrapper
def novaclient(context, admin_endpoint=False, privileged_user=False,
timeout=None):
"""Returns a Nova client
@param admin_endpoint: If True, use the admin endpoint template from
configuration ('nova_endpoint_admin_template' and 'nova_catalog_info')
@param privileged_user: If True, use the account from configuration
(requires 'os_privileged_user_name', 'os_privileged_user_password' and
'os_privileged_user_tenant' to be set)
@param timeout: Number of seconds to wait for an answer before raising a
Timeout exception (None to disable)
"""
# FIXME: the novaclient ServiceCatalog object is mis-named.
# It actually contains the entire access blob.
# Only needed parts of the service catalog are passed in, see
# nova/context.py.
compat_catalog = {
'access': {'serviceCatalog': context.service_catalog or []}
}
sc = service_catalog.ServiceCatalog(compat_catalog)
nova_endpoint_template = CONF.nova_endpoint_template
nova_catalog_info = CONF.nova_catalog_info
if admin_endpoint:
nova_endpoint_template = CONF.nova_endpoint_admin_template
nova_catalog_info = CONF.nova_catalog_admin_info
service_type, service_name, endpoint_type = nova_catalog_info.split(':')
# Extract the region if set in configuration
if CONF.os_region_name:
region_filter = {'attr': 'region', 'filter_value': CONF.os_region_name}
else:
region_filter = {}
if privileged_user and CONF.os_privileged_user_name:
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)
# When privileged_user is used, it needs to authenticate to Keystone
# before querying Nova, so we set auth_url to the identity service
# endpoint.
if CONF.os_privileged_user_auth_url:
url = CONF.os_privileged_user_auth_url
else:
# We then pass region_name, endpoint_type, etc. to the
# Client() constructor so that the final endpoint is
# chosen correctly.
url = sc.url_for(service_type='identity',
endpoint_type=endpoint_type,
**region_filter)
LOG.debug('Creating a Nova client using "%s" user',
CONF.os_privileged_user_name)
else:
if nova_endpoint_template:
url = nova_endpoint_template % context.to_dict()
else:
url = sc.url_for(service_type=service_type,
service_name=service_name,
endpoint_type=endpoint_type,
**region_filter)
LOG.debug('Nova client connection created using URL: %s', url)
# 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)
keystone_session = keystoneauth1.session.Session(auth=auth)
c = nova_client.Client(api_versions.APIVersion(NOVA_API_VERSION),
session=keystone_session,
insecure=CONF.nova_api_insecure,
timeout=timeout,
region_name=CONF.os_region_name,
endpoint_type=endpoint_type,
cacert=CONF.nova_ca_certificates_file,
extensions=nova_extensions)
if not privileged_user:
# noauth extracts user_id:project_id from auth_token
c.client.auth_token = (context.auth_token or '%s:%s'
% (context.user_id, context.project_id))
c.client.management_url = url
return c
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, admin_endpoint=True,
privileged_user=True)
LOG.info(_LI('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, admin_endpoint=True,
privileged_user=True)
if not enable:
LOG.info(_LI('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(_LI('Enable nova-compute on %s'), host_name)
nova.services.enable(host_name, 'nova-compute')
@translate_nova_exception
def evacuate_instance(self, context, uuid, target=None,
on_shared_storage=True):
"""Evacuate an instance from failed host to specified host."""
msg = (_LI('Call evacuate command for instance %(uuid)s on host '
'%(target)s'))
LOG.info(msg, {'uuid': uuid, 'target': target})
nova = novaclient(context, admin_endpoint=True,
privileged_user=True)
nova.servers.evacuate(uuid, host=target,
on_shared_storage=on_shared_storage)
@translate_nova_exception
def reset_instance_state(self, context, uuid, status='error'):
"""Reset the state of an instance to active or error."""
msg = (_LI('Call reset state command on instance %(uuid)s to '
'status: %(status)s.'))
LOG.info(msg, {'uuid': uuid, 'status': status})
nova = novaclient(context, admin_endpoint=True,
privileged_user=True)
nova.servers.reset_state(uuid, status)
@translate_nova_exception
def get_server(self, context, uuid):
"""Get a server."""
nova = novaclient(context, admin_endpoint=True,
privileged_user=True)
msg = (_LI('Call get server command for instance %(uuid)s'))
LOG.info(msg, {'uuid': uuid})
return nova.servers.get(uuid)

View File

@ -20,6 +20,7 @@ from masakari.conf import base
from masakari.conf import database from masakari.conf import database
from masakari.conf import engine from masakari.conf import engine
from masakari.conf import exceptions from masakari.conf import exceptions
from masakari.conf import nova
from masakari.conf import osapi_v1 from masakari.conf import osapi_v1
from masakari.conf import paths from masakari.conf import paths
from masakari.conf import service from masakari.conf import service
@ -33,6 +34,7 @@ base.register_opts(CONF)
database.register_opts(CONF) database.register_opts(CONF)
engine.register_opts(CONF) engine.register_opts(CONF)
exceptions.register_opts(CONF) exceptions.register_opts(CONF)
nova.register_opts(CONF)
osapi_v1.register_opts(CONF) osapi_v1.register_opts(CONF)
paths.register_opts(CONF) paths.register_opts(CONF)
ssl.register_opts(CONF) ssl.register_opts(CONF)

67
masakari/conf/nova.py Normal file
View File

@ -0,0 +1,67 @@
# Copyright (c) 2016 NTT DATA
# All Rights Reserved.
#
# 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.
from keystoneauth1 import loading as ks_loading
from oslo_config import cfg
nova_opts = [
cfg.StrOpt('nova_catalog_info',
default='compute:Compute Service:publicURL',
help='Match this value when searching for nova in the '
'service catalog. Format is: separated values of '
'the form: '
'<service_type>:<service_name>:<endpoint_type>'),
cfg.StrOpt('nova_catalog_admin_info',
default='compute:Compute Service:adminURL',
help='Same as nova_catalog_info, but for admin endpoint.'),
cfg.StrOpt('nova_endpoint_template',
help='Override service catalog lookup with template for nova '
'endpoint e.g. http://localhost:8774/v2/%(project_id)s'),
cfg.StrOpt('nova_endpoint_admin_template',
help='Same as nova_endpoint_template, but for admin endpoint.'),
cfg.StrOpt('os_region_name',
help='Region name of this node'),
cfg.StrOpt('nova_ca_certificates_file',
help='Location of ca certificates file to use for nova client '
'requests.'),
cfg.BoolOpt('nova_api_insecure',
default=False,
help='Allow to perform insecure SSL requests to nova'),
cfg.StrOpt('os_privileged_user_name',
help='OpenStack privileged account username. Used for requests '
'to other services (such as Nova) that require an account '
'with special rights.'),
cfg.StrOpt('os_privileged_user_password',
help='Password associated with the OpenStack privileged '
'account.',
secret=True),
cfg.StrOpt('os_privileged_user_tenant',
help='Tenant name associated with the OpenStack privileged '
'account.'),
cfg.URIOpt('os_privileged_user_auth_url',
help='Auth URL associated with the OpenStack privileged '
'account.'),
]
def register_opts(conf):
conf.register_opts(nova_opts)
ks_loading.register_session_conf_options(conf, 'DEFAULT')
def list_opts():
return {
'DEFAULT': nova_opts
}

View File

@ -127,7 +127,7 @@ class RequestContext(context.RequestContext):
# Only include required parts of service_catalog # Only include required parts of service_catalog
self.service_catalog = [ self.service_catalog = [
s for s in service_catalog if s.get('type') in ( s for s in service_catalog if s.get('type') in (
'key-manager')] 'compute', 'identity')]
else: else:
# if list is empty or none # if list is empty or none
self.service_catalog = [] self.service_catalog = []

View File

@ -155,6 +155,19 @@ class MasakariException(Exception):
return self.args[0] return self.args[0]
class APIException(MasakariException):
msg_fmt = _("Error while requesting %(service)s API.")
def __init__(self, message=None, **kwargs):
if 'service' not in kwargs:
kwargs['service'] = 'unknown'
super(APIException, self).__init__(message, **kwargs)
class APITimeout(APIException):
msg_fmt = _("Timeout while requesting %(service)s API.")
class Invalid(MasakariException): class Invalid(MasakariException):
msg_fmt = _("Bad Request - Invalid Parameters") msg_fmt = _("Bad Request - Invalid Parameters")
code = 400 code = 400

View File

@ -19,6 +19,7 @@ oslo.service>=1.10.0 # Apache-2.0
oslo.utils>=3.11.0 # Apache-2.0 oslo.utils>=3.11.0 # Apache-2.0
oslo.versionedobjects>=1.13.0 # Apache-2.0 oslo.versionedobjects>=1.13.0 # Apache-2.0
pbr>=1.6 # Apache-2.0 pbr>=1.6 # Apache-2.0
python-novaclient!=2.33.0,>=2.29.0 # Apache-2.0
setuptools>=16.0 # PSF/ZPL setuptools>=16.0 # PSF/ZPL
six>=1.9.0 # MIT six>=1.9.0 # MIT
stevedore>=1.10.0 # Apache-2.0 stevedore>=1.10.0 # Apache-2.0