diff --git a/google/cloud/spanner_v1/_helpers.py b/google/cloud/spanner_v1/_helpers.py index 00a69d462b..aa58c59199 100644 --- a/google/cloud/spanner_v1/_helpers.py +++ b/google/cloud/spanner_v1/_helpers.py @@ -20,6 +20,7 @@ import time import base64 import threading +import logging from google.protobuf.struct_pb2 import ListValue from google.protobuf.struct_pb2 import Value @@ -29,16 +30,27 @@ from google.api_core import datetime_helpers from google.api_core.exceptions import Aborted from google.cloud._helpers import _date_from_iso8601_date -from google.cloud.spanner_v1 import TypeCode -from google.cloud.spanner_v1 import ExecuteSqlRequest -from google.cloud.spanner_v1 import JsonObject, Interval -from google.cloud.spanner_v1 import TransactionOptions +from google.cloud.spanner_v1.types import ExecuteSqlRequest +from google.cloud.spanner_v1.types import TransactionOptions +from google.cloud.spanner_v1.data_types import JsonObject, Interval from google.cloud.spanner_v1.request_id_header import with_request_id +from google.cloud.spanner_v1.types import TypeCode + from google.rpc.error_details_pb2 import RetryInfo try: from opentelemetry.propagate import inject from opentelemetry.propagators.textmap import Setter + from opentelemetry.semconv.resource import ResourceAttributes + from opentelemetry.resourcedetector import gcp_resource_detector + from opentelemetry.resourcedetector.gcp_resource_detector import ( + GoogleCloudResourceDetector, + ) + + # Overwrite the requests timeout for the detector. + # This is necessary as the client will wait the full timeout if the + # code is not run in a GCP environment, with the location endpoints available. + gcp_resource_detector._TIMEOUT_SEC = 0.2 HAS_OPENTELEMETRY_INSTALLED = True except ImportError: @@ -55,6 +67,12 @@ + "numeric has a whole component with precision {}" ) +GOOGLE_CLOUD_REGION_GLOBAL = "global" + +log = logging.getLogger(__name__) + +_cloud_region: str = None + if HAS_OPENTELEMETRY_INSTALLED: @@ -79,6 +97,33 @@ def set(self, carrier: List[Tuple[str, str]], key: str, value: str) -> None: carrier.append((key, value)) +def _get_cloud_region() -> str: + """Get the location of the resource, caching the result. + + Returns: + str: The location of the resource. If OpenTelemetry is not installed, returns a global region. + """ + global _cloud_region + if _cloud_region is not None: + return _cloud_region + + try: + detector = GoogleCloudResourceDetector() + resources = detector.detect() + if ResourceAttributes.CLOUD_REGION in resources.attributes: + _cloud_region = resources.attributes[ResourceAttributes.CLOUD_REGION] + else: + _cloud_region = GOOGLE_CLOUD_REGION_GLOBAL + except Exception as e: + log.warning( + "Failed to detect GCP resource location for Spanner metrics, defaulting to 'global'. Error: %s", + e, + ) + _cloud_region = GOOGLE_CLOUD_REGION_GLOBAL + + return _cloud_region + + def _try_to_coerce_bytes(bytestring): """Try to coerce a byte string into the right thing based on Python version and whether or not it is base64 encoded. diff --git a/google/cloud/spanner_v1/_opentelemetry_tracing.py b/google/cloud/spanner_v1/_opentelemetry_tracing.py index 8abdb28ffb..c95f896298 100644 --- a/google/cloud/spanner_v1/_opentelemetry_tracing.py +++ b/google/cloud/spanner_v1/_opentelemetry_tracing.py @@ -21,6 +21,7 @@ from google.cloud.spanner_v1 import SpannerClient from google.cloud.spanner_v1 import gapic_version from google.cloud.spanner_v1._helpers import ( + _get_cloud_region, _metadata_with_span_context, ) @@ -75,6 +76,7 @@ def trace_call( enable_end_to_end_tracing = False db_name = "" + cloud_region = None if session and getattr(session, "_database", None): db_name = session._database.name @@ -88,6 +90,7 @@ def trace_call( ) db_name = observability_options.get("db_name", db_name) + cloud_region = _get_cloud_region() tracer = get_tracer(tracer_provider) # Set base attributes that we know for every trace created @@ -97,6 +100,7 @@ def trace_call( "db.instance": db_name, "net.host.name": SpannerClient.DEFAULT_ENDPOINT, OTEL_SCOPE_NAME: TRACER_NAME, + "cloud.region": cloud_region, OTEL_SCOPE_VERSION: TRACER_VERSION, # Standard GCP attributes for OTel, attributes are used for internal purpose and are subjected to change "gcp.client.service": "spanner", @@ -107,6 +111,11 @@ def trace_call( if extra_attributes: attributes.update(extra_attributes) + if "request_options" in attributes: + request_options = attributes.pop("request_options") + if request_options and request_options.request_tag: + attributes["request.tag"] = request_options.request_tag + if extended_tracing_globally_disabled: enable_extended_tracing = False diff --git a/google/cloud/spanner_v1/database.py b/google/cloud/spanner_v1/database.py index bd4116180a..33c442602c 100644 --- a/google/cloud/spanner_v1/database.py +++ b/google/cloud/spanner_v1/database.py @@ -1025,8 +1025,14 @@ def run_in_transaction(self, func, *args, **kw): reraises any non-ABORT exceptions raised by ``func``. """ observability_options = getattr(self, "observability_options", None) + transaction_tag = kw.get("transaction_tag") + extra_attributes = {} + if transaction_tag: + extra_attributes["transaction.tag"] = transaction_tag + with trace_call( "CloudSpanner.Database.run_in_transaction", + extra_attributes=extra_attributes, observability_options=observability_options, ), MetricsCapture(): # Sanity check: Is there a transaction already running? diff --git a/google/cloud/spanner_v1/metrics/spanner_metrics_tracer_factory.py b/google/cloud/spanner_v1/metrics/spanner_metrics_tracer_factory.py index 881a5bfca9..9566e61a28 100644 --- a/google/cloud/spanner_v1/metrics/spanner_metrics_tracer_factory.py +++ b/google/cloud/spanner_v1/metrics/spanner_metrics_tracer_factory.py @@ -18,20 +18,9 @@ from .metrics_tracer_factory import MetricsTracerFactory import os import logging -from .constants import ( - SPANNER_SERVICE_NAME, - GOOGLE_CLOUD_REGION_KEY, - GOOGLE_CLOUD_REGION_GLOBAL, -) +from .constants import SPANNER_SERVICE_NAME try: - from opentelemetry.resourcedetector import gcp_resource_detector - - # Overwrite the requests timeout for the detector. - # This is necessary as the client will wait the full timeout if the - # code is not run in a GCP environment, with the location endpoints available. - gcp_resource_detector._TIMEOUT_SEC = 0.2 - import mmh3 logging.getLogger("opentelemetry.resourcedetector.gcp_resource_detector").setLevel( @@ -44,6 +33,7 @@ from .metrics_tracer import MetricsTracer from google.cloud.spanner_v1 import __version__ +from google.cloud.spanner_v1._helpers import _get_cloud_region from uuid import uuid4 log = logging.getLogger(__name__) @@ -86,7 +76,7 @@ def __new__( cls._metrics_tracer_factory.set_client_hash( cls._generate_client_hash(client_uid) ) - cls._metrics_tracer_factory.set_location(cls._get_location()) + cls._metrics_tracer_factory.set_location(_get_cloud_region()) cls._metrics_tracer_factory.gfe_enabled = gfe_enabled if cls._metrics_tracer_factory.enabled != enabled: @@ -153,28 +143,3 @@ def _generate_client_hash(client_uid: str) -> str: # Return as 6 digit zero padded hex string return f"{sig_figs:06x}" - - @staticmethod - def _get_location() -> str: - """Get the location of the resource. - - In case of any error during detection, this method will log a warning - and default to the "global" location. - - Returns: - str: The location of the resource. If OpenTelemetry is not installed, returns a global region. - """ - if not HAS_OPENTELEMETRY_INSTALLED: - return GOOGLE_CLOUD_REGION_GLOBAL - try: - detector = gcp_resource_detector.GoogleCloudResourceDetector() - resources = detector.detect() - - if GOOGLE_CLOUD_REGION_KEY in resources.attributes: - return resources.attributes[GOOGLE_CLOUD_REGION_KEY] - except Exception as e: - log.warning( - "Failed to detect GCP resource location for Spanner metrics, defaulting to 'global'. Error: %s", - e, - ) - return GOOGLE_CLOUD_REGION_GLOBAL diff --git a/google/cloud/spanner_v1/session.py b/google/cloud/spanner_v1/session.py index 320ebef102..4c29014e15 100644 --- a/google/cloud/spanner_v1/session.py +++ b/google/cloud/spanner_v1/session.py @@ -532,9 +532,14 @@ def run_in_transaction(self, func, *args, **kw): database = self._database log_commit_stats = database.log_commit_stats + extra_attributes = {} + if transaction_tag: + extra_attributes["transaction.tag"] = transaction_tag + with trace_call( "CloudSpanner.Session.run_in_transaction", self, + extra_attributes=extra_attributes, observability_options=getattr(database, "observability_options", None), ) as span, MetricsCapture(): attempts: int = 0 diff --git a/google/cloud/spanner_v1/snapshot.py b/google/cloud/spanner_v1/snapshot.py index 5633cd4486..46b0f5af8d 100644 --- a/google/cloud/spanner_v1/snapshot.py +++ b/google/cloud/spanner_v1/snapshot.py @@ -409,7 +409,11 @@ def read( method=streaming_read_method, request=read_request, metadata=metadata, - trace_attributes={"table_id": table, "columns": columns}, + trace_attributes={ + "table_id": table, + "columns": columns, + "request_options": request_options, + }, column_info=column_info, lazy_decode=lazy_decode, ) @@ -601,7 +605,7 @@ def execute_sql( method=execute_streaming_sql_method, request=execute_sql_request, metadata=metadata, - trace_attributes={"db.statement": sql}, + trace_attributes={"db.statement": sql, "request_options": request_options}, column_info=column_info, lazy_decode=lazy_decode, ) diff --git a/google/cloud/spanner_v1/transaction.py b/google/cloud/spanner_v1/transaction.py index 5dd54eafe1..b9e14a0040 100644 --- a/google/cloud/spanner_v1/transaction.py +++ b/google/cloud/spanner_v1/transaction.py @@ -479,7 +479,10 @@ def execute_update( request_options = RequestOptions(request_options) request_options.transaction_tag = self.transaction_tag - trace_attributes = {"db.statement": dml} + trace_attributes = { + "db.statement": dml, + "request_options": request_options, + } # If this request begins the transaction, we need to lock # the transaction until the transaction ID is updated. @@ -629,7 +632,8 @@ def batch_update( trace_attributes = { # Get just the queries from the DML statement batch - "db.statement": ";".join([statement.sql for statement in parsed]) + "db.statement": ";".join([statement.sql for statement in parsed]), + "request_options": request_options, } # If this request begins the transaction, we need to lock diff --git a/tests/system/test_session_api.py b/tests/system/test_session_api.py index 6179892e02..2b0caba4e1 100644 --- a/tests/system/test_session_api.py +++ b/tests/system/test_session_api.py @@ -30,6 +30,7 @@ from google.cloud.spanner_admin_database_v1 import DatabaseDialect from google.cloud._helpers import UTC +from google.cloud.spanner_v1._helpers import _get_cloud_region from google.cloud.spanner_v1._helpers import AtomicCounter from google.cloud.spanner_v1.data_types import JsonObject from google.cloud.spanner_v1.database_sessions_manager import TransactionType @@ -356,6 +357,7 @@ def _make_attributes(db_instance, **kwargs): "db.url": "spanner.googleapis.com", "net.host.name": "spanner.googleapis.com", "db.instance": db_instance, + "cloud.region": _get_cloud_region(), "gcp.client.service": "spanner", "gcp.client.version": ot_helpers.LIB_VERSION, "gcp.client.repo": "googleapis/python-spanner", diff --git a/tests/unit/test__helpers.py b/tests/unit/test__helpers.py index 6f77d002cd..40db14607c 100644 --- a/tests/unit/test__helpers.py +++ b/tests/unit/test__helpers.py @@ -16,7 +16,11 @@ import unittest import mock -from google.cloud.spanner_v1 import TransactionOptions +from opentelemetry.sdk.resources import Resource +from opentelemetry.semconv.resource import ResourceAttributes + + +from google.cloud.spanner_v1 import TransactionOptions, _helpers class Test_merge_query_options(unittest.TestCase): @@ -89,6 +93,48 @@ def test_base_object_merge_dict(self): self.assertEqual(result, expected) +class Test_get_cloud_region(unittest.TestCase): + def setUp(self): + _helpers._cloud_region = None + + def _callFUT(self, *args, **kw): + from google.cloud.spanner_v1._helpers import _get_cloud_region + + return _get_cloud_region(*args, **kw) + + @mock.patch("google.cloud.spanner_v1._helpers.GoogleCloudResourceDetector.detect") + def test_get_location_with_region(self, mock_detect): + """Test that _get_cloud_region returns the region when detected.""" + mock_resource = Resource.create( + {ResourceAttributes.CLOUD_REGION: "us-central1"} + ) + mock_detect.return_value = mock_resource + + location = self._callFUT() + self.assertEqual(location, "us-central1") + + @mock.patch("google.cloud.spanner_v1._helpers.GoogleCloudResourceDetector.detect") + def test_get_location_without_region(self, mock_detect): + """Test that _get_cloud_region returns 'global' when no region is detected.""" + mock_resource = Resource.create({}) # No region attribute + mock_detect.return_value = mock_resource + + location = self._callFUT() + self.assertEqual(location, "global") + + @mock.patch("google.cloud.spanner_v1._helpers.GoogleCloudResourceDetector.detect") + def test_get_location_with_exception(self, mock_detect): + """Test that _get_cloud_region returns 'global' and logs a warning on exception.""" + mock_detect.side_effect = Exception("detector failed") + + with self.assertLogs( + "google.cloud.spanner_v1._helpers", level="WARNING" + ) as log: + location = self._callFUT() + self.assertEqual(location, "global") + self.assertIn("Failed to detect GCP resource location", log.output[0]) + + class Test_make_value_pb(unittest.TestCase): def _callFUT(self, *args, **kw): from google.cloud.spanner_v1._helpers import _make_value_pb diff --git a/tests/unit/test__opentelemetry_tracing.py b/tests/unit/test__opentelemetry_tracing.py index d722aceccc..da75e940b6 100644 --- a/tests/unit/test__opentelemetry_tracing.py +++ b/tests/unit/test__opentelemetry_tracing.py @@ -7,6 +7,7 @@ pass from google.api_core.exceptions import GoogleAPICallError +from google.cloud.spanner_v1._helpers import GOOGLE_CLOUD_REGION_GLOBAL from google.cloud.spanner_v1 import _opentelemetry_tracing from tests._helpers import ( @@ -31,7 +32,11 @@ def _make_session(): class TestTracing(OpenTelemetryBase): - def test_trace_call(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_trace_call(self, mock_region): extra_attributes = { "attribute1": "value1", # Since our database is mocked, we have to override the db.instance parameter so it is a string @@ -43,6 +48,7 @@ def test_trace_call(self): "db.type": "spanner", "db.url": "spanner.googleapis.com", "net.host.name": "spanner.googleapis.com", + "cloud.region": GOOGLE_CLOUD_REGION_GLOBAL, "gcp.client.service": "spanner", "gcp.client.version": LIB_VERSION, "gcp.client.repo": "googleapis/python-spanner", @@ -65,7 +71,11 @@ def test_trace_call(self): self.assertEqual(span.name, "CloudSpanner.Test") self.assertEqual(span.status.status_code, StatusCode.OK) - def test_trace_error(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_trace_error(self, mock_region): extra_attributes = {"db.instance": "database_name"} expected_attributes = enrich_with_otel_scope( @@ -73,6 +83,7 @@ def test_trace_error(self): "db.type": "spanner", "db.url": "spanner.googleapis.com", "net.host.name": "spanner.googleapis.com", + "cloud.region": GOOGLE_CLOUD_REGION_GLOBAL, "gcp.client.service": "spanner", "gcp.client.version": LIB_VERSION, "gcp.client.repo": "googleapis/python-spanner", diff --git a/tests/unit/test_batch.py b/tests/unit/test_batch.py index 1582fcf4a9..e8297030eb 100644 --- a/tests/unit/test_batch.py +++ b/tests/unit/test_batch.py @@ -30,6 +30,7 @@ BatchWriteResponse, DefaultTransactionOptions, ) +import mock from google.cloud._helpers import UTC, _datetime_to_pb_timestamp import datetime from google.api_core.exceptions import Aborted, Unknown @@ -57,6 +58,7 @@ "gcp.client.service": "spanner", "gcp.client.version": LIB_VERSION, "gcp.client.repo": "googleapis/python-spanner", + "cloud.region": "global", } enrich_with_otel_scope(BASE_ATTRIBUTES) @@ -198,7 +200,11 @@ def test_commit_already_committed(self): self.assertNoSpans() - def test_commit_grpc_error(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_commit_grpc_error(self, mock_region): keys = [[0], [1], [2]] keyset = KeySet(keys=keys) database = _Database() @@ -219,7 +225,11 @@ def test_commit_grpc_error(self): ), ) - def test_commit_ok(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_commit_ok(self, mock_region): now = datetime.datetime.utcnow().replace(tzinfo=UTC) now_pb = _datetime_to_pb_timestamp(now) response = CommitResponse(commit_timestamp=now_pb) @@ -376,35 +386,59 @@ def _test_commit_with_options( self.assertEqual(max_commit_delay_in, max_commit_delay) - def test_commit_w_request_tag_success(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_commit_w_request_tag_success(self, mock_region): request_options = RequestOptions( request_tag="tag-1", ) self._test_commit_with_options(request_options=request_options) - def test_commit_w_transaction_tag_success(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_commit_w_transaction_tag_success(self, mock_region): request_options = RequestOptions( transaction_tag="tag-1-1", ) self._test_commit_with_options(request_options=request_options) - def test_commit_w_request_and_transaction_tag_success(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_commit_w_request_and_transaction_tag_success(self, mock_region): request_options = RequestOptions( request_tag="tag-1", transaction_tag="tag-1-1", ) self._test_commit_with_options(request_options=request_options) - def test_commit_w_request_and_transaction_tag_dictionary_success(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_commit_w_request_and_transaction_tag_dictionary_success(self, mock_region): request_options = {"request_tag": "tag-1", "transaction_tag": "tag-1-1"} self._test_commit_with_options(request_options=request_options) - def test_commit_w_incorrect_tag_dictionary_error(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_commit_w_incorrect_tag_dictionary_error(self, mock_region): request_options = {"incorrect_tag": "tag-1-1"} with self.assertRaises(ValueError): self._test_commit_with_options(request_options=request_options) - def test_commit_w_max_commit_delay(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_commit_w_max_commit_delay(self, mock_region): request_options = RequestOptions( request_tag="tag-1", ) @@ -413,7 +447,11 @@ def test_commit_w_max_commit_delay(self): max_commit_delay_in=datetime.timedelta(milliseconds=100), ) - def test_commit_w_exclude_txn_from_change_streams(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_commit_w_exclude_txn_from_change_streams(self, mock_region): request_options = RequestOptions( request_tag="tag-1", ) @@ -421,7 +459,11 @@ def test_commit_w_exclude_txn_from_change_streams(self): request_options=request_options, exclude_txn_from_change_streams=True ) - def test_commit_w_isolation_level(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_commit_w_isolation_level(self, mock_region): request_options = RequestOptions( request_tag="tag-1", ) @@ -430,7 +472,11 @@ def test_commit_w_isolation_level(self): isolation_level=TransactionOptions.IsolationLevel.REPEATABLE_READ, ) - def test_commit_w_read_lock_mode(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_commit_w_read_lock_mode(self, mock_region): request_options = RequestOptions( request_tag="tag-1", ) @@ -439,7 +485,11 @@ def test_commit_w_read_lock_mode(self): read_lock_mode=TransactionOptions.ReadWrite.ReadLockMode.OPTIMISTIC, ) - def test_commit_w_isolation_level_and_read_lock_mode(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_commit_w_isolation_level_and_read_lock_mode(self, mock_region): request_options = RequestOptions( request_tag="tag-1", ) @@ -449,7 +499,11 @@ def test_commit_w_isolation_level_and_read_lock_mode(self): read_lock_mode=TransactionOptions.ReadWrite.ReadLockMode.PESSIMISTIC, ) - def test_context_mgr_already_committed(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_context_mgr_already_committed(self, mock_region): now = datetime.datetime.utcnow().replace(tzinfo=UTC) database = _Database() api = database.spanner_api = _FauxSpannerAPI() @@ -463,7 +517,11 @@ def test_context_mgr_already_committed(self): self.assertEqual(api._committed, None) - def test_context_mgr_success(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_context_mgr_success(self, mock_region): now = datetime.datetime.utcnow().replace(tzinfo=UTC) now_pb = _datetime_to_pb_timestamp(now) response = CommitResponse(commit_timestamp=now_pb) @@ -510,7 +568,11 @@ def test_context_mgr_success(self): ), ) - def test_context_mgr_failure(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_context_mgr_failure(self, mock_region): now = datetime.datetime.utcnow().replace(tzinfo=UTC) now_pb = _datetime_to_pb_timestamp(now) response = CommitResponse(commit_timestamp=now_pb) @@ -541,7 +603,11 @@ def test_ctor(self): groups = self._make_one(session) self.assertIs(groups._session, session) - def test_batch_write_already_committed(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_batch_write_already_committed(self, mock_region): keys = [[0], [1], [2]] keyset = KeySet(keys=keys) database = _Database() @@ -564,7 +630,11 @@ def test_batch_write_already_committed(self): with self.assertRaises(ValueError): groups.batch_write() - def test_batch_write_grpc_error(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_batch_write_grpc_error(self, mock_region): keys = [[0], [1], [2]] keyset = KeySet(keys=keys) database = _Database() @@ -662,25 +732,49 @@ def _test_batch_write_with_request_options( ), ) - def test_batch_write_no_request_options(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_batch_write_no_request_options(self, mock_region): self._test_batch_write_with_request_options() - def test_batch_write_end_to_end_tracing_enabled(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_batch_write_end_to_end_tracing_enabled(self, mock_region): self._test_batch_write_with_request_options(enable_end_to_end_tracing=True) - def test_batch_write_w_transaction_tag_success(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_batch_write_w_transaction_tag_success(self, mock_region): self._test_batch_write_with_request_options( RequestOptions(transaction_tag="tag-1-1") ) - def test_batch_write_w_transaction_tag_dictionary_success(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_batch_write_w_transaction_tag_dictionary_success(self, mock_region): self._test_batch_write_with_request_options({"transaction_tag": "tag-1-1"}) - def test_batch_write_w_incorrect_tag_dictionary_error(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_batch_write_w_incorrect_tag_dictionary_error(self, mock_region): with self.assertRaises(ValueError): self._test_batch_write_with_request_options({"incorrect_tag": "tag-1-1"}) - def test_batch_write_w_exclude_txn_from_change_streams(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_batch_write_w_exclude_txn_from_change_streams(self, mock_region): self._test_batch_write_with_request_options( exclude_txn_from_change_streams=True ) diff --git a/tests/unit/test_pool.py b/tests/unit/test_pool.py index 409f4b043b..ec03e4350b 100644 --- a/tests/unit/test_pool.py +++ b/tests/unit/test_pool.py @@ -155,6 +155,7 @@ class TestFixedSizePool(OpenTelemetryBase): "gcp.client.service": "spanner", "gcp.client.version": LIB_VERSION, "gcp.client.repo": "googleapis/python-spanner", + "cloud.region": "global", } enrich_with_otel_scope(BASE_ATTRIBUTES) @@ -175,7 +176,11 @@ def test_ctor_defaults(self): self.assertEqual(pool.labels, {}) self.assertIsNone(pool.database_role) - def test_ctor_explicit(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_ctor_explicit(self, mock_region): labels = {"foo": "bar"} database_role = "dummy-role" pool = self._make_one( @@ -188,7 +193,11 @@ def test_ctor_explicit(self): self.assertEqual(pool.labels, labels) self.assertEqual(pool.database_role, database_role) - def test_bind(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_bind(self, mock_region): database_role = "dummy-role" pool = self._make_one() database = _Database("name") @@ -209,7 +218,11 @@ def test_bind(self): for session in SESSIONS: session.create.assert_not_called() - def test_get_active(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_get_active(self, mock_region): pool = self._make_one(size=4) database = _Database("name") SESSIONS = sorted([_Session(database) for i in range(0, 4)]) @@ -223,7 +236,11 @@ def test_get_active(self): self.assertFalse(session._exists_checked) self.assertFalse(pool._sessions.full()) - def test_get_non_expired(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_get_non_expired(self, mock_region): pool = self._make_one(size=4) database = _Database("name") last_use_time = datetime.utcnow() - timedelta(minutes=56) @@ -240,7 +257,11 @@ def test_get_non_expired(self): self.assertTrue(session._exists_checked) self.assertFalse(pool._sessions.full()) - def test_spans_bind_get(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_spans_bind_get(self, mock_region): if not HAS_OPENTELEMETRY_INSTALLED: return @@ -285,7 +306,11 @@ def test_spans_bind_get(self): ] self.assertSpanEvents("pool.Get", wantEventNames, span_list[-1]) - def test_spans_bind_get_empty_pool(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_spans_bind_get_empty_pool(self, mock_region): if not HAS_OPENTELEMETRY_INSTALLED: return @@ -329,7 +354,11 @@ def test_spans_bind_get_empty_pool(self): ] assert got_all_events == want_all_events - def test_spans_pool_bind(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_spans_pool_bind(self, mock_region): if not HAS_OPENTELEMETRY_INSTALLED: return @@ -403,7 +432,11 @@ def test_spans_pool_bind(self): ] assert got_all_events == want_all_events - def test_get_expired(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_get_expired(self, mock_region): pool = self._make_one(size=4) database = _Database("name") last_use_time = datetime.utcnow() - timedelta(minutes=65) @@ -419,7 +452,11 @@ def test_get_expired(self): self.assertTrue(SESSIONS[0]._exists_checked) self.assertFalse(pool._sessions.full()) - def test_get_empty_default_timeout(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_get_empty_default_timeout(self, mock_region): import queue pool = self._make_one(size=1) @@ -430,7 +467,11 @@ def test_get_empty_default_timeout(self): self.assertEqual(session_queue._got, {"block": True, "timeout": 10}) - def test_get_empty_explicit_timeout(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_get_empty_explicit_timeout(self, mock_region): import queue pool = self._make_one(size=1, default_timeout=0.1) @@ -441,7 +482,11 @@ def test_get_empty_explicit_timeout(self): self.assertEqual(session_queue._got, {"block": True, "timeout": 1}) - def test_put_full(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_put_full(self, mock_region): import queue pool = self._make_one(size=4) @@ -456,7 +501,11 @@ def test_put_full(self): self.assertTrue(pool._sessions.full()) - def test_put_non_full(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_put_non_full(self, mock_region): pool = self._make_one(size=4) database = _Database("name") SESSIONS = [_Session(database)] * 4 @@ -468,7 +517,11 @@ def test_put_non_full(self): self.assertTrue(pool._sessions.full()) - def test_clear(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_clear(self, mock_region): pool = self._make_one() database = _Database("name") SESSIONS = [_Session(database)] * 10 @@ -496,6 +549,7 @@ class TestBurstyPool(OpenTelemetryBase): "gcp.client.service": "spanner", "gcp.client.version": LIB_VERSION, "gcp.client.repo": "googleapis/python-spanner", + "cloud.region": "global", } enrich_with_otel_scope(BASE_ATTRIBUTES) @@ -525,7 +579,11 @@ def test_ctor_explicit(self): self.assertEqual(pool.labels, labels) self.assertEqual(pool.database_role, database_role) - def test_ctor_explicit_w_database_role_in_db(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_ctor_explicit_w_database_role_in_db(self, mock_region): database_role = "dummy-role" pool = self._make_one() database = pool._database = _Database("name") @@ -533,7 +591,11 @@ def test_ctor_explicit_w_database_role_in_db(self): pool.bind(database) self.assertEqual(pool.database_role, database_role) - def test_get_empty(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_get_empty(self, mock_region): pool = self._make_one() database = _Database("name") pool._new_session = mock.Mock(return_value=_Session(database)) @@ -546,7 +608,11 @@ def test_get_empty(self): session.create.assert_called() self.assertTrue(pool._sessions.empty()) - def test_spans_get_empty_pool(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_spans_get_empty_pool(self, mock_region): if not HAS_OPENTELEMETRY_INSTALLED: return @@ -584,7 +650,11 @@ def test_spans_get_empty_pool(self): ] self.assertSpanEvents("pool.Get", wantEventNames, span=create_span) - def test_get_non_empty_session_exists(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_get_non_empty_session_exists(self, mock_region): pool = self._make_one() database = _Database("name") previous = _Session(database) @@ -598,7 +668,11 @@ def test_get_non_empty_session_exists(self): self.assertTrue(session._exists_checked) self.assertTrue(pool._sessions.empty()) - def test_spans_get_non_empty_session_exists(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_spans_get_non_empty_session_exists(self, mock_region): # Tests the spans produces when you invoke pool.bind # and then insert a session into the pool. pool = self._make_one() @@ -622,7 +696,11 @@ def test_spans_get_non_empty_session_exists(self): ["Acquiring session", "Waiting for a session to become available"], ) - def test_get_non_empty_session_expired(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_get_non_empty_session_expired(self, mock_region): pool = self._make_one() database = _Database("name") previous = _Session(database, exists=False) @@ -639,7 +717,11 @@ def test_get_non_empty_session_expired(self): self.assertFalse(session._exists_checked) self.assertTrue(pool._sessions.empty()) - def test_put_empty(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_put_empty(self, mock_region): pool = self._make_one() database = _Database("name") pool.bind(database) @@ -649,7 +731,11 @@ def test_put_empty(self): self.assertFalse(pool._sessions.empty()) - def test_spans_put_empty(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_spans_put_empty(self, mock_region): # Tests the spans produced when you put sessions into an empty pool. pool = self._make_one() database = _Database("name") @@ -665,7 +751,11 @@ def test_spans_put_empty(self): attributes=TestBurstyPool.BASE_ATTRIBUTES, ) - def test_put_full(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_put_full(self, mock_region): pool = self._make_one(target_size=1) database = _Database("name") pool.bind(database) @@ -679,7 +769,11 @@ def test_put_full(self): self.assertTrue(younger._deleted) self.assertIs(pool.get(), older) - def test_spans_put_full(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_spans_put_full(self, mock_region): # This scenario tests the spans produced from putting an older # session into a pool that is already full. pool = self._make_one(target_size=1) @@ -701,7 +795,11 @@ def test_spans_put_full(self): attributes=TestBurstyPool.BASE_ATTRIBUTES, ) - def test_put_full_expired(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_put_full_expired(self, mock_region): pool = self._make_one(target_size=1) database = _Database("name") pool.bind(database) @@ -715,7 +813,11 @@ def test_put_full_expired(self): self.assertTrue(younger._deleted) self.assertIs(pool.get(), older) - def test_clear(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_clear(self, mock_region): pool = self._make_one() database = _Database("name") pool.bind(database) @@ -737,6 +839,7 @@ class TestPingingPool(OpenTelemetryBase): "gcp.client.service": "spanner", "gcp.client.version": LIB_VERSION, "gcp.client.repo": "googleapis/python-spanner", + "cloud.region": "global", } enrich_with_otel_scope(BASE_ATTRIBUTES) @@ -776,7 +879,11 @@ def test_ctor_explicit(self): self.assertEqual(pool.labels, labels) self.assertEqual(pool.database_role, database_role) - def test_ctor_explicit_w_database_role_in_db(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_ctor_explicit_w_database_role_in_db(self, mock_region): database_role = "dummy-role" pool = self._make_one() database = pool._database = _Database("name") @@ -786,7 +893,11 @@ def test_ctor_explicit_w_database_role_in_db(self): pool.bind(database) self.assertEqual(pool.database_role, database_role) - def test_bind(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_bind(self, mock_region): pool = self._make_one() database = _Database("name") SESSIONS = [_Session(database)] * 10 @@ -804,7 +915,11 @@ def test_bind(self): for session in SESSIONS: session.create.assert_not_called() - def test_get_hit_no_ping(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_get_hit_no_ping(self, mock_region): pool = self._make_one(size=4) database = _Database("name") SESSIONS = [_Session(database)] * 4 @@ -819,7 +934,11 @@ def test_get_hit_no_ping(self): self.assertFalse(pool._sessions.full()) self.assertNoSpans() - def test_get_hit_w_ping(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_get_hit_w_ping(self, mock_region): import datetime from google.cloud._testing import _Monkey from google.cloud.spanner_v1 import pool as MUT @@ -843,7 +962,11 @@ def test_get_hit_w_ping(self): self.assertFalse(pool._sessions.full()) self.assertNoSpans() - def test_get_hit_w_ping_expired(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_get_hit_w_ping_expired(self, mock_region): import datetime from google.cloud._testing import _Monkey from google.cloud.spanner_v1 import pool as MUT @@ -868,7 +991,11 @@ def test_get_hit_w_ping_expired(self): self.assertFalse(pool._sessions.full()) self.assertNoSpans() - def test_get_empty_default_timeout(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_get_empty_default_timeout(self, mock_region): import queue pool = self._make_one(size=1) @@ -880,7 +1007,11 @@ def test_get_empty_default_timeout(self): self.assertEqual(session_queue._got, {"block": True, "timeout": 10}) self.assertNoSpans() - def test_get_empty_explicit_timeout(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_get_empty_explicit_timeout(self, mock_region): import queue pool = self._make_one(size=1, default_timeout=0.1) @@ -892,7 +1023,11 @@ def test_get_empty_explicit_timeout(self): self.assertEqual(session_queue._got, {"block": True, "timeout": 1}) self.assertNoSpans() - def test_put_full(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_put_full(self, mock_region): import queue pool = self._make_one(size=4) @@ -906,7 +1041,11 @@ def test_put_full(self): self.assertTrue(pool._sessions.full()) - def test_spans_put_full(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_spans_put_full(self, mock_region): if not HAS_OPENTELEMETRY_INSTALLED: return @@ -946,7 +1085,11 @@ def test_spans_put_full(self): "CloudSpanner.PingingPool.BatchCreateSessions", wantEventNames ) - def test_put_non_full(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_put_non_full(self, mock_region): import datetime from google.cloud._testing import _Monkey from google.cloud.spanner_v1 import pool as MUT @@ -967,7 +1110,11 @@ def test_put_non_full(self): self.assertIs(queued, session) self.assertNoSpans() - def test_clear(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_clear(self, mock_region): pool = self._make_one() database = _Database("name") SESSIONS = [_Session(database)] * 10 @@ -987,12 +1134,20 @@ def test_clear(self): self.assertTrue(session._deleted) self.assertNoSpans() - def test_ping_empty(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_ping_empty(self, mock_region): pool = self._make_one(size=1) pool.ping() # Does not raise 'Empty' self.assertNoSpans() - def test_ping_oldest_fresh(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_ping_oldest_fresh(self, mock_region): pool = self._make_one(size=1) database = _Database("name") SESSIONS = [_Session(database)] * 1 @@ -1005,7 +1160,11 @@ def test_ping_oldest_fresh(self): self.assertFalse(SESSIONS[0]._pinged) self.assertNoSpans() - def test_ping_oldest_stale_but_exists(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_ping_oldest_stale_but_exists(self, mock_region): import datetime from google.cloud._testing import _Monkey from google.cloud.spanner_v1 import pool as MUT @@ -1022,7 +1181,11 @@ def test_ping_oldest_stale_but_exists(self): self.assertTrue(SESSIONS[0]._pinged) - def test_ping_oldest_stale_and_not_exists(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_ping_oldest_stale_and_not_exists(self, mock_region): import datetime from google.cloud._testing import _Monkey from google.cloud.spanner_v1 import pool as MUT @@ -1043,7 +1206,11 @@ def test_ping_oldest_stale_and_not_exists(self): SESSIONS[1].create.assert_called() self.assertNoSpans() - def test_spans_get_and_leave_empty_pool(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_spans_get_and_leave_empty_pool(self, mock_region): if not HAS_OPENTELEMETRY_INSTALLED: return diff --git a/tests/unit/test_session.py b/tests/unit/test_session.py index 9b5499dfee..bfbd6edd5e 100644 --- a/tests/unit/test_session.py +++ b/tests/unit/test_session.py @@ -130,6 +130,7 @@ class TestSession(OpenTelemetryBase): "gcp.client.service": "spanner", "gcp.client.version": LIB_VERSION, "gcp.client.repo": "googleapis/python-spanner", + "cloud.region": "global", } enrich_with_otel_scope(BASE_ATTRIBUTES) @@ -222,7 +223,11 @@ def test_create_w_session_id(self): self.assertNoSpans() - def test_create_w_database_role(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_create_w_database_role(self, mock_region): session_pb = self._make_session_pb( self.SESSION_NAME, database_role=self.DATABASE_ROLE ) @@ -263,7 +268,11 @@ def test_create_w_database_role(self): ), ) - def test_create_session_span_annotations(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_create_session_span_annotations(self, mock_region): session_pb = self._make_session_pb( self.SESSION_NAME, database_role=self.DATABASE_ROLE ) @@ -301,7 +310,11 @@ def test_create_session_span_annotations(self): wantEventNames = ["Creating Session"] self.assertSpanEvents("TestSessionSpan", wantEventNames, span) - def test_create_wo_database_role(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_create_wo_database_role(self, mock_region): session_pb = self._make_session_pb(self.SESSION_NAME) gax_api = self._make_spanner_api() gax_api.create_session.return_value = session_pb @@ -337,7 +350,11 @@ def test_create_wo_database_role(self): ), ) - def test_create_ok(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_create_ok(self, mock_region): session_pb = self._make_session_pb(self.SESSION_NAME) gax_api = self._make_spanner_api() gax_api.create_session.return_value = session_pb @@ -373,7 +390,11 @@ def test_create_ok(self): ), ) - def test_create_w_labels(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_create_w_labels(self, mock_region): labels = {"foo": "bar"} session_pb = self._make_session_pb(self.SESSION_NAME, labels=labels) gax_api = self._make_spanner_api() @@ -411,7 +432,11 @@ def test_create_w_labels(self): ), ) - def test_create_error(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_create_error(self, mock_region): gax_api = self._make_spanner_api() gax_api.create_session.side_effect = Unknown("error") database = self._make_database() @@ -437,7 +462,11 @@ def test_exists_wo_session_id(self): self.assertNoSpans() - def test_exists_hit(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_exists_hit(self, mock_region): session_pb = self._make_session_pb(self.SESSION_NAME) gax_api = self._make_spanner_api() gax_api.get_session.return_value = session_pb @@ -470,7 +499,11 @@ def test_exists_hit(self): ), ) - def test_exists_miss(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_exists_miss(self, mock_region): gax_api = self._make_spanner_api() gax_api.get_session.side_effect = NotFound("testing") database = self._make_database() @@ -502,7 +535,11 @@ def test_exists_miss(self): ), ) - def test_exists_error(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_exists_error(self, mock_region): gax_api = self._make_spanner_api() gax_api.get_session.side_effect = Unknown("testing") database = self._make_database() @@ -540,7 +577,11 @@ def test_ping_wo_session_id(self): with self.assertRaises(ValueError): session.ping() - def test_ping_hit(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_ping_hit(self, mock_region): gax_api = self._make_spanner_api() gax_api.execute_sql.return_value = "1" database = self._make_database() @@ -572,7 +613,11 @@ def test_ping_hit(self): attributes=dict(self.BASE_ATTRIBUTES, x_goog_spanner_request_id=req_id), ) - def test_ping_miss(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_ping_miss(self, mock_region): gax_api = self._make_spanner_api() gax_api.execute_sql.side_effect = NotFound("testing") database = self._make_database() @@ -606,7 +651,11 @@ def test_ping_miss(self): attributes=dict(self.BASE_ATTRIBUTES, x_goog_spanner_request_id=req_id), ) - def test_ping_error(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_ping_error(self, mock_region): gax_api = self._make_spanner_api() gax_api.execute_sql.side_effect = Unknown("testing") database = self._make_database() @@ -649,7 +698,11 @@ def test_delete_wo_session_id(self): self.assertNoSpans() - def test_delete_hit(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_delete_hit(self, mock_region): gax_api = self._make_spanner_api() gax_api.delete_session.return_value = None database = self._make_database() @@ -678,7 +731,11 @@ def test_delete_hit(self): attributes=dict(attrs, x_goog_spanner_request_id=req_id), ) - def test_delete_miss(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_delete_miss(self, mock_region): gax_api = self._make_spanner_api() gax_api.delete_session.side_effect = NotFound("testing") database = self._make_database() @@ -714,7 +771,11 @@ def test_delete_miss(self): attributes=attrs, ) - def test_delete_error(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_delete_error(self, mock_region): gax_api = self._make_spanner_api() gax_api.delete_session.side_effect = Unknown("testing") database = self._make_database() diff --git a/tests/unit/test_snapshot.py b/tests/unit/test_snapshot.py index 5e60d71bd6..974cc8e75e 100644 --- a/tests/unit/test_snapshot.py +++ b/tests/unit/test_snapshot.py @@ -76,6 +76,7 @@ "db.url": "spanner.googleapis.com", "db.instance": "testing", "net.host.name": "spanner.googleapis.com", + "cloud.region": "global", "gcp.client.service": "spanner", "gcp.client.version": LIB_VERSION, "gcp.client.repo": "googleapis/python-spanner", @@ -115,6 +116,8 @@ class _Derived(_SnapshotBase): """A minimally-implemented _SnapshotBase-derived class for testing""" + transaction_tag = None + # Use a simplified implementation of _build_transaction_options_pb # that always returns the same transaction options. TRANSACTION_OPTIONS = TransactionOptions() @@ -556,7 +559,11 @@ def test_iteration_w_raw_raising_non_retryable_internal_error_after_token(self): ) self.assertNoSpans() - def test_iteration_w_span_creation(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_iteration_w_span_creation(self, mock_region): name = "TestSpan" extra_atts = {"test_att": 1} raw = _MockIterator() @@ -578,7 +585,11 @@ def test_iteration_w_span_creation(self): ), ) - def test_iteration_w_multiple_span_creation(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_iteration_w_multiple_span_creation(self, mock_region): from google.api_core.exceptions import ServiceUnavailable if HAS_OPENTELEMETRY_INSTALLED: @@ -680,7 +691,11 @@ def test_begin_error_already_begun(self): self.assertNoSpans() - def test_begin_error_other(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_begin_error_other(self, mock_region): derived = _build_snapshot_derived(multi_use=True) database = derived._session._database @@ -699,7 +714,11 @@ def test_begin_error_other(self): attributes=_build_span_attributes(database), ) - def test_begin_read_write(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_begin_read_write(self, mock_region): derived = _build_snapshot_derived(multi_use=True, read_only=False) begin_transaction = derived._session._database.spanner_api.begin_transaction @@ -707,7 +726,11 @@ def test_begin_read_write(self): self._execute_begin(derived) - def test_begin_read_only(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_begin_read_only(self, mock_region): derived = _build_snapshot_derived(multi_use=True, read_only=True) begin_transaction = derived._session._database.spanner_api.begin_transaction @@ -715,7 +738,11 @@ def test_begin_read_only(self): self._execute_begin(derived) - def test_begin_precommit_token(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_begin_precommit_token(self, mock_region): derived = _build_snapshot_derived(multi_use=True) begin_transaction = derived._session._database.spanner_api.begin_transaction @@ -725,7 +752,11 @@ def test_begin_precommit_token(self): self._execute_begin(derived) - def test_begin_retry_for_internal_server_error(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_begin_retry_for_internal_server_error(self, mock_region): derived = _build_snapshot_derived(multi_use=True) begin_transaction = derived._session._database.spanner_api.begin_transaction @@ -745,7 +776,11 @@ def test_begin_retry_for_internal_server_error(self): actual_statuses = self.finished_spans_events_statuses() self.assertEqual(expected_statuses, actual_statuses) - def test_begin_retry_for_aborted(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_begin_retry_for_aborted(self, mock_region): derived = _build_snapshot_derived(multi_use=True) begin_transaction = derived._session._database.spanner_api.begin_transaction @@ -813,7 +848,11 @@ def _execute_begin(self, derived: _Derived, attempts: int = 1): attributes=_build_span_attributes(database, attempt=attempts), ) - def test_read_other_error(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_read_other_error(self, mock_region): from google.cloud.spanner_v1.keyset import KeySet keyset = KeySet(all_=True) @@ -966,8 +1005,9 @@ def _execute_read( expected_limit = LIMIT # Transaction tag is ignored for read request. - expected_request_options = request_options - expected_request_options.transaction_tag = None + expected_request_options = RequestOptions(request_options) + if derived.transaction_tag: + expected_request_options.transaction_tag = derived.transaction_tag expected_directed_read_options = ( directed_read_options @@ -1000,15 +1040,16 @@ def _execute_read( retry=retry, timeout=timeout, ) - + expected_attributes = dict( + BASE_ATTRIBUTES, + table_id=TABLE_NAME, + columns=tuple(COLUMNS), + x_goog_spanner_request_id=req_id, + ) + if request_options and request_options.request_tag: + expected_attributes["request.tag"] = request_options.request_tag self.assertSpanAttributes( - "CloudSpanner._Derived.read", - attributes=dict( - BASE_ATTRIBUTES, - table_id=TABLE_NAME, - columns=tuple(COLUMNS), - x_goog_spanner_request_id=req_id, - ), + "CloudSpanner._Derived.read", attributes=expected_attributes ) if first: @@ -1017,89 +1058,162 @@ def _execute_read( if use_multiplexed: self.assertEqual(derived._precommit_token, PRECOMMIT_TOKEN_2) - def test_read_wo_multi_use(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_read_wo_multi_use(self, mock_region): self._execute_read(multi_use=False) - def test_read_w_request_tag_success(self): - request_options = RequestOptions( - request_tag="tag-1", - ) + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_read_w_request_tag_success(self, mock_region): + request_options = {"request_tag": "tag-1"} self._execute_read(multi_use=False, request_options=request_options) - def test_read_w_transaction_tag_success(self): - request_options = RequestOptions( - transaction_tag="tag-1-1", - ) + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_read_w_transaction_tag_success(self, mock_region): + request_options = {"transaction_tag": "tag-1-1"} self._execute_read(multi_use=False, request_options=request_options) - def test_read_w_request_and_transaction_tag_success(self): - request_options = RequestOptions( - request_tag="tag-1", - transaction_tag="tag-1-1", - ) + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_read_w_request_and_transaction_tag_success(self, mock_region): + request_options = {"request_tag": "tag-1", "transaction_tag": "tag-1-1"} self._execute_read(multi_use=False, request_options=request_options) - def test_read_w_request_and_transaction_tag_dictionary_success(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_read_w_request_and_transaction_tag_dictionary_success(self, mock_region): request_options = {"request_tag": "tag-1", "transaction_tag": "tag-1-1"} self._execute_read(multi_use=False, request_options=request_options) - def test_read_w_incorrect_tag_dictionary_error(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_read_w_incorrect_tag_dictionary_error(self, mock_region): request_options = {"incorrect_tag": "tag-1-1"} with self.assertRaises(ValueError): self._execute_read(multi_use=False, request_options=request_options) - def test_read_wo_multi_use_w_read_request_count_gt_0(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_read_wo_multi_use_w_read_request_count_gt_0(self, mock_region): with self.assertRaises(ValueError): self._execute_read(multi_use=False, count=1) - def test_read_w_multi_use_w_first(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_read_w_multi_use_w_first(self, mock_region): self._execute_read(multi_use=True, first=True) - def test_read_w_multi_use_wo_first(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_read_w_multi_use_wo_first(self, mock_region): self._execute_read(multi_use=True, first=False) - def test_read_w_multi_use_wo_first_w_count_gt_0(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_read_w_multi_use_wo_first_w_count_gt_0(self, mock_region): self._execute_read(multi_use=True, first=False, count=1) - def test_read_w_multi_use_w_first_w_partition(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_read_w_multi_use_w_first_w_partition(self, mock_region): PARTITION = b"FADEABED" self._execute_read(multi_use=True, first=True, partition=PARTITION) - def test_read_w_multi_use_w_first_w_count_gt_0(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_read_w_multi_use_w_first_w_count_gt_0(self, mock_region): with self.assertRaises(ValueError): self._execute_read(multi_use=True, first=True, count=1) - def test_read_w_timeout_param(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_read_w_timeout_param(self, mock_region): self._execute_read(multi_use=True, first=False, timeout=2.0) - def test_read_w_retry_param(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_read_w_retry_param(self, mock_region): self._execute_read(multi_use=True, first=False, retry=Retry(deadline=60)) - def test_read_w_timeout_and_retry_params(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_read_w_timeout_and_retry_params(self, mock_region): self._execute_read( multi_use=True, first=False, retry=Retry(deadline=60), timeout=2.0 ) - def test_read_w_directed_read_options(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_read_w_directed_read_options(self, mock_region): self._execute_read(multi_use=False, directed_read_options=DIRECTED_READ_OPTIONS) - def test_read_w_directed_read_options_at_client_level(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_read_w_directed_read_options_at_client_level(self, mock_region): self._execute_read( multi_use=False, directed_read_options_at_client_level=DIRECTED_READ_OPTIONS_FOR_CLIENT, ) - def test_read_w_directed_read_options_override(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_read_w_directed_read_options_override(self, mock_region): self._execute_read( multi_use=False, directed_read_options=DIRECTED_READ_OPTIONS, directed_read_options_at_client_level=DIRECTED_READ_OPTIONS_FOR_CLIENT, ) - def test_read_w_precommit_tokens(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_read_w_precommit_tokens(self, mock_region): self._execute_read(multi_use=True, use_multiplexed=True) - def test_execute_sql_other_error(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_execute_sql_other_error(self, mock_region): database = _Database() database.spanner_api = build_spanner_api() database.spanner_api.execute_streaming_sql.side_effect = RuntimeError() @@ -1243,10 +1357,11 @@ def _execute_sql_helper( expected_query_options, query_options ) - if derived._read_only: - # Transaction tag is ignored for read only requests. - expected_request_options = request_options - expected_request_options.transaction_tag = None + expected_request_options = RequestOptions(request_options) + if derived.transaction_tag: + expected_request_options.transaction_tag = derived.transaction_tag + if not derived._read_only and request_options.request_tag: + expected_request_options.request_tag = request_options.request_tag expected_directed_read_options = ( directed_read_options @@ -1283,16 +1398,20 @@ def _execute_sql_helper( self.assertEqual(derived._execute_sql_request_count, sql_count + 1) + expected_attributes = dict( + BASE_ATTRIBUTES, + **{ + "db.statement": SQL_QUERY_WITH_PARAM, + "x_goog_spanner_request_id": req_id, + }, + ) + if request_options and request_options.request_tag: + expected_attributes["request.tag"] = request_options.request_tag + self.assertSpanAttributes( "CloudSpanner._Derived.execute_sql", status=StatusCode.OK, - attributes=dict( - BASE_ATTRIBUTES, - **{ - "db.statement": SQL_QUERY_WITH_PARAM, - "x_goog_spanner_request_id": req_id, - }, - ), + attributes=expected_attributes, ) if first: @@ -1301,33 +1420,61 @@ def _execute_sql_helper( if use_multiplexed: self.assertEqual(derived._precommit_token, PRECOMMIT_TOKEN_2) - def test_execute_sql_wo_multi_use(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_execute_sql_wo_multi_use(self, mock_region): self._execute_sql_helper(multi_use=False) def test_execute_sql_wo_multi_use_w_read_request_count_gt_0(self): with self.assertRaises(ValueError): self._execute_sql_helper(multi_use=False, count=1) - def test_execute_sql_w_multi_use_wo_first(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_execute_sql_w_multi_use_wo_first(self, mock_region): self._execute_sql_helper(multi_use=True, first=False, sql_count=1) - def test_execute_sql_w_multi_use_wo_first_w_count_gt_0(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_execute_sql_w_multi_use_wo_first_w_count_gt_0(self, mock_region): self._execute_sql_helper(multi_use=True, first=False, count=1) - def test_execute_sql_w_multi_use_w_first(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_execute_sql_w_multi_use_w_first(self, mock_region): self._execute_sql_helper(multi_use=True, first=True) def test_execute_sql_w_multi_use_w_first_w_count_gt_0(self): with self.assertRaises(ValueError): self._execute_sql_helper(multi_use=True, first=True, count=1) - def test_execute_sql_w_retry(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_execute_sql_w_retry(self, mock_region): self._execute_sql_helper(multi_use=False, retry=None) - def test_execute_sql_w_timeout(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_execute_sql_w_timeout(self, mock_region): self._execute_sql_helper(multi_use=False, timeout=None) - def test_execute_sql_w_query_options(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_execute_sql_w_query_options(self, mock_region): from google.cloud.spanner_v1 import ExecuteSqlRequest self._execute_sql_helper( @@ -1335,7 +1482,11 @@ def test_execute_sql_w_query_options(self): query_options=ExecuteSqlRequest.QueryOptions(optimizer_version="3"), ) - def test_execute_sql_w_request_options(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_execute_sql_w_request_options(self, mock_region): self._execute_sql_helper( multi_use=False, request_options=RequestOptions( @@ -1343,26 +1494,37 @@ def test_execute_sql_w_request_options(self): ), ) - def test_execute_sql_w_request_tag_success(self): - request_options = RequestOptions( - request_tag="tag-1", - ) + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_execute_sql_w_request_tag_success(self, mock_region): + request_options = {"request_tag": "tag-1"} self._execute_sql_helper(multi_use=False, request_options=request_options) - def test_execute_sql_w_transaction_tag_success(self): - request_options = RequestOptions( - transaction_tag="tag-1-1", - ) + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_execute_sql_w_transaction_tag_success(self, mock_region): + request_options = {"transaction_tag": "tag-1-1"} self._execute_sql_helper(multi_use=False, request_options=request_options) - def test_execute_sql_w_request_and_transaction_tag_success(self): - request_options = RequestOptions( - request_tag="tag-1", - transaction_tag="tag-1-1", - ) + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_execute_sql_w_request_and_transaction_tag_success(self, mock_region): + request_options = {"request_tag": "tag-1", "transaction_tag": "tag-1-1"} self._execute_sql_helper(multi_use=False, request_options=request_options) - def test_execute_sql_w_request_and_transaction_tag_dictionary_success(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_execute_sql_w_request_and_transaction_tag_dictionary_success( + self, mock_region + ): request_options = {"request_tag": "tag-1", "transaction_tag": "tag-1-1"} self._execute_sql_helper(multi_use=False, request_options=request_options) @@ -1371,25 +1533,41 @@ def test_execute_sql_w_incorrect_tag_dictionary_error(self): with self.assertRaises(ValueError): self._execute_sql_helper(multi_use=False, request_options=request_options) - def test_execute_sql_w_directed_read_options(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_execute_sql_w_directed_read_options(self, mock_region): self._execute_sql_helper( multi_use=False, directed_read_options=DIRECTED_READ_OPTIONS ) - def test_execute_sql_w_directed_read_options_at_client_level(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_execute_sql_w_directed_read_options_at_client_level(self, mock_region): self._execute_sql_helper( multi_use=False, directed_read_options_at_client_level=DIRECTED_READ_OPTIONS_FOR_CLIENT, ) - def test_execute_sql_w_directed_read_options_override(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_execute_sql_w_directed_read_options_override(self, mock_region): self._execute_sql_helper( multi_use=False, directed_read_options=DIRECTED_READ_OPTIONS, directed_read_options_at_client_level=DIRECTED_READ_OPTIONS_FOR_CLIENT, ) - def test_execute_sql_w_precommit_tokens(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_execute_sql_w_precommit_tokens(self, mock_region): self._execute_sql_helper(multi_use=True, use_multiplexed=True) def _partition_read_helper( @@ -1497,7 +1675,11 @@ def test_partition_read_wo_existing_transaction_raises(self): with self.assertRaises(ValueError): self._partition_read_helper(multi_use=True, w_txn=False) - def test_partition_read_other_error(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_partition_read_other_error(self, mock_region): from google.cloud.spanner_v1.keyset import KeySet keyset = KeySet(all_=True) @@ -1556,24 +1738,48 @@ def test_partition_read_w_retry(self): self.assertEqual(api.partition_read.call_count, 2) - def test_partition_read_ok_w_index_no_options(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_partition_read_ok_w_index_no_options(self, mock_region): self._partition_read_helper(multi_use=True, w_txn=True, index="index") - def test_partition_read_ok_w_size(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_partition_read_ok_w_size(self, mock_region): self._partition_read_helper(multi_use=True, w_txn=True, size=2000) - def test_partition_read_ok_w_max_partitions(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_partition_read_ok_w_max_partitions(self, mock_region): self._partition_read_helper(multi_use=True, w_txn=True, max_partitions=4) - def test_partition_read_ok_w_timeout_param(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_partition_read_ok_w_timeout_param(self, mock_region): self._partition_read_helper(multi_use=True, w_txn=True, timeout=2.0) - def test_partition_read_ok_w_retry_param(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_partition_read_ok_w_retry_param(self, mock_region): self._partition_read_helper( multi_use=True, w_txn=True, retry=Retry(deadline=60) ) - def test_partition_read_ok_w_timeout_and_retry_params(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_partition_read_ok_w_timeout_and_retry_params(self, mock_region): self._partition_read_helper( multi_use=True, w_txn=True, retry=Retry(deadline=60), timeout=2.0 ) @@ -1676,7 +1882,11 @@ def _partition_query_helper( ), ) - def test_partition_query_other_error(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_partition_query_other_error(self, mock_region): database = _Database() database.spanner_api = build_spanner_api() database.spanner_api.partition_query.side_effect = RuntimeError() @@ -1705,24 +1915,48 @@ def test_partition_query_wo_transaction_raises(self): with self.assertRaises(ValueError): self._partition_query_helper(multi_use=True, w_txn=False) - def test_partition_query_ok_w_index_no_options(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_partition_query_ok_w_index_no_options(self, mock_region): self._partition_query_helper(multi_use=True, w_txn=True) - def test_partition_query_ok_w_size(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_partition_query_ok_w_size(self, mock_region): self._partition_query_helper(multi_use=True, w_txn=True, size=2000) - def test_partition_query_ok_w_max_partitions(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_partition_query_ok_w_max_partitions(self, mock_region): self._partition_query_helper(multi_use=True, w_txn=True, max_partitions=4) - def test_partition_query_ok_w_timeout_param(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_partition_query_ok_w_timeout_param(self, mock_region): self._partition_query_helper(multi_use=True, w_txn=True, timeout=2.0) - def test_partition_query_ok_w_retry_param(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_partition_query_ok_w_retry_param(self, mock_region): self._partition_query_helper( multi_use=True, w_txn=True, retry=Retry(deadline=30) ) - def test_partition_query_ok_w_timeout_and_retry_params(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_partition_query_ok_w_timeout_and_retry_params(self, mock_region): self._partition_query_helper( multi_use=True, w_txn=True, retry=Retry(deadline=60), timeout=2.0 ) @@ -1984,21 +2218,24 @@ def _build_snapshot_derived(session=None, multi_use=False, read_only=True) -> _D return derived -def _build_span_attributes(database: Database, attempt: int = 1) -> Mapping[str, str]: +def _build_span_attributes( + database: Database, attempt: int = 1, **extra_attributes +) -> Mapping[str, str]: """Builds the attributes for spans using the given database and extra attributes.""" - return enrich_with_otel_scope( - { - "db.type": "spanner", - "db.url": "spanner.googleapis.com", - "db.instance": database.name, - "net.host.name": "spanner.googleapis.com", - "gcp.client.service": "spanner", - "gcp.client.version": LIB_VERSION, - "gcp.client.repo": "googleapis/python-spanner", - "x_goog_spanner_request_id": _build_request_id(database, attempt), - } - ) + attributes = { + "db.type": "spanner", + "db.url": "spanner.googleapis.com", + "db.instance": database.name, + "net.host.name": "spanner.googleapis.com", + "cloud.region": "global", + "gcp.client.service": "spanner", + "gcp.client.version": LIB_VERSION, + "gcp.client.repo": "googleapis/python-spanner", + "x_goog_spanner_request_id": _build_request_id(database, attempt), + } + attributes.update(extra_attributes) + return enrich_with_otel_scope(attributes) def _build_request_id(database: Database, attempt: int) -> str: diff --git a/tests/unit/test_spanner_metrics_tracer_factory.py b/tests/unit/test_spanner_metrics_tracer_factory.py index 48fe1b4837..8ae7bfc694 100644 --- a/tests/unit/test_spanner_metrics_tracer_factory.py +++ b/tests/unit/test_spanner_metrics_tracer_factory.py @@ -14,14 +14,9 @@ # limitations under the License. import pytest -import unittest -from unittest import mock - -from google.cloud.spanner_v1.metrics.constants import GOOGLE_CLOUD_REGION_KEY from google.cloud.spanner_v1.metrics.spanner_metrics_tracer_factory import ( SpannerMetricsTracerFactory, ) -from opentelemetry.sdk.resources import Resource pytest.importorskip("opentelemetry") @@ -50,48 +45,3 @@ def test_get_instance_config(self): def test_get_client_name(self): client_name = SpannerMetricsTracerFactory._get_client_name() assert isinstance(client_name, str) - assert "spanner-python" in client_name - - def test_get_location(self): - location = SpannerMetricsTracerFactory._get_location() - assert isinstance(location, str) - assert location # Simply asserting for non empty as this can change depending on the instance this test runs in. - - -class TestSpannerMetricsTracerFactoryGetLocation(unittest.TestCase): - @mock.patch( - "opentelemetry.resourcedetector.gcp_resource_detector.GoogleCloudResourceDetector.detect" - ) - def test_get_location_with_region(self, mock_detect): - """Test that _get_location returns the region when detected.""" - mock_resource = Resource.create({GOOGLE_CLOUD_REGION_KEY: "us-central1"}) - mock_detect.return_value = mock_resource - - location = SpannerMetricsTracerFactory._get_location() - assert location == "us-central1" - - @mock.patch( - "opentelemetry.resourcedetector.gcp_resource_detector.GoogleCloudResourceDetector.detect" - ) - def test_get_location_without_region(self, mock_detect): - """Test that _get_location returns 'global' when no region is detected.""" - mock_resource = Resource.create({}) # No region attribute - mock_detect.return_value = mock_resource - - location = SpannerMetricsTracerFactory._get_location() - assert location == "global" - - @mock.patch( - "opentelemetry.resourcedetector.gcp_resource_detector.GoogleCloudResourceDetector.detect" - ) - def test_get_location_with_exception(self, mock_detect): - """Test that _get_location returns 'global' and logs a warning on exception.""" - mock_detect.side_effect = Exception("detector failed") - - with self.assertLogs( - "google.cloud.spanner_v1.metrics.spanner_metrics_tracer_factory", - level="WARNING", - ) as log: - location = SpannerMetricsTracerFactory._get_location() - assert location == "global" - self.assertIn("Failed to detect GCP resource location", log.output[0]) diff --git a/tests/unit/test_transaction.py b/tests/unit/test_transaction.py index 7a33372dae..39656cb8d1 100644 --- a/tests/unit/test_transaction.py +++ b/tests/unit/test_transaction.py @@ -26,6 +26,7 @@ TransactionOptions, ResultSetMetadata, ) +from google.cloud.spanner_v1._helpers import GOOGLE_CLOUD_REGION_GLOBAL from google.cloud.spanner_v1 import DefaultTransactionOptions from google.cloud.spanner_v1 import Type from google.cloud.spanner_v1 import TypeCode @@ -191,7 +192,11 @@ def test_rollback_already_rolled_back(self): self.assertNoSpans() - def test_rollback_w_other_error(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_rollback_w_other_error(self, mock_region): database = _Database() database.spanner_api = self._make_spanner_api() database.spanner_api.rollback.side_effect = RuntimeError("other error") @@ -214,7 +219,11 @@ def test_rollback_w_other_error(self): ), ) - def test_rollback_ok(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_rollback_ok(self, mock_region): from google.protobuf.empty_pb2 import Empty empty_pb = Empty() @@ -346,7 +355,11 @@ def test_commit_already_rolled_back(self): ] self.assertEqual(got_span_events_statuses, want_span_events_statuses) - def test_commit_w_other_error(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_commit_w_other_error(self, mock_region): database = _Database() database.spanner_api = self._make_spanner_api() database.spanner_api.commit.side_effect = RuntimeError() @@ -558,31 +571,55 @@ def _commit_helper( actual_statuses = self.finished_spans_events_statuses() self.assertEqual(actual_statuses, expected_statuses) - def test_commit_mutations_only_not_multiplexed(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_commit_mutations_only_not_multiplexed(self, mock_region): self._commit_helper(mutations=[DELETE_MUTATION], is_multiplexed=False) - def test_commit_mutations_only_multiplexed_w_non_insert_mutation(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_commit_mutations_only_multiplexed_w_non_insert_mutation(self, mock_region): self._commit_helper( mutations=[DELETE_MUTATION], is_multiplexed=True, expected_begin_mutation=DELETE_MUTATION, ) - def test_commit_mutations_only_multiplexed_w_insert_mutation(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_commit_mutations_only_multiplexed_w_insert_mutation(self, mock_region): self._commit_helper( mutations=[INSERT_MUTATION], is_multiplexed=True, expected_begin_mutation=INSERT_MUTATION, ) - def test_commit_mutations_only_multiplexed_w_non_insert_and_insert_mutations(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_commit_mutations_only_multiplexed_w_non_insert_and_insert_mutations( + self, mock_region + ): self._commit_helper( mutations=[INSERT_MUTATION, DELETE_MUTATION], is_multiplexed=True, expected_begin_mutation=DELETE_MUTATION, ) - def test_commit_mutations_only_multiplexed_w_multiple_insert_mutations(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_commit_mutations_only_multiplexed_w_multiple_insert_mutations( + self, mock_region + ): insert_1 = Mutation(insert=_make_write_pb(TABLE_NAME, COLUMNS, [VALUE_1])) insert_2 = Mutation( insert=_make_write_pb(TABLE_NAME, COLUMNS, [VALUE_1, VALUE_2]) @@ -594,7 +631,13 @@ def test_commit_mutations_only_multiplexed_w_multiple_insert_mutations(self): expected_begin_mutation=insert_2, ) - def test_commit_mutations_only_multiplexed_w_multiple_non_insert_mutations(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_commit_mutations_only_multiplexed_w_multiple_non_insert_mutations( + self, mock_region + ): mutations = [UPDATE_MUTATION, DELETE_MUTATION] self._commit_helper( mutations=mutations, @@ -602,7 +645,11 @@ def test_commit_mutations_only_multiplexed_w_multiple_non_insert_mutations(self) expected_begin_mutation=mutations[0], ) - def test_commit_w_return_commit_stats(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_commit_w_return_commit_stats(self, mock_region): self._commit_helper(return_commit_stats=True) def test_commit_w_max_commit_delay(self): @@ -629,7 +676,11 @@ def test_commit_w_incorrect_tag_dictionary_error(self): with self.assertRaises(ValueError): self._commit_helper(request_options=request_options) - def test_commit_w_retry_for_precommit_token(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_commit_w_retry_for_precommit_token(self, mock_region): self._commit_helper(retry_for_precommit_token=True) def test_commit_w_retry_for_precommit_token_then_error(self): @@ -659,7 +710,11 @@ def test__make_params_pb_w_params_w_param_types(self): ) self.assertEqual(params_pb, expected_params) - def test_execute_update_other_error(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_execute_update_other_error(self, mock_region): database = _Database() database.spanner_api = self._make_spanner_api() database.spanner_api.execute_sql.side_effect = RuntimeError() @@ -752,8 +807,9 @@ def _execute_update_helper( expected_query_options = _merge_query_options( expected_query_options, query_options ) - expected_request_options = request_options - expected_request_options.transaction_tag = TRANSACTION_TAG + expected_request_options = RequestOptions(request_options) + if request_options.request_tag: + expected_request_options.request_tag = request_options.request_tag expected_request = ExecuteSqlRequest( session=self.SESSION_NAME, @@ -763,7 +819,7 @@ def _execute_update_helper( param_types=PARAM_TYPES, query_mode=MODE, query_options=expected_query_options, - request_options=request_options, + request_options=expected_request_options, seqno=count, ) api.execute_sql.assert_called_once_with( @@ -780,11 +836,13 @@ def _execute_update_helper( ], ) + expected_attributes = self._build_span_attributes( + database, **{"db.statement": DML_QUERY_WITH_PARAM} + ) + if request_options.request_tag: + expected_attributes["request.tag"] = request_options.request_tag self.assertSpanAttributes( - "CloudSpanner.Transaction.execute_update", - attributes=self._build_span_attributes( - database, **{"db.statement": DML_QUERY_WITH_PARAM} - ), + "CloudSpanner.Transaction.execute_update", attributes=expected_attributes ) self.assertEqual(transaction._transaction_id, TRANSACTION_ID) @@ -793,29 +851,51 @@ def _execute_update_helper( if use_multiplexed: self.assertEqual(transaction._precommit_token, PRECOMMIT_TOKEN_PB_0) - def test_execute_update_new_transaction(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_execute_update_new_transaction(self, mock_region): self._execute_update_helper() - def test_execute_update_w_request_tag_success(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_execute_update_w_request_tag_success(self, mock_region): request_options = RequestOptions( request_tag="tag-1", ) self._execute_update_helper(request_options=request_options) - def test_execute_update_w_transaction_tag_success(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_execute_update_w_transaction_tag_success(self, mock_region): request_options = RequestOptions( transaction_tag="tag-1-1", ) self._execute_update_helper(request_options=request_options) - def test_execute_update_w_request_and_transaction_tag_success(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_execute_update_w_request_and_transaction_tag_success(self, mock_region): request_options = RequestOptions( request_tag="tag-1", transaction_tag="tag-1-1", ) self._execute_update_helper(request_options=request_options) - def test_execute_update_w_request_and_transaction_tag_dictionary_success(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_execute_update_w_request_and_transaction_tag_dictionary_success( + self, mock_region + ): request_options = {"request_tag": "tag-1", "transaction_tag": "tag-1-1"} self._execute_update_helper(request_options=request_options) @@ -824,16 +904,32 @@ def test_execute_update_w_incorrect_tag_dictionary_error(self): with self.assertRaises(ValueError): self._execute_update_helper(request_options=request_options) - def test_execute_update_w_count(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_execute_update_w_count(self, mock_region): self._execute_update_helper(count=1) - def test_execute_update_w_timeout_param(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_execute_update_w_timeout_param(self, mock_region): self._execute_update_helper(timeout=2.0) - def test_execute_update_w_retry_param(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_execute_update_w_retry_param(self, mock_region): self._execute_update_helper(retry=Retry(deadline=60)) - def test_execute_update_w_timeout_and_retry_params(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_execute_update_w_timeout_and_retry_params(self, mock_region): self._execute_update_helper(retry=Retry(deadline=60), timeout=2.0) def test_execute_update_error(self): @@ -849,27 +945,47 @@ def test_execute_update_error(self): self.assertEqual(transaction._execute_sql_request_count, 1) - def test_execute_update_w_query_options(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_execute_update_w_query_options(self, mock_region): from google.cloud.spanner_v1 import ExecuteSqlRequest self._execute_update_helper( query_options=ExecuteSqlRequest.QueryOptions(optimizer_version="3") ) - def test_execute_update_wo_begin(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_execute_update_wo_begin(self, mock_region): self._execute_update_helper(begin=False) - def test_execute_update_w_precommit_token(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_execute_update_w_precommit_token(self, mock_region): self._execute_update_helper(use_multiplexed=True) - def test_execute_update_w_request_options(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_execute_update_w_request_options(self, mock_region): self._execute_update_helper( request_options=RequestOptions( priority=RequestOptions.Priority.PRIORITY_MEDIUM ) ) - def test_batch_update_other_error(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_batch_update_other_error(self, mock_region): database = _Database() database.spanner_api = self._make_spanner_api() database.spanner_api.execute_batch_dml.side_effect = RuntimeError() @@ -1025,45 +1141,79 @@ def _batch_update_helper( if use_multiplexed: self.assertEqual(transaction._precommit_token, PRECOMMIT_TOKEN_PB_2) - def test_batch_update_wo_begin(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_batch_update_wo_begin(self, mock_region): self._batch_update_helper(begin=False) - def test_batch_update_wo_errors(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_batch_update_wo_errors(self, mock_region): self._batch_update_helper( request_options=RequestOptions( priority=RequestOptions.Priority.PRIORITY_MEDIUM ), ) - def test_batch_update_w_request_tag_success(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_batch_update_w_request_tag_success(self, mock_region): request_options = RequestOptions( request_tag="tag-1", ) self._batch_update_helper(request_options=request_options) - def test_batch_update_w_transaction_tag_success(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_batch_update_w_transaction_tag_success(self, mock_region): request_options = RequestOptions( transaction_tag="tag-1-1", ) self._batch_update_helper(request_options=request_options) - def test_batch_update_w_request_and_transaction_tag_success(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_batch_update_w_request_and_transaction_tag_success(self, mock_region): request_options = RequestOptions( request_tag="tag-1", transaction_tag="tag-1-1", ) self._batch_update_helper(request_options=request_options) - def test_batch_update_w_request_and_transaction_tag_dictionary_success(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_batch_update_w_request_and_transaction_tag_dictionary_success( + self, mock_region + ): request_options = {"request_tag": "tag-1", "transaction_tag": "tag-1-1"} self._batch_update_helper(request_options=request_options) - def test_batch_update_w_incorrect_tag_dictionary_error(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_batch_update_w_incorrect_tag_dictionary_error(self, mock_region): request_options = {"incorrect_tag": "tag-1-1"} with self.assertRaises(ValueError): self._batch_update_helper(request_options=request_options) - def test_batch_update_w_errors(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_batch_update_w_errors(self, mock_region): self._batch_update_helper(error_after=2, count=1) def test_batch_update_error(self): @@ -1097,19 +1247,39 @@ def test_batch_update_error(self): self.assertEqual(transaction._execute_sql_request_count, 1) - def test_batch_update_w_timeout_param(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_batch_update_w_timeout_param(self, mock_region): self._batch_update_helper(timeout=2.0) - def test_batch_update_w_retry_param(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_batch_update_w_retry_param(self, mock_region): self._batch_update_helper(retry=gapic_v1.method.DEFAULT) - def test_batch_update_w_timeout_and_retry_params(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_batch_update_w_timeout_and_retry_params(self, mock_region): self._batch_update_helper(retry=gapic_v1.method.DEFAULT, timeout=2.0) - def test_batch_update_w_precommit_token(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_batch_update_w_precommit_token(self, mock_region): self._batch_update_helper(use_multiplexed=True) - def test_context_mgr_success(self): + @mock.patch( + "google.cloud.spanner_v1._opentelemetry_tracing._get_cloud_region", + return_value="global", + ) + def test_context_mgr_success(self, mock_region): transaction = build_transaction() session = transaction._session database = session._database @@ -1163,7 +1333,7 @@ def test_context_mgr_failure(self): def _build_span_attributes( database: Database, **extra_attributes ) -> Mapping[str, str]: - """Builds the attributes for spans using the given database and extra attributes.""" + """Builds the attributes for spans using the given database and attempt number.""" attributes = enrich_with_otel_scope( { @@ -1174,6 +1344,7 @@ def _build_span_attributes( "gcp.client.service": "spanner", "gcp.client.version": LIB_VERSION, "gcp.client.repo": "googleapis/python-spanner", + "cloud.region": GOOGLE_CLOUD_REGION_GLOBAL, } )