diff --git a/instana/instrumentation/boto3_inst.py b/instana/instrumentation/boto3_inst.py index cf6511b5..7d1b8163 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 or 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.auth", "SigV4Auth.add_auth") + def emit_add_auth_with_instana(wrapped, instance, args, kwargs): + active_tracer = get_active_tracer() + + # 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, args[0].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_lambda.py b/tests/clients/boto3/test_boto3_lambda.py index 4c45af0e..83361606 100644 --- a/tests/clients/boto3/test_boto3_lambda.py +++ b/tests/clients/boto3/test_boto3_lambda.py @@ -2,74 +2,254 @@ # (c) Copyright Instana Inc. 2020 from __future__ import absolute_import +import unittest +import json -import os 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_sqs as mock_aws -from instana.singletons import tracer +from instana.singletons import tracer, agent from ...helpers import get_first_span_by_filter +@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 """ + self.recorder = tracer.recorder + self.recorder.clear_spans() + 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" + + def tearDown(self): + # Stop Moto after each test + self.mock.stop() + + + def test_lambda_invoke(self): + 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') + + + 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'] -@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) + # Access the event system on the S3 client + event_system = self.aws_lambda.meta.events - 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' + 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 diff --git a/tests/clients/boto3/test_boto3_s3.py b/tests/clients/boto3/test_boto3_s3.py index 8ba64f89..33446a40 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,8 +11,9 @@ from moto import mock_aws else: from moto import mock_s3 as mock_aws +import boto3 -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__)) @@ -22,257 +21,422 @@ 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 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.s3 = boto3.client('s3', region_name='us-east-1') + + def tearDown(self): + # Stop Moto after each test + self.mock.stop() + + + def test_vanilla_create_bucket(self): + 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') + + + def test_s3_create_bucket(self): + 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) + + 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') + + + def test_s3_list_buckets(self): + with tracer.start_active_span('test'): + result = self.s3.list_buckets() + + self.assertEqual(0, len(result['Buckets'])) + 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.assertIsNone(boto_span.ec) + + 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') -@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 test_s3_vanilla_upload_file(self): + 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) -@pytest.fixture(scope='function') -def s3(aws_credentials): - with mock_aws(): - yield boto3.client('s3', region_name='us-east-1') + def test_s3_upload_file(self): + object_name = 'aws_key_name' + bucket_name = '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") + self.s3.create_bucket(Bucket=bucket_name) - result = s3.list_buckets() - assert len(result['Buckets']) == 1 - assert result['Buckets'][0]['Name'] == 'aws_bucket_name' + with tracer.start_active_span('test'): + self.s3.upload_file(upload_filename, bucket_name, object_name) + spans = tracer.recorder.queued_spans() + self.assertEqual(2, len(spans)) -def test_s3_create_bucket(s3): - result = None - with tracer.start_active_span('test'): - result = s3.create_bucket(Bucket="aws_bucket_name") + filter = lambda span: span.n == "sdk" + test_span = get_first_span_by_filter(spans, filter) + self.assertTrue(test_span) - result = s3.list_buckets() - assert len(result['Buckets']) == 1 - assert result['Buckets'][0]['Name'] == 'aws_bucket_name' + filter = lambda span: span.n == "boto3" + boto_span = get_first_span_by_filter(spans, filter) + self.assertTrue(boto_span) - spans = tracer.recorder.queued_spans() - assert len(spans) == 2 + self.assertEqual(boto_span.t, test_span.t) + self.assertEqual(boto_span.p, test_span.s) - filter = lambda span: span.n == "sdk" - test_span = get_first_span_by_filter(spans, filter) - assert (test_span) + self.assertIsNone(test_span.ec) + self.assertIsNone(boto_span.ec) - filter = lambda span: span.n == "boto3" - boto_span = get_first_span_by_filter(spans, filter) - assert (boto_span) + 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.t == test_span.t) - assert (boto_span.p == test_span.s) - assert (test_span.ec is None) - assert (boto_span.ec is None) + def test_s3_upload_file_obj(self): + object_name = 'aws_key_name' + bucket_name = 'aws_bucket_name' - 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' + self.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) -def test_s3_list_buckets(s3): - result = None - with tracer.start_active_span('test'): - result = s3.list_buckets() + spans = tracer.recorder.queued_spans() + self.assertEqual(2, len(spans)) - result = s3.list_buckets() - assert len(result['Buckets']) == 0 - assert result['ResponseMetadata']['HTTPStatusCode'] is 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) + self.assertIsNone(boto_span.ec) - assert (boto_span.t == test_span.t) - assert (boto_span.p == test_span.s) + 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 (test_span.ec is None) - assert (boto_span.ec is None) - 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_download_file(self): + 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) -def test_s3_vanilla_upload_file(s3): - object_name = 'aws_key_name' - bucket_name = 'aws_bucket_name' + with tracer.start_active_span('test'): + self.s3.download_file(bucket_name, object_name, download_target_filename) - s3.create_bucket(Bucket=bucket_name) - result = s3.upload_file(upload_filename, bucket_name, object_name) - assert result is None + 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) -def test_s3_upload_file(s3): - object_name = 'aws_key_name' - bucket_name = 'aws_bucket_name' + filter = lambda span: span.n == "boto3" + boto_span = get_first_span_by_filter(spans, filter) + self.assertTrue(boto_span) - s3.create_bucket(Bucket=bucket_name) + self.assertEqual(boto_span.t, test_span.t) + self.assertEqual(boto_span.p, test_span.s) - result = None - with tracer.start_active_span('test'): - s3.upload_file(upload_filename, bucket_name, object_name) + self.assertIsNone(test_span.ec) + self.assertIsNone(boto_span.ec) - spans = tracer.recorder.queued_spans() - assert len(spans) == 2 + 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') - 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) + def test_s3_download_file_obj(self): + object_name = 'aws_key_name' + bucket_name = 'aws_bucket_name' - assert (boto_span.t == test_span.t) - assert (boto_span.p == test_span.s) + self.s3.create_bucket(Bucket=bucket_name) + self.s3.upload_file(upload_filename, bucket_name, object_name) - assert (test_span.ec is None) - assert (boto_span.ec is None) + with tracer.start_active_span('test'): + with open(download_target_filename, "wb") as fd: + self.s3.download_fileobj(bucket_name, object_name, fd) - 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' + 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) -def test_s3_upload_file_obj(s3): - object_name = 'aws_key_name' - bucket_name = 'aws_bucket_name' + filter = lambda span: span.n == "boto3" + boto_span = get_first_span_by_filter(spans, filter) + self.assertTrue(boto_span) - s3.create_bucket(Bucket=bucket_name) + self.assertEqual(boto_span.t, test_span.t) + self.assertEqual(boto_span.p, test_span.s) - result = None - with tracer.start_active_span('test'): - with open(upload_filename, "rb") as fd: - s3.upload_fileobj(fd, bucket_name, object_name) + self.assertIsNone(test_span.ec) + self.assertIsNone(boto_span.ec) - spans = tracer.recorder.queued_spans() - assert len(spans) == 2 + 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') - 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) + def test_request_header_capture_before_call(self): - assert (boto_span.t == test_span.t) - assert (boto_span.p == test_span.s) + original_extra_http_headers = agent.options.extra_http_headers + agent.options.extra_http_headers = ['X-Capture-This', 'X-Capture-That'] - assert (test_span.ec is None) - assert (boto_span.ec is None) + # Access the event system on the S3 client + event_system = self.s3.meta.events - 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' + 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) -def test_s3_download_file(s3): - object_name = 'aws_key_name' - bucket_name = 'aws_bucket_name' + # Register the function to before-call event. + event_system.register('before-call.s3.CreateBucket', add_custom_header_before_call) - s3.create_bucket(Bucket=bucket_name) - s3.upload_file(upload_filename, bucket_name, object_name) + 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') - 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'], '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'] == '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' + 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_s3_download_file_obj(s3): - object_name = 'aws_key_name' - bucket_name = 'aws_bucket_name' + def test_request_header_capture_before_sign(self): - s3.create_bucket(Bucket=bucket_name) - s3.upload_file(upload_filename, bucket_name, object_name) + original_extra_http_headers = agent.options.extra_http_headers + agent.options.extra_http_headers = ['X-Custom-1', 'X-Custom-2'] - result = None - with tracer.start_active_span('test'): - with open(download_target_filename, "wb") as fd: - s3.download_fileobj(bucket_name, object_name, fd) + # Access the event system on the S3 client + event_system = self.s3.meta.events - spans = tracer.recorder.queued_spans() - assert len(spans) == 2 + request_headers = { + 'X-Custom-1': 'Value1', + 'X-Custom-2': 'Value2' + } - filter = lambda span: span.n == "sdk" - test_span = get_first_span_by_filter(spans, filter) - assert (test_span) + # 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) - filter = lambda span: span.n == "boto3" - boto_span = get_first_span_by_filter(spans, filter) - assert (boto_span) + # Register the function to before-sign event. + event_system.register_first('before-sign.s3.CreateBucket', add_custom_header_before_sign) - assert (boto_span.t == test_span.t) - assert (boto_span.p == test_span.s) + 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) - assert (test_span.ec is None) - assert (boto_span.ec is None) + 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-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.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.s3.CreateBucket', modify_after_call_args) + + 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) - 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' + 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 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..54511cc0 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,232 @@ 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 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.ses = boto3.client('ses', region_name='us-east-1') + def tearDown(self): + # Stop Moto after each test + self.mock.stop() -@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 test_vanilla_verify_email(self): + result = self.ses.verify_email_identity(EmailAddress='pglombardo+instana299@tuta.io') + self.assertEqual(result['ResponseMetadata']['HTTPStatusCode'], 200) -@pytest.fixture(scope='function') -def ses(aws_credentials): - with mock_aws(): - yield boto3.client('ses', region_name='us-east-1') + def test_verify_email(self): + with tracer.start_active_span('test'): + result = self.ses.verify_email_identity(EmailAddress='pglombardo+instana299@tuta.io') -def test_vanilla_verify_email(ses): - result = ses.verify_email_identity(EmailAddress='pglombardo+instana299@tuta.io') - assert result['ResponseMetadata']['HTTPStatusCode'] == 200 + self.assertEqual(result['ResponseMetadata']['HTTPStatusCode'], 200) + spans = tracer.recorder.queued_spans() + self.assertEqual(2, len(spans)) -def test_verify_email(ses): - result = None + filter = lambda span: span.n == "sdk" + test_span = get_first_span_by_filter(spans, filter) + self.assertTrue(test_span) - with tracer.start_active_span('test'): - result = ses.verify_email_identity(EmailAddress='pglombardo+instana299@tuta.io') + filter = lambda span: span.n == "boto3" + boto_span = get_first_span_by_filter(spans, filter) + self.assertTrue(boto_span) - assert result['ResponseMetadata']['HTTPStatusCode'] == 200 + self.assertEqual(boto_span.t, test_span.t) + self.assertEqual(boto_span.p, test_span.s) - spans = tracer.recorder.queued_spans() - assert len(spans) == 2 + self.assertIsNone(test_span.ec) - filter = lambda span: span.n == "sdk" - test_span = get_first_span_by_filter(spans, filter) - assert(test_span) + 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'}) - filter = lambda span: span.n == "boto3" - boto_span = get_first_span_by_filter(spans, filter) - assert(boto_span) + 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.t == test_span.t) - assert(boto_span.p == test_span.s) - assert(test_span.ec is None) - assert(boto_span.ec is None) + def test_request_header_capture_before_call(self): - 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' + 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..990776a6 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,384 @@ 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 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.sqs = boto3.client('sqs', region_name='us-east-1') + self.http_client = urllib3.PoolManager() + + def tearDown(self): + # Stop Moto after each test + self.mock.stop() + + + 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