Integrate OSProfiler in Zun
* Add osprofiler wsgi middleware. This middleware is used for 2 things: * Add initialization of osprofiler at start of service Currently sending Ceilometer backend notifications will be implemented after ceilometer integrated. * Traces HTTP/RPC/DB API calls Demo:http://kevinzhao.org/container-create.html How to use it:http://kevinzhao.org/2017/03/14/zun-integrate-with-osprofile.html Change-Id: I28b3e7256235b3b8ad66ce60fd2e6bb51943a7f5 Implements: blueprint integrate-with-osprofiler Signed-off-by: Kevin Zhao <kevin.zhao@linaro.org>
This commit is contained in:
parent
92387ce610
commit
70c8790f34
@ -1,5 +1,5 @@
|
||||
[pipeline:main]
|
||||
pipeline = cors request_id authtoken api_v1
|
||||
pipeline = cors request_id osprofiler authtoken api_v1
|
||||
|
||||
[app:api_v1]
|
||||
paste.app_factory = zun.api.app:app_factory
|
||||
@ -8,6 +8,9 @@ paste.app_factory = zun.api.app:app_factory
|
||||
acl_public_routes = /, /v1
|
||||
paste.filter_factory = zun.api.middleware.auth_token:AuthTokenMiddleware.factory
|
||||
|
||||
[filter:osprofiler]
|
||||
paste.filter_factory = zun.common.profiler:WsgiMiddleware.factory
|
||||
|
||||
[filter:request_id]
|
||||
paste.filter_factory = oslo_middleware:RequestId.factory
|
||||
|
||||
|
@ -70,3 +70,7 @@ zun.image.driver =
|
||||
|
||||
tempest.test_plugins =
|
||||
zun_tests = zun.tests.tempest.plugin:ZunTempestPlugin
|
||||
|
||||
[extras]
|
||||
osprofiler =
|
||||
osprofiler>=1.4.0 # Apache-2.0
|
@ -13,6 +13,7 @@ python-subunit>=0.0.18 # Apache-2.0/BSD
|
||||
sphinx>=1.5.1 # BSD
|
||||
oslosphinx>=4.7.0 # Apache-2.0
|
||||
oslotest>=1.10.0 # Apache-2.0
|
||||
osprofiler>=1.4.0 # Apache-2.0
|
||||
os-testr>=0.8.0 # Apache-2.0
|
||||
PyMySQL>=0.7.6 # MIT License
|
||||
tempest>=14.0.0 # Apache-2.0
|
||||
|
@ -0,0 +1,20 @@
|
||||
# Copyright 2017 Fujitsu Ltd.
|
||||
# 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.
|
||||
|
||||
# NOTE(hieulq): we monkey patch all eventlet services for easier tracking/debug
|
||||
|
||||
import eventlet
|
||||
|
||||
eventlet.monkey_patch()
|
@ -19,6 +19,7 @@
|
||||
|
||||
import sys
|
||||
|
||||
from zun.common import profiler
|
||||
from zun.common import service as zun_service
|
||||
import zun.conf
|
||||
|
||||
@ -33,6 +34,9 @@ def main():
|
||||
# TODO(yuanying): Uncomment after rpc services are implemented
|
||||
# base.zunObject.indirection_api = base.zunObjectIndirectionAPI()
|
||||
|
||||
# Setup OSprofiler for WSGI service
|
||||
profiler.setup('zun-api', CONF.host)
|
||||
|
||||
# Build and start the WSGI app
|
||||
launcher = zun_service.process_launcher()
|
||||
server = zun_service.WSGIService(
|
||||
|
105
zun/common/profiler.py
Normal file
105
zun/common/profiler.py
Normal file
@ -0,0 +1,105 @@
|
||||
# Copyright 2017 Fujitsu Ltd.
|
||||
# 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.
|
||||
|
||||
###
|
||||
# This code is taken from nova. Goal is minimal modification.
|
||||
###
|
||||
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import importutils
|
||||
import webob.dec
|
||||
|
||||
from zun.common import context
|
||||
from zun.common.i18n import _
|
||||
import zun.conf
|
||||
|
||||
profiler = importutils.try_import("osprofiler.profiler")
|
||||
profiler_initializer = importutils.try_import("osprofiler.initializer")
|
||||
profiler_web = importutils.try_import("osprofiler.web")
|
||||
|
||||
|
||||
CONF = zun.conf.CONF
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class WsgiMiddleware(object):
|
||||
|
||||
def __init__(self, application, **kwargs):
|
||||
self.application = application
|
||||
|
||||
@classmethod
|
||||
def factory(cls, global_conf, **local_conf):
|
||||
if profiler_web:
|
||||
return profiler_web.WsgiMiddleware.factory(global_conf,
|
||||
**local_conf)
|
||||
|
||||
def filter_(app):
|
||||
return cls(app, **local_conf)
|
||||
|
||||
return filter_
|
||||
|
||||
@webob.dec.wsgify
|
||||
def __call__(self, request):
|
||||
return request.get_response(self.application)
|
||||
|
||||
|
||||
def setup(binary, host):
|
||||
if CONF.profiler.enabled:
|
||||
profiler_initializer.init_from_conf(
|
||||
conf=CONF,
|
||||
context=context.get_admin_context().to_dict(),
|
||||
project="zun",
|
||||
service=binary,
|
||||
host=host)
|
||||
LOG.info(_("OSprofiler is enabled."))
|
||||
|
||||
|
||||
def trace_cls(name, **kwargs):
|
||||
"""Wrap the OSprofiler trace_cls.
|
||||
|
||||
Wrap the OSprofiler trace_cls decorator so that it will not try to
|
||||
patch the class unless OSprofiler is present.
|
||||
|
||||
:param name: The name of action. For example, wsgi, rpc, db, ...
|
||||
:param kwargs: Any other keyword args used by profiler.trace_cls
|
||||
"""
|
||||
|
||||
def decorator(cls):
|
||||
if profiler and 'profiler' in CONF:
|
||||
trace_decorator = profiler.trace_cls(name, kwargs)
|
||||
return trace_decorator(cls)
|
||||
return cls
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
def trace(name, **kwargs):
|
||||
"""Wrap the OSprofiler trace.
|
||||
|
||||
Wrap the OSprofiler trace decorator so that it will not try to
|
||||
patch the functions unless OSprofiler is present.
|
||||
|
||||
:param name: The name of action. For example, wsgi, rpc, db, ...
|
||||
:param kwargs: Any other keyword args used by profiler.trace
|
||||
"""
|
||||
|
||||
def decorator(f):
|
||||
if profiler and 'profiler' in CONF:
|
||||
trace_decorator = profiler.trace(name, kwargs)
|
||||
return trace_decorator(f)
|
||||
return f
|
||||
|
||||
return decorator
|
@ -28,11 +28,14 @@ __all__ = [
|
||||
|
||||
import oslo_messaging as messaging
|
||||
from oslo_serialization import jsonutils
|
||||
from oslo_utils import importutils
|
||||
|
||||
from zun.common import context as zun_context
|
||||
from zun.common import exception
|
||||
import zun.conf
|
||||
|
||||
profiler = importutils.try_import("osprofiler.profiler")
|
||||
|
||||
CONF = zun.conf.CONF
|
||||
TRANSPORT = None
|
||||
NOTIFIER = None
|
||||
@ -104,22 +107,56 @@ class RequestContextSerializer(messaging.Serializer):
|
||||
return zun_context.RequestContext.from_dict(context)
|
||||
|
||||
|
||||
class ProfilerRequestContextSerializer(RequestContextSerializer):
|
||||
def serialize_context(self, context):
|
||||
_context = super(ProfilerRequestContextSerializer,
|
||||
self).serialize_context(context)
|
||||
|
||||
prof = profiler.get()
|
||||
if prof:
|
||||
trace_info = {
|
||||
"hmac_key": prof.hmac_key,
|
||||
"base_id": prof.get_base_id(),
|
||||
"parent_id": prof.get_id()
|
||||
}
|
||||
_context.update({"trace_info": trace_info})
|
||||
|
||||
return _context
|
||||
|
||||
def deserialize_context(self, context):
|
||||
trace_info = context.pop("trace_info", None)
|
||||
if trace_info:
|
||||
profiler.init(**trace_info)
|
||||
|
||||
return super(ProfilerRequestContextSerializer,
|
||||
self).deserialize_context(context)
|
||||
|
||||
|
||||
def get_transport_url(url_str=None):
|
||||
return messaging.TransportURL.parse(CONF, url_str)
|
||||
|
||||
|
||||
def get_client(target, version_cap=None, serializer=None):
|
||||
def get_client(target, version_cap=None, serializer=None, timeout=None):
|
||||
assert TRANSPORT is not None
|
||||
serializer = RequestContextSerializer(serializer)
|
||||
if profiler:
|
||||
serializer = ProfilerRequestContextSerializer(serializer)
|
||||
else:
|
||||
serializer = RequestContextSerializer(serializer)
|
||||
|
||||
return messaging.RPCClient(TRANSPORT,
|
||||
target,
|
||||
version_cap=version_cap,
|
||||
serializer=serializer)
|
||||
serializer=serializer,
|
||||
timeout=timeout)
|
||||
|
||||
|
||||
def get_server(target, endpoints, serializer=None):
|
||||
assert TRANSPORT is not None
|
||||
serializer = RequestContextSerializer(serializer)
|
||||
if profiler:
|
||||
serializer = ProfilerRequestContextSerializer(serializer)
|
||||
else:
|
||||
serializer = RequestContextSerializer(serializer)
|
||||
|
||||
return messaging.get_rpc_server(TRANSPORT,
|
||||
target,
|
||||
endpoints,
|
||||
|
@ -14,37 +14,43 @@
|
||||
|
||||
"""Common RPC service and API tools for Zun."""
|
||||
|
||||
import eventlet
|
||||
import oslo_messaging as messaging
|
||||
from oslo_service import service
|
||||
from oslo_utils import importutils
|
||||
|
||||
from zun.common import profiler
|
||||
from zun.common import rpc
|
||||
import zun.conf
|
||||
from zun.objects import base as objects_base
|
||||
from zun.servicegroup import zun_service_periodic as servicegroup
|
||||
|
||||
# NOTE(paulczar):
|
||||
# Ubuntu 14.04 forces librabbitmq when kombu is used
|
||||
# Unfortunately it forces a version that has a crash
|
||||
# bug. Calling eventlet.monkey_patch() tells kombu
|
||||
# to use libamqp instead.
|
||||
eventlet.monkey_patch()
|
||||
osprofiler = importutils.try_import("osprofiler.profiler")
|
||||
|
||||
CONF = zun.conf.CONF
|
||||
|
||||
|
||||
def _init_serializer():
|
||||
serializer = rpc.RequestContextSerializer(
|
||||
objects_base.ZunObjectSerializer())
|
||||
if osprofiler:
|
||||
serializer = rpc.ProfilerRequestContextSerializer(serializer)
|
||||
else:
|
||||
serializer = rpc.RequestContextSerializer(serializer)
|
||||
return serializer
|
||||
|
||||
|
||||
class Service(service.Service):
|
||||
|
||||
def __init__(self, topic, server, handlers, binary):
|
||||
super(Service, self).__init__()
|
||||
serializer = rpc.RequestContextSerializer(
|
||||
objects_base.ZunObjectSerializer())
|
||||
serializer = _init_serializer()
|
||||
transport = messaging.get_transport(CONF)
|
||||
# TODO(asalkeld) add support for version='x.y'
|
||||
target = messaging.Target(topic=topic, server=server)
|
||||
self._server = messaging.get_rpc_server(transport, target, handlers,
|
||||
serializer=serializer)
|
||||
self.binary = binary
|
||||
profiler.setup(binary, CONF.host)
|
||||
|
||||
def start(self):
|
||||
servicegroup.setup(CONF, self.binary, self.tg)
|
||||
@ -65,8 +71,7 @@ class Service(service.Service):
|
||||
class API(object):
|
||||
def __init__(self, transport=None, context=None, topic=None, server=None,
|
||||
timeout=None):
|
||||
serializer = rpc.RequestContextSerializer(
|
||||
objects_base.ZunObjectSerializer())
|
||||
serializer = _init_serializer()
|
||||
if transport is None:
|
||||
exmods = rpc.get_allowed_exmods()
|
||||
transport = messaging.get_transport(CONF,
|
||||
|
@ -13,11 +13,13 @@
|
||||
"""Handles all requests relating to compute resources (e.g. containers,
|
||||
networking and storage of containers, and compute hosts on which they run)."""
|
||||
|
||||
from zun.common import profiler
|
||||
from zun.compute import rpcapi
|
||||
from zun.objects import fields
|
||||
from zun.scheduler import client as scheduler_client
|
||||
|
||||
|
||||
@profiler.trace_cls("rpc")
|
||||
class API(object):
|
||||
"""API for interacting with the compute manager."""
|
||||
|
||||
|
@ -13,10 +13,12 @@
|
||||
# under the License.
|
||||
|
||||
|
||||
from zun.common import profiler
|
||||
from zun.common import rpc_service
|
||||
import zun.conf
|
||||
|
||||
|
||||
@profiler.trace_cls("rpc")
|
||||
class API(rpc_service.API):
|
||||
'''Client side of the container compute rpc API.
|
||||
|
||||
|
@ -23,6 +23,7 @@ from zun.conf import glance_client
|
||||
from zun.conf import image_driver
|
||||
from zun.conf import nova_client
|
||||
from zun.conf import path
|
||||
from zun.conf import profiler
|
||||
from zun.conf import scheduler
|
||||
from zun.conf import services
|
||||
from zun.conf import ssl
|
||||
@ -43,3 +44,4 @@ scheduler.register_opts(CONF)
|
||||
services.register_opts(CONF)
|
||||
zun_client.register_opts(CONF)
|
||||
ssl.register_opts(CONF)
|
||||
profiler.register_opts(CONF)
|
||||
|
27
zun/conf/profiler.py
Normal file
27
zun/conf/profiler.py
Normal file
@ -0,0 +1,27 @@
|
||||
# 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
|
||||
|
||||
|
||||
profiler_opts = importutils.try_import('osprofiler.opts')
|
||||
|
||||
|
||||
def register_opts(conf):
|
||||
if profiler_opts:
|
||||
profiler_opts.set_defaults(conf)
|
||||
|
||||
|
||||
def list_opts():
|
||||
return {
|
||||
profiler_opts._profiler_opt_group: profiler_opts._PROFILER_OPTS
|
||||
}
|
@ -19,6 +19,7 @@ from oslo_db import api as db_api
|
||||
|
||||
from zun.common import exception
|
||||
from zun.common.i18n import _
|
||||
from zun.common import profiler
|
||||
import zun.conf
|
||||
|
||||
"""Add the database backend mapping here"""
|
||||
@ -30,6 +31,7 @@ IMPL = db_api.DBAPI.from_config(CONF,
|
||||
lazy=True)
|
||||
|
||||
|
||||
@profiler.trace("db")
|
||||
def _get_dbdriver_instance():
|
||||
"""Return a DB API instance."""
|
||||
if CONF.db_type == 'sql':
|
||||
@ -43,6 +45,7 @@ def _get_dbdriver_instance():
|
||||
"must be sql or etcd") % CONF.db_type)
|
||||
|
||||
|
||||
@profiler.trace("db")
|
||||
def list_containers(context, filters=None, limit=None, marker=None,
|
||||
sort_key=None, sort_dir=None):
|
||||
"""List matching containers.
|
||||
@ -63,6 +66,7 @@ def list_containers(context, filters=None, limit=None, marker=None,
|
||||
context, filters, limit, marker, sort_key, sort_dir)
|
||||
|
||||
|
||||
@profiler.trace("db")
|
||||
def create_container(context, values):
|
||||
"""Create a new container.
|
||||
|
||||
@ -83,6 +87,7 @@ def create_container(context, values):
|
||||
return _get_dbdriver_instance().create_container(context, values)
|
||||
|
||||
|
||||
@profiler.trace("db")
|
||||
def get_container_by_uuid(context, container_uuid):
|
||||
"""Return a container.
|
||||
|
||||
@ -94,6 +99,7 @@ def get_container_by_uuid(context, container_uuid):
|
||||
context, container_uuid)
|
||||
|
||||
|
||||
@profiler.trace("db")
|
||||
def get_container_by_name(context, container_name):
|
||||
"""Return a container.
|
||||
|
||||
@ -105,6 +111,7 @@ def get_container_by_name(context, container_name):
|
||||
context, container_name)
|
||||
|
||||
|
||||
@profiler.trace("db")
|
||||
def destroy_container(context, container_id):
|
||||
"""Destroy a container and all associated interfaces.
|
||||
|
||||
@ -114,6 +121,7 @@ def destroy_container(context, container_id):
|
||||
return _get_dbdriver_instance().destroy_container(context, container_id)
|
||||
|
||||
|
||||
@profiler.trace("db")
|
||||
def update_container(context, container_id, values):
|
||||
"""Update properties of a container.
|
||||
|
||||
@ -127,6 +135,7 @@ def update_container(context, container_id, values):
|
||||
context, container_id, values)
|
||||
|
||||
|
||||
@profiler.trace("db")
|
||||
def destroy_zun_service(host, binary):
|
||||
"""Destroys a zun_service record.
|
||||
|
||||
@ -137,6 +146,7 @@ def destroy_zun_service(host, binary):
|
||||
return _get_dbdriver_instance().destroy_zun_service(host, binary)
|
||||
|
||||
|
||||
@profiler.trace("db")
|
||||
def update_zun_service(host, binary, values):
|
||||
"""Update properties of a zun_service.
|
||||
|
||||
@ -148,6 +158,7 @@ def update_zun_service(host, binary, values):
|
||||
return _get_dbdriver_instance().update_zun_service(host, binary, values)
|
||||
|
||||
|
||||
@profiler.trace("db")
|
||||
def get_zun_service(context, host, binary):
|
||||
"""Return a zun_service record.
|
||||
|
||||
@ -159,6 +170,7 @@ def get_zun_service(context, host, binary):
|
||||
return _get_dbdriver_instance().get_zun_service(host, binary)
|
||||
|
||||
|
||||
@profiler.trace("db")
|
||||
def create_zun_service(values):
|
||||
"""Create a new zun_service record.
|
||||
|
||||
@ -169,6 +181,7 @@ def create_zun_service(values):
|
||||
return _get_dbdriver_instance().create_zun_service(values)
|
||||
|
||||
|
||||
@profiler.trace("db")
|
||||
def list_zun_services(context, filters=None, limit=None,
|
||||
marker=None, sort_key=None, sort_dir=None):
|
||||
"""Get matching zun_service records.
|
||||
@ -190,6 +203,7 @@ def list_zun_services(context, filters=None, limit=None,
|
||||
filters, limit, marker, sort_key, sort_dir)
|
||||
|
||||
|
||||
@profiler.trace("db")
|
||||
def list_zun_services_by_binary(context, binary):
|
||||
"""List matching zun services.
|
||||
|
||||
@ -201,6 +215,7 @@ def list_zun_services_by_binary(context, binary):
|
||||
return _get_dbdriver_instance().list_zun_services_by_binary(binary)
|
||||
|
||||
|
||||
@profiler.trace("db")
|
||||
def pull_image(context, values):
|
||||
"""Create a new image.
|
||||
|
||||
@ -221,6 +236,7 @@ def pull_image(context, values):
|
||||
return _get_dbdriver_instance().pull_image(context, values)
|
||||
|
||||
|
||||
@profiler.trace("db")
|
||||
def update_image(image_id, values):
|
||||
"""Update properties of an image.
|
||||
|
||||
@ -231,6 +247,7 @@ def update_image(image_id, values):
|
||||
return _get_dbdriver_instance().update_image(image_id, values)
|
||||
|
||||
|
||||
@profiler.trace("db")
|
||||
def list_images(context, filters=None,
|
||||
limit=None, marker=None,
|
||||
sort_key=None, sort_dir=None):
|
||||
@ -252,6 +269,7 @@ def list_images(context, filters=None,
|
||||
context, filters, limit, marker, sort_key, sort_dir)
|
||||
|
||||
|
||||
@profiler.trace("db")
|
||||
def get_image_by_id(context, image_id):
|
||||
"""Return an image.
|
||||
|
||||
@ -262,6 +280,7 @@ def get_image_by_id(context, image_id):
|
||||
return _get_dbdriver_instance().get_image_by_id(context, image_id)
|
||||
|
||||
|
||||
@profiler.trace("db")
|
||||
def get_image_by_uuid(context, image_uuid):
|
||||
"""Return an image.
|
||||
|
||||
@ -272,6 +291,7 @@ def get_image_by_uuid(context, image_uuid):
|
||||
return _get_dbdriver_instance().get_image_by_uuid(context, image_uuid)
|
||||
|
||||
|
||||
@profiler.trace("db")
|
||||
def list_resource_providers(context, filters=None, limit=None, marker=None,
|
||||
sort_key=None, sort_dir=None):
|
||||
"""Get matching resource providers.
|
||||
@ -291,6 +311,7 @@ def list_resource_providers(context, filters=None, limit=None, marker=None,
|
||||
context, filters, limit, marker, sort_key, sort_dir)
|
||||
|
||||
|
||||
@profiler.trace("db")
|
||||
def create_resource_provider(context, values):
|
||||
"""Create a new resource provider.
|
||||
|
||||
@ -304,6 +325,7 @@ def create_resource_provider(context, values):
|
||||
return _get_dbdriver_instance().create_resource_provider(context, values)
|
||||
|
||||
|
||||
@profiler.trace("db")
|
||||
def get_resource_provider(context, provider_ident):
|
||||
"""Return a resource provider.
|
||||
|
||||
@ -315,6 +337,7 @@ def get_resource_provider(context, provider_ident):
|
||||
context, provider_ident)
|
||||
|
||||
|
||||
@profiler.trace("db")
|
||||
def destroy_resource_provider(context, provider_id):
|
||||
"""Destroy a resource provider and all associated interfaces.
|
||||
|
||||
@ -325,6 +348,7 @@ def destroy_resource_provider(context, provider_id):
|
||||
context, provider_id)
|
||||
|
||||
|
||||
@profiler.trace("db")
|
||||
def update_resource_provider(context, provider_id, values):
|
||||
"""Update properties of a resource provider.
|
||||
|
||||
@ -338,6 +362,7 @@ def update_resource_provider(context, provider_id, values):
|
||||
context, provider_id, values)
|
||||
|
||||
|
||||
@profiler.trace("db")
|
||||
def list_resource_classes(context, limit=None, marker=None, sort_key=None,
|
||||
sort_dir=None):
|
||||
"""Get matching resource classes.
|
||||
@ -356,6 +381,7 @@ def list_resource_classes(context, limit=None, marker=None, sort_key=None,
|
||||
context, limit, marker, sort_key, sort_dir)
|
||||
|
||||
|
||||
@profiler.trace("db")
|
||||
def create_resource_class(context, values):
|
||||
"""Create a new resource class.
|
||||
|
||||
@ -368,6 +394,7 @@ def create_resource_class(context, values):
|
||||
return _get_dbdriver_instance().create_resource_class(context, values)
|
||||
|
||||
|
||||
@profiler.trace("db")
|
||||
def get_resource_class(context, resource_ident):
|
||||
"""Return a resource class.
|
||||
|
||||
@ -379,6 +406,7 @@ def get_resource_class(context, resource_ident):
|
||||
context, resource_ident)
|
||||
|
||||
|
||||
@profiler.trace("db")
|
||||
def destroy_resource_class(context, resource_uuid):
|
||||
"""Destroy a resource class and all associated interfaces.
|
||||
|
||||
@ -389,6 +417,7 @@ def destroy_resource_class(context, resource_uuid):
|
||||
context, resource_uuid)
|
||||
|
||||
|
||||
@profiler.trace("db")
|
||||
def update_resource_class(context, resource_uuid, values):
|
||||
"""Update properties of a resource class.
|
||||
|
||||
@ -402,6 +431,7 @@ def update_resource_class(context, resource_uuid, values):
|
||||
context, resource_uuid, values)
|
||||
|
||||
|
||||
@profiler.trace("db")
|
||||
def list_inventories(context, filters=None, limit=None, marker=None,
|
||||
sort_key=None, sort_dir=None):
|
||||
"""List matching inventories.
|
||||
@ -422,6 +452,7 @@ def list_inventories(context, filters=None, limit=None, marker=None,
|
||||
context, filters, limit, marker, sort_key, sort_dir)
|
||||
|
||||
|
||||
@profiler.trace("db")
|
||||
def create_inventory(context, provider_id, values):
|
||||
"""Create a new inventory.
|
||||
|
||||
@ -436,6 +467,7 @@ def create_inventory(context, provider_id, values):
|
||||
context, provider_id, values)
|
||||
|
||||
|
||||
@profiler.trace("db")
|
||||
def get_inventory(context, inventory_ident):
|
||||
"""Return a inventory.
|
||||
|
||||
@ -447,6 +479,7 @@ def get_inventory(context, inventory_ident):
|
||||
context, inventory_ident)
|
||||
|
||||
|
||||
@profiler.trace("db")
|
||||
def destroy_inventory(context, inventory_id):
|
||||
"""Destroy an inventory and all associated interfaces.
|
||||
|
||||
@ -456,6 +489,7 @@ def destroy_inventory(context, inventory_id):
|
||||
return _get_dbdriver_instance().destroy_inventory(context, inventory_id)
|
||||
|
||||
|
||||
@profiler.trace("db")
|
||||
def update_inventory(context, inventory_id, values):
|
||||
"""Update properties of an inventory.
|
||||
|
||||
@ -469,6 +503,7 @@ def update_inventory(context, inventory_id, values):
|
||||
context, inventory_id, values)
|
||||
|
||||
|
||||
@profiler.trace("db")
|
||||
def list_allocations(context, filters=None, limit=None, marker=None,
|
||||
sort_key=None, sort_dir=None):
|
||||
"""List matching allocations.
|
||||
@ -489,6 +524,7 @@ def list_allocations(context, filters=None, limit=None, marker=None,
|
||||
context, filters, limit, marker, sort_key, sort_dir)
|
||||
|
||||
|
||||
@profiler.trace("db")
|
||||
def create_allocation(context, values):
|
||||
"""Create a new allocation.
|
||||
|
||||
@ -501,6 +537,7 @@ def create_allocation(context, values):
|
||||
return _get_dbdriver_instance().create_allocation(context, values)
|
||||
|
||||
|
||||
@profiler.trace("db")
|
||||
def get_allocation(context, allocation_id):
|
||||
"""Return an allocation.
|
||||
|
||||
@ -511,6 +548,7 @@ def get_allocation(context, allocation_id):
|
||||
return _get_dbdriver_instance().get_allocation(context, allocation_id)
|
||||
|
||||
|
||||
@profiler.trace("db")
|
||||
def destroy_allocation(context, allocation_id):
|
||||
"""Destroy an allocation and all associated interfaces.
|
||||
|
||||
@ -520,6 +558,7 @@ def destroy_allocation(context, allocation_id):
|
||||
return _get_dbdriver_instance().destroy_allocation(context, allocation_id)
|
||||
|
||||
|
||||
@profiler.trace("db")
|
||||
def update_allocation(context, allocation_id, values):
|
||||
"""Update properties of an allocation.
|
||||
|
||||
@ -533,6 +572,7 @@ def update_allocation(context, allocation_id, values):
|
||||
context, allocation_id, values)
|
||||
|
||||
|
||||
@profiler.trace("db")
|
||||
def list_compute_nodes(context, filters=None, limit=None, marker=None,
|
||||
sort_key=None, sort_dir=None):
|
||||
"""List matching compute nodes.
|
||||
@ -553,6 +593,7 @@ def list_compute_nodes(context, filters=None, limit=None, marker=None,
|
||||
context, filters, limit, marker, sort_key, sort_dir)
|
||||
|
||||
|
||||
@profiler.trace("db")
|
||||
def create_compute_node(context, values):
|
||||
"""Create a new compute node.
|
||||
|
||||
@ -565,6 +606,7 @@ def create_compute_node(context, values):
|
||||
return _get_dbdriver_instance().create_compute_node(context, values)
|
||||
|
||||
|
||||
@profiler.trace("db")
|
||||
def get_compute_node(context, node_uuid):
|
||||
"""Return a compute node.
|
||||
|
||||
@ -575,6 +617,7 @@ def get_compute_node(context, node_uuid):
|
||||
return _get_dbdriver_instance().get_compute_node(context, node_uuid)
|
||||
|
||||
|
||||
@profiler.trace("db")
|
||||
def get_compute_node_by_hostname(context, hostname):
|
||||
"""Return a compute node.
|
||||
|
||||
@ -586,6 +629,7 @@ def get_compute_node_by_hostname(context, hostname):
|
||||
context, hostname)
|
||||
|
||||
|
||||
@profiler.trace("db")
|
||||
def destroy_compute_node(context, node_uuid):
|
||||
"""Destroy a compute node and all associated interfaces.
|
||||
|
||||
@ -595,6 +639,7 @@ def destroy_compute_node(context, node_uuid):
|
||||
return _get_dbdriver_instance().destroy_compute_node(context, node_uuid)
|
||||
|
||||
|
||||
@profiler.trace("db")
|
||||
def update_compute_node(context, node_uuid, values):
|
||||
"""Update properties of a compute node.
|
||||
|
||||
|
@ -17,9 +17,11 @@
|
||||
from oslo_db import exception as db_exc
|
||||
from oslo_db.sqlalchemy import session as db_session
|
||||
from oslo_db.sqlalchemy import utils as db_utils
|
||||
from oslo_utils import importutils
|
||||
from oslo_utils import strutils
|
||||
from oslo_utils import timeutils
|
||||
from oslo_utils import uuidutils
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.orm import contains_eager
|
||||
from sqlalchemy.orm.exc import MultipleResultsFound
|
||||
from sqlalchemy.orm.exc import NoResultFound
|
||||
@ -30,6 +32,8 @@ from zun.common.i18n import _
|
||||
import zun.conf
|
||||
from zun.db.sqlalchemy import models
|
||||
|
||||
profiler_sqlalchemy = importutils.try_import('osprofiler.sqlalchemy')
|
||||
|
||||
CONF = zun.conf.CONF
|
||||
|
||||
_FACADE = None
|
||||
@ -39,6 +43,9 @@ def _create_facade_lazily():
|
||||
global _FACADE
|
||||
if _FACADE is None:
|
||||
_FACADE = db_session.enginefacade.get_legacy_facade()
|
||||
if profiler_sqlalchemy:
|
||||
if CONF.profiler.enabled and CONF.profiler.trace_sqlalchemy:
|
||||
profiler_sqlalchemy.add_tracing(sa, _FACADE.get_engine(), "db")
|
||||
return _FACADE
|
||||
|
||||
|
||||
|
70
zun/tests/unit/common/test_profiler.py
Normal file
70
zun/tests/unit/common/test_profiler.py
Normal file
@ -0,0 +1,70 @@
|
||||
# Copyright 2017 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
|
||||
#
|
||||
# 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 inspect
|
||||
import mock
|
||||
|
||||
from oslo_utils import importutils
|
||||
from osprofiler import initializer as profiler_init
|
||||
from osprofiler import opts as profiler_opts
|
||||
import six.moves as six
|
||||
|
||||
from zun.common import profiler
|
||||
from zun import conf
|
||||
from zun.tests import base
|
||||
|
||||
|
||||
class TestProfiler(base.TestCase):
|
||||
def test_all_public_methods_are_traced(self):
|
||||
profiler_opts.set_defaults(conf.CONF)
|
||||
self.config(enabled=True,
|
||||
group='profiler')
|
||||
|
||||
classes = [
|
||||
'zun.compute.api.API',
|
||||
'zun.compute.rpcapi.API',
|
||||
]
|
||||
for clsname in classes:
|
||||
# give the metaclass and trace_cls() decorator a chance to patch
|
||||
# methods of the classes above
|
||||
six.reload_module(
|
||||
importutils.import_module(clsname.rsplit('.', 1)[0]))
|
||||
cls = importutils.import_class(clsname)
|
||||
|
||||
for attr, obj in cls.__dict__.items():
|
||||
# only public methods are traced
|
||||
if attr.startswith('_'):
|
||||
continue
|
||||
# only checks callables
|
||||
if not (inspect.ismethod(obj) or inspect.isfunction(obj)):
|
||||
continue
|
||||
# osprofiler skips static methods
|
||||
if isinstance(obj, staticmethod):
|
||||
continue
|
||||
|
||||
self.assertTrue(getattr(obj, '__traced__', False), obj)
|
||||
|
||||
@mock.patch.object(profiler_init, 'init_from_conf')
|
||||
def test_setup_profiler(self, mock_init):
|
||||
self.config(enabled=True,
|
||||
group='profiler')
|
||||
|
||||
profiler.setup('foo', 'localhost')
|
||||
|
||||
mock_init.assert_called_once_with(conf=conf.CONF,
|
||||
context=mock.ANY,
|
||||
project="zun",
|
||||
service='foo',
|
||||
host='localhost')
|
228
zun/tests/unit/common/test_rpc.py
Normal file
228
zun/tests/unit/common/test_rpc.py
Normal file
@ -0,0 +1,228 @@
|
||||
# Copyright 2017 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
|
||||
#
|
||||
# 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
|
||||
import oslo_messaging as messaging
|
||||
from oslo_serialization import jsonutils
|
||||
|
||||
from zun.common import context
|
||||
from zun.common import rpc
|
||||
from zun.tests import base
|
||||
|
||||
|
||||
class TestRpc(base.TestCase):
|
||||
@mock.patch.object(rpc, 'profiler', None)
|
||||
@mock.patch.object(rpc, 'RequestContextSerializer')
|
||||
@mock.patch.object(messaging, 'RPCClient')
|
||||
def test_get_client(self, mock_client, mock_ser):
|
||||
rpc.TRANSPORT = mock.Mock()
|
||||
tgt = mock.Mock()
|
||||
ser = mock.Mock()
|
||||
mock_client.return_value = 'client'
|
||||
mock_ser.return_value = ser
|
||||
|
||||
client = rpc.get_client(tgt, version_cap='1.0', serializer='foo',
|
||||
timeout=7139)
|
||||
|
||||
mock_ser.assert_called_once_with('foo')
|
||||
mock_client.assert_called_once_with(rpc.TRANSPORT,
|
||||
tgt, version_cap='1.0',
|
||||
serializer=ser, timeout=7139)
|
||||
self.assertEqual('client', client)
|
||||
|
||||
@mock.patch.object(rpc, 'profiler', mock.Mock())
|
||||
@mock.patch.object(rpc, 'ProfilerRequestContextSerializer')
|
||||
@mock.patch.object(messaging, 'RPCClient')
|
||||
def test_get_client_profiler_enabled(self, mock_client, mock_ser):
|
||||
rpc.TRANSPORT = mock.Mock()
|
||||
tgt = mock.Mock()
|
||||
ser = mock.Mock()
|
||||
mock_client.return_value = 'client'
|
||||
mock_ser.return_value = ser
|
||||
|
||||
client = rpc.get_client(tgt, version_cap='1.0', serializer='foo',
|
||||
timeout=6969)
|
||||
|
||||
mock_ser.assert_called_once_with('foo')
|
||||
mock_client.assert_called_once_with(rpc.TRANSPORT,
|
||||
tgt, version_cap='1.0',
|
||||
serializer=ser, timeout=6969)
|
||||
self.assertEqual('client', client)
|
||||
|
||||
@mock.patch.object(rpc, 'profiler', None)
|
||||
@mock.patch.object(rpc, 'RequestContextSerializer')
|
||||
@mock.patch.object(messaging, 'get_rpc_server')
|
||||
def test_get_server(self, mock_get, mock_ser):
|
||||
rpc.TRANSPORT = mock.Mock()
|
||||
ser = mock.Mock()
|
||||
tgt = mock.Mock()
|
||||
ends = mock.Mock()
|
||||
mock_ser.return_value = ser
|
||||
mock_get.return_value = 'server'
|
||||
|
||||
server = rpc.get_server(tgt, ends, serializer='foo')
|
||||
|
||||
mock_ser.assert_called_once_with('foo')
|
||||
mock_get.assert_called_once_with(rpc.TRANSPORT, tgt, ends,
|
||||
executor='eventlet', serializer=ser)
|
||||
self.assertEqual('server', server)
|
||||
|
||||
@mock.patch.object(rpc, 'profiler', mock.Mock())
|
||||
@mock.patch.object(rpc, 'ProfilerRequestContextSerializer')
|
||||
@mock.patch.object(messaging, 'get_rpc_server')
|
||||
def test_get_server_profiler_enabled(self, mock_get, mock_ser):
|
||||
rpc.TRANSPORT = mock.Mock()
|
||||
ser = mock.Mock()
|
||||
tgt = mock.Mock()
|
||||
ends = mock.Mock()
|
||||
mock_ser.return_value = ser
|
||||
mock_get.return_value = 'server'
|
||||
|
||||
server = rpc.get_server(tgt, ends, serializer='foo')
|
||||
|
||||
mock_ser.assert_called_once_with('foo')
|
||||
mock_get.assert_called_once_with(rpc.TRANSPORT, tgt, ends,
|
||||
executor='eventlet', serializer=ser)
|
||||
self.assertEqual('server', server)
|
||||
|
||||
def test_cleanup_transport_null(self):
|
||||
rpc.TRANSPORT = None
|
||||
rpc.NOTIFIER = mock.Mock()
|
||||
self.assertRaises(AssertionError, rpc.cleanup)
|
||||
|
||||
def test_cleanup_notifier_null(self):
|
||||
rpc.TRANSPORT = mock.Mock()
|
||||
rpc.NOTIFIER = None
|
||||
self.assertRaises(AssertionError, rpc.cleanup)
|
||||
|
||||
def test_cleanup(self):
|
||||
rpc.NOTIFIER = mock.Mock()
|
||||
rpc.TRANSPORT = mock.Mock()
|
||||
trans_cleanup = mock.Mock()
|
||||
rpc.TRANSPORT.cleanup = trans_cleanup
|
||||
|
||||
rpc.cleanup()
|
||||
|
||||
trans_cleanup.assert_called_once_with()
|
||||
self.assertIsNone(rpc.TRANSPORT)
|
||||
self.assertIsNone(rpc.NOTIFIER)
|
||||
|
||||
def test_add_extra_exmods(self):
|
||||
rpc.EXTRA_EXMODS = []
|
||||
|
||||
rpc.add_extra_exmods('foo', 'bar')
|
||||
|
||||
self.assertEqual(['foo', 'bar'], rpc.EXTRA_EXMODS)
|
||||
|
||||
def test_clear_extra_exmods(self):
|
||||
rpc.EXTRA_EXMODS = ['foo', 'bar']
|
||||
|
||||
rpc.clear_extra_exmods()
|
||||
|
||||
self.assertEqual(0, len(rpc.EXTRA_EXMODS))
|
||||
|
||||
def test_serialize_entity(self):
|
||||
with mock.patch.object(jsonutils, 'to_primitive') as mock_prim:
|
||||
rpc.JsonPayloadSerializer.serialize_entity('context', 'entity')
|
||||
|
||||
mock_prim.assert_called_once_with('entity', convert_instances=True)
|
||||
|
||||
|
||||
class TestRequestContextSerializer(base.TestCase):
|
||||
def setUp(self):
|
||||
super(TestRequestContextSerializer, self).setUp()
|
||||
self.mock_base = mock.Mock()
|
||||
self.ser = rpc.RequestContextSerializer(self.mock_base)
|
||||
self.ser_null = rpc.RequestContextSerializer(None)
|
||||
|
||||
def test_serialize_entity(self):
|
||||
self.mock_base.serialize_entity.return_value = 'foo'
|
||||
|
||||
ser_ent = self.ser.serialize_entity('context', 'entity')
|
||||
|
||||
self.mock_base.serialize_entity.assert_called_once_with('context',
|
||||
'entity')
|
||||
self.assertEqual('foo', ser_ent)
|
||||
|
||||
def test_serialize_entity_null_base(self):
|
||||
ser_ent = self.ser_null.serialize_entity('context', 'entity')
|
||||
|
||||
self.assertEqual('entity', ser_ent)
|
||||
|
||||
def test_deserialize_entity(self):
|
||||
self.mock_base.deserialize_entity.return_value = 'foo'
|
||||
|
||||
deser_ent = self.ser.deserialize_entity('context', 'entity')
|
||||
|
||||
self.mock_base.deserialize_entity.assert_called_once_with('context',
|
||||
'entity')
|
||||
self.assertEqual('foo', deser_ent)
|
||||
|
||||
def test_deserialize_entity_null_base(self):
|
||||
deser_ent = self.ser_null.deserialize_entity('context', 'entity')
|
||||
|
||||
self.assertEqual('entity', deser_ent)
|
||||
|
||||
def test_serialize_context(self):
|
||||
context = mock.Mock()
|
||||
|
||||
self.ser.serialize_context(context)
|
||||
|
||||
context.to_dict.assert_called_once_with()
|
||||
|
||||
@mock.patch.object(context, 'RequestContext')
|
||||
def test_deserialize_context(self, mock_req):
|
||||
self.ser.deserialize_context('context')
|
||||
|
||||
mock_req.from_dict.assert_called_once_with('context')
|
||||
|
||||
|
||||
class TestProfilerRequestContextSerializer(base.TestCase):
|
||||
def setUp(self):
|
||||
super(TestProfilerRequestContextSerializer, self).setUp()
|
||||
self.ser = rpc.ProfilerRequestContextSerializer(mock.Mock())
|
||||
|
||||
@mock.patch('zun.common.rpc.profiler')
|
||||
def test_serialize_context(self, mock_profiler):
|
||||
prof = mock_profiler.get.return_value
|
||||
prof.hmac_key = 'swordfish'
|
||||
prof.get_base_id.return_value = 'baseid'
|
||||
prof.get_id.return_value = 'parentid'
|
||||
|
||||
context = mock.Mock()
|
||||
context.to_dict.return_value = {'project_id': 'test'}
|
||||
|
||||
self.assertEqual({
|
||||
'project_id': 'test',
|
||||
'trace_info': {
|
||||
'hmac_key': 'swordfish',
|
||||
'base_id': 'baseid',
|
||||
'parent_id': 'parentid'
|
||||
}
|
||||
}, self.ser.serialize_context(context))
|
||||
|
||||
@mock.patch('zun.common.rpc.profiler')
|
||||
def test_deserialize_context(self, mock_profiler):
|
||||
serialized = {'project_id': 'test',
|
||||
'trace_info': {
|
||||
'hmac_key': 'swordfish',
|
||||
'base_id': 'baseid',
|
||||
'parent_id': 'parentid'}}
|
||||
|
||||
context = self.ser.deserialize_context(serialized)
|
||||
|
||||
self.assertEqual('test', context.project_id)
|
||||
mock_profiler.init.assert_called_once_with(
|
||||
hmac_key='swordfish', base_id='baseid', parent_id='parentid')
|
Loading…
x
Reference in New Issue
Block a user