22# SPDX-License-Identifier: Apache-2.0
33import os
44import time
5- from botocore .credentials import Credentials
65from unittest import TestCase
76from unittest .mock import ANY , MagicMock , PropertyMock , patch
8- from opentelemetry .sdk .trace import Tracer , Resource , SpanLimits , SpanContext
9- from opentelemetry .sdk .trace .id_generator import RandomIdGenerator
10- from opentelemetry .sdk .trace .sampling import ALWAYS_ON
11-
12- from opentelemetry .trace import SpanKind
137
148import requests
9+ from botocore .credentials import Credentials
1510
1611from amazon .opentelemetry .distro .aws_opentelemetry_configurator import OTLPAwsSigV4Exporter
1712from opentelemetry .exporter .otlp .proto .http .trace_exporter import (
2419from opentelemetry .sdk .environment_variables import (
2520 OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ,
2621)
27-
28- from opentelemetry .sdk .trace import _Span
29- from opentelemetry .trace import SpanKind , TraceFlags
30- from opentelemetry .sdk .trace import Resource
22+ from opentelemetry .sdk .trace import Resource , SpanContext , SpanLimits , Tracer , _Span
3123from opentelemetry .sdk .trace .export import SimpleSpanProcessor
24+ from opentelemetry .sdk .trace .id_generator import RandomIdGenerator
25+ from opentelemetry .sdk .trace .sampling import ALWAYS_ON
26+ from opentelemetry .trace import SpanKind , TraceFlags
3227
3328OTLP_CW_ENDPOINT = "https://xray.us-east-1.amazonaws.com/v1/traces"
29+ USER_AGENT = "OTel-OTLP-Exporter-Python/" + __version__
30+ CONTENT_TYPE = "application/x-protobuf"
31+ AUTHORIZATION_HEADER = "Authorization"
32+ X_AMZ_DATE_HEADER = "X-Amz-Date"
33+ X_AMZ_SECURITY_TOKEN_HEADER = "X-Amz-Security-Token"
3434
3535
3636class TestAwsSigV4Exporter (TestCase ):
@@ -40,19 +40,18 @@ def setUp(self):
4040 self .create_span ("test_span2" , SpanKind .SERVER ),
4141 self .create_span ("test_span3" , SpanKind .CLIENT ),
4242 self .create_span ("test_span4" , SpanKind .PRODUCER ),
43- self .create_span ("test_span5" , SpanKind .CONSUMER )
43+ self .create_span ("test_span5" , SpanKind .CONSUMER ),
4444 ]
4545
4646 self .invalid_cw_otlp_tracing_endpoints = [
4747 "https://xray.bad-region-1.amazonaws.com/v1/traces" ,
4848 "https://xray.us-east-1.amaz.com/v1/traces" ,
49- "https://logs.us-east-1.amazonaws.com/v1/logs"
50- "https://test-endpoint123.com/test"
49+ "https://logs.us-east-1.amazonaws.com/v1/logs" "https://test-endpoint123.com/test" ,
5150 ]
5251
53- self .expected_auth_header = ' AWS4-HMAC-SHA256 Credential=test_key/some_date/us-east-1/xray/aws4_request'
54- self .expected_auth_x_amz_date = ' some_date'
55- self .expected_auth_security_token = ' test_token'
52+ self .expected_auth_header = " AWS4-HMAC-SHA256 Credential=test_key/some_date/us-east-1/xray/aws4_request"
53+ self .expected_auth_x_amz_date = " some_date"
54+ self .expected_auth_security_token = " test_token"
5655
5756 # Tests that the default exporter is OTLP protobuf/http Span Exporter if no endpoint is set
5857 @patch .dict (os .environ , {}, clear = True )
@@ -61,7 +60,7 @@ def test_sigv4_exporter_init_default(self):
6160 self .validate_exporter_extends_http_span_exporter (exporter , DEFAULT_ENDPOINT + DEFAULT_TRACES_EXPORT_PATH )
6261 self .assertIsInstance (exporter ._session , requests .Session )
6362
64- # Tests that the endpoint is validated and sets the aws_region but still uses the OTLP protobuf/http
63+ # Tests that the endpoint is validated and sets the aws_region but still uses the OTLP protobuf/http
6564 # Span Exporter exporter constructor behavior if a valid OTLP CloudWatch endpoint is set
6665 @patch .dict (os .environ , {OTEL_EXPORTER_OTLP_TRACES_ENDPOINT : OTLP_CW_ENDPOINT }, clear = True )
6766 @patch ("botocore.session.Session" )
@@ -77,7 +76,7 @@ def test_sigv4_exporter_init_valid_cw_otlp_endpoint(self, session_mock):
7776
7877 mock_session .get_available_regions .assert_called_once_with ("xray" )
7978
80- # Tests that the exporter constructor behavior is set by OTLP protobuf/http Span Exporter
79+ # Tests that the exporter constructor behavior is set by OTLP protobuf/http Span Exporter
8180 # if an invalid OTLP CloudWatch endpoint is set
8281 @patch ("botocore.session.Session" )
8382 def test_sigv4_exporter_init_invalid_cw_otlp_endpoint (self , botocore_mock ):
@@ -95,136 +94,144 @@ def test_sigv4_exporter_init_invalid_cw_otlp_endpoint(self, botocore_mock):
9594
9695 self .assertIsNone (exporter ._aws_region )
9796
98-
9997 # Tests that if the OTLP endpoint is not a valid CW endpoint but the credentials are valid, SigV4 authentication method is NOT called and
10098 # is NOT injected into the existing Session headers.
10199 @patch ("botocore.session.Session.get_available_regions" )
102100 @patch ("requests.Session.post" )
103101 @patch ("botocore.auth.SigV4Auth.add_auth" )
104- def test_sigv4_exporter_export_does_not_add_sigv4_if_not_valid_cw_endpoint (self , mock_sigv4_auth , requests_mock , botocore_mock ):
102+ def test_sigv4_exporter_export_does_not_add_sigv4_if_not_valid_cw_endpoint (
103+ self , mock_sigv4_auth , requests_mock , botocore_mock
104+ ):
105105 # Setting the exporter response
106106 mock_response = MagicMock ()
107107 mock_response .status_code = 200
108108 type(mock_response ).ok = PropertyMock (return_value = True )
109-
109+
110110 # Setting the request session headers to make the call to endpoint
111111 mock_session = MagicMock ()
112- mock_session .headers = {
113- 'User-Agent' : 'OTel-OTLP-Exporter-Python/' + __version__ ,
114- 'Content-Type' : 'application/x-protobuf'
115- }
112+ mock_session .headers = {"User-Agent" : USER_AGENT , "Content-Type" : CONTENT_TYPE }
116113 requests_mock .return_value = mock_session
117114 mock_session .post .return_value = mock_response
118115
119116 mock_botocore_session = MagicMock ()
120117 botocore_mock .return_value = mock_botocore_session
121118 mock_botocore_session .get_available_regions .return_value = ["us-east-1" , "us-west-2" ]
122119 mock_botocore_session .get_credentials .return_value = Credentials (
123- access_key = "test_key" ,
124- secret_key = "test_secret" ,
125- token = "test_token"
120+ access_key = "test_key" , secret_key = "test_secret" , token = "test_token"
126121 )
127122
128- #SigV4 mock authentication injection
123+ # SigV4 mock authentication injection
129124 mock_sigv4_auth .side_effect = self .mock_add_auth
130125
131126 # Initialize and call exporter
132127 exporter = OTLPAwsSigV4Exporter (endpoint = OTLP_CW_ENDPOINT )
133128 exporter .export (self .testing_spans )
134129
135- # For each invalid CW OTLP endpoint, vdalidate that the sigv4
130+ # For each invalid CW OTLP endpoint, vdalidate that the sigv4
136131 for bad_endpoint in self .invalid_cw_otlp_tracing_endpoints :
137132 with self .subTest (endpoint = bad_endpoint ):
138133 with patch .dict (os .environ , {OTEL_EXPORTER_OTLP_TRACES_ENDPOINT : bad_endpoint }):
139134 botocore_mock .return_value = ["us-east-1" , "us-west-2" ]
140135
141136 exporter = OTLPAwsSigV4Exporter (endpoint = bad_endpoint )
142-
137+
143138 self .validate_exporter_extends_http_span_exporter (exporter , bad_endpoint )
139+
140+ # Test case, should not have detected a valid region
144141 self .assertIsNone (exporter ._aws_region )
145-
142+
146143 exporter .export (self .testing_spans )
147144
148145 mock_sigv4_auth .assert_not_called ()
149-
146+
150147 # Verify that SigV4 request headers were not injected
151148 actual_headers = mock_session .headers
152- self .assertNotIn ('Authorization' , actual_headers )
153- self .assertNotIn ('X-Amz-Date' , actual_headers )
154- self .assertNotIn ('X-Amz-Security-Token' , actual_headers )
155-
149+ self .assertNotIn (AUTHORIZATION_HEADER , actual_headers )
150+ self .assertNotIn (X_AMZ_DATE_HEADER , actual_headers )
151+ self .assertNotIn (X_AMZ_SECURITY_TOKEN_HEADER , actual_headers )
152+
156153 requests_mock .assert_called_with (
157- url = bad_endpoint ,
158- data = ANY ,
159- verify = ANY ,
160- timeout = ANY ,
161- cert = ANY ,
162- )
154+ url = bad_endpoint ,
155+ data = ANY ,
156+ verify = ANY ,
157+ timeout = ANY ,
158+ cert = ANY ,
159+ )
163160
164161 # Tests that if the OTLP endpoint is a valid CW endpoint but no credentials are returned, SigV4 authentication method is NOT called and
165162 # is NOT injected into the existing Session headers.
166- @patch ("botocore.session.Session.get_available_regions " )
163+ @patch ("botocore.session.Session" )
167164 @patch ("requests.Session" )
168165 @patch ("botocore.auth.SigV4Auth.add_auth" )
169166 @patch .dict (os .environ , {OTEL_EXPORTER_OTLP_TRACES_ENDPOINT : OTLP_CW_ENDPOINT })
170- def test_sigv4_exporter_export_does_not_add_sigv4_if_no_credentials (self , mock_sigv4 , requests_mock , botcore_mock ):
167+ def test_sigv4_exporter_export_does_not_add_sigv4_if_not_valid_credentials (
168+ self , mock_sigv4_auth , requests_posts_mock , botocore_mock
169+ ):
170+
171+ # Setting the exporter response
171172 mock_response = MagicMock ()
172173 mock_response .status_code = 200
173174 type(mock_response ).ok = PropertyMock (return_value = True )
174-
175- requests_mock .return_value = mock_response
176175
177- botcore_mock .return_value = ["us-east-1" , "us-west-2" ]
176+ # Setting the request session headers to make the call to endpoint
177+ mock_session = MagicMock ()
178+ mock_session .headers = {"User-Agent" : USER_AGENT , "Content-Type" : CONTENT_TYPE }
179+ requests_posts_mock .return_value = mock_session
180+ mock_session .post .return_value = mock_response
181+
182+ mock_botocore_session = MagicMock ()
183+ botocore_mock .return_value = mock_botocore_session
184+ mock_botocore_session .get_available_regions .return_value = ["us-east-1" , "us-west-2" ]
185+
186+ # Test case, return None for get credentials
187+ mock_botocore_session .get_credentials .return_value = None
178188
189+ # Initialize and call exporter
179190 exporter = OTLPAwsSigV4Exporter (endpoint = OTLP_CW_ENDPOINT )
180-
181- self . validate_exporter_extends_http_span_exporter ( exporter , OTLP_CW_ENDPOINT )
182- self .assertIsNone (exporter ._aws_region )
183-
191+
192+ # Validate that the region is valid
193+ self .assertEqual (exporter ._aws_region , "us-east-1" )
194+
184195 exporter .export (self .testing_spans )
185196
186- mock_sigv4 .assert_not_called ()
197+ # Verify SigV4 auth was not called
198+ mock_sigv4_auth .assert_not_called ()
199+
200+ # Verify that SigV4 request headers were properly injected
201+ actual_headers = mock_session .headers
202+ self .assertNotIn (AUTHORIZATION_HEADER , actual_headers )
203+ self .assertNotIn (X_AMZ_DATE_HEADER , actual_headers )
204+ self .assertNotIn (X_AMZ_SECURITY_TOKEN_HEADER , actual_headers )
187205
188- requests_mock .assert_called_with (
189- url = OTLP_CW_ENDPOINT ,
190- data = ANY ,
191- verify = ANY ,
192- timeout = ANY ,
193- cert = ANY ,
194- )
195-
196206 # Tests that if the OTLP endpoint is valid and credentials are valid, SigV4 authentication method is called and
197207 # is injected into the existing Session headers.
198208 @patch ("botocore.session.Session" )
199209 @patch ("requests.Session" )
200210 @patch ("botocore.auth.SigV4Auth.add_auth" )
201211 @patch .dict (os .environ , {OTEL_EXPORTER_OTLP_TRACES_ENDPOINT : OTLP_CW_ENDPOINT })
202- def test_sigv4_exporter_export_adds_sigv4_authentication_if_valid_cw_endpoint (self , mock_sigv4_auth , requests_posts_mock , botocore_mock ):
212+ def test_sigv4_exporter_export_adds_sigv4_authentication_if_valid_cw_endpoint (
213+ self , mock_sigv4_auth , requests_posts_mock , botocore_mock
214+ ):
203215
204216 # Setting the exporter response
205217 mock_response = MagicMock ()
206218 mock_response .status_code = 200
207219 type(mock_response ).ok = PropertyMock (return_value = True )
208-
220+
209221 # Setting the request session headers to make the call to endpoint
210222 mock_session = MagicMock ()
211- mock_session .headers = {
212- 'User-Agent' : 'OTel-OTLP-Exporter-Python/' + __version__ ,
213- 'Content-Type' : 'application/x-protobuf'
214- }
223+ mock_session .headers = {"User-Agent" : USER_AGENT , "Content-Type" : CONTENT_TYPE }
215224 requests_posts_mock .return_value = mock_session
216225 mock_session .post .return_value = mock_response
217226
218227 mock_botocore_session = MagicMock ()
219228 botocore_mock .return_value = mock_botocore_session
220229 mock_botocore_session .get_available_regions .return_value = ["us-east-1" , "us-west-2" ]
221230 mock_botocore_session .get_credentials .return_value = Credentials (
222- access_key = "test_key" ,
223- secret_key = "test_secret" ,
224- token = "test_token"
231+ access_key = "test_key" , secret_key = "test_secret" , token = "test_token"
225232 )
226233
227- #SigV4 mock authentication injection
234+ # SigV4 mock authentication injection
228235 mock_sigv4_auth .side_effect = self .mock_add_auth
229236
230237 # Initialize and call exporter
@@ -236,14 +243,13 @@ def test_sigv4_exporter_export_adds_sigv4_authentication_if_valid_cw_endpoint(se
236243
237244 # Verify that SigV4 request headers were properly injected
238245 actual_headers = mock_session .headers
239- self .assertIn ('Authorization' , actual_headers )
240- self .assertIn ('X-Amz-Date' , actual_headers )
241- self .assertIn ('X-Amz-Security-Token' , actual_headers )
242-
243- self .assertEqual (actual_headers ['Authorization' ], self .expected_auth_header )
244- self .assertEqual (actual_headers ['X-Amz-Date' ], self .expected_auth_x_amz_date )
245- self .assertEqual (actual_headers ['X-Amz-Security-Token' ], self .expected_auth_security_token )
246+ self .assertIn ("Authorization" , actual_headers )
247+ self .assertIn ("X-Amz-Date" , actual_headers )
248+ self .assertIn ("X-Amz-Security-Token" , actual_headers )
246249
250+ self .assertEqual (actual_headers [AUTHORIZATION_HEADER ], self .expected_auth_header )
251+ self .assertEqual (actual_headers [X_AMZ_DATE_HEADER ], self .expected_auth_x_amz_date )
252+ self .assertEqual (actual_headers [X_AMZ_SECURITY_TOKEN_HEADER ], self .expected_auth_security_token )
247253
248254 def validate_exporter_extends_http_span_exporter (self , exporter , endpoint ):
249255 self .assertEqual (exporter ._endpoint , endpoint )
@@ -256,13 +262,9 @@ def validate_exporter_extends_http_span_exporter(self, exporter, endpoint):
256262 self .assertIn ("User-Agent" , exporter ._session .headers )
257263 self .assertEqual (
258264 exporter ._session .headers .get ("Content-Type" ),
259- "application/x-protobuf" ,
260- )
261- self .assertEqual (
262- exporter ._session .headers .get ("User-Agent" ),
263- "OTel-OTLP-Exporter-Python/" + __version__ ,
265+ CONTENT_TYPE ,
264266 )
265-
267+ self . assertEqual ( exporter . _session . headers . get ( "User-Agent" ), USER_AGENT )
266268
267269 def create_span (self , name = "test_span" , kind = SpanKind .INTERNAL ):
268270 span = _Span (
@@ -271,15 +273,17 @@ def create_span(self, name="test_span", kind=SpanKind.INTERNAL):
271273 trace_id = 0x1234567890ABCDEF ,
272274 span_id = 0x9876543210 ,
273275 is_remote = False ,
274- trace_flags = TraceFlags (TraceFlags .SAMPLED )
276+ trace_flags = TraceFlags (TraceFlags .SAMPLED ),
275277 ),
276- kind = kind
278+ kind = kind ,
277279 )
278280 return span
279281
280282 def mock_add_auth (self , request ):
281- request .headers ._headers .extend ([
282- ('Authorization' , self .expected_auth_header ),
283- ('X-Amz-Date' , self .expected_auth_x_amz_date ),
284- ('X-Amz-Security-Token' , self .expected_auth_security_token )
285- ])
283+ request .headers ._headers .extend (
284+ [
285+ (AUTHORIZATION_HEADER , self .expected_auth_header ),
286+ (X_AMZ_DATE_HEADER , self .expected_auth_x_amz_date ),
287+ (X_AMZ_SECURITY_TOKEN_HEADER , self .expected_auth_security_token ),
288+ ]
289+ )
0 commit comments