def test_run_in_transaction_w_abort_w_retry_metadata_deadline(self): import datetime from google.gax.errors import GaxError from google.gax.grpc import exc_to_code from grpc import StatusCode from google.cloud.spanner_v1.proto.spanner_pb2 import CommitResponse from google.cloud.spanner_v1.proto.transaction_pb2 import ( Transaction as TransactionPB) from google.cloud._helpers import UTC from google.cloud._helpers import _datetime_to_pb_timestamp from google.cloud.spanner_v1.transaction import Transaction from google.cloud.spanner_v1 import session as MUT from google.cloud._testing import _Monkey TABLE_NAME = 'citizens' COLUMNS = ['email', 'first_name', 'last_name', 'age'] VALUES = [ ['*****@*****.**', 'Phred', 'Phlyntstone', 32], ['*****@*****.**', 'Bharney', 'Rhubble', 31], ] TRANSACTION_ID = b'FACEDACE' RETRY_SECONDS = 1 RETRY_NANOS = 3456 transaction_pb = TransactionPB(id=TRANSACTION_ID) now = datetime.datetime.utcnow().replace(tzinfo=UTC) now_pb = _datetime_to_pb_timestamp(now) response = CommitResponse(commit_timestamp=now_pb) gax_api = _SpannerApi( _begin_transaction_response=transaction_pb, _commit_abort_count=1, _commit_abort_retry_seconds=RETRY_SECONDS, _commit_abort_retry_nanos=RETRY_NANOS, _commit_response=response, ) database = _Database(self.DATABASE_NAME) database.spanner_api = gax_api session = self._make_one(database) session._session_id = 'DEADBEEF' called_with = [] def unit_of_work(txn, *args, **kw): called_with.append((txn, args, kw)) txn.insert(TABLE_NAME, COLUMNS, VALUES) time_module = _FauxTimeModule() with _Monkey(MUT, time=time_module): with self.assertRaises(GaxError) as exc: session.run_in_transaction( unit_of_work, 'abc', some_arg='def', timeout_secs=0.01) self.assertEqual(exc_to_code(exc.exception.cause), StatusCode.ABORTED) self.assertIsNone(time_module._slept) self.assertEqual(len(called_with), 1) txn, args, kw = called_with[0] self.assertIsInstance(txn, Transaction) self.assertIsNone(txn.committed) self.assertEqual(args, ('abc',)) self.assertEqual(kw, {'some_arg': 'def'})
def test_context_mgr_success(self): import datetime from google.cloud.spanner_v1.proto.spanner_pb2 import CommitResponse from google.cloud.spanner_v1.proto.transaction_pb2 import ( Transaction as TransactionPB, ) from google.cloud._helpers import UTC from google.cloud._helpers import _datetime_to_pb_timestamp transaction_pb = TransactionPB(id=self.TRANSACTION_ID) database = _Database() now = datetime.datetime.utcnow().replace(tzinfo=UTC) now_pb = _datetime_to_pb_timestamp(now) response = CommitResponse(commit_timestamp=now_pb) database = _Database() api = database.spanner_api = _FauxSpannerAPI( _begin_transaction_response=transaction_pb, _commit_response=response ) session = _Session(database) transaction = self._make_one(session) with transaction: transaction.insert(TABLE_NAME, COLUMNS, VALUES) self.assertEqual(transaction.committed, now) session_id, mutations, txn_id, metadata = api._committed self.assertEqual(session_id, self.SESSION_NAME) self.assertEqual(txn_id, self.TRANSACTION_ID) self.assertEqual(mutations, transaction._mutations) self.assertEqual(metadata, [("google-cloud-resource-prefix", database.name)])
def test_begin_ok(self): from google.cloud.spanner_v1.proto.transaction_pb2 import ( Transaction as TransactionPB, ) transaction_pb = TransactionPB(id=self.TRANSACTION_ID) database = _Database() api = database.spanner_api = _FauxSpannerAPI( _begin_transaction_response=transaction_pb ) session = _Session(database) transaction = self._make_one(session) txn_id = transaction.begin() self.assertEqual(txn_id, self.TRANSACTION_ID) self.assertEqual(transaction._transaction_id, self.TRANSACTION_ID) session_id, txn_options, metadata = api._begun self.assertEqual(session_id, session.name) self.assertTrue(txn_options.HasField("read_write")) self.assertEqual(metadata, [("google-cloud-resource-prefix", database.name)]) self.assertSpanAttributes( "CloudSpanner.BeginTransaction", attributes=TestTransaction.BASE_ATTRIBUTES )
def test_context_mgr_failure(self): from google.protobuf.empty_pb2 import Empty empty_pb = Empty() from google.cloud.spanner_v1.proto.transaction_pb2 import ( Transaction as TransactionPB, ) transaction_pb = TransactionPB(id=self.TRANSACTION_ID) database = _Database() api = database.spanner_api = _FauxSpannerAPI( _begin_transaction_response=transaction_pb, _rollback_response=empty_pb ) session = _Session(database) transaction = self._make_one(session) with self.assertRaises(Exception): with transaction: transaction.insert(TABLE_NAME, COLUMNS, VALUES) raise Exception("bail out") self.assertEqual(transaction.committed, None) self.assertTrue(transaction.rolled_back) self.assertEqual(len(transaction._mutations), 1) self.assertEqual(api._committed, None) session_id, txn_id, metadata = api._rolled_back self.assertEqual(session_id, session.name) self.assertEqual(txn_id, self.TRANSACTION_ID) self.assertEqual(metadata, [("google-cloud-resource-prefix", database.name)])
def test_run_in_transaction_w_args_w_kwargs_wo_abort(self): import datetime from google.cloud.spanner_v1.proto.spanner_pb2 import CommitResponse from google.cloud.spanner_v1.proto.transaction_pb2 import ( Transaction as TransactionPB, TransactionOptions) from google.cloud._helpers import UTC from google.cloud._helpers import _datetime_to_pb_timestamp from google.cloud.spanner_v1.transaction import Transaction TABLE_NAME = 'citizens' COLUMNS = ['email', 'first_name', 'last_name', 'age'] VALUES = [ ['*****@*****.**', 'Phred', 'Phlyntstone', 32], ['*****@*****.**', 'Bharney', 'Rhubble', 31], ] TRANSACTION_ID = b'FACEDACE' transaction_pb = TransactionPB(id=TRANSACTION_ID) now = datetime.datetime.utcnow().replace(tzinfo=UTC) now_pb = _datetime_to_pb_timestamp(now) response = CommitResponse(commit_timestamp=now_pb) gax_api = self._make_spanner_api() gax_api.begin_transaction.return_value = transaction_pb gax_api.commit.return_value = response database = self._make_database() database.spanner_api = gax_api session = self._make_one(database) session._session_id = self.SESSION_ID called_with = [] def unit_of_work(txn, *args, **kw): called_with.append((txn, args, kw)) txn.insert(TABLE_NAME, COLUMNS, VALUES) return 42 return_value = session.run_in_transaction(unit_of_work, 'abc', some_arg='def') self.assertIsNone(session._transaction) self.assertEqual(len(called_with), 1) txn, args, kw = called_with[0] self.assertIsInstance(txn, Transaction) self.assertEqual(return_value, 42) self.assertEqual(args, ('abc', )) self.assertEqual(kw, {'some_arg': 'def'}) expected_options = TransactionOptions( read_write=TransactionOptions.ReadWrite(), ) gax_api.begin_transaction.assert_called_once_with( self.SESSION_NAME, expected_options, metadata=[('google-cloud-resource-prefix', database.name)], ) gax_api.commit.assert_called_once_with( self.SESSION_NAME, txn._mutations, transaction_id=TRANSACTION_ID, metadata=[('google-cloud-resource-prefix', database.name)], )
def test_begin_ok_exact_staleness(self): from google.cloud.spanner_v1.proto.transaction_pb2 import ( Transaction as TransactionPB) transaction_pb = TransactionPB(id=self.TRANSACTION_ID) database = _Database() api = database.spanner_api = _FauxSpannerAPI( _begin_transaction_response=transaction_pb) duration = self._makeDuration(seconds=3, microseconds=123456) session = _Session(database) snapshot = self._make_one(session, exact_staleness=duration, multi_use=True) txn_id = snapshot.begin() self.assertEqual(txn_id, self.TRANSACTION_ID) self.assertEqual(snapshot._transaction_id, self.TRANSACTION_ID) session_id, txn_options, metadata = api._begun self.assertEqual(session_id, session.name) read_only = txn_options.read_only self.assertEqual(read_only.exact_staleness.seconds, 3) self.assertEqual(read_only.exact_staleness.nanos, 123456000) self.assertEqual(metadata, [('google-cloud-resource-prefix', database.name)])
def test_begin_ok_exact_staleness(self): from google.protobuf.duration_pb2 import Duration from google.cloud.spanner_v1.proto.transaction_pb2 import ( Transaction as TransactionPB, TransactionOptions) transaction_pb = TransactionPB(id=TXN_ID) database = _Database() api = database.spanner_api = self._make_spanner_api() api.begin_transaction.return_value = transaction_pb duration = self._makeDuration(seconds=SECONDS, microseconds=MICROS) session = _Session(database) snapshot = self._make_one(session, exact_staleness=duration, multi_use=True) txn_id = snapshot.begin() self.assertEqual(txn_id, TXN_ID) self.assertEqual(snapshot._transaction_id, TXN_ID) expected_duration = Duration(seconds=SECONDS, nanos=MICROS * 1000) expected_txn_options = TransactionOptions( read_only=TransactionOptions.ReadOnly( exact_staleness=expected_duration)) api.begin_transaction.assert_called_once_with( session.name, expected_txn_options, metadata=[('google-cloud-resource-prefix', database.name)])
def test_begin_ok_exact_strong(self): from google.cloud.spanner_v1.proto.transaction_pb2 import ( Transaction as TransactionPB, TransactionOptions, ) transaction_pb = TransactionPB(id=TXN_ID) database = _Database() api = database.spanner_api = self._make_spanner_api() api.begin_transaction.return_value = transaction_pb session = _Session(database) snapshot = self._make_one(session, multi_use=True) txn_id = snapshot.begin() self.assertEqual(txn_id, TXN_ID) self.assertEqual(snapshot._transaction_id, TXN_ID) expected_txn_options = TransactionOptions( read_only=TransactionOptions.ReadOnly(strong=True)) api.begin_transaction.assert_called_once_with( session.name, expected_txn_options, metadata=[("google-cloud-resource-prefix", database.name)], )
def test_run_in_transaction_w_callback_raises_abort_wo_metadata(self): import datetime from google.api_core.exceptions import Aborted from google.cloud.spanner_v1.proto.spanner_pb2 import CommitResponse from google.cloud.spanner_v1.proto.transaction_pb2 import ( Transaction as TransactionPB) from google.cloud._helpers import UTC from google.cloud._helpers import _datetime_to_pb_timestamp from google.cloud.spanner_v1.transaction import Transaction from google.cloud.spanner_v1 import session as MUT from google.cloud._testing import _Monkey TABLE_NAME = 'citizens' COLUMNS = ['email', 'first_name', 'last_name', 'age'] VALUES = [ ['*****@*****.**', 'Phred', 'Phlyntstone', 32], ['*****@*****.**', 'Bharney', 'Rhubble', 31], ] TRANSACTION_ID = b'FACEDACE' RETRY_SECONDS = 1 RETRY_NANOS = 3456 transaction_pb = TransactionPB(id=TRANSACTION_ID) now = datetime.datetime.utcnow().replace(tzinfo=UTC) now_pb = _datetime_to_pb_timestamp(now) response = CommitResponse(commit_timestamp=now_pb) gax_api = _SpannerApi( _begin_transaction_response=transaction_pb, _commit_abort_retry_seconds=RETRY_SECONDS, _commit_abort_retry_nanos=RETRY_NANOS, _commit_response=response, ) database = _Database(self.DATABASE_NAME) database.spanner_api = gax_api session = self._make_one(database) session._session_id = 'DEADBEEF' called_with = [] def unit_of_work(txn, *args, **kw): called_with.append((txn, args, kw)) if len(called_with) < 2: raise _make_rpc_error(Aborted, gax_api._trailing_metadata()) txn.insert(TABLE_NAME, COLUMNS, VALUES) time_module = _FauxTimeModule() with _Monkey(MUT, time=time_module): session.run_in_transaction(unit_of_work) self.assertEqual(time_module._slept, RETRY_SECONDS + RETRY_NANOS / 1.0e9) self.assertEqual(len(called_with), 2) for index, (txn, args, kw) in enumerate(called_with): self.assertIsInstance(txn, Transaction) if index == 0: self.assertIsNone(txn.committed) else: self.assertEqual(txn.committed, now) self.assertEqual(args, ()) self.assertEqual(kw, {})
def test_run_in_transaction_callback_raises_non_gax_error(self): from google.cloud.spanner_v1.proto.transaction_pb2 import ( Transaction as TransactionPB, TransactionOptions, ) from google.cloud.spanner_v1.transaction import Transaction TABLE_NAME = "citizens" COLUMNS = ["email", "first_name", "last_name", "age"] VALUES = [ ["*****@*****.**", "Phred", "Phlyntstone", 32], ["*****@*****.**", "Bharney", "Rhubble", 31], ] TRANSACTION_ID = b"FACEDACE" transaction_pb = TransactionPB(id=TRANSACTION_ID) gax_api = self._make_spanner_api() gax_api.begin_transaction.return_value = transaction_pb gax_api.rollback.return_value = None database = self._make_database() database.spanner_api = gax_api session = self._make_one(database) session._session_id = self.SESSION_ID called_with = [] class Testing(Exception): pass def unit_of_work(txn, *args, **kw): called_with.append((txn, args, kw)) txn.insert(TABLE_NAME, COLUMNS, VALUES) raise Testing() with self.assertRaises(Testing): session.run_in_transaction(unit_of_work) self.assertIsNone(session._transaction) self.assertEqual(len(called_with), 1) txn, args, kw = called_with[0] self.assertIsInstance(txn, Transaction) self.assertIsNone(txn.committed) self.assertTrue(txn._rolled_back) self.assertEqual(args, ()) self.assertEqual(kw, {}) expected_options = TransactionOptions( read_write=TransactionOptions.ReadWrite()) gax_api.begin_transaction.assert_called_once_with( self.SESSION_NAME, expected_options, metadata=[("google-cloud-resource-prefix", database.name)], ) gax_api.rollback.assert_called_once_with( self.SESSION_NAME, TRANSACTION_ID, metadata=[("google-cloud-resource-prefix", database.name)], )
def test_run_in_transaction_callback_raises_non_abort_rpc_error(self): from google.api_core.exceptions import Cancelled from google.cloud.spanner_v1.proto.transaction_pb2 import ( Transaction as TransactionPB, TransactionOptions) from google.cloud.spanner_v1.transaction import Transaction TABLE_NAME = 'citizens' COLUMNS = ['email', 'first_name', 'last_name', 'age'] VALUES = [ ['*****@*****.**', 'Phred', 'Phlyntstone', 32], ['*****@*****.**', 'Bharney', 'Rhubble', 31], ] TRANSACTION_ID = b'FACEDACE' transaction_pb = TransactionPB(id=TRANSACTION_ID) gax_api = self._make_spanner_api() gax_api.begin_transaction.return_value = transaction_pb gax_api.rollback.return_value = None database = self._make_database() database.spanner_api = gax_api session = self._make_one(database) session._session_id = self.SESSION_ID called_with = [] def unit_of_work(txn, *args, **kw): called_with.append((txn, args, kw)) txn.insert(TABLE_NAME, COLUMNS, VALUES) raise Cancelled('error') with self.assertRaises(Cancelled): session.run_in_transaction(unit_of_work) self.assertIsNone(session._transaction) self.assertEqual(len(called_with), 1) txn, args, kw = called_with[0] self.assertIsInstance(txn, Transaction) self.assertIsNone(txn.committed) self.assertFalse(txn._rolled_back) self.assertEqual(args, ()) self.assertEqual(kw, {}) expected_options = TransactionOptions( read_write=TransactionOptions.ReadWrite(), ) gax_api.begin_transaction.assert_called_once_with( self.SESSION_NAME, expected_options, metadata=[('google-cloud-resource-prefix', database.name)], ) gax_api.rollback.assert_not_called()
def test_run_in_transaction_w_timeout(self): from google.cloud.spanner_v1 import session as MUT from google.cloud._testing import _Monkey from google.gax.errors import GaxError from google.gax.grpc import exc_to_code from google.cloud.spanner_v1.proto.transaction_pb2 import ( Transaction as TransactionPB) from grpc import StatusCode from google.cloud.spanner_v1.transaction import Transaction TABLE_NAME = 'citizens' COLUMNS = ['email', 'first_name', 'last_name', 'age'] VALUES = [ ['*****@*****.**', 'Phred', 'Phlyntstone', 32], ['*****@*****.**', 'Bharney', 'Rhubble', 31], ] TRANSACTION_ID = b'FACEDACE' transaction_pb = TransactionPB(id=TRANSACTION_ID) gax_api = _SpannerApi( _begin_transaction_response=transaction_pb, _commit_abort_count=1e6, ) database = _Database(self.DATABASE_NAME) database.spanner_api = gax_api session = self._make_one(database) session._session_id = 'DEADBEEF' called_with = [] def unit_of_work(txn, *args, **kw): called_with.append((txn, args, kw)) txn.insert(TABLE_NAME, COLUMNS, VALUES) time_module = _FauxTimeModule() time_module._times = [1, 1.5, 2.5] # retry once w/ timeout_secs=1 with _Monkey(MUT, time=time_module): with self.assertRaises(GaxError) as exc: session.run_in_transaction(unit_of_work, timeout_secs=1) self.assertEqual(exc_to_code(exc.exception.cause), StatusCode.ABORTED) self.assertEqual(time_module._slept, None) self.assertEqual(len(called_with), 2) for txn, args, kw in called_with: self.assertIsInstance(txn, Transaction) self.assertIsNone(txn.committed) self.assertEqual(args, ()) self.assertEqual(kw, {})
def test_run_in_transaction_w_abort_no_retry_metadata(self): import datetime from google.cloud.spanner_v1.proto.spanner_pb2 import CommitResponse from google.cloud.spanner_v1.proto.transaction_pb2 import ( Transaction as TransactionPB) from google.cloud._helpers import UTC from google.cloud._helpers import _datetime_to_pb_timestamp from google.cloud.spanner_v1.transaction import Transaction TABLE_NAME = 'citizens' COLUMNS = ['email', 'first_name', 'last_name', 'age'] VALUES = [ ['*****@*****.**', 'Phred', 'Phlyntstone', 32], ['*****@*****.**', 'Bharney', 'Rhubble', 31], ] TRANSACTION_ID = b'FACEDACE' transaction_pb = TransactionPB(id=TRANSACTION_ID) now = datetime.datetime.utcnow().replace(tzinfo=UTC) now_pb = _datetime_to_pb_timestamp(now) response = CommitResponse(commit_timestamp=now_pb) gax_api = _SpannerApi( _begin_transaction_response=transaction_pb, _commit_abort_count=1, _commit_response=response, ) database = _Database(self.DATABASE_NAME) database.spanner_api = gax_api session = self._make_one(database) session._session_id = 'DEADBEEF' called_with = [] def unit_of_work(txn, *args, **kw): called_with.append((txn, args, kw)) txn.insert(TABLE_NAME, COLUMNS, VALUES) return 'answer' return_value = session.run_in_transaction(unit_of_work, 'abc', some_arg='def') self.assertEqual(len(called_with), 2) for index, (txn, args, kw) in enumerate(called_with): self.assertIsInstance(txn, Transaction) self.assertEqual(return_value, 'answer') self.assertEqual(args, ('abc', )) self.assertEqual(kw, {'some_arg': 'def'})
def test_run_in_transaction_callback_raises_gax_error_non_abort(self): from google.gax.errors import GaxError from google.cloud.spanner_v1.proto.transaction_pb2 import ( Transaction as TransactionPB) from google.cloud.spanner_v1.transaction import Transaction TABLE_NAME = 'citizens' COLUMNS = ['email', 'first_name', 'last_name', 'age'] VALUES = [ ['*****@*****.**', 'Phred', 'Phlyntstone', 32], ['*****@*****.**', 'Bharney', 'Rhubble', 31], ] TRANSACTION_ID = b'FACEDACE' transaction_pb = TransactionPB(id=TRANSACTION_ID) gax_api = _SpannerApi( _begin_transaction_response=transaction_pb, _rollback_response=None, ) database = _Database(self.DATABASE_NAME) database.spanner_api = gax_api session = self._make_one(database) session._session_id = 'DEADBEEF' called_with = [] class Testing(GaxError): pass def unit_of_work(txn, *args, **kw): called_with.append((txn, args, kw)) txn.insert(TABLE_NAME, COLUMNS, VALUES) raise Testing('testing') with self.assertRaises(Testing): session.run_in_transaction(unit_of_work) self.assertIsNone(session._transaction) self.assertEqual(len(called_with), 1) txn, args, kw = called_with[0] self.assertIsInstance(txn, Transaction) self.assertIsNone(txn.committed) self.assertFalse(txn._rolled_back) self.assertEqual(args, ()) self.assertEqual(kw, {})
def test_begin_ok_exact_strong(self): from google.cloud.spanner_v1.proto.transaction_pb2 import ( Transaction as TransactionPB) transaction_pb = TransactionPB(id=self.TRANSACTION_ID) database = _Database() api = database.spanner_api = _FauxSpannerAPI( _begin_transaction_response=transaction_pb) session = _Session(database) snapshot = self._make_one(session, multi_use=True) txn_id = snapshot.begin() self.assertEqual(txn_id, self.TRANSACTION_ID) self.assertEqual(snapshot._transaction_id, self.TRANSACTION_ID) session_id, txn_options, metadata = api._begun self.assertEqual(session_id, session.name) self.assertTrue(txn_options.read_only.strong) self.assertEqual(metadata, [('google-cloud-resource-prefix', database.name)])
def test_begin_ok(self): from google.cloud.spanner_v1.proto.transaction_pb2 import ( Transaction as TransactionPB) transaction_pb = TransactionPB(id=self.TRANSACTION_ID) database = _Database() api = database.spanner_api = _FauxSpannerAPI( _begin_transaction_response=transaction_pb) session = _Session(database) transaction = self._make_one(session) txn_id = transaction.begin() self.assertEqual(txn_id, self.TRANSACTION_ID) self.assertEqual(transaction._transaction_id, self.TRANSACTION_ID) session_id, txn_options, metadata = api._begun self.assertEqual(session_id, session.name) self.assertTrue(txn_options.HasField('read_write')) self.assertEqual(metadata, [('google-cloud-resource-prefix', database.name)])
def test_run_in_transaction_w_abort_w_retry_metadata(self): import datetime from google.api_core.exceptions import Aborted from google.protobuf.duration_pb2 import Duration from google.rpc.error_details_pb2 import RetryInfo from google.cloud.spanner_v1.proto.spanner_pb2 import CommitResponse from google.cloud.spanner_v1.proto.transaction_pb2 import ( Transaction as TransactionPB, TransactionOptions) from google.cloud._helpers import UTC from google.cloud._helpers import _datetime_to_pb_timestamp from google.cloud.spanner_v1.transaction import Transaction TABLE_NAME = 'citizens' COLUMNS = ['email', 'first_name', 'last_name', 'age'] VALUES = [ ['*****@*****.**', 'Phred', 'Phlyntstone', 32], ['*****@*****.**', 'Bharney', 'Rhubble', 31], ] TRANSACTION_ID = b'FACEDACE' RETRY_SECONDS = 12 RETRY_NANOS = 3456 retry_info = RetryInfo( retry_delay=Duration(seconds=RETRY_SECONDS, nanos=RETRY_NANOS)) trailing_metadata = [ ('google.rpc.retryinfo-bin', retry_info.SerializeToString()), ] aborted = _make_rpc_error( Aborted, trailing_metadata=trailing_metadata, ) transaction_pb = TransactionPB(id=TRANSACTION_ID) now = datetime.datetime.utcnow().replace(tzinfo=UTC) now_pb = _datetime_to_pb_timestamp(now) response = CommitResponse(commit_timestamp=now_pb) gax_api = self._make_spanner_api() gax_api.begin_transaction.return_value = transaction_pb gax_api.commit.side_effect = [aborted, response] database = self._make_database() database.spanner_api = gax_api session = self._make_one(database) session._session_id = self.SESSION_ID called_with = [] def unit_of_work(txn, *args, **kw): called_with.append((txn, args, kw)) txn.insert(TABLE_NAME, COLUMNS, VALUES) with mock.patch('time.sleep') as sleep_mock: session.run_in_transaction(unit_of_work, 'abc', some_arg='def') sleep_mock.assert_called_once_with(RETRY_SECONDS + RETRY_NANOS / 1.0e9) self.assertEqual(len(called_with), 2) for index, (txn, args, kw) in enumerate(called_with): self.assertIsInstance(txn, Transaction) if index == 1: self.assertEqual(txn.committed, now) else: self.assertIsNone(txn.committed) self.assertEqual(args, ('abc', )) self.assertEqual(kw, {'some_arg': 'def'}) expected_options = TransactionOptions( read_write=TransactionOptions.ReadWrite(), ) self.assertEqual(gax_api.begin_transaction.call_args_list, [ mock.call( self.SESSION_NAME, expected_options, metadata=[('google-cloud-resource-prefix', database.name)], ) ] * 2) self.assertEqual(gax_api.commit.call_args_list, [ mock.call( self.SESSION_NAME, txn._mutations, transaction_id=TRANSACTION_ID, metadata=[('google-cloud-resource-prefix', database.name)], ) ] * 2)
def test_run_in_transaction_w_timeout(self): from google.api_core.exceptions import Aborted from google.cloud.spanner_v1.proto.transaction_pb2 import ( Transaction as TransactionPB, TransactionOptions, ) from google.cloud.spanner_v1.transaction import Transaction TABLE_NAME = "citizens" COLUMNS = ["email", "first_name", "last_name", "age"] VALUES = [ ["*****@*****.**", "Phred", "Phlyntstone", 32], ["*****@*****.**", "Bharney", "Rhubble", 31], ] TRANSACTION_ID = b"FACEDACE" transaction_pb = TransactionPB(id=TRANSACTION_ID) aborted = _make_rpc_error(Aborted, trailing_metadata=[]) gax_api = self._make_spanner_api() gax_api.begin_transaction.return_value = transaction_pb gax_api.commit.side_effect = aborted database = self._make_database() database.spanner_api = gax_api session = self._make_one(database) session._session_id = self.SESSION_ID called_with = [] def unit_of_work(txn, *args, **kw): called_with.append((txn, args, kw)) txn.insert(TABLE_NAME, COLUMNS, VALUES) # retry once w/ timeout_secs=1 def _time(_results=[1, 1.5, 2.5]): return _results.pop(0) with mock.patch("time.time", _time): with mock.patch("time.sleep") as sleep_mock: with self.assertRaises(Aborted): session.run_in_transaction(unit_of_work, timeout_secs=1) sleep_mock.assert_not_called() self.assertEqual(len(called_with), 2) for txn, args, kw in called_with: self.assertIsInstance(txn, Transaction) self.assertIsNone(txn.committed) self.assertEqual(args, ()) self.assertEqual(kw, {}) expected_options = TransactionOptions( read_write=TransactionOptions.ReadWrite()) self.assertEqual( gax_api.begin_transaction.call_args_list, [ mock.call( self.SESSION_NAME, expected_options, metadata=[("google-cloud-resource-prefix", database.name)], ) ] * 2, ) self.assertEqual( gax_api.commit.call_args_list, [ mock.call( self.SESSION_NAME, txn._mutations, transaction_id=TRANSACTION_ID, metadata=[("google-cloud-resource-prefix", database.name)], ) ] * 2, )
def test_run_in_transaction_w_callback_raises_abort_wo_metadata(self): import datetime from google.api_core.exceptions import Aborted from google.protobuf.duration_pb2 import Duration from google.rpc.error_details_pb2 import RetryInfo from google.cloud.spanner_v1.proto.spanner_pb2 import CommitResponse from google.cloud.spanner_v1.proto.transaction_pb2 import ( Transaction as TransactionPB, TransactionOptions, ) from google.cloud._helpers import UTC from google.cloud._helpers import _datetime_to_pb_timestamp from google.cloud.spanner_v1.transaction import Transaction TABLE_NAME = "citizens" COLUMNS = ["email", "first_name", "last_name", "age"] VALUES = [ ["*****@*****.**", "Phred", "Phlyntstone", 32], ["*****@*****.**", "Bharney", "Rhubble", 31], ] TRANSACTION_ID = b"FACEDACE" RETRY_SECONDS = 1 RETRY_NANOS = 3456 transaction_pb = TransactionPB(id=TRANSACTION_ID) now = datetime.datetime.utcnow().replace(tzinfo=UTC) now_pb = _datetime_to_pb_timestamp(now) response = CommitResponse(commit_timestamp=now_pb) retry_info = RetryInfo( retry_delay=Duration(seconds=RETRY_SECONDS, nanos=RETRY_NANOS) ) trailing_metadata = [ ("google.rpc.retryinfo-bin", retry_info.SerializeToString()) ] gax_api = self._make_spanner_api() gax_api.begin_transaction.return_value = transaction_pb gax_api.commit.side_effect = [response] database = self._make_database() database.spanner_api = gax_api session = self._make_one(database) session._session_id = self.SESSION_ID called_with = [] def unit_of_work(txn, *args, **kw): called_with.append((txn, args, kw)) if len(called_with) < 2: raise _make_rpc_error(Aborted, trailing_metadata) txn.insert(TABLE_NAME, COLUMNS, VALUES) with mock.patch("time.sleep") as sleep_mock: session.run_in_transaction(unit_of_work) sleep_mock.assert_called_once_with(RETRY_SECONDS + RETRY_NANOS / 1.0e9) self.assertEqual(len(called_with), 2) for index, (txn, args, kw) in enumerate(called_with): self.assertIsInstance(txn, Transaction) if index == 0: self.assertIsNone(txn.committed) else: self.assertEqual(txn.committed, now) self.assertEqual(args, ()) self.assertEqual(kw, {}) expected_options = TransactionOptions(read_write=TransactionOptions.ReadWrite()) self.assertEqual( gax_api.begin_transaction.call_args_list, [ mock.call( self.SESSION_NAME, expected_options, metadata=[("google-cloud-resource-prefix", database.name)], ) ] * 2, ) gax_api.commit.assert_called_once_with( self.SESSION_NAME, mutations=txn._mutations, transaction_id=TRANSACTION_ID, metadata=[("google-cloud-resource-prefix", database.name)], )
def test_run_in_transaction_w_abort_no_retry_metadata(self): import datetime from google.api_core.exceptions import Aborted from google.cloud.spanner_v1.proto.spanner_pb2 import CommitResponse from google.cloud.spanner_v1.proto.transaction_pb2 import ( Transaction as TransactionPB, TransactionOptions, ) from google.cloud._helpers import UTC from google.cloud._helpers import _datetime_to_pb_timestamp from google.cloud.spanner_v1.transaction import Transaction TABLE_NAME = "citizens" COLUMNS = ["email", "first_name", "last_name", "age"] VALUES = [ ["*****@*****.**", "Phred", "Phlyntstone", 32], ["*****@*****.**", "Bharney", "Rhubble", 31], ] TRANSACTION_ID = b"FACEDACE" transaction_pb = TransactionPB(id=TRANSACTION_ID) now = datetime.datetime.utcnow().replace(tzinfo=UTC) now_pb = _datetime_to_pb_timestamp(now) aborted = _make_rpc_error(Aborted, trailing_metadata=[]) response = CommitResponse(commit_timestamp=now_pb) gax_api = self._make_spanner_api() gax_api.begin_transaction.return_value = transaction_pb gax_api.commit.side_effect = [aborted, response] database = self._make_database() database.spanner_api = gax_api session = self._make_one(database) session._session_id = self.SESSION_ID called_with = [] def unit_of_work(txn, *args, **kw): called_with.append((txn, args, kw)) txn.insert(TABLE_NAME, COLUMNS, VALUES) return "answer" return_value = session.run_in_transaction(unit_of_work, "abc", some_arg="def") self.assertEqual(len(called_with), 2) for index, (txn, args, kw) in enumerate(called_with): self.assertIsInstance(txn, Transaction) self.assertEqual(return_value, "answer") self.assertEqual(args, ("abc",)) self.assertEqual(kw, {"some_arg": "def"}) expected_options = TransactionOptions(read_write=TransactionOptions.ReadWrite()) self.assertEqual( gax_api.begin_transaction.call_args_list, [ mock.call( self.SESSION_NAME, expected_options, metadata=[("google-cloud-resource-prefix", database.name)], ) ] * 2, ) self.assertEqual( gax_api.commit.call_args_list, [ mock.call( self.SESSION_NAME, mutations=txn._mutations, transaction_id=TRANSACTION_ID, metadata=[("google-cloud-resource-prefix", database.name)], ) ] * 2, )
def test_run_in_transaction_w_timeout(self): from google.api_core.exceptions import Aborted from google.cloud.spanner_v1.proto.transaction_pb2 import ( Transaction as TransactionPB, TransactionOptions, ) from google.cloud.spanner_v1.transaction import Transaction TABLE_NAME = "citizens" COLUMNS = ["email", "first_name", "last_name", "age"] VALUES = [ ["*****@*****.**", "Phred", "Phlyntstone", 32], ["*****@*****.**", "Bharney", "Rhubble", 31], ] TRANSACTION_ID = b"FACEDACE" transaction_pb = TransactionPB(id=TRANSACTION_ID) aborted = _make_rpc_error(Aborted, trailing_metadata=[]) gax_api = self._make_spanner_api() gax_api.begin_transaction.return_value = transaction_pb gax_api.commit.side_effect = aborted database = self._make_database() database.spanner_api = gax_api session = self._make_one(database) session._session_id = self.SESSION_ID called_with = [] def unit_of_work(txn, *args, **kw): called_with.append((txn, args, kw)) txn.insert(TABLE_NAME, COLUMNS, VALUES) # retry several times to check backoff def _time(_results=[1, 2, 4, 8]): return _results.pop(0) with mock.patch("time.time", _time): if HAS_OPENTELEMETRY_INSTALLED: with mock.patch("opentelemetry.util.time", _ConstantTime()): with mock.patch("time.sleep") as sleep_mock: with self.assertRaises(Aborted): session.run_in_transaction(unit_of_work, timeout_secs=8) else: with mock.patch("time.sleep") as sleep_mock: with self.assertRaises(Aborted): session.run_in_transaction(unit_of_work, timeout_secs=8) # unpacking call args into list call_args = [call_[0][0] for call_ in sleep_mock.call_args_list] call_args = list(map(int, call_args)) assert call_args == [2, 4] assert sleep_mock.call_count == 2 self.assertEqual(len(called_with), 3) for txn, args, kw in called_with: self.assertIsInstance(txn, Transaction) self.assertIsNone(txn.committed) self.assertEqual(args, ()) self.assertEqual(kw, {}) expected_options = TransactionOptions(read_write=TransactionOptions.ReadWrite()) self.assertEqual( gax_api.begin_transaction.call_args_list, [ mock.call( self.SESSION_NAME, expected_options, metadata=[("google-cloud-resource-prefix", database.name)], ) ] * 3, ) self.assertEqual( gax_api.commit.call_args_list, [ mock.call( self.SESSION_NAME, mutations=txn._mutations, transaction_id=TRANSACTION_ID, metadata=[("google-cloud-resource-prefix", database.name)], ) ] * 3, )
def test_run_in_transaction_w_abort_w_retry_metadata_deadline(self): import datetime from google.api_core.exceptions import Aborted from google.protobuf.duration_pb2 import Duration from google.rpc.error_details_pb2 import RetryInfo from google.cloud.spanner_v1.proto.spanner_pb2 import CommitResponse from google.cloud.spanner_v1.proto.transaction_pb2 import ( Transaction as TransactionPB, TransactionOptions, ) from google.cloud.spanner_v1.transaction import Transaction from google.cloud._helpers import UTC from google.cloud._helpers import _datetime_to_pb_timestamp TABLE_NAME = "citizens" COLUMNS = ["email", "first_name", "last_name", "age"] VALUES = [ ["*****@*****.**", "Phred", "Phlyntstone", 32], ["*****@*****.**", "Bharney", "Rhubble", 31], ] TRANSACTION_ID = b"FACEDACE" RETRY_SECONDS = 1 RETRY_NANOS = 3456 transaction_pb = TransactionPB(id=TRANSACTION_ID) now = datetime.datetime.utcnow().replace(tzinfo=UTC) now_pb = _datetime_to_pb_timestamp(now) response = CommitResponse(commit_timestamp=now_pb) retry_info = RetryInfo( retry_delay=Duration(seconds=RETRY_SECONDS, nanos=RETRY_NANOS) ) trailing_metadata = [ ("google.rpc.retryinfo-bin", retry_info.SerializeToString()) ] aborted = _make_rpc_error(Aborted, trailing_metadata=trailing_metadata) gax_api = self._make_spanner_api() gax_api.begin_transaction.return_value = transaction_pb gax_api.commit.side_effect = [aborted, response] database = self._make_database() database.spanner_api = gax_api session = self._make_one(database) session._session_id = self.SESSION_ID called_with = [] def unit_of_work(txn, *args, **kw): called_with.append((txn, args, kw)) txn.insert(TABLE_NAME, COLUMNS, VALUES) # retry once w/ timeout_secs=1 def _time(_results=[1, 1.5]): return _results.pop(0) with mock.patch("time.time", _time): if HAS_OPENTELEMETRY_INSTALLED: with mock.patch("opentelemetry.util.time", _ConstantTime()): with mock.patch("time.sleep") as sleep_mock: with self.assertRaises(Aborted): session.run_in_transaction( unit_of_work, "abc", timeout_secs=1 ) else: with mock.patch("time.sleep") as sleep_mock: with self.assertRaises(Aborted): session.run_in_transaction(unit_of_work, "abc", timeout_secs=1) sleep_mock.assert_not_called() self.assertEqual(len(called_with), 1) txn, args, kw = called_with[0] self.assertIsInstance(txn, Transaction) self.assertIsNone(txn.committed) self.assertEqual(args, ("abc",)) self.assertEqual(kw, {}) expected_options = TransactionOptions(read_write=TransactionOptions.ReadWrite()) gax_api.begin_transaction.assert_called_once_with( self.SESSION_NAME, expected_options, metadata=[("google-cloud-resource-prefix", database.name)], ) gax_api.commit.assert_called_once_with( self.SESSION_NAME, mutations=txn._mutations, transaction_id=TRANSACTION_ID, metadata=[("google-cloud-resource-prefix", database.name)], )