Add profiler support into Tempest
The primary goal is to be able to run all Tempest tests and verify OpenStack when profiling is enabled. Also this patch allows to: * manually verify that certain services are properly instrumented and produce trace events when a scenario is executed; * write automatic tests for trace coverage; * profile certain tests from performance perspective. A new parameter is introduced into tempest.conf: * profiler.key - the key used to enable OSProfiler (should match the one configured in OpenStack services) To test the patch on DevStack: 1. Enable osprofiler with Redis collector in local.conf: enable_plugin osprofiler https://git.openstack.org/openstack/osprofiler master OSPROFILER_COLLECTOR=redis 2. Run all Tempest tests or select some, e.g.: tempest run --regex tempest.api.network.test_networks.NetworksTest.test_list_networks* Change-Id: I64f30c36adbf7fb26609142f22d3e305ac9e82b5
This commit is contained in:
parent
4b8a7b8638
commit
1291bb4736
@ -0,0 +1,10 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
Add support of `OSProfiler library`_ for profiling and distributed
|
||||||
|
tracing of OpenStack. A new config option ``key`` in section ``profiler``
|
||||||
|
is added, the option sets the secret key used to enable profiling in
|
||||||
|
OpenStack services. The value needs to correspond to the one specified
|
||||||
|
in [profiler]/hmac_keys option of OpenStack services.
|
||||||
|
|
||||||
|
.. _OSProfiler library: https://docs.openstack.org/osprofiler/
|
@ -1100,6 +1100,18 @@ specify .* as the regex.
|
|||||||
""")
|
""")
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
profiler_group = cfg.OptGroup(name="profiler",
|
||||||
|
title="OpenStack Profiler")
|
||||||
|
|
||||||
|
ProfilerGroup = [
|
||||||
|
cfg.StrOpt('key',
|
||||||
|
help="The secret key to enable OpenStack Profiler. The value "
|
||||||
|
"should match the one configured in OpenStack services "
|
||||||
|
"under `[profiler]/hmac_keys` property. The default empty "
|
||||||
|
"value keeps profiling disabled"),
|
||||||
|
]
|
||||||
|
|
||||||
DefaultGroup = [
|
DefaultGroup = [
|
||||||
cfg.BoolOpt('pause_teardown',
|
cfg.BoolOpt('pause_teardown',
|
||||||
default=False,
|
default=False,
|
||||||
@ -1132,6 +1144,7 @@ _opts = [
|
|||||||
(service_available_group, ServiceAvailableGroup),
|
(service_available_group, ServiceAvailableGroup),
|
||||||
(debug_group, DebugGroup),
|
(debug_group, DebugGroup),
|
||||||
(placement_group, PlacementGroup),
|
(placement_group, PlacementGroup),
|
||||||
|
(profiler_group, ProfilerGroup),
|
||||||
(None, DefaultGroup)
|
(None, DefaultGroup)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
64
tempest/lib/common/profiler.py
Normal file
64
tempest/lib/common/profiler.py
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
# 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 base64
|
||||||
|
import hashlib
|
||||||
|
import hmac
|
||||||
|
import json
|
||||||
|
|
||||||
|
from oslo_utils import encodeutils
|
||||||
|
from oslo_utils import uuidutils
|
||||||
|
|
||||||
|
_profiler = {}
|
||||||
|
|
||||||
|
|
||||||
|
def enable(profiler_key, trace_id=None):
|
||||||
|
"""Enable global profiler instance
|
||||||
|
|
||||||
|
:param profiler_key: the secret key used to enable profiling in services
|
||||||
|
:param trace_id: unique id of the trace, if empty the id is generated
|
||||||
|
automatically
|
||||||
|
"""
|
||||||
|
_profiler['key'] = profiler_key
|
||||||
|
_profiler['uuid'] = trace_id or uuidutils.generate_uuid()
|
||||||
|
|
||||||
|
|
||||||
|
def disable():
|
||||||
|
"""Disable global profiler instance"""
|
||||||
|
_profiler.clear()
|
||||||
|
|
||||||
|
|
||||||
|
def serialize_as_http_headers():
|
||||||
|
"""Serialize profiler state as HTTP headers
|
||||||
|
|
||||||
|
This function corresponds to the one from osprofiler library.
|
||||||
|
:return: dictionary with 2 keys `X-Trace-Info` and `X-Trace-HMAC`.
|
||||||
|
"""
|
||||||
|
p = _profiler
|
||||||
|
if not p: # profiler is not enabled
|
||||||
|
return {}
|
||||||
|
|
||||||
|
info = {'base_id': p['uuid'], 'parent_id': p['uuid']}
|
||||||
|
trace_info = base64.urlsafe_b64encode(
|
||||||
|
encodeutils.to_utf8(json.dumps(info)))
|
||||||
|
trace_hmac = _sign(trace_info, p['key'])
|
||||||
|
|
||||||
|
return {
|
||||||
|
'X-Trace-Info': trace_info,
|
||||||
|
'X-Trace-HMAC': trace_hmac,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _sign(trace_info, key):
|
||||||
|
h = hmac.new(encodeutils.to_utf8(key), digestmod=hashlib.sha1)
|
||||||
|
h.update(trace_info)
|
||||||
|
return h.hexdigest()
|
@ -27,6 +27,7 @@ from six.moves import urllib
|
|||||||
|
|
||||||
from tempest.lib.common import http
|
from tempest.lib.common import http
|
||||||
from tempest.lib.common import jsonschema_validator
|
from tempest.lib.common import jsonschema_validator
|
||||||
|
from tempest.lib.common import profiler
|
||||||
from tempest.lib.common.utils import test_utils
|
from tempest.lib.common.utils import test_utils
|
||||||
from tempest.lib import exceptions
|
from tempest.lib import exceptions
|
||||||
|
|
||||||
@ -131,8 +132,10 @@ class RestClient(object):
|
|||||||
accept_type = 'json'
|
accept_type = 'json'
|
||||||
if send_type is None:
|
if send_type is None:
|
||||||
send_type = 'json'
|
send_type = 'json'
|
||||||
return {'Content-Type': 'application/%s' % send_type,
|
headers = {'Content-Type': 'application/%s' % send_type,
|
||||||
'Accept': 'application/%s' % accept_type}
|
'Accept': 'application/%s' % accept_type}
|
||||||
|
headers.update(profiler.serialize_as_http_headers())
|
||||||
|
return headers
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
STRING_LIMIT = 80
|
STRING_LIMIT = 80
|
||||||
|
@ -28,6 +28,7 @@ from tempest.common import credentials_factory as credentials
|
|||||||
from tempest.common import utils
|
from tempest.common import utils
|
||||||
from tempest import config
|
from tempest import config
|
||||||
from tempest.lib.common import fixed_network
|
from tempest.lib.common import fixed_network
|
||||||
|
from tempest.lib.common import profiler
|
||||||
from tempest.lib.common import validation_resources as vr
|
from tempest.lib.common import validation_resources as vr
|
||||||
from tempest.lib import decorators
|
from tempest.lib import decorators
|
||||||
from tempest.lib import exceptions as lib_exc
|
from tempest.lib import exceptions as lib_exc
|
||||||
@ -231,6 +232,9 @@ class BaseTestCase(testtools.testcase.WithAttributes,
|
|||||||
if CONF.pause_teardown:
|
if CONF.pause_teardown:
|
||||||
BaseTestCase.insert_pdb_breakpoint()
|
BaseTestCase.insert_pdb_breakpoint()
|
||||||
|
|
||||||
|
if CONF.profiler.key:
|
||||||
|
profiler.disable()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def insert_pdb_breakpoint(cls):
|
def insert_pdb_breakpoint(cls):
|
||||||
"""Add pdb breakpoint.
|
"""Add pdb breakpoint.
|
||||||
@ -608,6 +612,8 @@ class BaseTestCase(testtools.testcase.WithAttributes,
|
|||||||
self.useFixture(fixtures.LoggerFixture(nuke_handlers=False,
|
self.useFixture(fixtures.LoggerFixture(nuke_handlers=False,
|
||||||
format=self.log_format,
|
format=self.log_format,
|
||||||
level=None))
|
level=None))
|
||||||
|
if CONF.profiler.key:
|
||||||
|
profiler.enable(CONF.profiler.key)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def credentials_provider(self):
|
def credentials_provider(self):
|
||||||
|
63
tempest/tests/lib/common/test_profiler.py
Normal file
63
tempest/tests/lib/common/test_profiler.py
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
# 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 testtools
|
||||||
|
|
||||||
|
from tempest.lib.common import profiler
|
||||||
|
|
||||||
|
|
||||||
|
class TestProfiler(testtools.TestCase):
|
||||||
|
|
||||||
|
def test_serialize(self):
|
||||||
|
key = 'SECRET_KEY'
|
||||||
|
pm = {'key': key, 'uuid': 'ID'}
|
||||||
|
|
||||||
|
with mock.patch('tempest.lib.common.profiler._profiler', pm):
|
||||||
|
with mock.patch('json.dumps') as jdm:
|
||||||
|
jdm.return_value = '{"base_id": "ID", "parent_id": "ID"}'
|
||||||
|
|
||||||
|
expected = {
|
||||||
|
'X-Trace-HMAC':
|
||||||
|
'887292df9f13b8b5ecd6bbbd2e16bfaaa4d914b0',
|
||||||
|
'X-Trace-Info':
|
||||||
|
b'eyJiYXNlX2lkIjogIklEIiwgInBhcmVudF9pZCI6ICJJRCJ9'
|
||||||
|
}
|
||||||
|
|
||||||
|
self.assertEqual(expected,
|
||||||
|
profiler.serialize_as_http_headers())
|
||||||
|
|
||||||
|
def test_profiler_lifecycle(self):
|
||||||
|
key = 'SECRET_KEY'
|
||||||
|
uuid = 'ID'
|
||||||
|
|
||||||
|
self.assertEqual({}, profiler._profiler)
|
||||||
|
|
||||||
|
profiler.enable(key, uuid)
|
||||||
|
self.assertEqual({'key': key, 'uuid': uuid}, profiler._profiler)
|
||||||
|
|
||||||
|
profiler.disable()
|
||||||
|
self.assertEqual({}, profiler._profiler)
|
||||||
|
|
||||||
|
@mock.patch('oslo_utils.uuidutils.generate_uuid')
|
||||||
|
def test_profiler_lifecycle_generate_trace_id(self, generate_uuid_mock):
|
||||||
|
key = 'SECRET_KEY'
|
||||||
|
uuid = 'ID'
|
||||||
|
generate_uuid_mock.return_value = uuid
|
||||||
|
|
||||||
|
self.assertEqual({}, profiler._profiler)
|
||||||
|
|
||||||
|
profiler.enable(key)
|
||||||
|
self.assertEqual({'key': key, 'uuid': uuid}, profiler._profiler)
|
||||||
|
|
||||||
|
profiler.disable()
|
||||||
|
self.assertEqual({}, profiler._profiler)
|
Loading…
Reference in New Issue
Block a user