Use openstacksdk to communicate with other services

Change-Id: I083fc384b79665f9c64efa867a3b6d93f094dd0e
This commit is contained in:
lvdongbing 2016-02-02 16:29:59 +08:00
parent 3120e19f61
commit d6b03252ad
33 changed files with 996 additions and 1545 deletions

View File

@ -0,0 +1,82 @@
# 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_config import cfg
from oslo_middleware import request_id as oslo_request_id
from oslo_utils import encodeutils
from bilean.common import context
from bilean.common import exception
from bilean.common import wsgi
class ContextMiddleware(wsgi.Middleware):
def process_request(self, req):
'''Build context from authentication info extracted from request.'''
headers = req.headers
environ = req.environ
try:
auth_url = headers.get('X-Auth-Url')
if not auth_url:
# Use auth_url defined in bilean.conf
auth_url = cfg.CONF.authentication.auth_url
auth_token = headers.get('X-Auth-Token')
auth_token_info = environ.get('keystone.token_info')
project = headers.get('X-Project-Id')
project_name = headers.get('X-Project-Name')
project_domain = headers.get('X-Project-Domain-Id')
project_domain_name = headers.get('X-Project-Domain-Name')
user = headers.get('X-User-Id')
user_name = headers.get('X-User-Name')
user_domain = headers.get('X-User-Domain-Id')
user_domain_name = headers.get('X-User-Domain-Name')
domain = headers.get('X-Domain-Id')
domain_name = headers.get('X-Domain-Name')
region_name = headers.get('X-Region-Name')
roles = headers.get('X-Roles')
if roles is not None:
roles = roles.split(',')
env_req_id = environ.get(oslo_request_id.ENV_REQUEST_ID)
if env_req_id is None:
request_id = None
else:
request_id = encodeutils.safe_decode(env_req_id)
except Exception:
raise exception.NotAuthenticated()
req.context = context.RequestContext(
auth_token=auth_token,
user=user,
project=project,
domain=domain,
user_domain=user_domain,
project_domain=project_domain,
request_id=request_id,
auth_url=auth_url,
user_name=user_name,
project_name=project_name,
domain_name=domain_name,
user_domain_name=user_domain_name,
project_domain_name=project_domain_name,
auth_token_info=auth_token_info,
region_name=region_name,
roles=roles)

View File

@ -11,11 +11,11 @@
# License for the specific language governing permissions and limitations
# under the License.
from bilean.api.middleware.context import ContextMiddleware
from bilean.api.middleware.fault import FaultWrapper
from bilean.api.middleware.ssl import SSLMiddleware
from bilean.api.middleware.version_negotiation import VersionNegotiationFilter
from bilean.api.openstack import versions
from bilean.common import context
def version_negotiation_filter(app, conf, **local_conf):
@ -32,4 +32,4 @@ def sslmiddleware_filter(app, conf, **local_conf):
def contextmiddleware_filter(app, conf, **local_conf):
return context.ContextMiddleware(app)
return ContextMiddleware(app)

View File

@ -12,10 +12,12 @@
# under the License.
import functools
import six
import six
from webob import exc
from bilean.common import policy
def policy_enforce(handler):
"""Decorator that enforces policies.
@ -27,11 +29,14 @@ def policy_enforce(handler):
"""
@functools.wraps(handler)
def handle_bilean_method(controller, req, tenant_id, **kwargs):
if req.context.tenant_id != tenant_id:
import pdb
pdb.set_trace()
if req.context.project != tenant_id:
raise exc.HTTPForbidden()
allowed = req.context.policy.enforce(context=req.context,
action=handler.__name__,
scope=controller.REQUEST_SCOPE)
rule = "%s:%s" % (controller.REQUEST_SCOPE, handler.__name__)
allowed = policy.enforce(context=req.context,
rule=rule, target={})
if not allowed:
raise exc.HTTPForbidden()
return handler(controller, req, **kwargs)

View File

@ -55,6 +55,11 @@ rpc_opts = [
'It is not necessarily a hostname, FQDN, '
'or IP address.'))]
cloud_backend_opts = [
cfg.StrOpt('cloud_backend',
default='openstack',
help=_('Default cloud backend to use.'))]
authentication_group = cfg.OptGroup('authentication')
authentication_opts = [
cfg.StrOpt('auth_url', default='',
@ -104,6 +109,7 @@ revision_opts = [
def list_opts():
yield None, rpc_opts
yield None, service_opts
yield None, cloud_backend_opts
yield paste_deploy_group.name, paste_deploy_opts
yield authentication_group.name, authentication_opts
yield revision_group.name, revision_opts

View File

@ -1,91 +1,76 @@
#
# 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
# 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.
# 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 keystoneclient import access
from keystoneclient import auth
from keystoneclient.auth.identity import access as access_plugin
from keystoneclient.auth.identity import v3
from keystoneclient.auth import token_endpoint
from oslo_config import cfg
from oslo_context import context
from oslo_log import log as logging
import oslo_messaging
from oslo_middleware import request_id as oslo_request_id
from oslo_utils import importutils
import six
from oslo_context import context as base_context
from oslo_utils import encodeutils
from bilean.common import exception
from bilean.common.i18n import _LE, _LW
from bilean.common import policy
from bilean.common import wsgi
from bilean.db import api as db_api
from bilean.engine import clients
LOG = logging.getLogger(__name__)
TRUSTEE_CONF_GROUP = 'trustee'
auth.register_conf_options(cfg.CONF, TRUSTEE_CONF_GROUP)
cfg.CONF.import_group('authentication', 'bilean.common.config')
from bilean.drivers import base as driver_base
class RequestContext(context.RequestContext):
"""Stores information about the security context.
class RequestContext(base_context.RequestContext):
'''Stores information about the security context.
Under the security context the user accesses the system, as well as
additional request information.
"""
The context encapsulates information related to the user accessing the
the system, as well as additional request information.
'''
def __init__(self, auth_token=None, username=None, password=None,
aws_creds=None, tenant=None, user_id=None,
tenant_id=None, auth_url=None, roles=None, is_admin=None,
read_only=False, show_deleted=False,
overwrite=True, trust_id=None, trustor_user_id=None,
request_id=None, auth_token_info=None, region_name=None,
auth_plugin=None, trusts_auth_plugin=None, **kwargs):
"""Initialisation of the request context.
def __init__(self, auth_token=None, user=None, project=None,
domain=None, user_domain=None, project_domain=None,
is_admin=None, read_only=False, show_deleted=False,
request_id=None, auth_url=None, trusts=None,
user_name=None, project_name=None, domain_name=None,
user_domain_name=None, project_domain_name=None,
auth_token_info=None, region_name=None, roles=None,
password=None, **kwargs):
:param overwrite: Set to False to ensure that the greenthread local
copy of the index is not overwritten.
'''Initializer of request context.'''
# We still have 'tenant' param because oslo_context still use it.
super(RequestContext, self).__init__(
auth_token=auth_token, user=user, tenant=project,
domain=domain, user_domain=user_domain,
project_domain=project_domain,
read_only=read_only, show_deleted=show_deleted,
request_id=request_id)
:param kwargs: Extra arguments that might be present, but we ignore
because they possibly came in from older rpc messages.
"""
super(RequestContext, self).__init__(auth_token=auth_token,
user=username, tenant=tenant,
is_admin=is_admin,
read_only=read_only,
show_deleted=show_deleted,
request_id=request_id)
# request_id might be a byte array
self.request_id = encodeutils.safe_decode(self.request_id)
self.username = username
self.user_id = user_id
self.password = password
self.region_name = region_name
self.aws_creds = aws_creds
self.tenant_id = tenant_id
self.auth_token_info = auth_token_info
self.auth_url = auth_url
self.roles = roles or []
# we save an additional 'project' internally for use
self.project = project
# Session for DB access
self._session = None
self._clients = None
self.trust_id = trust_id
self.trustor_user_id = trustor_user_id
self.policy = policy.Enforcer()
self._auth_plugin = auth_plugin
self._trusts_auth_plugin = trusts_auth_plugin
self.auth_url = auth_url
self.trusts = trusts
self.user_name = user_name
self.project_name = project_name
self.domain_name = domain_name
self.user_domain_name = user_domain_name
self.project_domain_name = project_domain_name
self.auth_token_info = auth_token_info
self.region_name = region_name
self.roles = roles or []
self.password = password
# Check user is admin or not
if is_admin is None:
self.is_admin = self.policy.check_is_admin(self)
self.is_admin = policy.enforce(self, 'context_is_admin',
target={'project': self.project},
do_raise=False)
else:
self.is_admin = is_admin
@ -95,205 +80,48 @@ class RequestContext(context.RequestContext):
self._session = db_api.get_session()
return self._session
@property
def clients(self):
if self._clients is None:
self._clients = clients.Clients(self)
return self._clients
def to_dict(self):
user_idt = '{user} {tenant}'.format(user=self.user_id or '-',
tenant=self.tenant_id or '-')
return {'auth_token': self.auth_token,
'username': self.username,
'user_id': self.user_id,
'password': self.password,
'aws_creds': self.aws_creds,
'tenant': self.tenant,
'tenant_id': self.tenant_id,
'trust_id': self.trust_id,
'trustor_user_id': self.trustor_user_id,
'auth_token_info': self.auth_token_info,
'auth_url': self.auth_url,
'roles': self.roles,
'is_admin': self.is_admin,
'user': self.user,
'request_id': self.request_id,
'show_deleted': self.show_deleted,
'region_name': self.region_name,
'user_identity': user_idt}
return {
'auth_url': self.auth_url,
'auth_token': self.auth_token,
'auth_token_info': self.auth_token_info,
'user': self.user,
'user_name': self.user_name,
'user_domain': self.user_domain,
'user_domain_name': self.user_domain_name,
'project': self.project,
'project_name': self.project_name,
'project_domain': self.project_domain,
'project_domain_name': self.project_domain_name,
'domain': self.domain,
'domain_name': self.domain_name,
'trusts': self.trusts,
'region_name': self.region_name,
'roles': self.roles,
'show_deleted': self.show_deleted,
'is_admin': self.is_admin,
'request_id': self.request_id,
'password': self.password,
}
@classmethod
def from_dict(cls, values):
return cls(**values)
@property
def keystone_v3_endpoint(self):
if self.auth_url:
return self.auth_url.replace('v2.0', 'v3')
raise exception.AuthorizationFailure()
@property
def trusts_auth_plugin(self):
if self._trusts_auth_plugin:
return self._trusts_auth_plugin
def get_service_context(**kwargs):
'''An abstraction layer for getting service credential.
self._trusts_auth_plugin = auth.load_from_conf_options(
cfg.CONF, TRUSTEE_CONF_GROUP, trust_id=self.trust_id)
if self._trusts_auth_plugin:
return self._trusts_auth_plugin
LOG.warn(_LW('Using the keystone_authtoken user as the bilean '
'trustee user directly is deprecated. Please add the '
'trustee credentials you need to the %s section of '
'your bilean.conf file.') % TRUSTEE_CONF_GROUP)
cfg.CONF.import_group('keystone_authtoken',
'keystonemiddleware.auth_token')
self._trusts_auth_plugin = v3.Password(
username=cfg.CONF.keystone_authtoken.admin_user,
password=cfg.CONF.keystone_authtoken.admin_password,
user_domain_id='default',
auth_url=self.keystone_v3_endpoint,
trust_id=self.trust_id)
return self._trusts_auth_plugin
def _create_auth_plugin(self):
if self.auth_token_info:
auth_ref = access.AccessInfo.factory(body=self.auth_token_info,
auth_token=self.auth_token)
return access_plugin.AccessInfoPlugin(
auth_url=self.keystone_v3_endpoint,
auth_ref=auth_ref)
if self.auth_token:
# FIXME(jamielennox): This is broken but consistent. If you
# only have a token but don't load a service catalog then
# url_for wont work. Stub with the keystone endpoint so at
# least it might be right.
return token_endpoint.Token(endpoint=self.keystone_v3_endpoint,
token=self.auth_token)
if self.password:
return v3.Password(username=self.username,
password=self.password,
project_id=self.tenant_id,
user_domain_id='default',
auth_url=self.keystone_v3_endpoint)
LOG.error(_LE("Keystone v3 API connection failed, no password "
"trust or auth_token!"))
raise exception.AuthorizationFailure()
def reload_auth_plugin(self):
self._auth_plugin = None
@property
def auth_plugin(self):
if not self._auth_plugin:
if self.trust_id:
self._auth_plugin = self.trusts_auth_plugin
else:
self._auth_plugin = self._create_auth_plugin()
return self._auth_plugin
There could be multiple cloud backends for bilean to use. This
abstraction layer provides an indirection for bilean to get the
credentials of 'bilean' user on the specific cloud. By default,
this credential refers to the credentials built for keystone middleware
in an OpenStack cloud.
'''
identity_service = driver_base.BileanDriver().identity
service_creds = identity_service.get_service_credentials(**kwargs)
return RequestContext(**service_creds)
def get_admin_context(show_deleted=False):
return RequestContext(is_admin=True, show_deleted=show_deleted)
def get_service_context(show_deleted=False):
conf = cfg.CONF.authentication
return RequestContext(username=conf.service_username,
password=conf.service_password,
tenant=conf.service_project_name,
auth_url=conf.auth_url)
class ContextMiddleware(wsgi.Middleware):
def __init__(self, app, conf, **local_conf):
# Determine the context class to use
self.ctxcls = RequestContext
if 'context_class' in local_conf:
self.ctxcls = importutils.import_class(local_conf['context_class'])
super(ContextMiddleware, self).__init__(app)
def make_context(self, *args, **kwargs):
"""Create a context with the given arguments."""
return self.ctxcls(*args, **kwargs)
def process_request(self, req):
"""Constructs an appropriate context from extracted auth information.
Extract any authentication information in the request and construct an
appropriate context from it.
"""
headers = req.headers
environ = req.environ
try:
username = None
password = None
aws_creds = None
if headers.get('X-Auth-User') is not None:
username = headers.get('X-Auth-User')
password = headers.get('X-Auth-Key')
elif headers.get('X-Auth-EC2-Creds') is not None:
aws_creds = headers.get('X-Auth-EC2-Creds')
user_id = headers.get('X-User-Id')
token = headers.get('X-Auth-Token')
tenant = headers.get('X-Project-Name')
tenant_id = headers.get('X-Project-Id')
region_name = headers.get('X-Region-Name')
auth_url = headers.get('X-Auth-Url')
roles = headers.get('X-Roles')
if roles is not None:
roles = roles.split(',')
token_info = environ.get('keystone.token_info')
auth_plugin = environ.get('keystone.token_auth')
req_id = environ.get(oslo_request_id.ENV_REQUEST_ID)
except Exception:
raise exception.NotAuthenticated()
req.context = self.make_context(auth_token=token,
tenant=tenant, tenant_id=tenant_id,
aws_creds=aws_creds,
username=username,
user_id=user_id,
password=password,
auth_url=auth_url,
roles=roles,
request_id=req_id,
auth_token_info=token_info,
region_name=region_name,
auth_plugin=auth_plugin)
def ContextMiddleware_filter_factory(global_conf, **local_conf):
"""Factory method for paste.deploy."""
conf = global_conf.copy()
conf.update(local_conf)
def filter(app):
return ContextMiddleware(app, conf)
return filter
def request_context(func):
@six.wraps(func)
def wrapped(self, ctx, *args, **kwargs):
try:
return func(self, ctx, *args, **kwargs)
except exception.BileanException:
raise oslo_messaging.rpc.dispatcher.ExpectedException()
return wrapped

View File

@ -1,115 +1,47 @@
#
# Copyright (c) 2011 OpenStack Foundation
# 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
# 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.
# 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.
# Based on glance/api/policy.py
"""Policy Engine For Bilean."""
"""
Policy Engine For Bilean
"""
from oslo_config import cfg
from oslo_log import log as logging
from oslo_policy import policy
import six
from bilean.common import exception
POLICY_ENFORCER = None
CONF = cfg.CONF
LOG = logging.getLogger(__name__)
DEFAULT_RULES = policy.Rules.from_dict({'default': '!'})
DEFAULT_RESOURCE_RULES = policy.Rules.from_dict({'default': '@'})
class Enforcer(object):
"""Responsible for loading and enforcing rules."""
def _get_enforcer(policy_file=None, rules=None, default_rule=None):
def __init__(self, scope='bilean', exc=exception.Forbidden,
default_rule=DEFAULT_RULES['default'], policy_file=None):
self.scope = scope
self.exc = exc
self.default_rule = default_rule
self.enforcer = policy.Enforcer(
CONF, default_rule=default_rule, policy_file=policy_file)
global POLICY_ENFORCER
def set_rules(self, rules, overwrite=True):
"""Create a new Rules object based on the provided dict of rules."""
rules_obj = policy.Rules(rules, self.default_rule)
self.enforcer.set_rules(rules_obj, overwrite)
def load_rules(self, force_reload=False):
"""Set the rules found in the json file on disk."""
self.enforcer.load_rules(force_reload)
def _check(self, context, rule, target, exc, *args, **kwargs):
"""Verifies that the action is valid on the target in this context.
:param context: Bilean request context
:param rule: String representing the action to be checked
:param target: Dictionary representing the object of the action.
:raises: self.exc (defaults to bilean.common.exception.Forbidden)
:returns: A non-False value if access is allowed.
"""
do_raise = False if not exc else True
credentials = context.to_dict()
return self.enforcer.enforce(rule, target, credentials,
do_raise, exc=exc, *args, **kwargs)
def enforce(self, context, action, scope=None, target=None):
"""Verifies that the action is valid on the target in this context.
:param context: Bilean request context
:param action: String representing the action to be checked
:param target: Dictionary representing the object of the action.
:raises: self.exc (defaults to bilean.common.exception.Forbidden)
:returns: A non-False value if access is allowed.
"""
_action = '%s:%s' % (scope or self.scope, action)
_target = target or {}
return self._check(context, _action, _target, self.exc, action=action)
def check_is_admin(self, context):
"""Whether or not roles contains 'admin' role according to policy.json.
:param context: Bilean request context
:returns: A non-False value if the user is admin according to policy
"""
return self._check(context, 'context_is_admin', target={}, exc=None)
if POLICY_ENFORCER is None:
POLICY_ENFORCER = policy.Enforcer(CONF,
policy_file=policy_file,
rules=rules,
default_rule=default_rule)
return POLICY_ENFORCER
class ResourceEnforcer(Enforcer):
def __init__(self, default_rule=DEFAULT_RESOURCE_RULES['default'],
**kwargs):
super(ResourceEnforcer, self).__init__(
default_rule=default_rule, **kwargs)
def enforce(context, rule, target, do_raise=True, *args, **kwargs):
def enforce(self, context, res_type, scope=None, target=None):
# NOTE(pas-ha): try/except just to log the exception
try:
result = super(ResourceEnforcer, self).enforce(
context, res_type,
scope=scope or 'resource_types',
target=target)
except self.exc as ex:
LOG.info(six.text_type(ex))
raise
if not result:
if self.exc:
raise self.exc(action=res_type)
else:
return result
enforcer = _get_enforcer()
credentials = context.to_dict()
target = target or {}
if do_raise:
kwargs.update(exc=exception.Forbidden)
def enforce_stack(self, stack, scope=None, target=None):
for res in stack.resources.values():
self.enforce(stack.context, res.type(), scope=scope, target=target)
return enforcer.enforce(rule, target, credentials, do_raise,
*args, **kwargs)

52
bilean/drivers/base.py Normal file
View File

@ -0,0 +1,52 @@
# 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.
import copy
from oslo_config import cfg
from bilean.engine import environment
CONF = cfg.CONF
class DriverBase(object):
'''Base class for all drivers.'''
def __init__(self, params=None):
if params is None:
params = {
'auth_url': CONF.authentication.auth_url,
'username': CONF.authentication.service_username,
'password': CONF.authentication.service_password,
'project_name': CONF.authentication.service_project_name,
'user_domain_name':
cfg.CONF.authentication.service_user_domain,
'project_domain_name':
cfg.CONF.authentication.service_project_domain,
}
self.conn_params = copy.deepcopy(params)
class BileanDriver(object):
'''Generic driver class'''
def __init__(self, backend_name=None):
if backend_name is None:
backend_name = cfg.CONF.cloud_backend
backend = environment.global_env().get_driver(backend_name)
self.compute = backend.compute
self.network = backend.network
self.identity = backend.identity

View File

@ -0,0 +1,20 @@
# 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 bilean.drivers.openstack import keystone_v3
from bilean.drivers.openstack import neutron_v2
from bilean.drivers.openstack import nova_v2
compute = nova_v2.NovaClient
identity = keystone_v3.KeystoneClient
network = neutron_v2.NeutronClient

View File

@ -0,0 +1,69 @@
# 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_config import cfg
from oslo_log import log
from bilean.drivers import base
from bilean.drivers.openstack import sdk
LOG = log.getLogger(__name__)
CONF = cfg.CONF
class KeystoneClient(base.DriverBase):
'''Keystone V3 driver.'''
def __init__(self, params=None):
super(KeystoneClient, self).__init__(params)
self.conn = sdk.create_connection(self.conn_params)
@sdk.translate_exception
def project_find(self, name_or_id, ignore_missing=True):
'''Find a single project
:param name_or_id: The name or ID of a project.
:param bool ignore_missing: When set to ``False``
:class:`~openstack.exceptions.ResourceNotFound` will be
raised when the resource does not exist.
When set to ``True``, None will be returned when
attempting to find a nonexistent resource.
:returns: One :class:`~openstack.identity.v3.project.Project` or None
'''
project = self.conn.identity.find_project(
name_or_id, ignore_missing=ignore_missing)
return project
@sdk.translate_exception
def project_list(self, **queries):
'''Function to get project list.'''
return self.conn.identity.projects(**queries)
@classmethod
def get_service_credentials(cls, **kwargs):
'''Bilean service credential to use with Keystone.
:param kwargs: An additional keyword argument list that can be used
for customizing the default settings.
'''
creds = {
'auth_url': CONF.authentication.auth_url,
'username': CONF.authentication.service_username,
'password': CONF.authentication.service_password,
'project_name': CONF.authentication.service_project_name,
'user_domain_name': cfg.CONF.authentication.service_user_domain,
'project_domain_name':
cfg.CONF.authentication.service_project_domain,
}
creds.update(**kwargs)
return creds

View File

@ -0,0 +1,125 @@
# 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 bilean.drivers import base
from bilean.drivers.openstack import sdk
class NeutronClient(base.DriverBase):
'''Neutron V2 driver.'''
def __init__(self, params=None):
super(NeutronClient, self).__init__(params)
self.conn = sdk.create_connection(self.conn_params)
@sdk.translate_exception
def network_get(self, name_or_id):
network = self.conn.network.find_network(name_or_id)
return network
@sdk.translate_exception
def network_delete(self, network, ignore_missing=True):
self.conn.network.delete_network(
network, ignore_missing=ignore_missing)
return
@sdk.translate_exception
def subnet_get(self, name_or_id):
subnet = self.conn.network.find_subnet(name_or_id)
return subnet
@sdk.translate_exception
def subnet_delete(self, subnet, ignore_missing=True):
self.conn.network.delete_subnet(
subnet, ignore_missing=ignore_missing)
return
@sdk.translate_exception
def loadbalancer_get(self, name_or_id):
lb = self.conn.network.find_load_balancer(name_or_id)
return lb
@sdk.translate_exception
def loadbalancer_list(self):
lbs = [lb for lb in self.conn.network.load_balancers()]
return lbs
@sdk.translate_exception
def loadbalancer_delete(self, lb_id, ignore_missing=True):
self.conn.network.delete_load_balancer(
lb_id, ignore_missing=ignore_missing)
return
@sdk.translate_exception
def listener_get(self, name_or_id):
listener = self.conn.network.find_listener(name_or_id)
return listener
@sdk.translate_exception
def listener_list(self):
listeners = [i for i in self.conn.network.listeners()]
return listeners
@sdk.translate_exception
def listener_delete(self, listener_id, ignore_missing=True):
self.conn.network.delete_listener(listener_id,
ignore_missing=ignore_missing)
return
@sdk.translate_exception
def pool_get(self, name_or_id):
pool = self.conn.network.find_pool(name_or_id)
return pool
@sdk.translate_exception
def pool_list(self):
pools = [p for p in self.conn.network.pools()]
return pools
@sdk.translate_exception
def pool_delete(self, pool_id, ignore_missing=True):
self.conn.network.delete_pool(pool_id,
ignore_missing=ignore_missing)
return
@sdk.translate_exception
def pool_member_get(self, pool_id, name_or_id):
member = self.conn.network.find_pool_member(name_or_id,
pool_id)
return member
@sdk.translate_exception
def pool_member_list(self, pool_id):
members = [m for m in self.conn.network.pool_members(pool_id)]
return members
@sdk.translate_exception
def pool_member_delete(self, pool_id, member_id, ignore_missing=True):
self.conn.network.delete_pool_member(
member_id, pool_id, ignore_missing=ignore_missing)
return
@sdk.translate_exception
def healthmonitor_get(self, name_or_id):
hm = self.conn.network.find_health_monitor(name_or_id)
return hm
@sdk.translate_exception
def healthmonitor_list(self):
hms = [hm for hm in self.conn.network.health_monitors()]
return hms
@sdk.translate_exception
def healthmonitor_delete(self, hm_id, ignore_missing=True):
self.conn.network.delete_health_monitor(
hm_id, ignore_missing=ignore_missing)
return

View File

@ -0,0 +1,75 @@
# 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_config import cfg
from oslo_log import log
from bilean.drivers import base
from bilean.drivers.openstack import sdk
LOG = log.getLogger(__name__)
class NovaClient(base.DriverBase):
'''Nova V2 driver.'''
def __init__(self, params=None):
super(NovaClient, self).__init__(params)
self.conn = sdk.create_connection(self.conn_params)
@sdk.translate_exception
def flavor_find(self, name_or_id, ignore_missing=False):
return self.conn.compute.find_flavor(name_or_id, ignore_missing)
@sdk.translate_exception
def flavor_list(self, details=True, **query):
return self.conn.compute.flavors(details, **query)
@sdk.translate_exception
def image_find(self, name_or_id, ignore_missing=False):
return self.conn.compute.find_image(name_or_id, ignore_missing)
@sdk.translate_exception
def image_list(self, details=True, **query):
return self.conn.compute.images(details, **query)
@sdk.translate_exception
def image_delete(self, value, ignore_missing=True):
return self.conn.compute.delete_image(value, ignore_missing)
@sdk.translate_exception
def server_get(self, value):
return self.conn.compute.get_server(value)
@sdk.translate_exception
def server_list(self, details=True, **query):
return self.conn.compute.servers(details, **query)
@sdk.translate_exception
def server_update(self, value, **attrs):
return self.conn.compute.update_server(value, **attrs)
@sdk.translate_exception
def server_delete(self, value, ignore_missing=True):
return self.conn.compute.delete_server(value, ignore_missing)
@sdk.translate_exception
def wait_for_server_delete(self, value, timeout=None):
'''Wait for server deleting complete'''
if timeout is None:
timeout = cfg.CONF.default_action_timeout
server_obj = self.conn.compute.find_server(value, True)
if server_obj:
self.conn.compute.wait_for_delete(server_obj, wait=timeout)
return

View File

@ -0,0 +1,114 @@
# 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.
'''
SDK Client
'''
import functools
from oslo_log import log as logging
import six
from openstack import connection
from openstack import exceptions as sdk_exc
from openstack import profile
from oslo_serialization import jsonutils
from requests import exceptions as req_exc
from bilean.common import exception as bilean_exc
USER_AGENT = 'bilean'
exc = sdk_exc
LOG = logging.getLogger(__name__)
def parse_exception(ex):
'''Parse exception code and yield useful information.'''
code = 500
if isinstance(ex, sdk_exc.HttpException):
# some exceptions don't contain status_code
if ex.http_status is not None:
code = ex.http_status
message = ex.message
data = {}
try:
data = jsonutils.loads(ex.details)
except Exception:
# Some exceptions don't have details record or
# are not in JSON format
pass
# try dig more into the exception record
# usually 'data' has two types of format :
# type1: {"forbidden": {"message": "error message", "code": 403}
# type2: {"code": 404, "error": { "message": "not found"}}
if data:
code = data.get('code', code)
message = data.get('message', message)
error = data.get('error', None)
if error:
code = data.get('code', code)
message = data['error'].get('message', message)
else:
for value in data.values():
code = value.get('code', code)
message = value.get('message', message)
elif isinstance(ex, sdk_exc.SDKException):
# Besides HttpException there are some other exceptions like
# ResourceTimeout can be raised from SDK, handle them here.
message = ex.message
elif isinstance(ex, req_exc.RequestException):
# Exceptions that are not captured by SDK
code = ex.errno
message = six.text_type(ex)
elif isinstance(ex, Exception):
message = six.text_type(ex)
raise bilean_exc.InternalError(code=code, message=message)
def translate_exception(func):
"""Decorator for exception translation."""
@functools.wraps(func)
def invoke_with_catch(driver, *args, **kwargs):
try:
return func(driver, *args, **kwargs)
except Exception as ex:
LOG.exception(ex)
raise parse_exception(ex)
return invoke_with_catch
def create_connection(params=None):
if params is None:
params = {}
if params.get('token', None):
auth_plugin = 'token'
else:
auth_plugin = 'password'
prof = profile.Profile()
prof.set_version('identity', 'v3')
if 'region_name' in params:
prof.set_region(prof.ALL, params['region_name'])
params.pop('region_name')
try:
conn = connection.Connection(profile=prof, user_agent=USER_AGENT,
auth_plugin=auth_plugin, **params)
except Exception as ex:
raise parse_exception(ex)
return conn

View File

@ -1,142 +0,0 @@
#
# 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.
import weakref
from oslo_config import cfg
from oslo_log import log as logging
from oslo_utils import importutils
import six
from stevedore import enabled
from bilean.common import exception
from bilean.common.i18n import _
from bilean.common.i18n import _LW
LOG = logging.getLogger(__name__)
_default_backend = "bilean.engine.clients.OpenStackClients"
cloud_opts = [
cfg.StrOpt('client_backend',
default=_default_backend,
help="Fully qualified class name to use as a client backend.")
]
cfg.CONF.register_opts(cloud_opts)
class OpenStackClients(object):
"""Convenience class to create and cache client instances."""
def __init__(self, context):
self._context = weakref.ref(context)
self._clients = {}
self._client_plugins = {}
@property
def context(self):
ctxt = self._context()
assert ctxt is not None, "Need a reference to the context"
return ctxt
def invalidate_plugins(self):
"""Used to force plugins to clear any cached client."""
for name in self._client_plugins:
self._client_plugins[name].invalidate()
def client_plugin(self, name):
global _mgr
if name in self._client_plugins:
return self._client_plugins[name]
if _mgr and name in _mgr.names():
client_plugin = _mgr[name].plugin(self.context)
self._client_plugins[name] = client_plugin
return client_plugin
def client(self, name):
client_plugin = self.client_plugin(name)
if client_plugin:
return client_plugin.client()
if name in self._clients:
return self._clients[name]
# call the local method _<name>() if a real client plugin
# doesn't exist
method_name = '_%s' % name
if callable(getattr(self, method_name, None)):
client = getattr(self, method_name)()
self._clients[name] = client
return client
LOG.warn(_LW('Requested client "%s" not found'), name)
@property
def auth_token(self):
# Always use the auth_token from the keystone() client, as
# this may be refreshed if the context contains credentials
# which allow reissuing of a new token before the context
# auth_token expiry (e.g trust_id or username/password)
return self.client('keystone').auth_token
class ClientBackend(object):
"""Class for delaying choosing the backend client module.
Delay choosing the backend client module until the client's class needs
to be initialized.
"""
def __new__(cls, context):
if cfg.CONF.client_backend == _default_backend:
return OpenStackClients(context)
else:
try:
return importutils.import_object(cfg.CONF.cloud_backend,
context)
except (ImportError, RuntimeError, cfg.NoSuchOptError) as err:
msg = _('Invalid cloud_backend setting in bilean.conf '
'detected - %s') % six.text_type(err)
LOG.error(msg)
raise exception.Invalid(reason=msg)
Clients = ClientBackend
_mgr = None
def has_client(name):
return _mgr and name in _mgr.names()
def initialise():
global _mgr
if _mgr:
return
def client_is_available(client_plugin):
if not hasattr(client_plugin.plugin, 'is_available'):
# if the client does not have a is_available() class method, then
# we assume it wants to be always available
return True
# let the client plugin decide if it wants to register or not
return client_plugin.plugin.is_available()
_mgr = enabled.EnabledExtensionManager(
namespace='bilean.clients',
check_func=client_is_available,
invoke_on_load=False)
def list_opts():
yield None, cloud_opts

View File

@ -1,92 +0,0 @@
#
# 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.
import abc
import six
from oslo_config import cfg
@six.add_metaclass(abc.ABCMeta)
class ClientPlugin(object):
# Module which contains all exceptions classes which the client
# may emit
exceptions_module = None
def __init__(self, context):
self.context = context
self.clients = context.clients
self._client = None
def client(self):
if not self._client:
self._client = self._create()
return self._client
@abc.abstractmethod
def _create(self):
'''Return a newly created client.'''
pass
@property
def auth_token(self):
# Always use the auth_token from the keystone client, as
# this may be refreshed if the context contains credentials
# which allow reissuing of a new token before the context
# auth_token expiry (e.g trust_id or username/password)
return self.clients.client('keystone').auth_token
def url_for(self, **kwargs):
kc = self.clients.client('keystone')
return kc.service_catalog.url_for(**kwargs)
def _get_client_option(self, client, option):
# look for the option in the [clients_${client}] section
# unknown options raise cfg.NoSuchOptError
try:
group_name = 'clients_' + client
cfg.CONF.import_opt(option, 'bilean.common.config',
group=group_name)
v = getattr(getattr(cfg.CONF, group_name), option)
if v is not None:
return v
except cfg.NoSuchGroupError:
pass # do not error if the client is unknown
# look for the option in the generic [clients] section
cfg.CONF.import_opt(option, 'bilean.common.config', group='clients')
return getattr(cfg.CONF.clients, option)
def is_client_exception(self, ex):
'''Returns True if the current exception comes from the client.'''
if self.exceptions_module:
if isinstance(self.exceptions_module, list):
for m in self.exceptions_module:
if type(ex) in m.__dict__.values():
return True
else:
return type(ex) in self.exceptions_module.__dict__.values()
return False
def is_not_found(self, ex):
'''Returns True if the exception is a not-found.'''
return False
def is_over_limit(self, ex):
'''Returns True if the exception is an over-limit.'''
return False
def ignore_not_found(self, ex):
'''Raises the exception unless it is a not-found.'''
if not self.is_not_found(ex):
raise ex

View File

@ -1,52 +0,0 @@
#
# 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 ceilometerclient import client as cc
from ceilometerclient import exc
from ceilometerclient.openstack.common.apiclient import exceptions as api_exc
from bilean.engine.clients import client_plugin
class CeilometerClientPlugin(client_plugin.ClientPlugin):
exceptions_module = [exc, api_exc]
def _create(self):
con = self.context
endpoint_type = self._get_client_option('ceilometer', 'endpoint_type')
endpoint = self.url_for(service_type='metering',
endpoint_type=endpoint_type)
args = {
'auth_url': con.auth_url,
'service_type': 'metering',
'project_id': con.tenant,
'token': lambda: self.auth_token,
'endpoint_type': endpoint_type,
'cacert': self._get_client_option('ceilometer', 'ca_file'),
'cert_file': self._get_client_option('ceilometer', 'cert_file'),
'key_file': self._get_client_option('ceilometer', 'key_file'),
'insecure': self._get_client_option('ceilometer', 'insecure')
}
return cc.Client('2', endpoint, **args)
def is_not_found(self, ex):
return isinstance(ex, (exc.HTTPNotFound, api_exc.NotFound))
def is_over_limit(self, ex):
return isinstance(ex, exc.HTTPOverLimit)
def is_conflict(self, ex):
return isinstance(ex, exc.HTTPConflict)

View File

@ -1,99 +0,0 @@
#
# 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 bilean.common import exception
from bilean.common.i18n import _
from bilean.common.i18n import _LI
from bilean.engine.clients import client_plugin
from cinderclient import client as cc
from cinderclient import exceptions
from keystoneclient import exceptions as ks_exceptions
from oslo_log import log as logging
LOG = logging.getLogger(__name__)
class CinderClientPlugin(client_plugin.ClientPlugin):
exceptions_module = exceptions
def get_volume_api_version(self):
'''Returns the most recent API version.'''
endpoint_type = self._get_client_option('cinder', 'endpoint_type')
try:
self.url_for(service_type='volumev2', endpoint_type=endpoint_type)
return 2
except ks_exceptions.EndpointNotFound:
try:
self.url_for(service_type='volume',
endpoint_type=endpoint_type)
return 1
except ks_exceptions.EndpointNotFound:
return None
def _create(self):
con = self.context
volume_api_version = self.get_volume_api_version()
if volume_api_version == 1:
service_type = 'volume'
client_version = '1'
elif volume_api_version == 2:
service_type = 'volumev2'
client_version = '2'
else:
raise exception.Error(_('No volume service available.'))
LOG.info(_LI('Creating Cinder client with volume API version %d.'),
volume_api_version)
endpoint_type = self._get_client_option('cinder', 'endpoint_type')
args = {
'service_type': service_type,
'auth_url': con.auth_url or '',
'project_id': con.tenant,
'username': None,
'api_key': None,
'endpoint_type': endpoint_type,
'http_log_debug': self._get_client_option('cinder',
'http_log_debug'),
'cacert': self._get_client_option('cinder', 'ca_file'),
'insecure': self._get_client_option('cinder', 'insecure')
}
client = cc.Client(client_version, **args)
management_url = self.url_for(service_type=service_type,
endpoint_type=endpoint_type)
client.client.auth_token = self.auth_token
client.client.management_url = management_url
client.volume_api_version = volume_api_version
return client
def is_not_found(self, ex):
return isinstance(ex, exceptions.NotFound)
def is_over_limit(self, ex):
return isinstance(ex, exceptions.OverLimit)
def is_conflict(self, ex):
return (isinstance(ex, exceptions.ClientException) and
ex.code == 409)
def delete(self, volume_id):
"""Delete a volume by given volume id"""
self.client().volumes.delete(volume_id)

View File

@ -1,103 +0,0 @@
#
# 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 bilean.common import exception
from bilean.common.i18n import _
from bilean.common.i18n import _LI
from bilean.engine.clients import client_plugin
from oslo_log import log as logging
from oslo_utils import uuidutils
from glanceclient import client as gc
from glanceclient import exc
LOG = logging.getLogger(__name__)
class GlanceClientPlugin(client_plugin.ClientPlugin):
exceptions_module = exc
def _create(self):
con = self.context
endpoint_type = self._get_client_option('glance', 'endpoint_type')
endpoint = self.url_for(service_type='image',
endpoint_type=endpoint_type)
args = {
'auth_url': con.auth_url,
'service_type': 'image',
'project_id': con.tenant,
'token': self.auth_token,
'endpoint_type': endpoint_type,
'cacert': self._get_client_option('glance', 'ca_file'),
'cert_file': self._get_client_option('glance', 'cert_file'),
'key_file': self._get_client_option('glance', 'key_file'),
'insecure': self._get_client_option('glance', 'insecure')
}
return gc.Client('1', endpoint, **args)
def is_not_found(self, ex):
return isinstance(ex, exc.HTTPNotFound)
def is_over_limit(self, ex):
return isinstance(ex, exc.HTTPOverLimit)
def is_conflict(self, ex):
return isinstance(ex, exc.HTTPConflict)
def get_image_id(self, image_identifier):
'''Return an id for the specified image name or identifier.
:param image_identifier: image name or a UUID-like identifier
:returns: the id of the requested :image_identifier:
:raises: exception.ImageNotFound,
exception.PhysicalResourceNameAmbiguity
'''
if uuidutils.is_uuid_like(image_identifier):
try:
image_id = self.client().images.get(image_identifier).id
except exc.HTTPNotFound:
image_id = self.get_image_id_by_name(image_identifier)
else:
image_id = self.get_image_id_by_name(image_identifier)
return image_id
def get_image_id_by_name(self, image_identifier):
'''Return an id for the specified image name.
:param image_identifier: image name
:returns: the id of the requested :image_identifier:
:raises: exception.ImageNotFound,
exception.PhysicalResourceNameAmbiguity
'''
try:
filters = {'name': image_identifier}
image_list = list(self.client().images.list(filters=filters))
except exc.ClientException as ex:
raise exception.Error(
_("Error retrieving image list from glance: %s") % ex)
num_matches = len(image_list)
if num_matches == 0:
LOG.info(_LI("Image %s was not found in glance"),
image_identifier)
raise exception.ImageNotFound(image_name=image_identifier)
elif num_matches > 1:
LOG.info(_LI("Multiple images %s were found in glance with name"),
image_identifier)
raise exception.PhysicalResourceNameAmbiguity(
name=image_identifier)
else:
return image_list[0].id

View File

@ -1,65 +0,0 @@
#
# 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 bilean.engine.clients import client_plugin
from heatclient import client as hc
from heatclient import exc
class HeatClientPlugin(client_plugin.ClientPlugin):
exceptions_module = exc
def _create(self):
args = {
'auth_url': self.context.auth_url,
'token': self.auth_token,
'username': None,
'password': None,
'ca_file': self._get_client_option('heat', 'ca_file'),
'cert_file': self._get_client_option('heat', 'cert_file'),
'key_file': self._get_client_option('heat', 'key_file'),
'insecure': self._get_client_option('heat', 'insecure')
}
endpoint = self.get_heat_url()
if self._get_client_option('heat', 'url'):
# assume that the heat API URL is manually configured because
# it is not in the keystone catalog, so include the credentials
# for the standalone auth_password middleware
args['username'] = self.context.username
args['password'] = self.context.password
del(args['token'])
return hc.Client('1', endpoint, **args)
def is_not_found(self, ex):
return isinstance(ex, exc.HTTPNotFound)
def is_over_limit(self, ex):
return isinstance(ex, exc.HTTPOverLimit)
def is_conflict(self, ex):
return isinstance(ex, exc.HTTPConflict)
def get_heat_url(self):
heat_url = self._get_client_option('heat', 'url')
if heat_url:
tenant_id = self.context.tenant_id
heat_url = heat_url % {'tenant_id': tenant_id}
else:
endpoint_type = self._get_client_option('heat', 'endpoint_type')
heat_url = self.url_for(service_type='orchestration',
endpoint_type=endpoint_type)
return heat_url

View File

@ -1,44 +0,0 @@
#
# 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 bilean.engine.clients import client_plugin
from oslo_config import cfg
from keystoneclient import exceptions
from keystoneclient.v2_0 import client as keystone_client
class KeystoneClientPlugin(client_plugin.ClientPlugin):
exceptions_module = exceptions
@property
def kclient(self):
return keystone_client.Client(
username=cfg.CONF.authentication.service_username,
password=cfg.CONF.authentication.service_password,
tenant_name=cfg.CONF.authentication.service_project_name,
auth_url=cfg.CONF.authentication.auth_url)
def _create(self):
return self.kclient
def is_not_found(self, ex):
return isinstance(ex, exceptions.NotFound)
def is_over_limit(self, ex):
return isinstance(ex, exceptions.RequestEntityTooLarge)
def is_conflict(self, ex):
return isinstance(ex, exceptions.Conflict)

View File

@ -1,119 +0,0 @@
#
# 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 bilean.common import exception
from bilean.engine.clients import client_plugin
from oslo_utils import uuidutils
from neutronclient.common import exceptions
from neutronclient.neutron import v2_0 as neutronV20
from neutronclient.v2_0 import client as nc
class NeutronClientPlugin(client_plugin.ClientPlugin):
exceptions_module = exceptions
def _create(self):
con = self.context
endpoint_type = self._get_client_option('neutron', 'endpoint_type')
endpoint = self.url_for(service_type='network',
endpoint_type=endpoint_type)
args = {
'auth_url': con.auth_url,
'service_type': 'network',
'token': self.auth_token,
'endpoint_url': endpoint,
'endpoint_type': endpoint_type,
'ca_cert': self._get_client_option('neutron', 'ca_file'),
'insecure': self._get_client_option('neutron', 'insecure')
}
return nc.Client(**args)
def is_not_found(self, ex):
if isinstance(ex, (exceptions.NotFound,
exceptions.NetworkNotFoundClient,
exceptions.PortNotFoundClient)):
return True
return (isinstance(ex, exceptions.NeutronClientException) and
ex.status_code == 404)
def is_conflict(self, ex):
if not isinstance(ex, exceptions.NeutronClientException):
return False
return ex.status_code == 409
def is_over_limit(self, ex):
if not isinstance(ex, exceptions.NeutronClientException):
return False
return ex.status_code == 413
def find_neutron_resource(self, props, key, key_type):
return neutronV20.find_resourceid_by_name_or_id(
self.client(), key_type, props.get(key))
def resolve_network(self, props, net_key, net_id_key):
if props.get(net_key):
props[net_id_key] = self.find_neutron_resource(
props, net_key, 'network')
props.pop(net_key)
return props[net_id_key]
def resolve_subnet(self, props, subnet_key, subnet_id_key):
if props.get(subnet_key):
props[subnet_id_key] = self.find_neutron_resource(
props, subnet_key, 'subnet')
props.pop(subnet_key)
return props[subnet_id_key]
def network_id_from_subnet_id(self, subnet_id):
subnet_info = self.client().show_subnet(subnet_id)
return subnet_info['subnet']['network_id']
def get_secgroup_uuids(self, security_groups):
'''Returns a list of security group UUIDs.
Args:
security_groups: List of security group names or UUIDs
'''
seclist = []
all_groups = None
for sg in security_groups:
if uuidutils.is_uuid_like(sg):
seclist.append(sg)
else:
if not all_groups:
response = self.client().list_security_groups()
all_groups = response['security_groups']
same_name_groups = [g for g in all_groups if g['name'] == sg]
groups = [g['id'] for g in same_name_groups]
if len(groups) == 0:
raise exception.PhysicalResourceNotFound(resource_id=sg)
elif len(groups) == 1:
seclist.append(groups[0])
else:
# for admin roles, can get the other users'
# securityGroups, so we should match the tenant_id with
# the groups, and return the own one
own_groups = [g['id'] for g in same_name_groups
if g['tenant_id'] == self.context.tenant_id]
if len(own_groups) == 1:
seclist.append(own_groups[0])
else:
raise exception.PhysicalResourceNameAmbiguity(name=sg)
return seclist

View File

@ -1,294 +0,0 @@
#
# 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.
import collections
import json
import six
from novaclient import client as nc
from novaclient import exceptions
from novaclient import shell as novashell
from bilean.common import exception
from bilean.common.i18n import _
from bilean.common.i18n import _LW
from bilean.engine.clients import client_plugin
from oslo_log import log as logging
LOG = logging.getLogger(__name__)
class NovaClientPlugin(client_plugin.ClientPlugin):
deferred_server_statuses = ['BUILD',
'HARD_REBOOT',
'PASSWORD',
'REBOOT',
'RESCUE',
'RESIZE',
'REVERT_RESIZE',
'SHUTOFF',
'SUSPENDED',
'VERIFY_RESIZE']
exceptions_module = exceptions
def _create(self):
computeshell = novashell.OpenStackComputeShell()
extensions = computeshell._discover_extensions("1.1")
endpoint_type = self._get_client_option('nova', 'endpoint_type')
args = {
'project_id': self.context.tenant,
'auth_url': self.context.auth_url,
'service_type': 'compute',
'username': None,
'api_key': None,
'extensions': extensions,
'endpoint_type': endpoint_type,
'http_log_debug': self._get_client_option('nova',
'http_log_debug'),
'cacert': self._get_client_option('nova', 'ca_file'),
'insecure': self._get_client_option('nova', 'insecure')
}
client = nc.Client(1.1, **args)
management_url = self.url_for(service_type='compute',
endpoint_type=endpoint_type)
client.client.auth_token = self.auth_token
client.client.management_url = management_url
return client
def is_not_found(self, ex):
return isinstance(ex, exceptions.NotFound)
def is_over_limit(self, ex):
return isinstance(ex, exceptions.OverLimit)
def is_bad_request(self, ex):
return isinstance(ex, exceptions.BadRequest)
def is_conflict(self, ex):
return isinstance(ex, exceptions.Conflict)
def is_unprocessable_entity(self, ex):
http_status = (getattr(ex, 'http_status', None) or
getattr(ex, 'code', None))
return (isinstance(ex, exceptions.ClientException) and
http_status == 422)
def refresh_server(self, server):
'''Refresh server's attributes.
Log warnings for non-critical API errors.
'''
try:
server.get()
except exceptions.OverLimit as exc:
LOG.warn(_LW("Server %(name)s (%(id)s) received an OverLimit "
"response during server.get(): %(exception)s"),
{'name': server.name,
'id': server.id,
'exception': exc})
except exceptions.ClientException as exc:
if ((getattr(exc, 'http_status', getattr(exc, 'code', None)) in
(500, 503))):
LOG.warn(_LW('Server "%(name)s" (%(id)s) received the '
'following exception during server.get(): '
'%(exception)s'),
{'name': server.name,
'id': server.id,
'exception': exc})
else:
raise
def get_ip(self, server, net_type, ip_version):
"""Return the server's IP of the given type and version."""
if net_type in server.addresses:
for ip in server.addresses[net_type]:
if ip['version'] == ip_version:
return ip['addr']
def get_status(self, server):
'''Return the server's status.
:param server: server object
:returns: status as a string
'''
# Some clouds append extra (STATUS) strings to the status, strip it
return server.status.split('(')[0]
def get_flavor_id(self, flavor):
'''Get the id for the specified flavor name.
If the specified value is flavor id, just return it.
:param flavor: the name of the flavor to find
:returns: the id of :flavor:
:raises: exception.FlavorMissing
'''
flavor_id = None
flavor_list = self.client().flavors.list()
for o in flavor_list:
if o.name == flavor:
flavor_id = o.id
break
if o.id == flavor:
flavor_id = o.id
break
if flavor_id is None:
raise exception.FlavorMissing(flavor_id=flavor)
return flavor_id
def get_keypair(self, key_name):
'''Get the public key specified by :key_name:
:param key_name: the name of the key to look for
:returns: the keypair (name, public_key) for :key_name:
:raises: exception.UserKeyPairMissing
'''
try:
return self.client().keypairs.get(key_name)
except exceptions.NotFound:
raise exception.UserKeyPairMissing(key_name=key_name)
def delete_server(self, server):
'''Deletes a server and waits for it to disappear from Nova.'''
if not server:
return
try:
server.delete()
except Exception as exc:
self.ignore_not_found(exc)
return
while True:
yield
try:
self.refresh_server(server)
except Exception as exc:
self.ignore_not_found(exc)
break
else:
# Some clouds append extra (STATUS) strings to the status
short_server_status = server.status.split('(')[0]
if short_server_status in ("DELETED", "SOFT_DELETED"):
break
if short_server_status == "ERROR":
fault = getattr(server, 'fault', {})
message = fault.get('message', 'Unknown')
code = fault.get('code')
errmsg = (_("Server %(name)s delete failed: (%(code)s) "
"%(message)s"))
raise exception.Error(errmsg % {"name": server.name,
"code": code,
"message": message})
def delete(self, server_id):
'''Delete a server by given server id'''
self.client().servers.delete(server_id)
def resize(self, server, flavor, flavor_id):
"""Resize the server and then call check_resize task to verify."""
server.resize(flavor_id)
yield self.check_resize(server, flavor, flavor_id)
def rename(self, server, name):
"""Update the name for a server."""
server.update(name)
def check_resize(self, server, flavor, flavor_id):
"""Verify that a resizing server is properly resized.
If that's the case, confirm the resize, if not raise an error.
"""
self.refresh_server(server)
while server.status == 'RESIZE':
yield
self.refresh_server(server)
if server.status == 'VERIFY_RESIZE':
server.confirm_resize()
else:
raise exception.Error(
_("Resizing to '%(flavor)s' failed, status '%(status)s'") %
dict(flavor=flavor, status=server.status))
def rebuild(self, server, image_id, preserve_ephemeral=False):
"""Rebuild the server and call check_rebuild to verify."""
server.rebuild(image_id, preserve_ephemeral=preserve_ephemeral)
yield self.check_rebuild(server, image_id)
def check_rebuild(self, server, image_id):
"""Verify that a rebuilding server is rebuilt.
Raise error if it ends up in an ERROR state.
"""
self.refresh_server(server)
while server.status == 'REBUILD':
yield
self.refresh_server(server)
if server.status == 'ERROR':
raise exception.Error(
_("Rebuilding server failed, status '%s'") % server.status)
def meta_serialize(self, metadata):
"""Serialize non-string metadata values before sending them to Nova."""
if not isinstance(metadata, collections.Mapping):
raise exception.StackValidationFailed(message=_(
"nova server metadata needs to be a Map."))
return dict((key, (value if isinstance(value,
six.string_types)
else json.dumps(value))
) for (key, value) in metadata.items())
def meta_update(self, server, metadata):
"""Delete/Add the metadata in nova as needed."""
metadata = self.meta_serialize(metadata)
current_md = server.metadata
to_del = [key for key in current_md.keys() if key not in metadata]
client = self.client()
if len(to_del) > 0:
client.servers.delete_meta(server, to_del)
client.servers.set_meta(server, metadata)
def server_to_ipaddress(self, server):
'''Return the server's IP address, fetching it from Nova.'''
try:
server = self.client().servers.get(server)
except exceptions.NotFound as ex:
LOG.warn(_LW('Instance (%(server)s) not found: %(ex)s'),
{'server': server, 'ex': ex})
else:
for n in server.networks:
if len(server.networks[n]) > 0:
return server.networks[n][0]
def get_server(self, server):
try:
return self.client().servers.get(server)
except exceptions.NotFound as ex:
LOG.warn(_LW('Server (%(server)s) not found: %(ex)s'),
{'server': server, 'ex': ex})
raise exception.ServerNotFound(server=server)
def absolute_limits(self):
"""Return the absolute limits as a dictionary."""
limits = self.client().limits.get()
return dict([(limit.name, limit.value)
for limit in list(limits.absolute)])

View File

@ -1,51 +0,0 @@
# Copyright (c) 2014 Mirantis Inc.
#
# 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 bilean.engine.clients import client_plugin
from saharaclient.api import base as sahara_base
from saharaclient import client as sahara_client
class SaharaClientPlugin(client_plugin.ClientPlugin):
exceptions_module = sahara_base
def _create(self):
con = self.context
endpoint_type = self._get_client_option('sahara', 'endpoint_type')
endpoint = self.url_for(service_type='data_processing',
endpoint_type=endpoint_type)
args = {
'service_type': 'data_processing',
'input_auth_token': self.auth_token,
'auth_url': con.auth_url,
'project_name': con.tenant,
'sahara_url': endpoint
}
client = sahara_client.Client('1.1', **args)
return client
def is_not_found(self, ex):
return (isinstance(ex, sahara_base.APIException) and
ex.error_code == 404)
def is_over_limit(self, ex):
return (isinstance(ex, sahara_base.APIException) and
ex.error_code == 413)
def is_conflict(self, ex):
return (isinstance(ex, sahara_base.APIException) and
ex.error_code == 409)

View File

@ -1,77 +0,0 @@
#
# 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 troveclient import client as tc
from troveclient.openstack.common.apiclient import exceptions
from bilean.common import exception
from bilean.engine.clients import client_plugin
class TroveClientPlugin(client_plugin.ClientPlugin):
exceptions_module = exceptions
def _create(self):
con = self.context
endpoint_type = self._get_client_option('trove', 'endpoint_type')
args = {
'service_type': 'database',
'auth_url': con.auth_url or '',
'proxy_token': con.auth_token,
'username': None,
'password': None,
'cacert': self._get_client_option('trove', 'ca_file'),
'insecure': self._get_client_option('trove', 'insecure'),
'endpoint_type': endpoint_type
}
client = tc.Client('1.0', **args)
management_url = self.url_for(service_type='database',
endpoint_type=endpoint_type)
client.client.auth_token = con.auth_token
client.client.management_url = management_url
return client
def is_not_found(self, ex):
return isinstance(ex, exceptions.NotFound)
def is_over_limit(self, ex):
return isinstance(ex, exceptions.RequestEntityTooLarge)
def is_conflict(self, ex):
return isinstance(ex, exceptions.Conflict)
def get_flavor_id(self, flavor):
'''Get the id for the specified flavor name.
If the specified value is flavor id, just return it.
:param flavor: the name of the flavor to find
:returns: the id of :flavor:
:raises: exception.FlavorMissing
'''
flavor_id = None
flavor_list = self.client().flavors.list()
for o in flavor_list:
if o.name == flavor:
flavor_id = o.id
break
if o.id == flavor:
flavor_id = o.id
break
if flavor_id is None:
raise exception.FlavorMissing(flavor_id=flavor)
return flavor_id

View File

@ -11,6 +11,7 @@
# License for the specific language governing permissions and limitations
# under the License.
import functools
import six
import socket
@ -38,6 +39,19 @@ from bilean.rules import base as rule_base
LOG = logging.getLogger(__name__)
def request_context(func):
@functools.wraps(func)
def wrapped(self, ctx, *args, **kwargs):
if ctx is not None and not isinstance(ctx,
bilean_context.RequestContext):
ctx = bilean_context.RequestContext.from_dict(ctx.to_dict())
try:
return func(self, ctx, *args, **kwargs)
except exception.BileanException:
raise oslo_messaging.rpc.dispatcher.ExpectedException()
return wrapped
class EngineService(service.Service):
"""Manages the running instances from creation to destruction.
@ -64,7 +78,7 @@ class EngineService(service.Service):
bilean_clients.initialise()
if context is None:
self.context = bilean_context.get_service_context()
self.context = bilean_context.get_admin_context()
def start(self):
self.engine_id = socket.gethostname()
@ -109,7 +123,7 @@ class EngineService(service.Service):
super(EngineService, self).stop()
@bilean_context.request_context
@request_context
def user_list(self, cnxt, show_deleted=False, limit=None,
marker=None, sort_keys=None, sort_dir=None,
filters=None):
@ -132,7 +146,7 @@ class EngineService(service.Service):
return user.to_dict()
@bilean_context.request_context
@request_context
def user_get(self, cnxt, user_id):
"""Show detailed info about a specify user.
@ -141,7 +155,7 @@ class EngineService(service.Service):
user = user_mod.User.load(cnxt, user_id=user_id, realtime=True)
return user.to_dict()
@bilean_context.request_context
@request_context
def user_recharge(self, cnxt, user_id, value):
"""Do recharge for specify user."""
user = user_mod.User.load(cnxt, user_id=user_id)
@ -156,7 +170,7 @@ class EngineService(service.Service):
LOG.info(_LI('Deleging user: %s'), user_id)
user_mod.User.delete(cnxt, user_id=user_id)
@bilean_context.request_context
@request_context
def rule_create(self, cnxt, name, spec, metadata=None):
if len(rule_base.Rule.load_all(cnxt, filters={'name': name})) > 0:
msg = _("The rule (%(name)s) already exists."
@ -186,7 +200,7 @@ class EngineService(service.Service):
{'name': name, 'id': rule.id})
return rule.to_dict()
@bilean_context.request_context
@request_context
def rule_list(self, cnxt, limit=None, marker=None, sort_keys=None,
sort_dir=None, filters=None, show_deleted=False):
if limit is not None:
@ -203,21 +217,21 @@ class EngineService(service.Service):
return [rule.to_dict() for rule in rules]
@bilean_context.request_context
@request_context
def rule_get(self, cnxt, rule_id):
rule = rule_base.Rule.load(cnxt, rule_id=rule_id)
return rule.to_dict()
@bilean_context.request_context
@request_context
def rule_update(self, cnxt, rule_id, values):
return NotImplemented
@bilean_context.request_context
@request_context
def rule_delete(self, cnxt, rule_id):
LOG.info(_LI("Deleting rule: '%s'."), rule_id)
rule_base.Rule.delete(cnxt, rule_id)
@bilean_context.request_context
@request_context
def validate_creation(self, cnxt, resources):
"""Validate resources creation.
@ -271,7 +285,7 @@ class EngineService(service.Service):
return resource.to_dict()
@bilean_context.request_context
@request_context
def resource_list(self, cnxt, user_id=None, limit=None, marker=None,
sort_keys=None, sort_dir=None, filters=None,
tenant_safe=True, show_deleted=False):
@ -289,7 +303,7 @@ class EngineService(service.Service):
show_deleted=show_deleted)
return [r.to_dict() for r in resources]
@bilean_context.request_context
@request_context
def resource_get(self, cnxt, resource_id):
resource = resource_mod.Resource.load(cnxt, resource_id=resource_id)
return resource.to_dict()
@ -323,7 +337,7 @@ class EngineService(service.Service):
LOG.warn(_("Delete resource error %s"), ex)
return
@bilean_context.request_context
@request_context
def event_list(self, cnxt, user_id=None, limit=None, marker=None,
sort_keys=None, sort_dir=None, filters=None,
start_time=None, end_time=None, tenant_safe=True,
@ -345,7 +359,7 @@ class EngineService(service.Service):
show_deleted=show_deleted)
return [e.to_dict() for e in events]
@bilean_context.request_context
@request_context
def policy_create(self, cnxt, name, rule_ids=None, metadata=None):
"""Create a new policy."""
if len(policy_mod.Policy.load_all(cnxt, filters={'name': name})) > 0:
@ -378,7 +392,7 @@ class EngineService(service.Service):
LOG.info(_LI("Policy is created: %(id)s."), policy.id)
return policy.to_dict()
@bilean_context.request_context
@request_context
def policy_list(self, cnxt, limit=None, marker=None, sort_keys=None,
sort_dir=None, filters=None, show_deleted=False):
if limit is not None:
@ -395,12 +409,12 @@ class EngineService(service.Service):
return [policy.to_dict() for policy in policies]
@bilean_context.request_context
@request_context
def policy_get(self, cnxt, policy_id):
policy = policy_mod.Policy.load(cnxt, policy_id=policy_id)
return policy.to_dict()
@bilean_context.request_context
@request_context
def policy_update(self, cnxt, policy_id, name=None, metadata=None,
is_default=None):
LOG.info(_LI("Updating policy: '%(id)s'"), {'id': policy_id})
@ -437,15 +451,15 @@ class EngineService(service.Service):
LOG.info(_LI("Policy '%(id)s' is updated."), {'id': policy_id})
return policy.to_dict()
@bilean_context.request_context
@request_context
def policy_add_rule(self, cnxt, policy_id, rule_ids):
return NotImplemented
@bilean_context.request_context
@request_context
def policy_remove_rule(self, cnxt, policy_id, rule_ids):
return NotImplemented
@bilean_context.request_context
@request_context
def policy_delete(self, cnxt, policy_id):
LOG.info(_LI("Deleting policy: '%s'."), policy_id)
policy_mod.Policy.delete(cnxt, policy_id)

View File

@ -11,10 +11,13 @@
# License for the specific language governing permissions and limitations
# under the License.
import six
from bilean.common import exception
from bilean.common.i18n import _
from bilean.common import utils
from bilean.db import api as db_api
from bilean.drivers import base as driver_base
from bilean.engine import event as event_mod
from bilean.engine import resource as resource_mod
@ -77,15 +80,20 @@ class User(object):
@classmethod
def init_users(cls, context):
"""Init users from keystone."""
k_client = context.clients.client('keystone')
tenants = k_client.tenants.list()
tenant_ids = [tenant.id for tenant in tenants]
keystoneclient = driver_base.BileanDriver().identity()
try:
projects = keystoneclient.project_list()
except exception.InternalError as ex:
LOG.exception(_('Failed in retrieving project list: %s'),
six.text_type(ex))
return False
project_ids = [project.id for project in projects]
users = cls.load_all(context)
user_ids = [user.id for user in users]
for tid in tenant_ids:
if tid not in user_ids:
user = cls(tid, status=cls.INIT,
for pid in project_ids:
if pid not in user_ids:
user = cls(pid, status=cls.INIT,
status_reason='Init from keystone')
user.store(context)
return True

View File

View File

@ -0,0 +1,47 @@
# 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.
import mock
from oslo_config import cfg
from bilean.drivers import base as driver_base
from bilean.engine import environment
from bilean.tests.common import base
class TestBileanDriver(base.BileanTestCase):
def test_init_using_default_cloud_backend(self):
plugin1 = mock.Mock()
plugin1.compute = 'Compute1'
plugin1.network = 'Network1'
env = environment.global_env()
env.register_driver('cloud_backend_1', plugin1)
# Using default cloud backend defined in configure file
cfg.CONF.set_override('cloud_backend', 'cloud_backend_1',
enforce_type=True)
bd = driver_base.BileanDriver()
self.assertEqual('Compute1', bd.compute)
self.assertEqual('Network1', bd.network)
def test_init_using_specified_cloud_backend(self):
plugin2 = mock.Mock()
plugin2.compute = 'Compute2'
plugin2.network = 'Network2'
env = environment.global_env()
env.register_driver('cloud_backend_2', plugin2)
# Using specified cloud backend
bd = driver_base.BileanDriver('cloud_backend_2')
self.assertEqual('Compute2', bd.compute)
self.assertEqual('Network2', bd.network)

View File

@ -0,0 +1,211 @@
# 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.
import mock
from openstack import connection
from openstack import profile
from oslo_serialization import jsonutils
from requests import exceptions as req_exc
import six
from bilean.common import exception as bilean_exc
from bilean.drivers.openstack import sdk
from bilean.tests.common import base
class OpenStackSDKTest(base.BileanTestCase):
def setUp(self):
super(OpenStackSDKTest, self).setUp()
def test_parse_exception_http_exception_with_details(self):
details = jsonutils.dumps({
'error': {
'code': 404,
'message': 'Resource BAR is not found.'
}
})
raw = sdk.exc.ResourceNotFound('A message', details, http_status=404)
ex = self.assertRaises(bilean_exc.InternalError,
sdk.parse_exception, raw)
self.assertEqual(404, ex.code)
self.assertEqual('Resource BAR is not found.', six.text_type(ex))
# key name is not 'error' case
details = jsonutils.dumps({
'forbidden': {
'code': 403,
'message': 'Quota exceeded for instances.'
}
})
raw = sdk.exc.ResourceNotFound('A message', details, 403)
ex = self.assertRaises(bilean_exc.InternalError,
sdk.parse_exception, raw)
self.assertEqual(403, ex.code)
self.assertEqual('Quota exceeded for instances.', six.text_type(ex))
def test_parse_exception_http_exception_no_details(self):
details = "An error message"
raw = sdk.exc.ResourceNotFound('A message.', details, http_status=404)
ex = self.assertRaises(bilean_exc.InternalError,
sdk.parse_exception, raw)
self.assertEqual(404, ex.code)
self.assertEqual('A message.', six.text_type(ex))
def test_parse_exception_http_exception_code_displaced(self):
details = jsonutils.dumps({
'code': 400,
'error': {
'message': 'Resource BAR is in error state.'
}
})
raw = sdk.exc.HttpException(message='A message.', details=details,
http_status=400)
ex = self.assertRaises(bilean_exc.InternalError,
sdk.parse_exception, raw)
self.assertEqual(400, ex.code)
self.assertEqual('Resource BAR is in error state.', six.text_type(ex))
def test_parse_exception_sdk_exception(self):
raw = sdk.exc.InvalidResponse('INVALID')
ex = self.assertRaises(bilean_exc.InternalError,
sdk.parse_exception, raw)
self.assertEqual(500, ex.code)
self.assertEqual('InvalidResponse', six.text_type(ex))
def test_parse_exception_request_exception(self):
raw = req_exc.HTTPError(401, 'ERROR')
ex = self.assertRaises(bilean_exc.InternalError,
sdk.parse_exception, raw)
self.assertEqual(401, ex.code)
self.assertEqual('[Errno 401] ERROR', ex.message)
def test_parse_exception_other_exceptions(self):
raw = Exception('Unknown Error')
ex = self.assertRaises(bilean_exc.InternalError,
sdk.parse_exception, raw)
self.assertEqual(500, ex.code)
self.assertEqual('Unknown Error', six.text_type(ex))
def test_translate_exception_wrapper(self):
test_func = mock.Mock()
test_func.__name__ = 'test_func'
res = sdk.translate_exception(test_func)
self.assertEqual('function', res.__class__.__name__)
def test_translate_exception_with_exception(self):
@sdk.translate_exception
def test_func(driver):
raise(Exception('test exception'))
error = bilean_exc.InternalError(code=500, message='BOOM')
self.patchobject(sdk, 'parse_exception', side_effect=error)
ex = self.assertRaises(bilean_exc.InternalError,
test_func, mock.Mock())
self.assertEqual(500, ex.code)
self.assertEqual('BOOM', ex.message)
@mock.patch.object(profile, 'Profile')
@mock.patch.object(connection, 'Connection')
def test_create_connection_token(self, mock_conn, mock_profile):
x_profile = mock.Mock()
mock_profile.return_value = x_profile
x_conn = mock.Mock()
mock_conn.return_value = x_conn
res = sdk.create_connection({'token': 'TOKEN', 'foo': 'bar'})
self.assertEqual(x_conn, res)
mock_profile.assert_called_once_with()
x_profile.set_version.assert_called_once_with('identity', 'v3')
mock_conn.assert_called_once_with(profile=x_profile,
user_agent=sdk.USER_AGENT,
auth_plugin='token',
token='TOKEN',
foo='bar')
@mock.patch.object(profile, 'Profile')
@mock.patch.object(connection, 'Connection')
def test_create_connection_password(self, mock_conn, mock_profile):
x_profile = mock.Mock()
mock_profile.return_value = x_profile
x_conn = mock.Mock()
mock_conn.return_value = x_conn
res = sdk.create_connection({'user_id': '123', 'password': 'abc',
'foo': 'bar'})
self.assertEqual(x_conn, res)
mock_profile.assert_called_once_with()
x_profile.set_version.assert_called_once_with('identity', 'v3')
mock_conn.assert_called_once_with(profile=x_profile,
user_agent=sdk.USER_AGENT,
auth_plugin='password',
user_id='123',
password='abc',
foo='bar')
@mock.patch.object(profile, 'Profile')
@mock.patch.object(connection, 'Connection')
def test_create_connection_with_region(self, mock_conn, mock_profile):
x_profile = mock.Mock()
mock_profile.return_value = x_profile
x_conn = mock.Mock()
mock_conn.return_value = x_conn
res = sdk.create_connection({'region_name': 'REGION_ONE'})
self.assertEqual(x_conn, res)
mock_profile.assert_called_once_with()
x_profile.set_region.assert_called_once_with(x_profile.ALL,
'REGION_ONE')
mock_conn.assert_called_once_with(profile=x_profile,
user_agent=sdk.USER_AGENT,
auth_plugin='password')
@mock.patch.object(profile, 'Profile')
@mock.patch.object(connection, 'Connection')
@mock.patch.object(sdk, 'parse_exception')
def test_create_connection_with_exception(self, mock_parse, mock_conn,
mock_profile):
x_profile = mock.Mock()
mock_profile.return_value = x_profile
ex_raw = Exception('Whatever')
mock_conn.side_effect = ex_raw
mock_parse.side_effect = bilean_exc.InternalError(code=123,
message='BOOM')
ex = self.assertRaises(bilean_exc.InternalError,
sdk.create_connection)
mock_profile.assert_called_once_with()
mock_conn.assert_called_once_with(profile=x_profile,
user_agent=sdk.USER_AGENT,
auth_plugin='password')
mock_parse.assert_called_once_with(ex_raw)
self.assertEqual(123, ex.code)
self.assertEqual('BOOM', ex.message)

View File

@ -16,7 +16,7 @@ bilean.filter_factory = bilean.api.openstack:faultwrap_filter
[filter:context]
paste.filter_factory = bilean.common.wsgi:filter_factory
paste.filter_factory = bilean.common.context:ContextMiddleware_filter_factory
bilean.filter_factory = bilean.api.openstack:contextmiddleware_filter
[filter:ssl]
paste.filter_factory = bilean.common.wsgi:filter_factory

View File

@ -6,7 +6,8 @@ apscheduler>=3.0.1
cryptography>=1.0 # Apache-2.0
eventlet>=0.17.4
jsonpath-rw-ext>=0.1.9
keystonemiddleware>=4.0.0
keystonemiddleware!=4.1.0,>=4.0.0 # Apache-2.0
openstacksdk>=0.7.4 # Apache-2.0
oslo.config>=2.7.0 # Apache-2.0
oslo.context>=0.2.0 # Apache-2.0
oslo.db>=4.1.0 # Apache-2.0

View File

@ -35,15 +35,8 @@ oslo.config.opts =
bilean.engine.scheduler = bilean.engine.scheduler:list_opts
bilean.notification.converter = bilean.notification.converter:list_opts
bilean.clients =
ceilometer = bilean.engine.clients.os.ceilometer:CeilometerClientPlugin
cinder = bilean.engine.clients.os.cinder:CinderClientPlugin
glance = bilean.engine.clients.os.glance:GlanceClientPlugin
keystone = bilean.engine.clients.os.keystone:KeystoneClientPlugin
nova = bilean.engine.clients.os.nova:NovaClientPlugin
neutron = bilean.engine.clients.os.neutron:NeutronClientPlugin
trove = bilean.engine.clients.os.trove:TroveClientPlugin
sahara = bilean.engine.clients.os.sahara:SaharaClientPlugin
bilean.drivers =
openstack = bilean.drivers.openstack
bilean.rules =
os.nova.server = bilean.rules.os.nova.server:ServerRule

View File

@ -35,12 +35,19 @@ if [[ -z $SERVICE_ID ]]; then
exit
fi
openstack endpoint create \
--adminurl "http://$HOST:$PORT/v1" \
--publicurl "http://$HOST:$PORT/v1" \
--internalurl "http://$HOST:$PORT/v1" \
--region RegionOne \
bilean
#openstack endpoint create \
# --adminurl "http://$HOST:$PORT/v1/\$(tenant_id)s" \
# --publicurl "http://$HOST:$PORT/v1/\$(tenant_id)s" \
# --internalurl "http://$HOST:$PORT/v1/\$(tenant_id)s" \
# --region RegionOne \
# bilean
openstack endpoint create bilean admin \
"http://$HOST:$PORT/v1/\$(tenant_id)s" --region RegionOne
openstack endpoint create bilean public \
"http://$HOST:$PORT/v1/\$(tenant_id)s" --region RegionOne
openstack endpoint create bilean internal \
"http://$HOST:$PORT/v1/\$(tenant_id)s" --region RegionOne
openstack user create \
--password "$SVC_PASSWD" \