Skip to content

Commit 0fed79f

Browse files
boris-42Tovin Seven
authored andcommitted
Add profiling support to novaclient
To be able to create profiling traces for Nova, client should be able to send special HTTP header that contains trace info. This patch is also important to be able to make cross project traces. (Typical case heat calls nova via python client, if profiler is initialized in heat, nova client will add extra header, that will be parsed by special osprofiler middleware in nova api.) Security considerations: trace information is signed by one of the HMAC keys that are set in nova.conf. So only person who knows HMAC key is able to send proper header. oslo-spec: https://review.openstack.org/#/c/103825/ Based on: https://review.openstack.org/#/c/105089/ Co-Authored-By: Dina Belova <dbelova@mirantis.com> Co-Authored-By: Roman Podoliaka <rpodolyaka@mirantis.com> Co-Authored-By: Tovin Seven <vinhnt@vn.fujitsu.com> Partially implements: blueprint osprofiler-support-in-nova Depends-On: I82d2badc8c1fcec27c3fce7c3c20e0f3b76414f1 Change-Id: I56ce4b547230e475854994c9d2249ef90e5b656c
1 parent c732a5e commit 0fed79f

File tree

6 files changed

+101
-5
lines changed

6 files changed

+101
-5
lines changed

novaclient/client.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@
3030
from oslo_utils import importutils
3131
import pkg_resources
3232

33+
osprofiler_profiler = importutils.try_import("osprofiler.profiler")
34+
osprofiler_web = importutils.try_import("osprofiler.web")
35+
3336
from novaclient import api_versions
3437
from novaclient import exceptions
3538
from novaclient import extension as ext
@@ -67,6 +70,12 @@ def __init__(self, *args, **kwargs):
6770
def request(self, url, method, **kwargs):
6871
kwargs.setdefault('headers', kwargs.get('headers', {}))
6972
api_versions.update_headers(kwargs["headers"], self.api_version)
73+
74+
# NOTE(dbelova): osprofiler_web.get_trace_id_headers does not add any
75+
# headers in case if osprofiler is not initialized.
76+
if osprofiler_web:
77+
kwargs['headers'].update(osprofiler_web.get_trace_id_headers())
78+
7079
# NOTE(jamielennox): The standard call raises errors from
7180
# keystoneauth1, where we need to raise the novaclient errors.
7281
raise_exc = kwargs.pop('raise_exc', True)
@@ -343,6 +352,17 @@ def Client(version, username=None, password=None, project_id=None,
343352

344353
api_version, client_class = _get_client_class_and_version(version)
345354
kwargs.pop("direct_use", None)
355+
356+
profile = kwargs.pop("profile", None)
357+
if osprofiler_profiler and profile:
358+
# Initialize the root of the future trace: the created trace ID will
359+
# be used as the very first parent to which all related traces will be
360+
# bound to. The given HMAC key must correspond to the one set in
361+
# nova-api nova.conf, otherwise the latter will fail to check the
362+
# request signature and will skip initialization of osprofiler on
363+
# the server side.
364+
osprofiler_profiler.init(profile)
365+
346366
return client_class(api_version=api_version,
347367
auth_url=auth_url,
348368
direct_use=False,

novaclient/shell.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030
from oslo_utils import strutils
3131
import six
3232

33+
osprofiler_profiler = importutils.try_import("osprofiler.profiler")
34+
3335
HAS_KEYRING = False
3436
all_errors = ValueError
3537
try:
@@ -509,6 +511,19 @@ def get_base_parser(self, argv):
509511
dest='endpoint_override',
510512
help=argparse.SUPPRESS)
511513

514+
if osprofiler_profiler:
515+
parser.add_argument('--profile',
516+
metavar='HMAC_KEY',
517+
help='HMAC key to use for encrypting context '
518+
'data for performance profiling of operation. '
519+
'This key should be the value of the HMAC key '
520+
'configured for the OSprofiler middleware in '
521+
'nova; it is specified in the Nova '
522+
'configuration file at "/etc/nova/nova.conf". '
523+
'Without the key, profiling will not be '
524+
'triggered even if OSprofiler is enabled on '
525+
'the server side.')
526+
512527
self._append_global_identity_args(parser, argv)
513528

514529
return parser
@@ -749,6 +764,10 @@ def main(self, argv):
749764
_("You must provide an auth url "
750765
"via either --os-auth-url or env[OS_AUTH_URL]"))
751766

767+
additional_kwargs = {}
768+
if osprofiler_profiler:
769+
additional_kwargs["profile"] = args.profile
770+
752771
# This client is just used to discover api version. Version API needn't
753772
# microversion, so we just pass version 2 at here.
754773
self.cs = client.Client(
@@ -767,7 +786,8 @@ def main(self, argv):
767786
project_domain_id=os_project_domain_id,
768787
project_domain_name=os_project_domain_name,
769788
user_domain_id=os_user_domain_id,
770-
user_domain_name=os_user_domain_name)
789+
user_domain_name=os_user_domain_name,
790+
**additional_kwargs)
771791

772792
if not skip_auth:
773793
if not api_version.is_latest():
@@ -858,6 +878,11 @@ def main(self, argv):
858878

859879
args.func(self.cs, args)
860880

881+
if osprofiler_profiler and args.profile:
882+
trace_id = osprofiler_profiler.get().get_base_id()
883+
print("To display trace use the command:\n\n"
884+
" osprofiler trace show --html %s " % trace_id)
885+
861886
if args.timings:
862887
self._dump_timings(self.times + self.cs.get_timings())
863888

novaclient/tests/unit/fixture_data/client.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@
2424
class V1(fixtures.Fixture):
2525

2626
def __init__(self, requests_mock,
27-
compute_url=COMPUTE_URL, identity_url=IDENTITY_URL):
27+
compute_url=COMPUTE_URL, identity_url=IDENTITY_URL,
28+
**client_kwargs):
2829
super(V1, self).__init__()
2930
self.identity_url = identity_url
3031
self.compute_url = compute_url
@@ -41,6 +42,8 @@ def __init__(self, requests_mock,
4142
s = self.token.add_service('computev3')
4243
s.add_endpoint(self.compute_url)
4344

45+
self._client_kwargs = client_kwargs
46+
4447
def setUp(self):
4548
super(V1, self).setUp()
4649

@@ -52,13 +55,14 @@ def setUp(self):
5255
self.requests_mock.get(self.identity_url,
5356
json=self.discovery,
5457
headers=headers)
55-
self.client = self.new_client()
58+
self.client = self.new_client(**self._client_kwargs)
5659

57-
def new_client(self):
60+
def new_client(self, **client_kwargs):
5861
return client.Client("2", username='xx',
5962
password='xx',
6063
project_id='xx',
61-
auth_url=self.identity_url)
64+
auth_url=self.identity_url,
65+
**client_kwargs)
6266

6367

6468
class SessionV1(V1):

novaclient/tests/unit/test_shell.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -655,6 +655,27 @@ def test_timing(self, m_times, m_requests):
655655
exc = self.assertRaises(RuntimeError, self.shell, '--timings list')
656656
self.assertEqual('Boom!', str(exc))
657657

658+
@requests_mock.Mocker()
659+
def test_osprofiler(self, m_requests):
660+
self.make_env()
661+
662+
def client(*args, **kwargs):
663+
self.assertEqual('swordfish', kwargs['profile'])
664+
with mock.patch('novaclient.client.Client', client):
665+
# we are only interested in the fact Client is initialized properly
666+
self.shell('list --profile swordfish', (0, 2))
667+
668+
@requests_mock.Mocker()
669+
def test_osprofiler_not_installed(self, m_requests):
670+
self.make_env()
671+
672+
# NOTE(rpodolyaka): osprofiler is in test-requirements, so we have to
673+
# simulate its absence here
674+
with mock.patch('novaclient.shell.osprofiler_profiler', None):
675+
_, stderr = self.shell('list --profile swordfish', (0, 2))
676+
self.assertIn('unrecognized arguments: --profile swordfish',
677+
stderr)
678+
658679
@mock.patch('novaclient.shell.SecretsHelper.tenant_id',
659680
return_value=True)
660681
@mock.patch('novaclient.shell.SecretsHelper.auth_token',
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
---
2+
prelude: >
3+
OSprofiler support was added to the client. That makes
4+
possible to trigger Nova operation trace generation from
5+
the CLI.
6+
features:
7+
- A new ``--profile`` option was added to allow Nova
8+
profiling from the CLI. If the user wishes to trace a
9+
nova boot request he or she needs to type the following
10+
command -- ``nova --profile <secret_key> boot --image <image>
11+
--flavor <flavor> <vm_name>``, where ``secret_key`` should match one
12+
of the keys defined in nova.conf. As a result of this operation
13+
additional information regarding ``trace_id`` will be
14+
printed, that can be used to generate human-friendly
15+
html report -- ``osprofiler trace show --html <trace_id> --out
16+
trace.html``.
17+
To enable profiling, user needs to have osprofiler
18+
installed in the local environment via ``pip install osprofiler``.
19+
security:
20+
- OSprofiler support, that was added during the Ocata release cycle,
21+
requires passing of trace information between various
22+
OpenStack services. This information is signed by one of
23+
the HMAC keys defined in nova.conf file. That means that
24+
only someone who knows this key is able to send the proper
25+
header to trigger profiling.

test-requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ requests-mock>=1.1 # Apache-2.0
1515
sphinx!=1.3b1,<1.4,>=1.2.1 # BSD
1616
os-client-config>=1.22.0 # Apache-2.0
1717
oslosphinx>=4.7.0 # Apache-2.0
18+
osprofiler>=1.4.0 # Apache-2.0
1819
testrepository>=0.0.18 # Apache-2.0/BSD
1920
testscenarios>=0.4 # Apache-2.0/BSD
2021
testtools>=1.4.0 # MIT

0 commit comments

Comments
 (0)