From 9f6c3686577101c953a078c9dee9f7b97ae6add8 Mon Sep 17 00:00:00 2001 From: Varsha GS Date: Thu, 25 Jan 2024 22:50:37 +0530 Subject: [PATCH 01/12] boto3_s3: capture req and res HTTPHeaders Signed-off-by: Varsha GS --- instana/instrumentation/boto3_inst.py | 29 +++++- tests/clients/boto3/test_boto3_s3.py | 126 +++++++++++++++++++++++++- 2 files changed, 152 insertions(+), 3 deletions(-) diff --git a/instana/instrumentation/boto3_inst.py b/instana/instrumentation/boto3_inst.py index cf6511b5..2786e869 100644 --- a/instana/instrumentation/boto3_inst.py +++ b/instana/instrumentation/boto3_inst.py @@ -8,7 +8,7 @@ import inspect from ..log import logger -from ..singletons import tracer +from ..singletons import tracer, agent from ..util.traceutils import get_active_tracer try: @@ -16,6 +16,17 @@ import boto3 from boto3.s3 import inject + def extract_custom_headers(span, headers): + if agent.options.extra_http_headers is None: + return + try: + for custom_header in agent.options.extra_http_headers: + if custom_header in headers: + span.set_tag("http.header.%s" % custom_header, headers[custom_header]) + + except Exception: + logger.debug("extract_custom_headers: ", exc_info=True) + def lambda_inject_context(payload, scope): """ @@ -35,6 +46,20 @@ def lambda_inject_context(payload, scope): logger.debug("non-fatal lambda_inject_context: ", exc_info=True) + @wrapt.patch_function_wrapper("botocore.hooks", "HierarchicalEmitter.emit_until_response") + def emit_until_response_with_instana(wrapped, instance, args, kwargs): + active_tracer = get_active_tracer() + + # If we're not tracing or the event emitted is not before-call, just return; + if active_tracer is None or args[0].split(".")[0] != "before-call": + return wrapped(*args, **kwargs) + + span = active_tracer.active_span + if "custom_request_headers" in kwargs["context"]: + extract_custom_headers(span, kwargs["context"]["custom_request_headers"]) + + return wrapped(*args, **kwargs) + @wrapt.patch_function_wrapper('botocore.client', 'BaseClient._make_api_call') def make_api_call_with_instana(wrapped, instance, arg_list, kwargs): # pylint: disable=protected-access @@ -76,6 +101,8 @@ def make_api_call_with_instana(wrapped, instance, arg_list, kwargs): status = http_dict.get('HTTPStatusCode') if status is not None: scope.span.set_tag('http.status_code', status) + headers = http_dict.get('HTTPHeaders') + extract_custom_headers(scope.span, headers) return result except Exception as exc: diff --git a/tests/clients/boto3/test_boto3_s3.py b/tests/clients/boto3/test_boto3_s3.py index 8ba64f89..942378b3 100644 --- a/tests/clients/boto3/test_boto3_s3.py +++ b/tests/clients/boto3/test_boto3_s3.py @@ -14,7 +14,7 @@ else: from moto import mock_s3 as mock_aws -from instana.singletons import tracer +from instana.singletons import tracer, agent from ...helpers import get_first_span_by_filter pwd = os.path.dirname(os.path.abspath(__file__)) @@ -95,7 +95,7 @@ def test_s3_list_buckets(s3): result = s3.list_buckets() assert len(result['Buckets']) == 0 - assert result['ResponseMetadata']['HTTPStatusCode'] is 200 + assert result['ResponseMetadata']['HTTPStatusCode'] == 200 spans = tracer.recorder.queued_spans() assert len(spans) == 2 @@ -276,3 +276,125 @@ def test_s3_download_file_obj(s3): assert boto_span.data['boto3']['reg'] == 'us-east-1' assert boto_span.data['http']['method'] == 'POST' assert boto_span.data['http']['url'] == 'https://s3.amazonaws.com:443/download_fileobj' + + +def test_request_header_capture(s3): + + original_extra_http_headers = agent.options.extra_http_headers + agent.options.extra_http_headers = ['X-Capture-This', 'X-Capture-That'] + + # Access the event system on the S3 client + event_system = s3.meta.events + + request_headers = { + 'X-Capture-This': 'this', + 'X-Capture-That': 'that' + } + + # We set the custom headers in the request context instead of params + # because later in the processing of the request, there is a parameter validation step, + # which doesn't allow for custom arguments. + def process_custom_arguments(params, context, **kwargs): + if "custom_request_headers" not in context: + context["custom_request_headers"] = request_headers + + event_system.register('before-parameter-build', process_custom_arguments) + + with tracer.start_active_span('test'): + result = s3.create_bucket(Bucket="aws_bucket_name") + + result = s3.list_buckets() + assert len(result['Buckets']) == 1 + assert result['Buckets'][0]['Name'] == 'aws_bucket_name' + + spans = tracer.recorder.queued_spans() + assert len(spans) == 2 + + filter = lambda span: span.n == "sdk" + test_span = get_first_span_by_filter(spans, filter) + assert (test_span) + + filter = lambda span: span.n == "boto3" + boto_span = get_first_span_by_filter(spans, filter) + assert (boto_span) + + assert (boto_span.t == test_span.t) + assert (boto_span.p == test_span.s) + + assert (test_span.ec is None) + assert (boto_span.ec is None) + + assert boto_span.data['boto3']['op'] == 'CreateBucket' + assert boto_span.data['boto3']['ep'] == 'https://s3.amazonaws.com' + assert boto_span.data['boto3']['reg'] == 'us-east-1' + assert boto_span.data['boto3']['payload'] == {'Bucket': 'aws_bucket_name'} + assert boto_span.data['http']['status'] == 200 + assert boto_span.data['http']['method'] == 'POST' + assert boto_span.data['http']['url'] == 'https://s3.amazonaws.com:443/CreateBucket' + + assert ("X-Capture-This" in boto_span.data["http"]["header"]) + assert ("this" == boto_span.data["http"]["header"]["X-Capture-This"]) + assert ("X-Capture-That" in boto_span.data["http"]["header"]) + assert ("that" == boto_span.data["http"]["header"]["X-Capture-That"]) + + agent.options.extra_http_headers = original_extra_http_headers + + +def test_response_header_capture(s3): + + original_extra_http_headers = agent.options.extra_http_headers + agent.options.extra_http_headers = ['X-Capture-This-Too', 'X-Capture-That-Too'] + + # Access the event system on the S3 client + event_system = s3.meta.events + + response_headers = { + "X-Capture-This-Too": "this too", + "X-Capture-That-Too": "that too", + } + + # Create a function that sets the custom headers in the after-call event. + def modify_after_call_args(parsed, **kwargs): + parsed['ResponseMetadata']['HTTPHeaders'].update(response_headers) + + # Register the function to an event + event_system.register('after-call', modify_after_call_args) + + with tracer.start_active_span('test'): + result = s3.create_bucket(Bucket="aws_bucket_name") + + result = s3.list_buckets() + assert len(result['Buckets']) == 1 + assert result['Buckets'][0]['Name'] == 'aws_bucket_name' + + spans = tracer.recorder.queued_spans() + assert len(spans) == 2 + + filter = lambda span: span.n == "sdk" + test_span = get_first_span_by_filter(spans, filter) + assert (test_span) + + filter = lambda span: span.n == "boto3" + boto_span = get_first_span_by_filter(spans, filter) + assert (boto_span) + + assert (boto_span.t == test_span.t) + assert (boto_span.p == test_span.s) + + assert (test_span.ec is None) + assert (boto_span.ec is None) + + assert boto_span.data['boto3']['op'] == 'CreateBucket' + assert boto_span.data['boto3']['ep'] == 'https://s3.amazonaws.com' + assert boto_span.data['boto3']['reg'] == 'us-east-1' + assert boto_span.data['boto3']['payload'] == {'Bucket': 'aws_bucket_name'} + assert boto_span.data['http']['status'] == 200 + assert boto_span.data['http']['method'] == 'POST' + assert boto_span.data['http']['url'] == 'https://s3.amazonaws.com:443/CreateBucket' + + assert ("X-Capture-This-Too" in boto_span.data["http"]["header"]) + assert ("this too" == boto_span.data["http"]["header"]["X-Capture-This-Too"]) + assert ("X-Capture-That-Too" in boto_span.data["http"]["header"]) + assert ("that too" == boto_span.data["http"]["header"]["X-Capture-That-Too"]) + + agent.options.extra_http_headers = original_extra_http_headers From 6c78140c13fdb091ae473e03997bed49adc0ae66 Mon Sep 17 00:00:00 2001 From: Varsha GS Date: Mon, 29 Jan 2024 13:37:16 +0530 Subject: [PATCH 02/12] refactor(test_boto3_s3): use unittest.TestCase class and respective assert statements Signed-off-by: Varsha GS --- tests/clients/boto3/test_boto3_s3.py | 593 +++++++++++++-------------- 1 file changed, 293 insertions(+), 300 deletions(-) diff --git a/tests/clients/boto3/test_boto3_s3.py b/tests/clients/boto3/test_boto3_s3.py index 942378b3..27465d92 100644 --- a/tests/clients/boto3/test_boto3_s3.py +++ b/tests/clients/boto3/test_boto3_s3.py @@ -2,10 +2,8 @@ # (c) Copyright Instana Inc. 2020 from __future__ import absolute_import - import os -import boto3 -import pytest +import unittest # TODO: Remove branching when we drop support for Python 3.7 import sys @@ -13,6 +11,7 @@ from moto import mock_aws else: from moto import mock_s3 as mock_aws +import boto3 from instana.singletons import tracer, agent from ...helpers import get_first_span_by_filter @@ -22,379 +21,373 @@ download_target_filename = os.path.abspath(pwd + '/../../data/boto3/download_target_file.asdf') -def setup_method(): - """ Clear all spans before a test run """ - tracer.recorder.clear_spans() - os.remove(download_target_filename) +class TestS3(unittest.TestCase): + def aws_credentials(self): + """Mocked AWS Credentials for moto.""" + os.environ['AWS_ACCESS_KEY_ID'] = 'testing' + os.environ['AWS_SECRET_ACCESS_KEY'] = 'testing' + os.environ['AWS_SECURITY_TOKEN'] = 'testing' + os.environ['AWS_SESSION_TOKEN'] = 'testing' + def setUp(self): + """ Clear all spans before a test run """ + self.recorder = tracer.recorder + self.recorder.clear_spans() + self.aws_credentials() + self.mock = mock_aws() + self.mock.start() + self.s3 = boto3.client('s3', region_name='us-east-1') -@pytest.fixture(scope='function') -def aws_credentials(): - """Mocked AWS Credentials for moto.""" - os.environ['AWS_ACCESS_KEY_ID'] = 'testing' - os.environ['AWS_SECRET_ACCESS_KEY'] = 'testing' - os.environ['AWS_SECURITY_TOKEN'] = 'testing' - os.environ['AWS_SESSION_TOKEN'] = 'testing' + def tearDown(self): + # Stop Moto after each test + self.mock.stop() -@pytest.fixture(scope='function') -def s3(aws_credentials): - with mock_aws(): - yield boto3.client('s3', region_name='us-east-1') + def test_vanilla_create_bucket(self): + self.s3.create_bucket(Bucket="aws_bucket_name") -def test_vanilla_create_bucket(s3): - # s3 is a fixture defined above that yields a boto3 s3 client. - # Feel free to instantiate another boto3 S3 client -- Keep note of the region though. - s3.create_bucket(Bucket="aws_bucket_name") + result = self.s3.list_buckets() + self.assertEqual(1, len(result['Buckets'])) + self.assertEqual(result['Buckets'][0]['Name'], 'aws_bucket_name') - result = s3.list_buckets() - assert len(result['Buckets']) == 1 - assert result['Buckets'][0]['Name'] == 'aws_bucket_name' + def test_s3_create_bucket(self): + with tracer.start_active_span('test'): + self.s3.create_bucket(Bucket="aws_bucket_name") -def test_s3_create_bucket(s3): - result = None - with tracer.start_active_span('test'): - result = s3.create_bucket(Bucket="aws_bucket_name") + result = self.s3.list_buckets() + self.assertEqual(1, len(result['Buckets'])) + self.assertEqual(result['Buckets'][0]['Name'], 'aws_bucket_name') - result = s3.list_buckets() - assert len(result['Buckets']) == 1 - assert result['Buckets'][0]['Name'] == 'aws_bucket_name' + spans = tracer.recorder.queued_spans() + self.assertEqual(2, len(spans)) - spans = tracer.recorder.queued_spans() - assert len(spans) == 2 + filter = lambda span: span.n == "sdk" + test_span = get_first_span_by_filter(spans, filter) + self.assertTrue(test_span) - filter = lambda span: span.n == "sdk" - test_span = get_first_span_by_filter(spans, filter) - assert (test_span) + filter = lambda span: span.n == "boto3" + boto_span = get_first_span_by_filter(spans, filter) + self.assertTrue(boto_span) - filter = lambda span: span.n == "boto3" - boto_span = get_first_span_by_filter(spans, filter) - assert (boto_span) + self.assertEqual(boto_span.t, test_span.t) + self.assertEqual(boto_span.p, test_span.s) - assert (boto_span.t == test_span.t) - assert (boto_span.p == test_span.s) + self.assertIsNone(test_span.ec) + self.assertIsNone(boto_span.ec) - assert (test_span.ec is None) - assert (boto_span.ec is None) + self.assertEqual(boto_span.data['boto3']['op'], 'CreateBucket') + self.assertEqual(boto_span.data['boto3']['ep'], 'https://s3.amazonaws.com') + self.assertEqual(boto_span.data['boto3']['reg'], 'us-east-1') + self.assertDictEqual(boto_span.data['boto3']['payload'], {'Bucket': 'aws_bucket_name'}) + self.assertEqual(boto_span.data['http']['status'], 200) + self.assertEqual(boto_span.data['http']['method'], 'POST') + self.assertEqual(boto_span.data['http']['url'], 'https://s3.amazonaws.com:443/CreateBucket') - assert boto_span.data['boto3']['op'] == 'CreateBucket' - assert boto_span.data['boto3']['ep'] == 'https://s3.amazonaws.com' - assert boto_span.data['boto3']['reg'] == 'us-east-1' - assert boto_span.data['boto3']['payload'] == {'Bucket': 'aws_bucket_name'} - assert boto_span.data['http']['status'] == 200 - assert boto_span.data['http']['method'] == 'POST' - assert boto_span.data['http']['url'] == 'https://s3.amazonaws.com:443/CreateBucket' + def test_s3_list_buckets(self): + with tracer.start_active_span('test'): + self.s3.list_buckets() -def test_s3_list_buckets(s3): - result = None - with tracer.start_active_span('test'): - result = s3.list_buckets() + result = self.s3.list_buckets() + self.assertEqual(0, len(result['Buckets'])) + self.assertEqual(result['ResponseMetadata']['HTTPStatusCode'], 200) - result = s3.list_buckets() - assert len(result['Buckets']) == 0 - assert result['ResponseMetadata']['HTTPStatusCode'] == 200 + spans = tracer.recorder.queued_spans() + self.assertEqual(2, len(spans)) - spans = tracer.recorder.queued_spans() - assert len(spans) == 2 + filter = lambda span: span.n == "sdk" + test_span = get_first_span_by_filter(spans, filter) + self.assertTrue(test_span) - filter = lambda span: span.n == "sdk" - test_span = get_first_span_by_filter(spans, filter) - assert (test_span) + filter = lambda span: span.n == "boto3" + boto_span = get_first_span_by_filter(spans, filter) + self.assertTrue(boto_span) - filter = lambda span: span.n == "boto3" - boto_span = get_first_span_by_filter(spans, filter) - assert (boto_span) + self.assertEqual(boto_span.t, test_span.t) + self.assertEqual(boto_span.p, test_span.s) - assert (boto_span.t == test_span.t) - assert (boto_span.p == test_span.s) + self.assertIsNone(test_span.ec) + self.assertIsNone(boto_span.ec) - assert (test_span.ec is None) - assert (boto_span.ec is None) + self.assertEqual(boto_span.data['boto3']['op'], 'ListBuckets') + self.assertEqual(boto_span.data['boto3']['ep'], 'https://s3.amazonaws.com') + self.assertEqual(boto_span.data['boto3']['reg'], 'us-east-1') + self.assertDictEqual(boto_span.data['boto3']['payload'], {}) + self.assertEqual(boto_span.data['http']['status'], 200) + self.assertEqual(boto_span.data['http']['method'], 'POST') + self.assertEqual(boto_span.data['http']['url'], 'https://s3.amazonaws.com:443/ListBuckets') - assert boto_span.data['boto3']['op'] == 'ListBuckets' - assert boto_span.data['boto3']['ep'] == 'https://s3.amazonaws.com' - assert boto_span.data['boto3']['reg'] == 'us-east-1' - assert boto_span.data['boto3']['payload'] == {} - assert boto_span.data['http']['status'] == 200 - assert boto_span.data['http']['method'] == 'POST' - assert boto_span.data['http']['url'] == 'https://s3.amazonaws.com:443/ListBuckets' + def test_s3_vanilla_upload_file(self): + object_name = 'aws_key_name' + bucket_name = 'aws_bucket_name' -def test_s3_vanilla_upload_file(s3): - object_name = 'aws_key_name' - bucket_name = 'aws_bucket_name' + self.s3.create_bucket(Bucket=bucket_name) + result = self.s3.upload_file(upload_filename, bucket_name, object_name) + self.assertIsNone(result) - s3.create_bucket(Bucket=bucket_name) - result = s3.upload_file(upload_filename, bucket_name, object_name) - assert result is None + def test_s3_upload_file(self): + object_name = 'aws_key_name' + bucket_name = 'aws_bucket_name' -def test_s3_upload_file(s3): - object_name = 'aws_key_name' - bucket_name = 'aws_bucket_name' + self.s3.create_bucket(Bucket=bucket_name) - s3.create_bucket(Bucket=bucket_name) + with tracer.start_active_span('test'): + self.s3.upload_file(upload_filename, bucket_name, object_name) - result = None - with tracer.start_active_span('test'): - s3.upload_file(upload_filename, bucket_name, object_name) + spans = tracer.recorder.queued_spans() + self.assertEqual(2, len(spans)) - spans = tracer.recorder.queued_spans() - assert len(spans) == 2 + filter = lambda span: span.n == "sdk" + test_span = get_first_span_by_filter(spans, filter) + self.assertTrue(test_span) - filter = lambda span: span.n == "sdk" - test_span = get_first_span_by_filter(spans, filter) - assert (test_span) + filter = lambda span: span.n == "boto3" + boto_span = get_first_span_by_filter(spans, filter) + self.assertTrue(boto_span) - filter = lambda span: span.n == "boto3" - boto_span = get_first_span_by_filter(spans, filter) - assert (boto_span) + self.assertEqual(boto_span.t, test_span.t) + self.assertEqual(boto_span.p, test_span.s) - assert (boto_span.t == test_span.t) - assert (boto_span.p == test_span.s) + self.assertIsNone(test_span.ec) + self.assertIsNone(boto_span.ec) - assert (test_span.ec is None) - assert (boto_span.ec is None) + self.assertEqual(boto_span.data['boto3']['op'], 'upload_file') + self.assertEqual(boto_span.data['boto3']['ep'], 'https://s3.amazonaws.com') + self.assertEqual(boto_span.data['boto3']['reg'], 'us-east-1') + payload = {'Filename': upload_filename, 'Bucket': 'aws_bucket_name', 'Key': 'aws_key_name'} + self.assertDictEqual(boto_span.data['boto3']['payload'], payload) + self.assertEqual(boto_span.data['http']['method'], 'POST') + self.assertEqual(boto_span.data['http']['url'], 'https://s3.amazonaws.com:443/upload_file') - assert boto_span.data['boto3']['op'] == 'upload_file' - assert boto_span.data['boto3']['ep'] == 'https://s3.amazonaws.com' - assert boto_span.data['boto3']['reg'] == 'us-east-1' - payload = {'Filename': upload_filename, 'Bucket': 'aws_bucket_name', 'Key': 'aws_key_name'} - assert boto_span.data['boto3']['payload'] == payload - assert boto_span.data['http']['method'] == 'POST' - assert boto_span.data['http']['url'] == 'https://s3.amazonaws.com:443/upload_file' + def test_s3_upload_file_obj(self): + object_name = 'aws_key_name' + bucket_name = 'aws_bucket_name' -def test_s3_upload_file_obj(s3): - object_name = 'aws_key_name' - bucket_name = 'aws_bucket_name' + self.s3.create_bucket(Bucket=bucket_name) - s3.create_bucket(Bucket=bucket_name) + with tracer.start_active_span('test'): + with open(upload_filename, "rb") as fd: + self.s3.upload_fileobj(fd, bucket_name, object_name) - result = None - with tracer.start_active_span('test'): - with open(upload_filename, "rb") as fd: - s3.upload_fileobj(fd, bucket_name, object_name) + spans = tracer.recorder.queued_spans() + self.assertEqual(2, len(spans)) - spans = tracer.recorder.queued_spans() - assert len(spans) == 2 + filter = lambda span: span.n == "sdk" + test_span = get_first_span_by_filter(spans, filter) + self.assertTrue(test_span) - filter = lambda span: span.n == "sdk" - test_span = get_first_span_by_filter(spans, filter) - assert (test_span) + filter = lambda span: span.n == "boto3" + boto_span = get_first_span_by_filter(spans, filter) + self.assertTrue(boto_span) - filter = lambda span: span.n == "boto3" - boto_span = get_first_span_by_filter(spans, filter) - assert (boto_span) + self.assertEqual(boto_span.t, test_span.t) + self.assertEqual(boto_span.p, test_span.s) - assert (boto_span.t == test_span.t) - assert (boto_span.p == test_span.s) + self.assertIsNone(test_span.ec) + self.assertIsNone(boto_span.ec) - assert (test_span.ec is None) - assert (boto_span.ec is None) + self.assertEqual(boto_span.data['boto3']['op'], 'upload_fileobj') + self.assertEqual(boto_span.data['boto3']['ep'], 'https://s3.amazonaws.com') + self.assertEqual(boto_span.data['boto3']['reg'], 'us-east-1') + payload = {'Bucket': 'aws_bucket_name', 'Key': 'aws_key_name'} + self.assertDictEqual(boto_span.data['boto3']['payload'], payload) + self.assertEqual(boto_span.data['http']['method'], 'POST') + self.assertEqual(boto_span.data['http']['url'], 'https://s3.amazonaws.com:443/upload_fileobj') - assert (boto_span.data['boto3']['op'] == 'upload_fileobj') - assert (boto_span.data['boto3']['ep'] == 'https://s3.amazonaws.com') - assert (boto_span.data['boto3']['reg'] == 'us-east-1') - payload = {'Bucket': 'aws_bucket_name', 'Key': 'aws_key_name'} - assert boto_span.data['boto3']['payload'] == payload - assert boto_span.data['http']['method'] == 'POST' - assert boto_span.data['http']['url'] == 'https://s3.amazonaws.com:443/upload_fileobj' + def test_s3_download_file(self): + object_name = 'aws_key_name' + bucket_name = 'aws_bucket_name' -def test_s3_download_file(s3): - object_name = 'aws_key_name' - bucket_name = 'aws_bucket_name' + self.s3.create_bucket(Bucket=bucket_name) + self.s3.upload_file(upload_filename, bucket_name, object_name) - s3.create_bucket(Bucket=bucket_name) - s3.upload_file(upload_filename, bucket_name, object_name) + with tracer.start_active_span('test'): + self.s3.download_file(bucket_name, object_name, download_target_filename) - result = None - with tracer.start_active_span('test'): - s3.download_file(bucket_name, object_name, download_target_filename) + spans = tracer.recorder.queued_spans() + self.assertEqual(2, len(spans)) - spans = tracer.recorder.queued_spans() - assert len(spans) == 2 + filter = lambda span: span.n == "sdk" + test_span = get_first_span_by_filter(spans, filter) + self.assertTrue(test_span) - filter = lambda span: span.n == "sdk" - test_span = get_first_span_by_filter(spans, filter) - assert (test_span) + filter = lambda span: span.n == "boto3" + boto_span = get_first_span_by_filter(spans, filter) + self.assertTrue(boto_span) - filter = lambda span: span.n == "boto3" - boto_span = get_first_span_by_filter(spans, filter) - assert (boto_span) + self.assertEqual(boto_span.t, test_span.t) + self.assertEqual(boto_span.p, test_span.s) - assert (boto_span.t == test_span.t) - assert (boto_span.p == test_span.s) + self.assertIsNone(test_span.ec) + self.assertIsNone(boto_span.ec) - assert (test_span.ec is None) - assert (boto_span.ec is None) + self.assertEqual(boto_span.data['boto3']['op'], 'download_file') + self.assertEqual(boto_span.data['boto3']['ep'], 'https://s3.amazonaws.com') + self.assertEqual(boto_span.data['boto3']['reg'], 'us-east-1') + payload = {'Bucket': 'aws_bucket_name', 'Key': 'aws_key_name', 'Filename': '%s' % download_target_filename} + self.assertDictEqual(boto_span.data['boto3']['payload'], payload) + self.assertEqual(boto_span.data['http']['method'], 'POST') + self.assertEqual(boto_span.data['http']['url'], 'https://s3.amazonaws.com:443/download_file') - assert (boto_span.data['boto3']['op'] == 'download_file') - assert (boto_span.data['boto3']['ep'] == 'https://s3.amazonaws.com') - assert (boto_span.data['boto3']['reg'] == 'us-east-1') - payload = {'Bucket': 'aws_bucket_name', 'Key': 'aws_key_name', 'Filename': '%s' % download_target_filename} - assert boto_span.data['boto3']['payload'] == payload - assert boto_span.data['http']['method'] == 'POST' - assert boto_span.data['http']['url'] == 'https://s3.amazonaws.com:443/download_file' + def test_s3_download_file_obj(self): + object_name = 'aws_key_name' + bucket_name = 'aws_bucket_name' -def test_s3_download_file_obj(s3): - object_name = 'aws_key_name' - bucket_name = 'aws_bucket_name' + self.s3.create_bucket(Bucket=bucket_name) + self.s3.upload_file(upload_filename, bucket_name, object_name) - s3.create_bucket(Bucket=bucket_name) - s3.upload_file(upload_filename, bucket_name, object_name) + with tracer.start_active_span('test'): + with open(download_target_filename, "wb") as fd: + self.s3.download_fileobj(bucket_name, object_name, fd) - result = None - with tracer.start_active_span('test'): - with open(download_target_filename, "wb") as fd: - s3.download_fileobj(bucket_name, object_name, fd) + spans = tracer.recorder.queued_spans() + self.assertEqual(2, len(spans)) - spans = tracer.recorder.queued_spans() - assert len(spans) == 2 + filter = lambda span: span.n == "sdk" + test_span = get_first_span_by_filter(spans, filter) + self.assertTrue(test_span) - filter = lambda span: span.n == "sdk" - test_span = get_first_span_by_filter(spans, filter) - assert (test_span) + filter = lambda span: span.n == "boto3" + boto_span = get_first_span_by_filter(spans, filter) + self.assertTrue(boto_span) - filter = lambda span: span.n == "boto3" - boto_span = get_first_span_by_filter(spans, filter) - assert (boto_span) + self.assertEqual(boto_span.t, test_span.t) + self.assertEqual(boto_span.p, test_span.s) - assert (boto_span.t == test_span.t) - assert (boto_span.p == test_span.s) + self.assertIsNone(test_span.ec) + self.assertIsNone(boto_span.ec) - assert (test_span.ec is None) - assert (boto_span.ec is None) + self.assertEqual(boto_span.data['boto3']['op'], 'download_fileobj') + self.assertEqual(boto_span.data['boto3']['ep'], 'https://s3.amazonaws.com') + self.assertEqual(boto_span.data['boto3']['reg'], 'us-east-1') + self.assertEqual(boto_span.data['http']['method'], 'POST') + self.assertEqual(boto_span.data['http']['url'], 'https://s3.amazonaws.com:443/download_fileobj') - assert boto_span.data['boto3']['op'] == 'download_fileobj' - assert boto_span.data['boto3']['ep'] == 'https://s3.amazonaws.com' - assert boto_span.data['boto3']['reg'] == 'us-east-1' - assert boto_span.data['http']['method'] == 'POST' - assert boto_span.data['http']['url'] == 'https://s3.amazonaws.com:443/download_fileobj' + def test_request_header_capture(self): -def test_request_header_capture(s3): + original_extra_http_headers = agent.options.extra_http_headers + agent.options.extra_http_headers = ['X-Capture-This', 'X-Capture-That'] - original_extra_http_headers = agent.options.extra_http_headers - agent.options.extra_http_headers = ['X-Capture-This', 'X-Capture-That'] + # Access the event system on the S3 client + event_system = self.s3.meta.events - # Access the event system on the S3 client - event_system = s3.meta.events + request_headers = { + 'X-Capture-This': 'this', + 'X-Capture-That': 'that' + } + + # We set the custom headers in the request context instead of params + # because later in the processing of the request, there is a parameter validation step, + # which doesn't allow for custom arguments. + def process_custom_arguments(params, context, **kwargs): + if "custom_request_headers" not in context: + context["custom_request_headers"] = request_headers + + event_system.register('before-parameter-build.s3.CreateBucket', process_custom_arguments) - request_headers = { - 'X-Capture-This': 'this', - 'X-Capture-That': 'that' - } - - # We set the custom headers in the request context instead of params - # because later in the processing of the request, there is a parameter validation step, - # which doesn't allow for custom arguments. - def process_custom_arguments(params, context, **kwargs): - if "custom_request_headers" not in context: - context["custom_request_headers"] = request_headers + with tracer.start_active_span('test'): + result = self.s3.create_bucket(Bucket="aws_bucket_name") - event_system.register('before-parameter-build', process_custom_arguments) - - with tracer.start_active_span('test'): - result = s3.create_bucket(Bucket="aws_bucket_name") - - result = s3.list_buckets() - assert len(result['Buckets']) == 1 - assert result['Buckets'][0]['Name'] == 'aws_bucket_name' - - spans = tracer.recorder.queued_spans() - assert len(spans) == 2 - - filter = lambda span: span.n == "sdk" - test_span = get_first_span_by_filter(spans, filter) - assert (test_span) - - filter = lambda span: span.n == "boto3" - boto_span = get_first_span_by_filter(spans, filter) - assert (boto_span) - - assert (boto_span.t == test_span.t) - assert (boto_span.p == test_span.s) - - assert (test_span.ec is None) - assert (boto_span.ec is None) - - assert boto_span.data['boto3']['op'] == 'CreateBucket' - assert boto_span.data['boto3']['ep'] == 'https://s3.amazonaws.com' - assert boto_span.data['boto3']['reg'] == 'us-east-1' - assert boto_span.data['boto3']['payload'] == {'Bucket': 'aws_bucket_name'} - assert boto_span.data['http']['status'] == 200 - assert boto_span.data['http']['method'] == 'POST' - assert boto_span.data['http']['url'] == 'https://s3.amazonaws.com:443/CreateBucket' - - assert ("X-Capture-This" in boto_span.data["http"]["header"]) - assert ("this" == boto_span.data["http"]["header"]["X-Capture-This"]) - assert ("X-Capture-That" in boto_span.data["http"]["header"]) - assert ("that" == boto_span.data["http"]["header"]["X-Capture-That"]) - - agent.options.extra_http_headers = original_extra_http_headers + result = self.s3.list_buckets() + self.assertEqual(1, len(result['Buckets'])) + self.assertEqual(result['Buckets'][0]['Name'], 'aws_bucket_name') + spans = tracer.recorder.queued_spans() + self.assertEqual(2, len(spans)) -def test_response_header_capture(s3): + filter = lambda span: span.n == "sdk" + test_span = get_first_span_by_filter(spans, filter) + self.assertTrue(test_span) - original_extra_http_headers = agent.options.extra_http_headers - agent.options.extra_http_headers = ['X-Capture-This-Too', 'X-Capture-That-Too'] + filter = lambda span: span.n == "boto3" + boto_span = get_first_span_by_filter(spans, filter) + self.assertTrue(boto_span) - # Access the event system on the S3 client - event_system = s3.meta.events - - response_headers = { - "X-Capture-This-Too": "this too", - "X-Capture-That-Too": "that too", - } - - # Create a function that sets the custom headers in the after-call event. - def modify_after_call_args(parsed, **kwargs): - parsed['ResponseMetadata']['HTTPHeaders'].update(response_headers) - - # Register the function to an event - event_system.register('after-call', modify_after_call_args) - - with tracer.start_active_span('test'): - result = s3.create_bucket(Bucket="aws_bucket_name") - - result = s3.list_buckets() - assert len(result['Buckets']) == 1 - assert result['Buckets'][0]['Name'] == 'aws_bucket_name' - - spans = tracer.recorder.queued_spans() - assert len(spans) == 2 - - filter = lambda span: span.n == "sdk" - test_span = get_first_span_by_filter(spans, filter) - assert (test_span) - - filter = lambda span: span.n == "boto3" - boto_span = get_first_span_by_filter(spans, filter) - assert (boto_span) - - assert (boto_span.t == test_span.t) - assert (boto_span.p == test_span.s) - - assert (test_span.ec is None) - assert (boto_span.ec is None) - - assert boto_span.data['boto3']['op'] == 'CreateBucket' - assert boto_span.data['boto3']['ep'] == 'https://s3.amazonaws.com' - assert boto_span.data['boto3']['reg'] == 'us-east-1' - assert boto_span.data['boto3']['payload'] == {'Bucket': 'aws_bucket_name'} - assert boto_span.data['http']['status'] == 200 - assert boto_span.data['http']['method'] == 'POST' - assert boto_span.data['http']['url'] == 'https://s3.amazonaws.com:443/CreateBucket' - - assert ("X-Capture-This-Too" in boto_span.data["http"]["header"]) - assert ("this too" == boto_span.data["http"]["header"]["X-Capture-This-Too"]) - assert ("X-Capture-That-Too" in boto_span.data["http"]["header"]) - assert ("that too" == boto_span.data["http"]["header"]["X-Capture-That-Too"]) + self.assertEqual(boto_span.t, test_span.t) + self.assertEqual(boto_span.p, test_span.s) + + self.assertIsNone(test_span.ec) + self.assertIsNone(boto_span.ec) + + self.assertEqual(boto_span.data['boto3']['op'], 'CreateBucket') + self.assertEqual(boto_span.data['boto3']['ep'], 'https://s3.amazonaws.com') + self.assertEqual(boto_span.data['boto3']['reg'], 'us-east-1') + self.assertDictEqual(boto_span.data['boto3']['payload'], {'Bucket': 'aws_bucket_name'}) + self.assertEqual(boto_span.data['http']['status'], 200) + self.assertEqual(boto_span.data['http']['method'], 'POST') + self.assertEqual(boto_span.data['http']['url'], 'https://s3.amazonaws.com:443/CreateBucket') + + self.assertIn("X-Capture-This", boto_span.data["http"]["header"]) + self.assertEqual("this", boto_span.data["http"]["header"]["X-Capture-This"]) + self.assertIn("X-Capture-That", boto_span.data["http"]["header"]) + self.assertEqual("that", boto_span.data["http"]["header"]["X-Capture-That"]) + + agent.options.extra_http_headers = original_extra_http_headers + + + def test_response_header_capture(self): + + original_extra_http_headers = agent.options.extra_http_headers + agent.options.extra_http_headers = ['X-Capture-This-Too', 'X-Capture-That-Too'] + + # Access the event system on the S3 client + event_system = self.s3.meta.events - agent.options.extra_http_headers = original_extra_http_headers + response_headers = { + "X-Capture-This-Too": "this too", + "X-Capture-That-Too": "that too", + } + + # Create a function that sets the custom headers in the after-call event. + def modify_after_call_args(parsed, **kwargs): + parsed['ResponseMetadata']['HTTPHeaders'].update(response_headers) + + # Register the function to an event + event_system.register('after-call.s3.CreateBucket', modify_after_call_args) + + with tracer.start_active_span('test'): + result = self.s3.create_bucket(Bucket="aws_bucket_name") + + result = self.s3.list_buckets() + self.assertEqual(1, len(result['Buckets'])) + self.assertEqual(result['Buckets'][0]['Name'], 'aws_bucket_name') + + spans = tracer.recorder.queued_spans() + self.assertEqual(2, len(spans)) + + filter = lambda span: span.n == "sdk" + test_span = get_first_span_by_filter(spans, filter) + self.assertTrue(test_span) + + filter = lambda span: span.n == "boto3" + boto_span = get_first_span_by_filter(spans, filter) + self.assertTrue(boto_span) + + self.assertEqual(boto_span.t, test_span.t) + self.assertEqual(boto_span.p, test_span.s) + + self.assertIsNone(test_span.ec) + self.assertIsNone(boto_span.ec) + + self.assertEqual(boto_span.data['boto3']['op'], 'CreateBucket') + self.assertEqual(boto_span.data['boto3']['ep'], 'https://s3.amazonaws.com') + self.assertEqual(boto_span.data['boto3']['reg'], 'us-east-1') + self.assertDictEqual(boto_span.data['boto3']['payload'], {'Bucket': 'aws_bucket_name'}) + self.assertEqual(boto_span.data['http']['status'], 200) + self.assertEqual(boto_span.data['http']['method'], 'POST') + self.assertEqual(boto_span.data['http']['url'], 'https://s3.amazonaws.com:443/CreateBucket') + + self.assertIn("X-Capture-This-Too", boto_span.data["http"]["header"]) + self.assertEqual("this too", boto_span.data["http"]["header"]["X-Capture-This-Too"]) + self.assertIn("X-Capture-That-Too", boto_span.data["http"]["header"]) + self.assertEqual("that too", boto_span.data["http"]["header"]["X-Capture-That-Too"]) + + agent.options.extra_http_headers = original_extra_http_headers From 05a66e1d6ec0b7a0ea56803c4f66f5b8abedf814 Mon Sep 17 00:00:00 2001 From: Varsha GS Date: Wed, 31 Jan 2024 16:25:20 +0530 Subject: [PATCH 03/12] add custom headers to before-call and access them in request-created Signed-off-by: Varsha GS --- instana/instrumentation/boto3_inst.py | 12 ++++----- tests/clients/boto3/test_boto3_s3.py | 38 ++++++++++++++++----------- 2 files changed, 28 insertions(+), 22 deletions(-) diff --git a/instana/instrumentation/boto3_inst.py b/instana/instrumentation/boto3_inst.py index 2786e869..ee969cd1 100644 --- a/instana/instrumentation/boto3_inst.py +++ b/instana/instrumentation/boto3_inst.py @@ -46,20 +46,20 @@ def lambda_inject_context(payload, scope): logger.debug("non-fatal lambda_inject_context: ", exc_info=True) - @wrapt.patch_function_wrapper("botocore.hooks", "HierarchicalEmitter.emit_until_response") - def emit_until_response_with_instana(wrapped, instance, args, kwargs): + @wrapt.patch_function_wrapper("botocore.hooks", "HierarchicalEmitter.emit") + def emit_request_created_with_instana(wrapped, instance, args, kwargs): active_tracer = get_active_tracer() - # If we're not tracing or the event emitted is not before-call, just return; - if active_tracer is None or args[0].split(".")[0] != "before-call": + # If we're not tracing or the event emitted is not request-created, just return; + if active_tracer is None or args[0].split(".")[0] != "request-created": return wrapped(*args, **kwargs) span = active_tracer.active_span - if "custom_request_headers" in kwargs["context"]: - extract_custom_headers(span, kwargs["context"]["custom_request_headers"]) + extract_custom_headers(span, kwargs["request"].headers) return wrapped(*args, **kwargs) + @wrapt.patch_function_wrapper('botocore.client', 'BaseClient._make_api_call') def make_api_call_with_instana(wrapped, instance, arg_list, kwargs): # pylint: disable=protected-access diff --git a/tests/clients/boto3/test_boto3_s3.py b/tests/clients/boto3/test_boto3_s3.py index 27465d92..f110443a 100644 --- a/tests/clients/boto3/test_boto3_s3.py +++ b/tests/clients/boto3/test_boto3_s3.py @@ -22,8 +22,8 @@ class TestS3(unittest.TestCase): - def aws_credentials(self): - """Mocked AWS Credentials for moto.""" + def set_aws_credentials(self): + """ Mocked AWS Credentials for moto """ os.environ['AWS_ACCESS_KEY_ID'] = 'testing' os.environ['AWS_SECRET_ACCESS_KEY'] = 'testing' os.environ['AWS_SECURITY_TOKEN'] = 'testing' @@ -33,16 +33,25 @@ def setUp(self): """ Clear all spans before a test run """ self.recorder = tracer.recorder self.recorder.clear_spans() - self.aws_credentials() + self.set_aws_credentials() self.mock = mock_aws() self.mock.start() self.s3 = boto3.client('s3', region_name='us-east-1') + def unset_aws_credentials(self): + """ Reset all environment variables of consequence """ + variable_names = ( + "AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", + "AWS_SECURITY_TOKEN", "AWS_SESSION_TOKEN" + ) + + for variable_name in variable_names: + os.environ.pop(variable_name, None) def tearDown(self): # Stop Moto after each test self.mock.stop() - + self.unset_aws_credentials() def test_vanilla_create_bucket(self): self.s3.create_bucket(Bucket="aws_bucket_name") @@ -88,9 +97,8 @@ def test_s3_create_bucket(self): def test_s3_list_buckets(self): with tracer.start_active_span('test'): - self.s3.list_buckets() + result = self.s3.list_buckets() - result = self.s3.list_buckets() self.assertEqual(0, len(result['Buckets'])) self.assertEqual(result['ResponseMetadata']['HTTPStatusCode'], 200) @@ -283,15 +291,13 @@ def test_request_header_capture(self): 'X-Capture-This': 'this', 'X-Capture-That': 'that' } - - # We set the custom headers in the request context instead of params - # because later in the processing of the request, there is a parameter validation step, - # which doesn't allow for custom arguments. - def process_custom_arguments(params, context, **kwargs): - if "custom_request_headers" not in context: - context["custom_request_headers"] = request_headers - - event_system.register('before-parameter-build.s3.CreateBucket', process_custom_arguments) + + # Create a function that adds custom headers + def add_custom_header_before_call(params, **kwargs): + params['headers'].update(request_headers) + + # Register the function to an event. + event_system.register('before-call.s3.CreateBucket', add_custom_header_before_call) with tracer.start_active_span('test'): result = self.s3.create_bucket(Bucket="aws_bucket_name") @@ -354,7 +360,7 @@ def modify_after_call_args(parsed, **kwargs): event_system.register('after-call.s3.CreateBucket', modify_after_call_args) with tracer.start_active_span('test'): - result = self.s3.create_bucket(Bucket="aws_bucket_name") + self.s3.create_bucket(Bucket="aws_bucket_name") result = self.s3.list_buckets() self.assertEqual(1, len(result['Buckets'])) From 162dbe12cebc22ba975a5c59cb5ee9dfa64e8237 Mon Sep 17 00:00:00 2001 From: Varsha GS Date: Thu, 1 Feb 2024 10:42:48 +0530 Subject: [PATCH 04/12] adapt to both before-call and before-sign Signed-off-by: Varsha GS --- instana/instrumentation/boto3_inst.py | 12 ++++++------ tests/clients/boto3/test_boto3_s3.py | 11 +++++++++-- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/instana/instrumentation/boto3_inst.py b/instana/instrumentation/boto3_inst.py index ee969cd1..88d7c4bc 100644 --- a/instana/instrumentation/boto3_inst.py +++ b/instana/instrumentation/boto3_inst.py @@ -46,16 +46,16 @@ def lambda_inject_context(payload, scope): logger.debug("non-fatal lambda_inject_context: ", exc_info=True) - @wrapt.patch_function_wrapper("botocore.hooks", "HierarchicalEmitter.emit") - def emit_request_created_with_instana(wrapped, instance, args, kwargs): + @wrapt.patch_function_wrapper("botocore.auth", "SigV4Auth.add_auth") + def emit_add_auth_with_instana(wrapped, instance, args, kwargs): active_tracer = get_active_tracer() - - # If we're not tracing or the event emitted is not request-created, just return; - if active_tracer is None or args[0].split(".")[0] != "request-created": + + # If we're not tracing, just return; + if active_tracer is None: return wrapped(*args, **kwargs) span = active_tracer.active_span - extract_custom_headers(span, kwargs["request"].headers) + extract_custom_headers(span, args[0].headers) return wrapped(*args, **kwargs) diff --git a/tests/clients/boto3/test_boto3_s3.py b/tests/clients/boto3/test_boto3_s3.py index f110443a..fbd379b2 100644 --- a/tests/clients/boto3/test_boto3_s3.py +++ b/tests/clients/boto3/test_boto3_s3.py @@ -296,8 +296,15 @@ def test_request_header_capture(self): def add_custom_header_before_call(params, **kwargs): params['headers'].update(request_headers) - # Register the function to an event. - event_system.register('before-call.s3.CreateBucket', add_custom_header_before_call) + # # Register the function to before-call event. + # event_system.register('before-call.s3.CreateBucket', add_custom_header_before_call) + + def _add_header(request, **kwargs): + for name, value in request_headers.items(): + request.headers.add_header(name, value) + + # Register the function to before-sign event. + event_system.register_first('before-sign.s3.CreateBucket', _add_header) with tracer.start_active_span('test'): result = self.s3.create_bucket(Bucket="aws_bucket_name") From 770351703122da0c776cb639342a021ceebcdd5f Mon Sep 17 00:00:00 2001 From: Varsha GS Date: Thu, 1 Feb 2024 14:52:31 +0530 Subject: [PATCH 05/12] add separate TCs for before-call and before-sign Signed-off-by: Varsha GS --- tests/clients/boto3/test_boto3_s3.py | 92 ++++++++++++++++++++++------ 1 file changed, 72 insertions(+), 20 deletions(-) diff --git a/tests/clients/boto3/test_boto3_s3.py b/tests/clients/boto3/test_boto3_s3.py index fbd379b2..95814fe5 100644 --- a/tests/clients/boto3/test_boto3_s3.py +++ b/tests/clients/boto3/test_boto3_s3.py @@ -24,15 +24,17 @@ class TestS3(unittest.TestCase): def set_aws_credentials(self): """ Mocked AWS Credentials for moto """ - os.environ['AWS_ACCESS_KEY_ID'] = 'testing' - os.environ['AWS_SECRET_ACCESS_KEY'] = 'testing' - os.environ['AWS_SECURITY_TOKEN'] = 'testing' - os.environ['AWS_SESSION_TOKEN'] = 'testing' + for variable_name in self.variable_names: + os.environ[variable_name] = "testing" def setUp(self): """ Clear all spans before a test run """ self.recorder = tracer.recorder self.recorder.clear_spans() + self.variable_names = ( + "AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", + "AWS_SECURITY_TOKEN", "AWS_SESSION_TOKEN" + ) self.set_aws_credentials() self.mock = mock_aws() self.mock.start() @@ -40,12 +42,7 @@ def setUp(self): def unset_aws_credentials(self): """ Reset all environment variables of consequence """ - variable_names = ( - "AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", - "AWS_SECURITY_TOKEN", "AWS_SESSION_TOKEN" - ) - - for variable_name in variable_names: + for variable_name in self.variable_names: os.environ.pop(variable_name, None) def tearDown(self): @@ -53,6 +50,7 @@ def tearDown(self): self.mock.stop() self.unset_aws_credentials() + def test_vanilla_create_bucket(self): self.s3.create_bucket(Bucket="aws_bucket_name") @@ -279,7 +277,7 @@ def test_s3_download_file_obj(self): self.assertEqual(boto_span.data['http']['url'], 'https://s3.amazonaws.com:443/download_fileobj') - def test_request_header_capture(self): + def test_request_header_capture_before_call(self): original_extra_http_headers = agent.options.extra_http_headers agent.options.extra_http_headers = ['X-Capture-This', 'X-Capture-That'] @@ -296,18 +294,72 @@ def test_request_header_capture(self): def add_custom_header_before_call(params, **kwargs): params['headers'].update(request_headers) - # # Register the function to before-call event. - # event_system.register('before-call.s3.CreateBucket', add_custom_header_before_call) + # Register the function to before-call event. + event_system.register('before-call.s3.CreateBucket', add_custom_header_before_call) + + with tracer.start_active_span('test'): + self.s3.create_bucket(Bucket="aws_bucket_name") + + result = self.s3.list_buckets() + self.assertEqual(1, len(result['Buckets'])) + self.assertEqual(result['Buckets'][0]['Name'], 'aws_bucket_name') + + spans = tracer.recorder.queued_spans() + self.assertEqual(2, len(spans)) + + filter = lambda span: span.n == "sdk" + test_span = get_first_span_by_filter(spans, filter) + self.assertTrue(test_span) + + filter = lambda span: span.n == "boto3" + boto_span = get_first_span_by_filter(spans, filter) + self.assertTrue(boto_span) + + self.assertEqual(boto_span.t, test_span.t) + self.assertEqual(boto_span.p, test_span.s) + + self.assertIsNone(test_span.ec) + self.assertIsNone(boto_span.ec) - def _add_header(request, **kwargs): + self.assertEqual(boto_span.data['boto3']['op'], 'CreateBucket') + self.assertEqual(boto_span.data['boto3']['ep'], 'https://s3.amazonaws.com') + self.assertEqual(boto_span.data['boto3']['reg'], 'us-east-1') + self.assertDictEqual(boto_span.data['boto3']['payload'], {'Bucket': 'aws_bucket_name'}) + self.assertEqual(boto_span.data['http']['status'], 200) + self.assertEqual(boto_span.data['http']['method'], 'POST') + self.assertEqual(boto_span.data['http']['url'], 'https://s3.amazonaws.com:443/CreateBucket') + + self.assertIn("X-Capture-This", boto_span.data["http"]["header"]) + self.assertEqual("this", boto_span.data["http"]["header"]["X-Capture-This"]) + self.assertIn("X-Capture-That", boto_span.data["http"]["header"]) + self.assertEqual("that", boto_span.data["http"]["header"]["X-Capture-That"]) + + agent.options.extra_http_headers = original_extra_http_headers + + + def test_request_header_capture_before_sign(self): + + original_extra_http_headers = agent.options.extra_http_headers + agent.options.extra_http_headers = ['X-Custom-1', 'X-Custom-2'] + + # Access the event system on the S3 client + event_system = self.s3.meta.events + + request_headers = { + 'X-Custom-1': 'Value1', + 'X-Custom-2': 'Value2' + } + + # Create a function that adds custom headers + def add_custom_header_before_sign(request, **kwargs): for name, value in request_headers.items(): request.headers.add_header(name, value) # Register the function to before-sign event. - event_system.register_first('before-sign.s3.CreateBucket', _add_header) + event_system.register_first('before-sign.s3.CreateBucket', add_custom_header_before_sign) with tracer.start_active_span('test'): - result = self.s3.create_bucket(Bucket="aws_bucket_name") + self.s3.create_bucket(Bucket="aws_bucket_name") result = self.s3.list_buckets() self.assertEqual(1, len(result['Buckets'])) @@ -338,10 +390,10 @@ def _add_header(request, **kwargs): self.assertEqual(boto_span.data['http']['method'], 'POST') self.assertEqual(boto_span.data['http']['url'], 'https://s3.amazonaws.com:443/CreateBucket') - self.assertIn("X-Capture-This", boto_span.data["http"]["header"]) - self.assertEqual("this", boto_span.data["http"]["header"]["X-Capture-This"]) - self.assertIn("X-Capture-That", boto_span.data["http"]["header"]) - self.assertEqual("that", boto_span.data["http"]["header"]["X-Capture-That"]) + self.assertIn("X-Custom-1", boto_span.data["http"]["header"]) + self.assertEqual("Value1", boto_span.data["http"]["header"]["X-Custom-1"]) + self.assertIn("X-Custom-2", boto_span.data["http"]["header"]) + self.assertEqual("Value2", boto_span.data["http"]["header"]["X-Custom-2"]) agent.options.extra_http_headers = original_extra_http_headers From 5b06fe4cdd0bbc4cc9252b4328ec3f706f7401dd Mon Sep 17 00:00:00 2001 From: Varsha GS Date: Mon, 5 Feb 2024 14:53:55 +0530 Subject: [PATCH 06/12] boto3: test capture headers feature for secretsmanager, ses, sqs Signed-off-by: Varsha GS --- .../boto3/test_boto3_secretsmanager.py | 310 ++++++++++-- tests/clients/boto3/test_boto3_ses.py | 270 ++++++++-- tests/clients/boto3/test_boto3_sqs.py | 462 ++++++++++++++---- 3 files changed, 845 insertions(+), 197 deletions(-) diff --git a/tests/clients/boto3/test_boto3_secretsmanager.py b/tests/clients/boto3/test_boto3_secretsmanager.py index 2682646b..f5e3bb9b 100644 --- a/tests/clients/boto3/test_boto3_secretsmanager.py +++ b/tests/clients/boto3/test_boto3_secretsmanager.py @@ -5,7 +5,7 @@ import os import boto3 -import pytest +import unittest # TODO: Remove branching when we drop support for Python 3.7 import sys @@ -14,75 +14,285 @@ else: from moto import mock_secretsmanager as mock_aws -from instana.singletons import tracer +from instana.singletons import tracer, agent from ...helpers import get_first_span_by_filter pwd = os.path.dirname(os.path.abspath(__file__)) -def setup_method(): - """ Clear all spans before a test run """ - tracer.recorder.clear_spans() +class TestSecretsManager(unittest.TestCase): + def set_aws_credentials(self): + """ Mocked AWS Credentials for moto """ + for variable_name in self.variable_names: + os.environ[variable_name] = "testing" + def setUp(self): + """ Clear all spans before a test run """ + self.recorder = tracer.recorder + self.recorder.clear_spans() + self.variable_names = ( + "AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", + "AWS_SECURITY_TOKEN", "AWS_SESSION_TOKEN" + ) + self.set_aws_credentials() + self.mock = mock_aws() + self.mock.start() + self.secretsmanager = boto3.client('secretsmanager', region_name='us-east-1') -@pytest.fixture(scope='function') -def aws_credentials(): - """Mocked AWS Credentials for moto.""" - os.environ['AWS_ACCESS_KEY_ID'] = 'testing' - os.environ['AWS_SECRET_ACCESS_KEY'] = 'testing' - os.environ['AWS_SECURITY_TOKEN'] = 'testing' - os.environ['AWS_SESSION_TOKEN'] = 'testing' + def unset_aws_credentials(self): + """ Reset all environment variables of consequence """ + for variable_name in self.variable_names: + os.environ.pop(variable_name, None) + def tearDown(self): + # Stop Moto after each test + self.mock.stop() + self.unset_aws_credentials() -@pytest.fixture(scope='function') -def secretsmanager(aws_credentials): - with mock_aws(): - yield boto3.client('secretsmanager', region_name='us-east-1') + def test_vanilla_list_secrets(self): + result = self.secretsmanager.list_secrets(MaxResults=123) + self.assertListEqual(result['SecretList'], []) -def test_vanilla_list_secrets(secretsmanager): - result = secretsmanager.list_secrets(MaxResults=123) - assert result['SecretList'] == [] + def test_get_secret_value(self): + secret_id = 'Uber_Password' -def test_get_secret_value(secretsmanager): - result = None - secret_id = 'Uber_Password' + response = self.secretsmanager.create_secret( + Name=secret_id, + SecretBinary=b'password1', + SecretString='password1', + ) - response = secretsmanager.create_secret( - Name=secret_id, - SecretBinary=b'password1', - SecretString='password1', - ) + self.assertEqual(response['Name'], secret_id) - assert response['Name'] == secret_id + with tracer.start_active_span('test'): + result = self.secretsmanager.get_secret_value(SecretId=secret_id) - with tracer.start_active_span('test'): - result = secretsmanager.get_secret_value(SecretId=secret_id) + self.assertEqual(result['Name'], secret_id) - assert result['Name'] == secret_id + spans = tracer.recorder.queued_spans() + self.assertEqual(2, len(spans)) - spans = tracer.recorder.queued_spans() - assert len(spans) == 2 + filter = lambda span: span.n == "sdk" + test_span = get_first_span_by_filter(spans, filter) + self.assertTrue(test_span) - filter = lambda span: span.n == "sdk" - test_span = get_first_span_by_filter(spans, filter) - assert(test_span) + filter = lambda span: span.n == "boto3" + boto_span = get_first_span_by_filter(spans, filter) + self.assertTrue(boto_span) - filter = lambda span: span.n == "boto3" - boto_span = get_first_span_by_filter(spans, filter) - assert(boto_span) + self.assertEqual(boto_span.t, test_span.t) + self.assertEqual(boto_span.p, test_span.s) - assert(boto_span.t == test_span.t) - assert(boto_span.p == test_span.s) + self.assertIsNone(test_span.ec) - assert(test_span.ec is None) - assert(boto_span.ec is None) + self.assertEqual(boto_span.data['boto3']['op'], 'GetSecretValue') + self.assertEqual(boto_span.data['boto3']['ep'], 'https://secretsmanager.us-east-1.amazonaws.com') + self.assertEqual(boto_span.data['boto3']['reg'], 'us-east-1') + self.assertNotIn('payload', boto_span.data['boto3']) + + self.assertEqual(boto_span.data['http']['status'], 200) + self.assertEqual(boto_span.data['http']['method'], 'POST') + self.assertEqual(boto_span.data['http']['url'], 'https://secretsmanager.us-east-1.amazonaws.com:443/GetSecretValue') + + + def test_request_header_capture_before_call(self): + secret_id = 'Uber_Password' + + response = self.secretsmanager.create_secret( + Name=secret_id, + SecretBinary=b'password1', + SecretString='password1', + ) + + self.assertEqual(response['Name'], secret_id) + + original_extra_http_headers = agent.options.extra_http_headers + agent.options.extra_http_headers = ['X-Capture-This', 'X-Capture-That'] + + # Access the event system on the S3 client + event_system = self.secretsmanager.meta.events + + request_headers = { + 'X-Capture-This': 'this', + 'X-Capture-That': 'that' + } + + # Create a function that adds custom headers + def add_custom_header_before_call(params, **kwargs): + params['headers'].update(request_headers) + + # Register the function to before-call event. + event_system.register('before-call.secrets-manager.GetSecretValue', add_custom_header_before_call) + + with tracer.start_active_span('test'): + result = self.secretsmanager.get_secret_value(SecretId=secret_id) + + self.assertEqual(result['Name'], secret_id) + + spans = tracer.recorder.queued_spans() + self.assertEqual(2, len(spans)) + + filter = lambda span: span.n == "sdk" + test_span = get_first_span_by_filter(spans, filter) + self.assertTrue(test_span) + + filter = lambda span: span.n == "boto3" + boto_span = get_first_span_by_filter(spans, filter) + self.assertTrue(boto_span) + + self.assertEqual(boto_span.t, test_span.t) + self.assertEqual(boto_span.p, test_span.s) + + self.assertIsNone(test_span.ec) + + self.assertEqual(boto_span.data['boto3']['op'], 'GetSecretValue') + self.assertEqual(boto_span.data['boto3']['ep'], 'https://secretsmanager.us-east-1.amazonaws.com') + self.assertEqual(boto_span.data['boto3']['reg'], 'us-east-1') + self.assertNotIn('payload', boto_span.data['boto3']) + + self.assertEqual(boto_span.data['http']['status'], 200) + self.assertEqual(boto_span.data['http']['method'], 'POST') + self.assertEqual(boto_span.data['http']['url'], 'https://secretsmanager.us-east-1.amazonaws.com:443/GetSecretValue') + + self.assertIn("X-Capture-This", boto_span.data["http"]["header"]) + self.assertEqual("this", boto_span.data["http"]["header"]["X-Capture-This"]) + self.assertIn("X-Capture-That", boto_span.data["http"]["header"]) + self.assertEqual("that", boto_span.data["http"]["header"]["X-Capture-That"]) + + agent.options.extra_http_headers = original_extra_http_headers - assert boto_span.data['boto3']['op'] == 'GetSecretValue' - assert boto_span.data['boto3']['ep'] == 'https://secretsmanager.us-east-1.amazonaws.com' - assert boto_span.data['boto3']['reg'] == 'us-east-1' - assert 'payload' not in boto_span.data['boto3'] - assert boto_span.data['http']['status'] == 200 - assert boto_span.data['http']['method'] == 'POST' - assert boto_span.data['http']['url'] == 'https://secretsmanager.us-east-1.amazonaws.com:443/GetSecretValue' + def test_request_header_capture_before_sign(self): + secret_id = 'Uber_Password' + + response = self.secretsmanager.create_secret( + Name=secret_id, + SecretBinary=b'password1', + SecretString='password1', + ) + + self.assertEqual(response['Name'], secret_id) + + original_extra_http_headers = agent.options.extra_http_headers + agent.options.extra_http_headers = ['X-Custom-1', 'X-Custom-2'] + + # Access the event system on the S3 client + event_system = self.secretsmanager.meta.events + + request_headers = { + 'X-Custom-1': 'Value1', + 'X-Custom-2': 'Value2' + } + + # Create a function that adds custom headers + def add_custom_header_before_sign(request, **kwargs): + for name, value in request_headers.items(): + request.headers.add_header(name, value) + + # Register the function to before-sign event. + event_system.register_first('before-sign.secrets-manager.GetSecretValue', add_custom_header_before_sign) + + with tracer.start_active_span('test'): + result = self.secretsmanager.get_secret_value(SecretId=secret_id) + + self.assertEqual(result['Name'], secret_id) + + spans = tracer.recorder.queued_spans() + self.assertEqual(2, len(spans)) + + filter = lambda span: span.n == "sdk" + test_span = get_first_span_by_filter(spans, filter) + self.assertTrue(test_span) + + filter = lambda span: span.n == "boto3" + boto_span = get_first_span_by_filter(spans, filter) + self.assertTrue(boto_span) + + self.assertEqual(boto_span.t, test_span.t) + self.assertEqual(boto_span.p, test_span.s) + + self.assertIsNone(test_span.ec) + + self.assertEqual(boto_span.data['boto3']['op'], 'GetSecretValue') + self.assertEqual(boto_span.data['boto3']['ep'], 'https://secretsmanager.us-east-1.amazonaws.com') + self.assertEqual(boto_span.data['boto3']['reg'], 'us-east-1') + self.assertNotIn('payload', boto_span.data['boto3']) + + self.assertEqual(boto_span.data['http']['status'], 200) + self.assertEqual(boto_span.data['http']['method'], 'POST') + self.assertEqual(boto_span.data['http']['url'], 'https://secretsmanager.us-east-1.amazonaws.com:443/GetSecretValue') + + self.assertIn("X-Custom-1", boto_span.data["http"]["header"]) + self.assertEqual("Value1", boto_span.data["http"]["header"]["X-Custom-1"]) + self.assertIn("X-Custom-2", boto_span.data["http"]["header"]) + self.assertEqual("Value2", boto_span.data["http"]["header"]["X-Custom-2"]) + + agent.options.extra_http_headers = original_extra_http_headers + + + def test_response_header_capture(self): + secret_id = 'Uber_Password' + + response = self.secretsmanager.create_secret( + Name=secret_id, + SecretBinary=b'password1', + SecretString='password1', + ) + + self.assertEqual(response['Name'], secret_id) + + original_extra_http_headers = agent.options.extra_http_headers + agent.options.extra_http_headers = ['X-Capture-This-Too', 'X-Capture-That-Too'] + + # Access the event system on the S3 client + event_system = self.secretsmanager.meta.events + + response_headers = { + "X-Capture-This-Too": "this too", + "X-Capture-That-Too": "that too", + } + + # Create a function that sets the custom headers in the after-call event. + def modify_after_call_args(parsed, **kwargs): + parsed['ResponseMetadata']['HTTPHeaders'].update(response_headers) + + # Register the function to an event + event_system.register('after-call.secrets-manager.GetSecretValue', modify_after_call_args) + + with tracer.start_active_span('test'): + result = self.secretsmanager.get_secret_value(SecretId=secret_id) + + self.assertEqual(result['Name'], secret_id) + + spans = tracer.recorder.queued_spans() + self.assertEqual(2, len(spans)) + + filter = lambda span: span.n == "sdk" + test_span = get_first_span_by_filter(spans, filter) + self.assertTrue(test_span) + + filter = lambda span: span.n == "boto3" + boto_span = get_first_span_by_filter(spans, filter) + self.assertTrue(boto_span) + + self.assertEqual(boto_span.t, test_span.t) + self.assertEqual(boto_span.p, test_span.s) + + self.assertIsNone(test_span.ec) + + self.assertEqual(boto_span.data['boto3']['op'], 'GetSecretValue') + self.assertEqual(boto_span.data['boto3']['ep'], 'https://secretsmanager.us-east-1.amazonaws.com') + self.assertEqual(boto_span.data['boto3']['reg'], 'us-east-1') + self.assertNotIn('payload', boto_span.data['boto3']) + + self.assertEqual(boto_span.data['http']['status'], 200) + self.assertEqual(boto_span.data['http']['method'], 'POST') + self.assertEqual(boto_span.data['http']['url'], 'https://secretsmanager.us-east-1.amazonaws.com:443/GetSecretValue') + + self.assertIn("X-Capture-This-Too", boto_span.data["http"]["header"]) + self.assertEqual("this too", boto_span.data["http"]["header"]["X-Capture-This-Too"]) + self.assertIn("X-Capture-That-Too", boto_span.data["http"]["header"]) + self.assertEqual("that too", boto_span.data["http"]["header"]["X-Capture-That-Too"]) + + agent.options.extra_http_headers = original_extra_http_headers diff --git a/tests/clients/boto3/test_boto3_ses.py b/tests/clients/boto3/test_boto3_ses.py index 20d14bf2..b8b90dd4 100644 --- a/tests/clients/boto3/test_boto3_ses.py +++ b/tests/clients/boto3/test_boto3_ses.py @@ -5,7 +5,7 @@ import os import boto3 -import pytest +import unittest # TODO: Remove branching when we drop support for Python 3.7 import sys @@ -14,66 +14,248 @@ else: from moto import mock_ses as mock_aws -from instana.singletons import tracer +from instana.singletons import tracer, agent from ...helpers import get_first_span_by_filter pwd = os.path.dirname(os.path.abspath(__file__)) -def setup_method(): - """ Clear all spans before a test run """ - tracer.recorder.clear_spans() +class TestSes(unittest.TestCase): + def set_aws_credentials(self): + """ Mocked AWS Credentials for moto """ + for variable_name in self.variable_names: + os.environ[variable_name] = "testing" + def setUp(self): + """ Clear all spans before a test run """ + self.recorder = tracer.recorder + self.recorder.clear_spans() + self.variable_names = ( + "AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", + "AWS_SECURITY_TOKEN", "AWS_SESSION_TOKEN" + ) + self.set_aws_credentials() + self.mock = mock_aws() + self.mock.start() + self.ses = boto3.client('ses', region_name='us-east-1') -@pytest.fixture(scope='function') -def aws_credentials(): - """Mocked AWS Credentials for moto.""" - os.environ['AWS_ACCESS_KEY_ID'] = 'testing' - os.environ['AWS_SECRET_ACCESS_KEY'] = 'testing' - os.environ['AWS_SECURITY_TOKEN'] = 'testing' - os.environ['AWS_SESSION_TOKEN'] = 'testing' + def unset_aws_credentials(self): + """ Reset all environment variables of consequence """ + for variable_name in self.variable_names: + os.environ.pop(variable_name, None) + def tearDown(self): + # Stop Moto after each test + self.mock.stop() + self.unset_aws_credentials() -@pytest.fixture(scope='function') -def ses(aws_credentials): - with mock_aws(): - yield boto3.client('ses', region_name='us-east-1') + def test_vanilla_verify_email(self): + result = self.ses.verify_email_identity(EmailAddress='pglombardo+instana299@tuta.io') + self.assertEqual(result['ResponseMetadata']['HTTPStatusCode'], 200) -def test_vanilla_verify_email(ses): - result = ses.verify_email_identity(EmailAddress='pglombardo+instana299@tuta.io') - assert result['ResponseMetadata']['HTTPStatusCode'] == 200 + def test_verify_email(self): + with tracer.start_active_span('test'): + result = self.ses.verify_email_identity(EmailAddress='pglombardo+instana299@tuta.io') -def test_verify_email(ses): - result = None + self.assertEqual(result['ResponseMetadata']['HTTPStatusCode'], 200) - with tracer.start_active_span('test'): - result = ses.verify_email_identity(EmailAddress='pglombardo+instana299@tuta.io') + spans = tracer.recorder.queued_spans() + self.assertEqual(2, len(spans)) - assert result['ResponseMetadata']['HTTPStatusCode'] == 200 + filter = lambda span: span.n == "sdk" + test_span = get_first_span_by_filter(spans, filter) + self.assertTrue(test_span) - spans = tracer.recorder.queued_spans() - assert len(spans) == 2 + filter = lambda span: span.n == "boto3" + boto_span = get_first_span_by_filter(spans, filter) + self.assertTrue(boto_span) - filter = lambda span: span.n == "sdk" - test_span = get_first_span_by_filter(spans, filter) - assert(test_span) + self.assertEqual(boto_span.t, test_span.t) + self.assertEqual(boto_span.p, test_span.s) - filter = lambda span: span.n == "boto3" - boto_span = get_first_span_by_filter(spans, filter) - assert(boto_span) + self.assertIsNone(test_span.ec) - assert(boto_span.t == test_span.t) - assert(boto_span.p == test_span.s) + self.assertEqual(boto_span.data['boto3']['op'], 'VerifyEmailIdentity') + self.assertEqual(boto_span.data['boto3']['ep'], 'https://email.us-east-1.amazonaws.com') + self.assertEqual(boto_span.data['boto3']['reg'], 'us-east-1') + self.assertDictEqual(boto_span.data['boto3']['payload'], {'EmailAddress': 'pglombardo+instana299@tuta.io'}) - assert(test_span.ec is None) - assert(boto_span.ec is None) + self.assertEqual(boto_span.data['http']['status'], 200) + self.assertEqual(boto_span.data['http']['method'], 'POST') + self.assertEqual(boto_span.data['http']['url'], 'https://email.us-east-1.amazonaws.com:443/VerifyEmailIdentity') - assert boto_span.data['boto3']['op'] == 'VerifyEmailIdentity' - assert boto_span.data['boto3']['ep'] == 'https://email.us-east-1.amazonaws.com' - assert boto_span.data['boto3']['reg'] == 'us-east-1' - assert boto_span.data['boto3']['payload'] == {'EmailAddress': 'pglombardo+instana299@tuta.io'} - - assert boto_span.data['http']['status'] == 200 - assert boto_span.data['http']['method'] == 'POST' - assert boto_span.data['http']['url'] == 'https://email.us-east-1.amazonaws.com:443/VerifyEmailIdentity' + + def test_request_header_capture_before_call(self): + + original_extra_http_headers = agent.options.extra_http_headers + agent.options.extra_http_headers = ['X-Capture-This', 'X-Capture-That'] + + # Access the event system on the S3 client + event_system = self.ses.meta.events + + request_headers = { + 'X-Capture-This': 'this', + 'X-Capture-That': 'that' + } + + # Create a function that adds custom headers + def add_custom_header_before_call(params, **kwargs): + params['headers'].update(request_headers) + + # Register the function to before-call event. + event_system.register('before-call.ses.VerifyEmailIdentity', add_custom_header_before_call) + + with tracer.start_active_span('test'): + result = self.ses.verify_email_identity(EmailAddress='pglombardo+instana299@tuta.io') + + self.assertEqual(result['ResponseMetadata']['HTTPStatusCode'], 200) + + spans = tracer.recorder.queued_spans() + self.assertEqual(2, len(spans)) + + filter = lambda span: span.n == "sdk" + test_span = get_first_span_by_filter(spans, filter) + self.assertTrue(test_span) + + filter = lambda span: span.n == "boto3" + boto_span = get_first_span_by_filter(spans, filter) + self.assertTrue(boto_span) + + self.assertEqual(boto_span.t, test_span.t) + self.assertEqual(boto_span.p, test_span.s) + + self.assertIsNone(test_span.ec) + + self.assertEqual(boto_span.data['boto3']['op'], 'VerifyEmailIdentity') + self.assertEqual(boto_span.data['boto3']['ep'], 'https://email.us-east-1.amazonaws.com') + self.assertEqual(boto_span.data['boto3']['reg'], 'us-east-1') + self.assertDictEqual(boto_span.data['boto3']['payload'], {'EmailAddress': 'pglombardo+instana299@tuta.io'}) + + self.assertEqual(boto_span.data['http']['status'], 200) + self.assertEqual(boto_span.data['http']['method'], 'POST') + self.assertEqual(boto_span.data['http']['url'], 'https://email.us-east-1.amazonaws.com:443/VerifyEmailIdentity') + + self.assertIn("X-Capture-This", boto_span.data["http"]["header"]) + self.assertEqual("this", boto_span.data["http"]["header"]["X-Capture-This"]) + self.assertIn("X-Capture-That", boto_span.data["http"]["header"]) + self.assertEqual("that", boto_span.data["http"]["header"]["X-Capture-That"]) + + agent.options.extra_http_headers = original_extra_http_headers + + + def test_request_header_capture_before_sign(self): + + original_extra_http_headers = agent.options.extra_http_headers + agent.options.extra_http_headers = ['X-Custom-1', 'X-Custom-2'] + + # Access the event system on the S3 client + event_system = self.ses.meta.events + + request_headers = { + 'X-Custom-1': 'Value1', + 'X-Custom-2': 'Value2' + } + + # Create a function that adds custom headers + def add_custom_header_before_sign(request, **kwargs): + for name, value in request_headers.items(): + request.headers.add_header(name, value) + + # Register the function to before-sign event. + event_system.register_first('before-sign.ses.VerifyEmailIdentity', add_custom_header_before_sign) + + with tracer.start_active_span('test'): + result = self.ses.verify_email_identity(EmailAddress='pglombardo+instana299@tuta.io') + + self.assertEqual(result['ResponseMetadata']['HTTPStatusCode'], 200) + + spans = tracer.recorder.queued_spans() + self.assertEqual(2, len(spans)) + + filter = lambda span: span.n == "sdk" + test_span = get_first_span_by_filter(spans, filter) + self.assertTrue(test_span) + + filter = lambda span: span.n == "boto3" + boto_span = get_first_span_by_filter(spans, filter) + self.assertTrue(boto_span) + + self.assertEqual(boto_span.t, test_span.t) + self.assertEqual(boto_span.p, test_span.s) + + self.assertIsNone(test_span.ec) + + self.assertEqual(boto_span.data['boto3']['op'], 'VerifyEmailIdentity') + self.assertEqual(boto_span.data['boto3']['ep'], 'https://email.us-east-1.amazonaws.com') + self.assertEqual(boto_span.data['boto3']['reg'], 'us-east-1') + self.assertDictEqual(boto_span.data['boto3']['payload'], {'EmailAddress': 'pglombardo+instana299@tuta.io'}) + + self.assertEqual(boto_span.data['http']['status'], 200) + self.assertEqual(boto_span.data['http']['method'], 'POST') + self.assertEqual(boto_span.data['http']['url'], 'https://email.us-east-1.amazonaws.com:443/VerifyEmailIdentity') + + self.assertIn("X-Custom-1", boto_span.data["http"]["header"]) + self.assertEqual("Value1", boto_span.data["http"]["header"]["X-Custom-1"]) + self.assertIn("X-Custom-2", boto_span.data["http"]["header"]) + self.assertEqual("Value2", boto_span.data["http"]["header"]["X-Custom-2"]) + + agent.options.extra_http_headers = original_extra_http_headers + + + def test_response_header_capture(self): + + original_extra_http_headers = agent.options.extra_http_headers + agent.options.extra_http_headers = ['X-Capture-This-Too', 'X-Capture-That-Too'] + + # Access the event system on the S3 client + event_system = self.ses.meta.events + + response_headers = { + "X-Capture-This-Too": "this too", + "X-Capture-That-Too": "that too", + } + + # Create a function that sets the custom headers in the after-call event. + def modify_after_call_args(parsed, **kwargs): + parsed['ResponseMetadata']['HTTPHeaders'].update(response_headers) + + # Register the function to an event + event_system.register('after-call.ses.VerifyEmailIdentity', modify_after_call_args) + + with tracer.start_active_span('test'): + result = self.ses.verify_email_identity(EmailAddress='pglombardo+instana299@tuta.io') + + self.assertEqual(result['ResponseMetadata']['HTTPStatusCode'], 200) + + spans = tracer.recorder.queued_spans() + self.assertEqual(2, len(spans)) + + filter = lambda span: span.n == "sdk" + test_span = get_first_span_by_filter(spans, filter) + self.assertTrue(test_span) + + filter = lambda span: span.n == "boto3" + boto_span = get_first_span_by_filter(spans, filter) + self.assertTrue(boto_span) + + self.assertEqual(boto_span.t, test_span.t) + self.assertEqual(boto_span.p, test_span.s) + + self.assertIsNone(test_span.ec) + + self.assertEqual(boto_span.data['boto3']['op'], 'VerifyEmailIdentity') + self.assertEqual(boto_span.data['boto3']['ep'], 'https://email.us-east-1.amazonaws.com') + self.assertEqual(boto_span.data['boto3']['reg'], 'us-east-1') + self.assertDictEqual(boto_span.data['boto3']['payload'], {'EmailAddress': 'pglombardo+instana299@tuta.io'}) + + self.assertEqual(boto_span.data['http']['status'], 200) + self.assertEqual(boto_span.data['http']['method'], 'POST') + self.assertEqual(boto_span.data['http']['url'], 'https://email.us-east-1.amazonaws.com:443/VerifyEmailIdentity') + + self.assertIn("X-Capture-This-Too", boto_span.data["http"]["header"]) + self.assertEqual("this too", boto_span.data["http"]["header"]["X-Capture-This-Too"]) + self.assertIn("X-Capture-That-Too", boto_span.data["http"]["header"]) + self.assertEqual("that too", boto_span.data["http"]["header"]["X-Capture-That-Too"]) + + agent.options.extra_http_headers = original_extra_http_headers diff --git a/tests/clients/boto3/test_boto3_sqs.py b/tests/clients/boto3/test_boto3_sqs.py index 56fa0ca3..f04d7502 100644 --- a/tests/clients/boto3/test_boto3_sqs.py +++ b/tests/clients/boto3/test_boto3_sqs.py @@ -5,7 +5,7 @@ import os import boto3 -import pytest +import unittest import urllib3 # TODO: Remove branching when we drop support for Python 3.7 @@ -16,144 +16,400 @@ from moto import mock_sqs as mock_aws import tests.apps.flask_app -from instana.singletons import tracer +from instana.singletons import tracer, agent from ...helpers import get_first_span_by_filter, testenv pwd = os.path.dirname(os.path.abspath(__file__)) -def setup_method(): - """ Clear all spans before a test run """ - tracer.recorder.clear_spans() +class TestSqs(unittest.TestCase): + def set_aws_credentials(self): + """ Mocked AWS Credentials for moto """ + for variable_name in self.variable_names: + os.environ[variable_name] = "testing" + + def setUp(self): + """ Clear all spans before a test run """ + self.recorder = tracer.recorder + self.recorder.clear_spans() + self.variable_names = ( + "AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", + "AWS_SECURITY_TOKEN", "AWS_SESSION_TOKEN" + ) + self.set_aws_credentials() + self.mock = mock_aws() + self.mock.start() + self.sqs = boto3.client('sqs', region_name='us-east-1') + self.http_client = urllib3.PoolManager() + + def unset_aws_credentials(self): + """ Reset all environment variables of consequence """ + for variable_name in self.variable_names: + os.environ.pop(variable_name, None) + + def tearDown(self): + # Stop Moto after each test + self.mock.stop() + self.unset_aws_credentials() + + + def test_vanilla_create_queue(self): + result = self.sqs.create_queue( + QueueName='SQS_QUEUE_NAME', + Attributes={ + 'DelaySeconds': '60', + 'MessageRetentionPeriod': '86400' + }) + self.assertEqual(result['ResponseMetadata']['HTTPStatusCode'], 200) + + + def test_send_message(self): + # Create the Queue: + response = self.sqs.create_queue( + QueueName='SQS_QUEUE_NAME', + Attributes={ + 'DelaySeconds': '60', + 'MessageRetentionPeriod': '600' + } + ) + self.assertTrue(response['QueueUrl']) + queue_url = response['QueueUrl'] + + with tracer.start_active_span('test'): + response = self.sqs.send_message( + QueueUrl=queue_url, + DelaySeconds=10, + MessageAttributes={ + 'Website': { + 'DataType': 'String', + 'StringValue': 'https://www.instana.com' + }, + }, + MessageBody=('Monitor any application, service, or request ' + 'with Instana Application Performance Monitoring') + ) -@pytest.fixture(scope='function') -def aws_credentials(): - """Mocked AWS Credentials for moto.""" - os.environ['AWS_ACCESS_KEY_ID'] = 'testing' - os.environ['AWS_SECRET_ACCESS_KEY'] = 'testing' - os.environ['AWS_SECURITY_TOKEN'] = 'testing' - os.environ['AWS_SESSION_TOKEN'] = 'testing' + self.assertTrue(response['MessageId']) + spans = tracer.recorder.queued_spans() + self.assertEqual(2, len(spans)) -@pytest.fixture(scope='function') -def http_client(): - yield urllib3.PoolManager() + filter = lambda span: span.n == "sdk" + test_span = get_first_span_by_filter(spans, filter) + self.assertTrue(test_span) + filter = lambda span: span.n == "boto3" + boto_span = get_first_span_by_filter(spans, filter) + self.assertTrue(boto_span) -@pytest.fixture(scope='function') -def sqs(aws_credentials): - with mock_aws(): - yield boto3.client('sqs', region_name='us-east-1') + self.assertEqual(boto_span.t, test_span.t) + self.assertEqual(boto_span.p, test_span.s) + self.assertIsNone(test_span.ec) -def test_vanilla_create_queue(sqs): - result = sqs.create_queue( - QueueName='SQS_QUEUE_NAME', - Attributes={ - 'DelaySeconds': '60', - 'MessageRetentionPeriod': '86400' - }) - assert result['ResponseMetadata']['HTTPStatusCode'] == 200 + self.assertEqual(boto_span.data['boto3']['op'], 'SendMessage') + self.assertEqual(boto_span.data['boto3']['ep'], 'https://sqs.us-east-1.amazonaws.com') + self.assertEqual(boto_span.data['boto3']['reg'], 'us-east-1') + payload = {'QueueUrl': 'https://sqs.us-east-1.amazonaws.com/123456789012/SQS_QUEUE_NAME', 'DelaySeconds': 10, + 'MessageAttributes': {'Website': {'DataType': 'String', 'StringValue': 'https://www.instana.com'}}, + 'MessageBody': 'Monitor any application, service, or request with Instana Application Performance Monitoring'} + self.assertDictEqual(boto_span.data['boto3']['payload'], payload) -def test_send_message(sqs): - response = None + self.assertEqual(boto_span.data['http']['status'], 200) + self.assertEqual(boto_span.data['http']['method'], 'POST') + self.assertEqual(boto_span.data['http']['url'], 'https://sqs.us-east-1.amazonaws.com:443/SendMessage') - # Create the Queue: - response = sqs.create_queue( - QueueName='SQS_QUEUE_NAME', - Attributes={ - 'DelaySeconds': '60', - 'MessageRetentionPeriod': '600' - } - ) - assert response['QueueUrl'] - queue_url = response['QueueUrl'] - - with tracer.start_active_span('test'): - response = sqs.send_message( - QueueUrl=queue_url, - DelaySeconds=10, - MessageAttributes={ - 'Website': { - 'DataType': 'String', - 'StringValue': 'https://www.instana.com' + + def test_app_boto3_sqs(self): + with tracer.start_active_span('test'): + self.http_client.request('GET', testenv["wsgi_server"] + '/boto3/sqs') + + spans = tracer.recorder.queued_spans() + self.assertEqual(5, len(spans)) + + filter = lambda span: span.n == "sdk" + test_span = get_first_span_by_filter(spans, filter) + self.assertTrue(test_span) + + filter = lambda span: span.n == "urllib3" + http_span = get_first_span_by_filter(spans, filter) + self.assertTrue(http_span) + + filter = lambda span: span.n == "wsgi" + wsgi_span = get_first_span_by_filter(spans, filter) + self.assertTrue(wsgi_span) + + filter = lambda span: span.n == "boto3" and span.data['boto3']['op'] == 'CreateQueue' + bcq_span = get_first_span_by_filter(spans, filter) + self.assertTrue(bcq_span) + + filter = lambda span: span.n == "boto3" and span.data['boto3']['op'] == 'SendMessage' + bsm_span = get_first_span_by_filter(spans, filter) + self.assertTrue(bsm_span) + + self.assertEqual(http_span.t, test_span.t) + self.assertEqual(http_span.p, test_span.s) + + self.assertEqual(wsgi_span.t, test_span.t) + self.assertEqual(wsgi_span.p, http_span.s) + + self.assertEqual(bcq_span.t, test_span.t) + self.assertEqual(bcq_span.p, wsgi_span.s) + + self.assertEqual(bsm_span.t, test_span.t) + self.assertEqual(bsm_span.p, wsgi_span.s) + + + def test_request_header_capture_before_call(self): + # Create the Queue: + response = self.sqs.create_queue( + QueueName='SQS_QUEUE_NAME', + Attributes={ + 'DelaySeconds': '60', + 'MessageRetentionPeriod': '600' + } + ) + + self.assertTrue(response['QueueUrl']) + + original_extra_http_headers = agent.options.extra_http_headers + agent.options.extra_http_headers = ['X-Capture-This', 'X-Capture-That'] + + # Access the event system on the S3 client + event_system = self.sqs.meta.events + + request_headers = { + 'X-Capture-This': 'this', + 'X-Capture-That': 'that' + } + + # Create a function that adds custom headers + def add_custom_header_before_call(params, **kwargs): + params['headers'].update(request_headers) + + # Register the function to before-call event. + event_system.register('before-call.sqs.SendMessage', add_custom_header_before_call) + + queue_url = response['QueueUrl'] + with tracer.start_active_span('test'): + response = self.sqs.send_message( + QueueUrl=queue_url, + DelaySeconds=10, + MessageAttributes={ + 'Website': { + 'DataType': 'String', + 'StringValue': 'https://www.instana.com' + }, }, - }, - MessageBody=('Monitor any application, service, or request ' - 'with Instana Application Performance Monitoring') + MessageBody=('Monitor any application, service, or request ' + 'with Instana Application Performance Monitoring') + ) + + self.assertTrue(response['MessageId']) + + spans = tracer.recorder.queued_spans() + self.assertEqual(2, len(spans)) + + filter = lambda span: span.n == "sdk" + test_span = get_first_span_by_filter(spans, filter) + self.assertTrue(test_span) + + filter = lambda span: span.n == "boto3" + boto_span = get_first_span_by_filter(spans, filter) + self.assertTrue(boto_span) + + self.assertEqual(boto_span.t, test_span.t) + self.assertEqual(boto_span.p, test_span.s) + + self.assertIsNone(test_span.ec) + + self.assertEqual(boto_span.data['boto3']['op'], 'SendMessage') + self.assertEqual(boto_span.data['boto3']['ep'], 'https://sqs.us-east-1.amazonaws.com') + self.assertEqual(boto_span.data['boto3']['reg'], 'us-east-1') + + payload = {'QueueUrl': 'https://sqs.us-east-1.amazonaws.com/123456789012/SQS_QUEUE_NAME', 'DelaySeconds': 10, + 'MessageAttributes': {'Website': {'DataType': 'String', 'StringValue': 'https://www.instana.com'}}, + 'MessageBody': 'Monitor any application, service, or request with Instana Application Performance Monitoring'} + self.assertDictEqual(boto_span.data['boto3']['payload'], payload) + + self.assertEqual(boto_span.data['http']['status'], 200) + self.assertEqual(boto_span.data['http']['method'], 'POST') + self.assertEqual(boto_span.data['http']['url'], 'https://sqs.us-east-1.amazonaws.com:443/SendMessage') + + self.assertIn("X-Capture-This", boto_span.data["http"]["header"]) + self.assertEqual("this", boto_span.data["http"]["header"]["X-Capture-This"]) + self.assertIn("X-Capture-That", boto_span.data["http"]["header"]) + self.assertEqual("that", boto_span.data["http"]["header"]["X-Capture-That"]) + + agent.options.extra_http_headers = original_extra_http_headers + + + def test_request_header_capture_before_sign(self): + # Create the Queue: + response = self.sqs.create_queue( + QueueName='SQS_QUEUE_NAME', + Attributes={ + 'DelaySeconds': '60', + 'MessageRetentionPeriod': '600' + } ) - assert response['MessageId'] + self.assertTrue(response['QueueUrl']) + + original_extra_http_headers = agent.options.extra_http_headers + agent.options.extra_http_headers = ['X-Custom-1', 'X-Custom-2'] + + # Access the event system on the S3 client + event_system = self.sqs.meta.events + + request_headers = { + 'X-Custom-1': 'Value1', + 'X-Custom-2': 'Value2' + } + + # Create a function that adds custom headers + def add_custom_header_before_sign(request, **kwargs): + for name, value in request_headers.items(): + request.headers.add_header(name, value) + + # Register the function to before-sign event. + event_system.register_first('before-sign.sqs.SendMessage', add_custom_header_before_sign) + + queue_url = response['QueueUrl'] + with tracer.start_active_span('test'): + response = self.sqs.send_message( + QueueUrl=queue_url, + DelaySeconds=10, + MessageAttributes={ + 'Website': { + 'DataType': 'String', + 'StringValue': 'https://www.instana.com' + }, + }, + MessageBody=('Monitor any application, service, or request ' + 'with Instana Application Performance Monitoring') + ) + + self.assertTrue(response['MessageId']) - spans = tracer.recorder.queued_spans() - assert len(spans) == 2 + spans = tracer.recorder.queued_spans() + self.assertEqual(2, len(spans)) - filter = lambda span: span.n == "sdk" - test_span = get_first_span_by_filter(spans, filter) - assert (test_span) + filter = lambda span: span.n == "sdk" + test_span = get_first_span_by_filter(spans, filter) + self.assertTrue(test_span) - filter = lambda span: span.n == "boto3" - boto_span = get_first_span_by_filter(spans, filter) - assert (boto_span) + filter = lambda span: span.n == "boto3" + boto_span = get_first_span_by_filter(spans, filter) + self.assertTrue(boto_span) - assert (boto_span.t == test_span.t) - assert (boto_span.p == test_span.s) + self.assertEqual(boto_span.t, test_span.t) + self.assertEqual(boto_span.p, test_span.s) - assert (test_span.ec is None) - assert (boto_span.ec is None) + self.assertIsNone(test_span.ec) - assert boto_span.data['boto3']['op'] == 'SendMessage' - assert boto_span.data['boto3']['ep'] == 'https://sqs.us-east-1.amazonaws.com' - assert boto_span.data['boto3']['reg'] == 'us-east-1' + self.assertEqual(boto_span.data['boto3']['op'], 'SendMessage') + self.assertEqual(boto_span.data['boto3']['ep'], 'https://sqs.us-east-1.amazonaws.com') + self.assertEqual(boto_span.data['boto3']['reg'], 'us-east-1') - payload = {'QueueUrl': 'https://sqs.us-east-1.amazonaws.com/123456789012/SQS_QUEUE_NAME', 'DelaySeconds': 10, - 'MessageAttributes': {'Website': {'DataType': 'String', 'StringValue': 'https://www.instana.com'}}, - 'MessageBody': 'Monitor any application, service, or request with Instana Application Performance Monitoring'} - assert boto_span.data['boto3']['payload'] == payload + payload = {'QueueUrl': 'https://sqs.us-east-1.amazonaws.com/123456789012/SQS_QUEUE_NAME', 'DelaySeconds': 10, + 'MessageAttributes': {'Website': {'DataType': 'String', 'StringValue': 'https://www.instana.com'}}, + 'MessageBody': 'Monitor any application, service, or request with Instana Application Performance Monitoring'} + self.assertDictEqual(boto_span.data['boto3']['payload'], payload) - assert boto_span.data['http']['status'] == 200 - assert boto_span.data['http']['method'] == 'POST' - assert boto_span.data['http']['url'] == 'https://sqs.us-east-1.amazonaws.com:443/SendMessage' + self.assertEqual(boto_span.data['http']['status'], 200) + self.assertEqual(boto_span.data['http']['method'], 'POST') + self.assertEqual(boto_span.data['http']['url'], 'https://sqs.us-east-1.amazonaws.com:443/SendMessage') + self.assertIn("X-Custom-1", boto_span.data["http"]["header"]) + self.assertEqual("Value1", boto_span.data["http"]["header"]["X-Custom-1"]) + self.assertIn("X-Custom-2", boto_span.data["http"]["header"]) + self.assertEqual("Value2", boto_span.data["http"]["header"]["X-Custom-2"]) + + agent.options.extra_http_headers = original_extra_http_headers + + + def test_response_header_capture(self): + # Create the Queue: + response = self.sqs.create_queue( + QueueName='SQS_QUEUE_NAME', + Attributes={ + 'DelaySeconds': '60', + 'MessageRetentionPeriod': '600' + } + ) -@mock_aws -def test_app_boto3_sqs(http_client): - with tracer.start_active_span('test'): - response = http_client.request('GET', testenv["wsgi_server"] + '/boto3/sqs') + self.assertTrue(response['QueueUrl']) + + original_extra_http_headers = agent.options.extra_http_headers + agent.options.extra_http_headers = ['X-Capture-This-Too', 'X-Capture-That-Too'] + + # Access the event system on the S3 client + event_system = self.sqs.meta.events + + response_headers = { + "X-Capture-This-Too": "this too", + "X-Capture-That-Too": "that too", + } + + # Create a function that sets the custom headers in the after-call event. + def modify_after_call_args(parsed, **kwargs): + parsed['ResponseMetadata']['HTTPHeaders'].update(response_headers) + + # Register the function to an event + event_system.register('after-call.sqs.SendMessage', modify_after_call_args) + + queue_url = response['QueueUrl'] + with tracer.start_active_span('test'): + response = self.sqs.send_message( + QueueUrl=queue_url, + DelaySeconds=10, + MessageAttributes={ + 'Website': { + 'DataType': 'String', + 'StringValue': 'https://www.instana.com' + }, + }, + MessageBody=('Monitor any application, service, or request ' + 'with Instana Application Performance Monitoring') + ) - spans = tracer.recorder.queued_spans() - assert len(spans) == 5 + self.assertTrue(response['MessageId']) - filter = lambda span: span.n == "sdk" - test_span = get_first_span_by_filter(spans, filter) - assert test_span + spans = tracer.recorder.queued_spans() + self.assertEqual(2, len(spans)) - filter = lambda span: span.n == "urllib3" - http_span = get_first_span_by_filter(spans, filter) - assert http_span + filter = lambda span: span.n == "sdk" + test_span = get_first_span_by_filter(spans, filter) + self.assertTrue(test_span) - filter = lambda span: span.n == "wsgi" - wsgi_span = get_first_span_by_filter(spans, filter) - assert wsgi_span + filter = lambda span: span.n == "boto3" + boto_span = get_first_span_by_filter(spans, filter) + self.assertTrue(boto_span) - filter = lambda span: span.n == "boto3" and span.data['boto3']['op'] == 'CreateQueue' - bcq_span = get_first_span_by_filter(spans, filter) - assert bcq_span + self.assertEqual(boto_span.t, test_span.t) + self.assertEqual(boto_span.p, test_span.s) - filter = lambda span: span.n == "boto3" and span.data['boto3']['op'] == 'SendMessage' - bsm_span = get_first_span_by_filter(spans, filter) - assert bsm_span + self.assertIsNone(test_span.ec) - assert http_span.t == test_span.t - assert http_span.p == test_span.s + self.assertEqual(boto_span.data['boto3']['op'], 'SendMessage') + self.assertEqual(boto_span.data['boto3']['ep'], 'https://sqs.us-east-1.amazonaws.com') + self.assertEqual(boto_span.data['boto3']['reg'], 'us-east-1') - assert wsgi_span.t == test_span.t - assert wsgi_span.p == http_span.s + payload = {'QueueUrl': 'https://sqs.us-east-1.amazonaws.com/123456789012/SQS_QUEUE_NAME', 'DelaySeconds': 10, + 'MessageAttributes': {'Website': {'DataType': 'String', 'StringValue': 'https://www.instana.com'}}, + 'MessageBody': 'Monitor any application, service, or request with Instana Application Performance Monitoring'} + self.assertDictEqual(boto_span.data['boto3']['payload'], payload) - assert bcq_span.t == test_span.t - assert bcq_span.p == wsgi_span.s + self.assertEqual(boto_span.data['http']['status'], 200) + self.assertEqual(boto_span.data['http']['method'], 'POST') + self.assertEqual(boto_span.data['http']['url'], 'https://sqs.us-east-1.amazonaws.com:443/SendMessage') - assert bsm_span.t == test_span.t - assert bsm_span.p == wsgi_span.s + self.assertIn("X-Capture-This-Too", boto_span.data["http"]["header"]) + self.assertEqual("this too", boto_span.data["http"]["header"]["X-Capture-This-Too"]) + self.assertIn("X-Capture-That-Too", boto_span.data["http"]["header"]) + self.assertEqual("that too", boto_span.data["http"]["header"]["X-Capture-That-Too"]) + + agent.options.extra_http_headers = original_extra_http_headers From 80c38f1b13726f0f61be3db966d7ae8e5766286c Mon Sep 17 00:00:00 2001 From: Varsha GS Date: Tue, 6 Feb 2024 14:26:01 +0530 Subject: [PATCH 07/12] boto3: test lambda invoke method Signed-off-by: Varsha GS --- tests/clients/boto3/test_boto3_lambda.py | 143 ++++++++++++++--------- 1 file changed, 85 insertions(+), 58 deletions(-) diff --git a/tests/clients/boto3/test_boto3_lambda.py b/tests/clients/boto3/test_boto3_lambda.py index 4c45af0e..03b3febf 100644 --- a/tests/clients/boto3/test_boto3_lambda.py +++ b/tests/clients/boto3/test_boto3_lambda.py @@ -2,8 +2,11 @@ # (c) Copyright Instana Inc. 2020 from __future__ import absolute_import +from io import BytesIO +from zipfile import ZipFile +import unittest +import json -import os import boto3 import pytest @@ -12,64 +15,88 @@ if sys.version_info >= (3, 8): from moto import mock_aws else: - from moto import mock_sqs as mock_aws + from moto import mock_lambda as mock_aws from instana.singletons import tracer from ...helpers import get_first_span_by_filter - -@pytest.fixture(scope='function') -def aws_credentials(): - """Mocked AWS Credentials for moto.""" - os.environ['AWS_ACCESS_KEY_ID'] = 'testing' - os.environ['AWS_SECRET_ACCESS_KEY'] = 'testing' - os.environ['AWS_SECURITY_TOKEN'] = 'testing' - os.environ['AWS_SESSION_TOKEN'] = 'testing' - - -@pytest.fixture(scope='function') -def aws_lambda(aws_credentials): - with mock_aws(): - yield boto3.client('lambda', region_name='us-east-1') - - -def setup_method(): - """ Clear all spans before a test run """ - tracer.recorder.clear_spans() - - -@pytest.mark.skip("Lambda mocking requires docker") -def test_lambda_invoke(aws_lambda): - result = None - - with tracer.start_active_span('test'): - result = aws_lambda.invoke(FunctionName='arn:aws:lambda:us-west-1:410797082306:function:CanaryInACoalMine') - - assert result - assert len(result['Buckets']) == 1 - assert result['Buckets'][0]['Name'] == 'aws_bucket_name' - - spans = tracer.recorder.queued_spans() - assert len(spans) == 2 - - filter = lambda span: span.n == "sdk" - test_span = get_first_span_by_filter(spans, filter) - assert (test_span) - - filter = lambda span: span.n == "boto3" - boto_span = get_first_span_by_filter(spans, filter) - assert (boto_span) - - assert (boto_span.t == test_span.t) - assert (boto_span.p == test_span.s) - - assert (test_span.ec is None) - assert (boto_span.ec is None) - - assert boto_span.data['boto3']['op'] == 'CreateBucket' - assert boto_span.data['boto3']['ep'] == 'https://s3.amazonaws.com' - assert boto_span.data['boto3']['reg'] == 'us-east-1' - assert boto_span.data['boto3']['payload'] == {'Bucket': 'aws_bucket_name'} - assert boto_span.data['http']['status'] == 200 - assert boto_span.data['http']['method'] == 'POST' - assert boto_span.data['http']['url'] == 'https://s3.amazonaws.com:443/CreateBucket' +class TestLambda(unittest.TestCase): + def _get_role(self): + iam = boto3.client("iam", region_name=self.lambda_region) + return iam.create_role( + RoleName="my-role", + AssumeRolePolicyDocument="some policy" + )["Role"]["Arn"] + + def _process_lambda(self, func_str): + zip_output = BytesIO() + with ZipFile(zip_output, "w") as zip_file: + zip_file.writestr("lambda_function.py", func_str) + return zip_output.getvalue() + + def _get_test_zip_file(self): + pfunc = """ +def lambda_handler(event, context): + print("custom log event") + return {"message": "success"} +""" + return self._process_lambda(pfunc) + + def setUp(self): + """ Clear all spans before a test run """ + self.recorder = tracer.recorder + self.recorder.clear_spans() + self.mock = mock_aws() + self.mock.start() + self.lambda_region = "us-east-1" + self.aws_lambda = boto3.client('lambda', region_name=self.lambda_region) + self.function_name = "myfunc" + self.aws_lambda.create_function( + FunctionName=self.function_name, + Runtime="python3.9", + Role=self._get_role(), + Handler="lambda_function.lambda_handler", + Code={"ZipFile": self._get_test_zip_file()} + ) + + def tearDown(self): + # Stop Moto after each test + self.mock.stop() + + + @pytest.mark.skip("Lambda mocking requires docker") + def test_lambda_invoke(self): + with tracer.start_active_span('test'): + result = self.aws_lambda.invoke(FunctionName=self.function_name) + + self.assertEqual(result["StatusCode"], 200) + payload = json.loads(result["Payload"].read().decode("utf-8")) + self.assertIn("message", payload) + self.assertEqual("success", payload["message"]) + + spans = tracer.recorder.queued_spans() + self.assertEqual(2, len(spans)) + + filter = lambda span: span.n == "sdk" + test_span = get_first_span_by_filter(spans, filter) + self.assertTrue(test_span) + + filter = lambda span: span.n == "boto3" + boto_span = get_first_span_by_filter(spans, filter) + self.assertTrue(boto_span) + + self.assertEqual(boto_span.t, test_span.t) + self.assertEqual(boto_span.p, test_span.s) + + self.assertIsNone(test_span.ec) + self.assertIsNone(boto_span.ec) + + self.assertEqual(boto_span.data['boto3']['op'], 'Invoke') + endpoint = f'https://lambda.{self.lambda_region}.amazonaws.com' + self.assertEqual(boto_span.data['boto3']['ep'], endpoint) + self.assertEqual(boto_span.data['boto3']['reg'], 'us-east-1') + self.assertIn('FunctionName', boto_span.data['boto3']['payload']) + self.assertEqual(boto_span.data['boto3']['payload']['FunctionName'], self.function_name) + self.assertEqual(boto_span.data['http']['status'], 200) + self.assertEqual(boto_span.data['http']['method'], 'POST') + self.assertEqual(boto_span.data['http']['url'], f'{endpoint}:443/Invoke') From f0f2a7598f28d89dddf01219cd2d37cde08efedf Mon Sep 17 00:00:00 2001 From: Varsha GS Date: Tue, 6 Feb 2024 16:49:30 +0530 Subject: [PATCH 08/12] boto3: setting use_docker to false Signed-off-by: Varsha GS --- tests/clients/boto3/test_boto3_lambda.py | 41 ++++-------------------- 1 file changed, 6 insertions(+), 35 deletions(-) diff --git a/tests/clients/boto3/test_boto3_lambda.py b/tests/clients/boto3/test_boto3_lambda.py index 03b3febf..d449f40a 100644 --- a/tests/clients/boto3/test_boto3_lambda.py +++ b/tests/clients/boto3/test_boto3_lambda.py @@ -21,58 +21,29 @@ from ...helpers import get_first_span_by_filter class TestLambda(unittest.TestCase): - def _get_role(self): - iam = boto3.client("iam", region_name=self.lambda_region) - return iam.create_role( - RoleName="my-role", - AssumeRolePolicyDocument="some policy" - )["Role"]["Arn"] - - def _process_lambda(self, func_str): - zip_output = BytesIO() - with ZipFile(zip_output, "w") as zip_file: - zip_file.writestr("lambda_function.py", func_str) - return zip_output.getvalue() - - def _get_test_zip_file(self): - pfunc = """ -def lambda_handler(event, context): - print("custom log event") - return {"message": "success"} -""" - return self._process_lambda(pfunc) - def setUp(self): """ Clear all spans before a test run """ self.recorder = tracer.recorder self.recorder.clear_spans() - self.mock = mock_aws() + self.mock = mock_aws(config={"lambda": {"use_docker": False}}) self.mock.start() self.lambda_region = "us-east-1" self.aws_lambda = boto3.client('lambda', region_name=self.lambda_region) self.function_name = "myfunc" - self.aws_lambda.create_function( - FunctionName=self.function_name, - Runtime="python3.9", - Role=self._get_role(), - Handler="lambda_function.lambda_handler", - Code={"ZipFile": self._get_test_zip_file()} - ) def tearDown(self): # Stop Moto after each test self.mock.stop() - @pytest.mark.skip("Lambda mocking requires docker") def test_lambda_invoke(self): with tracer.start_active_span('test'): - result = self.aws_lambda.invoke(FunctionName=self.function_name) + result = self.aws_lambda.invoke(FunctionName=self.function_name, Payload=json.dumps({"message": "success"})) self.assertEqual(result["StatusCode"], 200) - payload = json.loads(result["Payload"].read().decode("utf-8")) - self.assertIn("message", payload) - self.assertEqual("success", payload["message"]) + result_payload = json.loads(result["Payload"].read().decode("utf-8")) + self.assertIn("message", result_payload) + self.assertEqual("success", result_payload["message"]) spans = tracer.recorder.queued_spans() self.assertEqual(2, len(spans)) @@ -94,7 +65,7 @@ def test_lambda_invoke(self): self.assertEqual(boto_span.data['boto3']['op'], 'Invoke') endpoint = f'https://lambda.{self.lambda_region}.amazonaws.com' self.assertEqual(boto_span.data['boto3']['ep'], endpoint) - self.assertEqual(boto_span.data['boto3']['reg'], 'us-east-1') + self.assertEqual(boto_span.data['boto3']['reg'], self.lambda_region) self.assertIn('FunctionName', boto_span.data['boto3']['payload']) self.assertEqual(boto_span.data['boto3']['payload']['FunctionName'], self.function_name) self.assertEqual(boto_span.data['http']['status'], 200) From 09f51b6ab3574154de4e01b8a7a8a1cc5226dff0 Mon Sep 17 00:00:00 2001 From: Varsha GS Date: Tue, 6 Feb 2024 17:25:41 +0530 Subject: [PATCH 09/12] capture headers for lambda Signed-off-by: Varsha GS --- tests/clients/boto3/test_boto3_lambda.py | 198 ++++++++++++++++++++++- 1 file changed, 191 insertions(+), 7 deletions(-) diff --git a/tests/clients/boto3/test_boto3_lambda.py b/tests/clients/boto3/test_boto3_lambda.py index d449f40a..f3e3c2cb 100644 --- a/tests/clients/boto3/test_boto3_lambda.py +++ b/tests/clients/boto3/test_boto3_lambda.py @@ -2,24 +2,21 @@ # (c) Copyright Instana Inc. 2020 from __future__ import absolute_import -from io import BytesIO -from zipfile import ZipFile import unittest import json import boto3 -import pytest - # TODO: Remove branching when we drop support for Python 3.7 -import sys -if sys.version_info >= (3, 8): +from sys import version_info +if version_info >= (3, 8): from moto import mock_aws else: from moto import mock_lambda as mock_aws -from instana.singletons import tracer +from instana.singletons import tracer, agent from ...helpers import get_first_span_by_filter +@unittest.skip(version_info < (3, 8), "Test skipped on Python < 3.8") class TestLambda(unittest.TestCase): def setUp(self): """ Clear all spans before a test run """ @@ -71,3 +68,190 @@ def test_lambda_invoke(self): self.assertEqual(boto_span.data['http']['status'], 200) self.assertEqual(boto_span.data['http']['method'], 'POST') self.assertEqual(boto_span.data['http']['url'], f'{endpoint}:443/Invoke') + + + def test_request_header_capture_before_call(self): + original_extra_http_headers = agent.options.extra_http_headers + agent.options.extra_http_headers = ['X-Capture-This', 'X-Capture-That'] + + # Access the event system on the S3 client + event_system = self.aws_lambda.meta.events + + request_headers = { + 'X-Capture-This': 'this', + 'X-Capture-That': 'that' + } + + # Create a function that adds custom headers + def add_custom_header_before_call(params, **kwargs): + params['headers'].update(request_headers) + + # Register the function to before-call event. + event_system.register('before-call.lambda.Invoke', add_custom_header_before_call) + + with tracer.start_active_span('test'): + result = self.aws_lambda.invoke(FunctionName=self.function_name, Payload=json.dumps({"message": "success"})) + + self.assertEqual(result["StatusCode"], 200) + result_payload = json.loads(result["Payload"].read().decode("utf-8")) + self.assertIn("message", result_payload) + self.assertEqual("success", result_payload["message"]) + + spans = tracer.recorder.queued_spans() + self.assertEqual(2, len(spans)) + + filter = lambda span: span.n == "sdk" + test_span = get_first_span_by_filter(spans, filter) + self.assertTrue(test_span) + + filter = lambda span: span.n == "boto3" + boto_span = get_first_span_by_filter(spans, filter) + self.assertTrue(boto_span) + + self.assertEqual(boto_span.t, test_span.t) + self.assertEqual(boto_span.p, test_span.s) + + self.assertIsNone(test_span.ec) + self.assertIsNone(boto_span.ec) + + self.assertEqual(boto_span.data['boto3']['op'], 'Invoke') + endpoint = f'https://lambda.{self.lambda_region}.amazonaws.com' + self.assertEqual(boto_span.data['boto3']['ep'], endpoint) + self.assertEqual(boto_span.data['boto3']['reg'], self.lambda_region) + self.assertIn('FunctionName', boto_span.data['boto3']['payload']) + self.assertEqual(boto_span.data['boto3']['payload']['FunctionName'], self.function_name) + self.assertEqual(boto_span.data['http']['status'], 200) + self.assertEqual(boto_span.data['http']['method'], 'POST') + self.assertEqual(boto_span.data['http']['url'], f'{endpoint}:443/Invoke') + + self.assertIn("X-Capture-This", boto_span.data["http"]["header"]) + self.assertEqual("this", boto_span.data["http"]["header"]["X-Capture-This"]) + self.assertIn("X-Capture-That", boto_span.data["http"]["header"]) + self.assertEqual("that", boto_span.data["http"]["header"]["X-Capture-That"]) + + agent.options.extra_http_headers = original_extra_http_headers + + + def test_request_header_capture_before_sign(self): + original_extra_http_headers = agent.options.extra_http_headers + agent.options.extra_http_headers = ['X-Custom-1', 'X-Custom-2'] + + # Access the event system on the S3 client + event_system = self.aws_lambda.meta.events + + request_headers = { + 'X-Custom-1': 'Value1', + 'X-Custom-2': 'Value2' + } + + # Create a function that adds custom headers + def add_custom_header_before_sign(request, **kwargs): + for name, value in request_headers.items(): + request.headers.add_header(name, value) + + # Register the function to before-sign event. + event_system.register_first('before-sign.lambda.Invoke', add_custom_header_before_sign) + + with tracer.start_active_span('test'): + result = self.aws_lambda.invoke(FunctionName=self.function_name, Payload=json.dumps({"message": "success"})) + + self.assertEqual(result["StatusCode"], 200) + result_payload = json.loads(result["Payload"].read().decode("utf-8")) + self.assertIn("message", result_payload) + self.assertEqual("success", result_payload["message"]) + + spans = tracer.recorder.queued_spans() + self.assertEqual(2, len(spans)) + + filter = lambda span: span.n == "sdk" + test_span = get_first_span_by_filter(spans, filter) + self.assertTrue(test_span) + + filter = lambda span: span.n == "boto3" + boto_span = get_first_span_by_filter(spans, filter) + self.assertTrue(boto_span) + + self.assertEqual(boto_span.t, test_span.t) + self.assertEqual(boto_span.p, test_span.s) + + self.assertIsNone(test_span.ec) + self.assertIsNone(boto_span.ec) + + self.assertEqual(boto_span.data['boto3']['op'], 'Invoke') + endpoint = f'https://lambda.{self.lambda_region}.amazonaws.com' + self.assertEqual(boto_span.data['boto3']['ep'], endpoint) + self.assertEqual(boto_span.data['boto3']['reg'], self.lambda_region) + self.assertIn('FunctionName', boto_span.data['boto3']['payload']) + self.assertEqual(boto_span.data['boto3']['payload']['FunctionName'], self.function_name) + self.assertEqual(boto_span.data['http']['status'], 200) + self.assertEqual(boto_span.data['http']['method'], 'POST') + self.assertEqual(boto_span.data['http']['url'], f'{endpoint}:443/Invoke') + + self.assertIn("X-Custom-1", boto_span.data["http"]["header"]) + self.assertEqual("Value1", boto_span.data["http"]["header"]["X-Custom-1"]) + self.assertIn("X-Custom-2", boto_span.data["http"]["header"]) + self.assertEqual("Value2", boto_span.data["http"]["header"]["X-Custom-2"]) + + agent.options.extra_http_headers = original_extra_http_headers + + + def test_response_header_capture(self): + original_extra_http_headers = agent.options.extra_http_headers + agent.options.extra_http_headers = ['X-Capture-This-Too', 'X-Capture-That-Too'] + + # Access the event system on the S3 client + event_system = self.aws_lambda.meta.events + + response_headers = { + "X-Capture-This-Too": "this too", + "X-Capture-That-Too": "that too", + } + + # Create a function that sets the custom headers in the after-call event. + def modify_after_call_args(parsed, **kwargs): + parsed['ResponseMetadata']['HTTPHeaders'].update(response_headers) + + # Register the function to an event + event_system.register('after-call.lambda.Invoke', modify_after_call_args) + + with tracer.start_active_span('test'): + result = self.aws_lambda.invoke(FunctionName=self.function_name, Payload=json.dumps({"message": "success"})) + + self.assertEqual(result["StatusCode"], 200) + result_payload = json.loads(result["Payload"].read().decode("utf-8")) + self.assertIn("message", result_payload) + self.assertEqual("success", result_payload["message"]) + + spans = tracer.recorder.queued_spans() + self.assertEqual(2, len(spans)) + + filter = lambda span: span.n == "sdk" + test_span = get_first_span_by_filter(spans, filter) + self.assertTrue(test_span) + + filter = lambda span: span.n == "boto3" + boto_span = get_first_span_by_filter(spans, filter) + self.assertTrue(boto_span) + + self.assertEqual(boto_span.t, test_span.t) + self.assertEqual(boto_span.p, test_span.s) + + self.assertIsNone(test_span.ec) + self.assertIsNone(boto_span.ec) + + self.assertEqual(boto_span.data['boto3']['op'], 'Invoke') + endpoint = f'https://lambda.{self.lambda_region}.amazonaws.com' + self.assertEqual(boto_span.data['boto3']['ep'], endpoint) + self.assertEqual(boto_span.data['boto3']['reg'], self.lambda_region) + self.assertIn('FunctionName', boto_span.data['boto3']['payload']) + self.assertEqual(boto_span.data['boto3']['payload']['FunctionName'], self.function_name) + self.assertEqual(boto_span.data['http']['status'], 200) + self.assertEqual(boto_span.data['http']['method'], 'POST') + self.assertEqual(boto_span.data['http']['url'], f'{endpoint}:443/Invoke') + + self.assertIn("X-Capture-This-Too", boto_span.data["http"]["header"]) + self.assertEqual("this too", boto_span.data["http"]["header"]["X-Capture-This-Too"]) + self.assertIn("X-Capture-That-Too", boto_span.data["http"]["header"]) + self.assertEqual("that too", boto_span.data["http"]["header"]["X-Capture-That-Too"]) + + agent.options.extra_http_headers = original_extra_http_headers From b586b355b6e27cb43f57ad80e5329ab945b012ef Mon Sep 17 00:00:00 2001 From: Varsha GS Date: Tue, 6 Feb 2024 17:29:22 +0530 Subject: [PATCH 10/12] boto3: remove explicit credential mocking Signed-off-by: Varsha GS --- tests/clients/boto3/test_boto3_s3.py | 16 ---------------- tests/clients/boto3/test_boto3_ses.py | 16 ---------------- tests/clients/boto3/test_boto3_sqs.py | 16 ---------------- 3 files changed, 48 deletions(-) diff --git a/tests/clients/boto3/test_boto3_s3.py b/tests/clients/boto3/test_boto3_s3.py index 95814fe5..33446a40 100644 --- a/tests/clients/boto3/test_boto3_s3.py +++ b/tests/clients/boto3/test_boto3_s3.py @@ -22,33 +22,17 @@ class TestS3(unittest.TestCase): - def set_aws_credentials(self): - """ Mocked AWS Credentials for moto """ - for variable_name in self.variable_names: - os.environ[variable_name] = "testing" - def setUp(self): """ Clear all spans before a test run """ self.recorder = tracer.recorder self.recorder.clear_spans() - self.variable_names = ( - "AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", - "AWS_SECURITY_TOKEN", "AWS_SESSION_TOKEN" - ) - self.set_aws_credentials() self.mock = mock_aws() self.mock.start() self.s3 = boto3.client('s3', region_name='us-east-1') - def unset_aws_credentials(self): - """ Reset all environment variables of consequence """ - for variable_name in self.variable_names: - os.environ.pop(variable_name, None) - def tearDown(self): # Stop Moto after each test self.mock.stop() - self.unset_aws_credentials() def test_vanilla_create_bucket(self): diff --git a/tests/clients/boto3/test_boto3_ses.py b/tests/clients/boto3/test_boto3_ses.py index b8b90dd4..54511cc0 100644 --- a/tests/clients/boto3/test_boto3_ses.py +++ b/tests/clients/boto3/test_boto3_ses.py @@ -20,33 +20,17 @@ pwd = os.path.dirname(os.path.abspath(__file__)) class TestSes(unittest.TestCase): - def set_aws_credentials(self): - """ Mocked AWS Credentials for moto """ - for variable_name in self.variable_names: - os.environ[variable_name] = "testing" - def setUp(self): """ Clear all spans before a test run """ self.recorder = tracer.recorder self.recorder.clear_spans() - self.variable_names = ( - "AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", - "AWS_SECURITY_TOKEN", "AWS_SESSION_TOKEN" - ) - self.set_aws_credentials() self.mock = mock_aws() self.mock.start() self.ses = boto3.client('ses', region_name='us-east-1') - def unset_aws_credentials(self): - """ Reset all environment variables of consequence """ - for variable_name in self.variable_names: - os.environ.pop(variable_name, None) - def tearDown(self): # Stop Moto after each test self.mock.stop() - self.unset_aws_credentials() def test_vanilla_verify_email(self): diff --git a/tests/clients/boto3/test_boto3_sqs.py b/tests/clients/boto3/test_boto3_sqs.py index f04d7502..990776a6 100644 --- a/tests/clients/boto3/test_boto3_sqs.py +++ b/tests/clients/boto3/test_boto3_sqs.py @@ -23,34 +23,18 @@ class TestSqs(unittest.TestCase): - def set_aws_credentials(self): - """ Mocked AWS Credentials for moto """ - for variable_name in self.variable_names: - os.environ[variable_name] = "testing" - def setUp(self): """ Clear all spans before a test run """ self.recorder = tracer.recorder self.recorder.clear_spans() - self.variable_names = ( - "AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", - "AWS_SECURITY_TOKEN", "AWS_SESSION_TOKEN" - ) - self.set_aws_credentials() self.mock = mock_aws() self.mock.start() self.sqs = boto3.client('sqs', region_name='us-east-1') self.http_client = urllib3.PoolManager() - def unset_aws_credentials(self): - """ Reset all environment variables of consequence """ - for variable_name in self.variable_names: - os.environ.pop(variable_name, None) - def tearDown(self): # Stop Moto after each test self.mock.stop() - self.unset_aws_credentials() def test_vanilla_create_queue(self): From 06268d2cc6849ca47dde6e74d9c73d2c3fb00f01 Mon Sep 17 00:00:00 2001 From: Varsha GS Date: Tue, 6 Feb 2024 17:31:59 +0530 Subject: [PATCH 11/12] skip --> skipIf Signed-off-by: Varsha GS --- tests/clients/boto3/test_boto3_lambda.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/clients/boto3/test_boto3_lambda.py b/tests/clients/boto3/test_boto3_lambda.py index f3e3c2cb..82153e69 100644 --- a/tests/clients/boto3/test_boto3_lambda.py +++ b/tests/clients/boto3/test_boto3_lambda.py @@ -16,7 +16,7 @@ from instana.singletons import tracer, agent from ...helpers import get_first_span_by_filter -@unittest.skip(version_info < (3, 8), "Test skipped on Python < 3.8") +@unittest.skipIf(version_info < (3, 8), "Test skipped on Python < 3.8") class TestLambda(unittest.TestCase): def setUp(self): """ Clear all spans before a test run """ From 0902f06b770f5e3eb63a71921e3c6ed7c7d15337 Mon Sep 17 00:00:00 2001 From: Varsha GS Date: Wed, 7 Feb 2024 14:41:23 +0530 Subject: [PATCH 12/12] minor fixes Signed-off-by: Varsha GS --- instana/instrumentation/boto3_inst.py | 2 +- tests/clients/boto3/test_boto3_lambda.py | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/instana/instrumentation/boto3_inst.py b/instana/instrumentation/boto3_inst.py index 88d7c4bc..7d1b8163 100644 --- a/instana/instrumentation/boto3_inst.py +++ b/instana/instrumentation/boto3_inst.py @@ -17,7 +17,7 @@ from boto3.s3 import inject def extract_custom_headers(span, headers): - if agent.options.extra_http_headers is None: + if agent.options.extra_http_headers is None or headers is None: return try: for custom_header in agent.options.extra_http_headers: diff --git a/tests/clients/boto3/test_boto3_lambda.py b/tests/clients/boto3/test_boto3_lambda.py index 82153e69..83361606 100644 --- a/tests/clients/boto3/test_boto3_lambda.py +++ b/tests/clients/boto3/test_boto3_lambda.py @@ -10,8 +10,6 @@ from sys import version_info if version_info >= (3, 8): from moto import mock_aws -else: - from moto import mock_lambda as mock_aws from instana.singletons import tracer, agent from ...helpers import get_first_span_by_filter