Skip to content

Commit 343e45f

Browse files
TimPansinohmstepanek
authored andcommitted
Correct Serverless Distributed Tracing Logic (#870)
* Fix serverless logic for distributed tracing * Test stubs * Collapse testing changes * Add negative testing to regular DT test suite * Apply linter fixes * [Mega-Linter] Apply linters fixes --------- Co-authored-by: TimPansino <[email protected]>
1 parent b7d2c4f commit 343e45f

File tree

3 files changed

+153
-76
lines changed

3 files changed

+153
-76
lines changed

newrelic/api/transaction.py

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1039,7 +1039,9 @@ def _create_distributed_trace_data(self):
10391039

10401040
settings = self._settings
10411041
account_id = settings.account_id
1042-
trusted_account_key = settings.trusted_account_key
1042+
trusted_account_key = settings.trusted_account_key or (
1043+
self._settings.serverless_mode.enabled and self._settings.account_id
1044+
)
10431045
application_id = settings.primary_application_id
10441046

10451047
if not (account_id and application_id and trusted_account_key and settings.distributed_tracing.enabled):
@@ -1130,7 +1132,10 @@ def _can_accept_distributed_trace_headers(self):
11301132
return False
11311133

11321134
settings = self._settings
1133-
if not (settings.distributed_tracing.enabled and settings.trusted_account_key):
1135+
trusted_account_key = settings.trusted_account_key or (
1136+
self._settings.serverless_mode.enabled and self._settings.account_id
1137+
)
1138+
if not (settings.distributed_tracing.enabled and trusted_account_key):
11341139
return False
11351140

11361141
if self._distributed_trace_state:
@@ -1176,10 +1181,13 @@ def _accept_distributed_trace_payload(self, payload, transport_type="HTTP"):
11761181

11771182
settings = self._settings
11781183
account_id = data.get("ac")
1184+
trusted_account_key = settings.trusted_account_key or (
1185+
self._settings.serverless_mode.enabled and self._settings.account_id
1186+
)
11791187

11801188
# If trust key doesn't exist in the payload, use account_id
11811189
received_trust_key = data.get("tk", account_id)
1182-
if settings.trusted_account_key != received_trust_key:
1190+
if trusted_account_key != received_trust_key:
11831191
self._record_supportability("Supportability/DistributedTrace/AcceptPayload/Ignored/UntrustedAccount")
11841192
if settings.debug.log_untrusted_distributed_trace_keys:
11851193
_logger.debug(
@@ -1288,8 +1296,10 @@ def accept_distributed_trace_headers(self, headers, transport_type="HTTP"):
12881296
tracestate = ensure_str(tracestate)
12891297
try:
12901298
vendors = W3CTraceState.decode(tracestate)
1291-
tk = self._settings.trusted_account_key
1292-
payload = vendors.pop(tk + "@nr", "")
1299+
trusted_account_key = self._settings.trusted_account_key or (
1300+
self._settings.serverless_mode.enabled and self._settings.account_id
1301+
)
1302+
payload = vendors.pop(trusted_account_key + "@nr", "")
12931303
self.tracing_vendors = ",".join(vendors.keys())
12941304
self.tracestate = vendors.text(limit=31)
12951305
except:
@@ -1298,7 +1308,7 @@ def accept_distributed_trace_headers(self, headers, transport_type="HTTP"):
12981308
# Remove trusted new relic header if available and parse
12991309
if payload:
13001310
try:
1301-
tracestate_data = NrTraceState.decode(payload, tk)
1311+
tracestate_data = NrTraceState.decode(payload, trusted_account_key)
13021312
except:
13031313
tracestate_data = None
13041314
if tracestate_data:

tests/agent_features/test_distributed_tracing.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030

3131
from newrelic.api.application import application_instance
3232
from newrelic.api.background_task import BackgroundTask, background_task
33+
from newrelic.api.external_trace import ExternalTrace
3334
from newrelic.api.time_trace import current_trace
3435
from newrelic.api.transaction import (
3536
accept_distributed_trace_headers,
@@ -361,3 +362,65 @@ def test_current_span_id_inside_transaction():
361362
def test_current_span_id_outside_transaction():
362363
span_id = current_span_id()
363364
assert span_id is None
365+
366+
367+
@pytest.mark.parametrize("trusted_account_key", ("1", None), ids=("tk_set", "tk_unset"))
368+
def test_outbound_dt_payload_generation(trusted_account_key):
369+
@override_application_settings(
370+
{
371+
"distributed_tracing.enabled": True,
372+
"account_id": "1",
373+
"trusted_account_key": trusted_account_key,
374+
"primary_application_id": "1",
375+
}
376+
)
377+
@background_task(name="test_outbound_dt_payload_generation")
378+
def _test_outbound_dt_payload_generation():
379+
transaction = current_transaction()
380+
payload = ExternalTrace.generate_request_headers(transaction)
381+
if trusted_account_key:
382+
assert payload
383+
# Ensure trusted account key present as vendor
384+
assert dict(payload)["tracestate"].startswith("1@nr=")
385+
else:
386+
assert not payload
387+
388+
_test_outbound_dt_payload_generation()
389+
390+
391+
@pytest.mark.parametrize("trusted_account_key", ("1", None), ids=("tk_set", "tk_unset"))
392+
def test_inbound_dt_payload_acceptance(trusted_account_key):
393+
@override_application_settings(
394+
{
395+
"distributed_tracing.enabled": True,
396+
"account_id": "1",
397+
"trusted_account_key": trusted_account_key,
398+
"primary_application_id": "1",
399+
}
400+
)
401+
@background_task(name="_test_inbound_dt_payload_acceptance")
402+
def _test_inbound_dt_payload_acceptance():
403+
transaction = current_transaction()
404+
405+
payload = {
406+
"v": [0, 1],
407+
"d": {
408+
"ty": "Mobile",
409+
"ac": "1",
410+
"tk": "1",
411+
"ap": "2827902",
412+
"pa": "5e5733a911cfbc73",
413+
"id": "7d3efb1b173fecfa",
414+
"tr": "d6b4ba0c3a712ca",
415+
"ti": 1518469636035,
416+
"tx": "8703ff3d88eefe9d",
417+
},
418+
}
419+
420+
result = transaction.accept_distributed_trace_payload(payload)
421+
if trusted_account_key:
422+
assert result
423+
else:
424+
assert not result
425+
426+
_test_inbound_dt_payload_acceptance()

tests/agent_features/test_serverless_mode.py

Lines changed: 74 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,16 @@
1313
# limitations under the License.
1414

1515
import json
16+
1617
import pytest
18+
from testing_support.fixtures import override_generic_settings
19+
from testing_support.validators.validate_serverless_data import validate_serverless_data
20+
from testing_support.validators.validate_serverless_metadata import (
21+
validate_serverless_metadata,
22+
)
23+
from testing_support.validators.validate_serverless_payload import (
24+
validate_serverless_payload,
25+
)
1726

1827
from newrelic.api.application import application_instance
1928
from newrelic.api.background_task import background_task
@@ -22,23 +31,14 @@
2231
from newrelic.api.transaction import current_transaction
2332
from newrelic.core.config import global_settings
2433

25-
from testing_support.fixtures import override_generic_settings
26-
from testing_support.validators.validate_serverless_data import (
27-
validate_serverless_data)
28-
from testing_support.validators.validate_serverless_payload import (
29-
validate_serverless_payload)
30-
from testing_support.validators.validate_serverless_metadata import (
31-
validate_serverless_metadata)
3234

33-
34-
@pytest.fixture(scope='function')
35+
@pytest.fixture(scope="function")
3536
def serverless_application(request):
3637
settings = global_settings()
3738
orig = settings.serverless_mode.enabled
3839
settings.serverless_mode.enabled = True
3940

40-
application_name = 'Python Agent Test (test_serverless_mode:%s)' % (
41-
request.node.name)
41+
application_name = "Python Agent Test (test_serverless_mode:%s)" % (request.node.name)
4242
application = application_instance(application_name)
4343
application.activate()
4444

@@ -48,17 +48,18 @@ def serverless_application(request):
4848

4949

5050
def test_serverless_payload(capsys, serverless_application):
51-
52-
@override_generic_settings(serverless_application.settings, {
53-
'distributed_tracing.enabled': True,
54-
})
51+
@override_generic_settings(
52+
serverless_application.settings,
53+
{
54+
"distributed_tracing.enabled": True,
55+
},
56+
)
5557
@validate_serverless_data(
56-
expected_methods=('metric_data', 'analytic_event_data'),
57-
forgone_methods=('preconnect', 'connect', 'get_agent_commands'))
58+
expected_methods=("metric_data", "analytic_event_data"),
59+
forgone_methods=("preconnect", "connect", "get_agent_commands"),
60+
)
5861
@validate_serverless_payload()
59-
@background_task(
60-
application=serverless_application,
61-
name='test_serverless_payload')
62+
@background_task(application=serverless_application, name="test_serverless_payload")
6263
def _test():
6364
transaction = current_transaction()
6465
assert transaction.settings.serverless_mode.enabled
@@ -75,17 +76,15 @@ def _test():
7576

7677

7778
def test_no_cat_headers(serverless_application):
78-
@background_task(
79-
application=serverless_application,
80-
name='test_cat_headers')
79+
@background_task(application=serverless_application, name="test_cat_headers")
8180
def _test_cat_headers():
8281
transaction = current_transaction()
8382

8483
payload = ExternalTrace.generate_request_headers(transaction)
8584
assert not payload
8685

87-
trace = ExternalTrace('testlib', 'http://example.com')
88-
response_headers = [('X-NewRelic-App-Data', 'Cookies')]
86+
trace = ExternalTrace("testlib", "http://example.com")
87+
response_headers = [("X-NewRelic-App-Data", "Cookies")]
8988
with trace:
9089
trace.process_response_headers(response_headers)
9190

@@ -94,61 +93,66 @@ def _test_cat_headers():
9493
_test_cat_headers()
9594

9695

97-
def test_dt_outbound(serverless_application):
98-
@override_generic_settings(serverless_application.settings, {
99-
'distributed_tracing.enabled': True,
100-
'account_id': '1',
101-
'trusted_account_key': '1',
102-
'primary_application_id': '1',
103-
})
104-
@background_task(
105-
application=serverless_application,
106-
name='test_dt_outbound')
107-
def _test_dt_outbound():
96+
@pytest.mark.parametrize("trusted_account_key", ("1", None), ids=("tk_set", "tk_unset"))
97+
def test_outbound_dt_payload_generation(serverless_application, trusted_account_key):
98+
@override_generic_settings(
99+
serverless_application.settings,
100+
{
101+
"distributed_tracing.enabled": True,
102+
"account_id": "1",
103+
"trusted_account_key": trusted_account_key,
104+
"primary_application_id": "1",
105+
},
106+
)
107+
@background_task(application=serverless_application, name="test_outbound_dt_payload_generation")
108+
def _test_outbound_dt_payload_generation():
108109
transaction = current_transaction()
109110
payload = ExternalTrace.generate_request_headers(transaction)
110111
assert payload
111-
112-
_test_dt_outbound()
113-
114-
115-
def test_dt_inbound(serverless_application):
116-
@override_generic_settings(serverless_application.settings, {
117-
'distributed_tracing.enabled': True,
118-
'account_id': '1',
119-
'trusted_account_key': '1',
120-
'primary_application_id': '1',
121-
})
122-
@background_task(
123-
application=serverless_application,
124-
name='test_dt_inbound')
125-
def _test_dt_inbound():
112+
# Ensure trusted account key or account ID present as vendor
113+
assert dict(payload)["tracestate"].startswith("1@nr=")
114+
115+
_test_outbound_dt_payload_generation()
116+
117+
118+
@pytest.mark.parametrize("trusted_account_key", ("1", None), ids=("tk_set", "tk_unset"))
119+
def test_inbound_dt_payload_acceptance(serverless_application, trusted_account_key):
120+
@override_generic_settings(
121+
serverless_application.settings,
122+
{
123+
"distributed_tracing.enabled": True,
124+
"account_id": "1",
125+
"trusted_account_key": trusted_account_key,
126+
"primary_application_id": "1",
127+
},
128+
)
129+
@background_task(application=serverless_application, name="test_inbound_dt_payload_acceptance")
130+
def _test_inbound_dt_payload_acceptance():
126131
transaction = current_transaction()
127132

128133
payload = {
129-
'v': [0, 1],
130-
'd': {
131-
'ty': 'Mobile',
132-
'ac': '1',
133-
'tk': '1',
134-
'ap': '2827902',
135-
'pa': '5e5733a911cfbc73',
136-
'id': '7d3efb1b173fecfa',
137-
'tr': 'd6b4ba0c3a712ca',
138-
'ti': 1518469636035,
139-
'tx': '8703ff3d88eefe9d',
140-
}
134+
"v": [0, 1],
135+
"d": {
136+
"ty": "Mobile",
137+
"ac": "1",
138+
"tk": "1",
139+
"ap": "2827902",
140+
"pa": "5e5733a911cfbc73",
141+
"id": "7d3efb1b173fecfa",
142+
"tr": "d6b4ba0c3a712ca",
143+
"ti": 1518469636035,
144+
"tx": "8703ff3d88eefe9d",
145+
},
141146
}
142147

143148
result = transaction.accept_distributed_trace_payload(payload)
144149
assert result
145150

146-
_test_dt_inbound()
151+
_test_inbound_dt_payload_acceptance()
147152

148153

149-
@pytest.mark.parametrize('arn_set', (True, False))
154+
@pytest.mark.parametrize("arn_set", (True, False))
150155
def test_payload_metadata_arn(serverless_application, arn_set):
151-
152156
# If the session object gathers the arn from the settings object before the
153157
# lambda handler records it there, then this test will fail.
154158

@@ -157,17 +161,17 @@ def test_payload_metadata_arn(serverless_application, arn_set):
157161

158162
arn = None
159163
if arn_set:
160-
arn = 'arrrrrrrrrrRrrrrrrrn'
164+
arn = "arrrrrrrrrrRrrrrrrrn"
161165

162-
settings.aws_lambda_metadata.update({'arn': arn, 'function_version': '$LATEST'})
166+
settings.aws_lambda_metadata.update({"arn": arn, "function_version": "$LATEST"})
163167

164168
class Context(object):
165169
invoked_function_arn = arn
166170

167-
@validate_serverless_metadata(exact_metadata={'arn': arn})
171+
@validate_serverless_metadata(exact_metadata={"arn": arn})
168172
@lambda_handler(application=serverless_application)
169173
def handler(event, context):
170-
assert settings.aws_lambda_metadata['arn'] == arn
174+
assert settings.aws_lambda_metadata["arn"] == arn
171175
return {}
172176

173177
try:

0 commit comments

Comments
 (0)