diff --git a/pymongo/change_stream.py b/pymongo/change_stream.py index f742e126c..fe685694a 100644 --- a/pymongo/change_stream.py +++ b/pymongo/change_stream.py @@ -41,11 +41,11 @@ _RESUMABLE_GETMORE_ERRORS = frozenset([ 189, # PrimarySteppedDown 262, # ExceededTimeLimit 9001, # SocketException - 10107, # NotMaster + 10107, # NotWritablePrimary 11600, # InterruptedAtShutdown 11602, # InterruptedDueToReplStateChange - 13435, # NotMasterNoSlaveOk - 13436, # NotMasterOrSecondary + 13435, # NotPrimaryNoSecondaryOk + 13436, # NotPrimaryOrSecondary 63, # StaleShardVersion 150, # StaleEpoch 13388, # StaleConfig diff --git a/pymongo/errors.py b/pymongo/errors.py index 257783f52..33629b5fa 100644 --- a/pymongo/errors.py +++ b/pymongo/errors.py @@ -94,7 +94,22 @@ def _format_detailed_error(message, details): class NotMasterError(AutoReconnect): - """The server responded "not master" or "node is recovering". + """**DEPRECATED** - The server responded "not master" or + "node is recovering". + + This exception has been deprecated and will be removed in PyMongo 4.0. + Use :exc:`~pymongo.errors.NotPrimaryError` instead. + + .. versionchanged:: 3.12 + Deprecated. Use :exc:`~pymongo.errors.NotPrimaryError` instead. + """ + def __init__(self, message='', errors=None): + super(NotMasterError, self).__init__( + _format_detailed_error(message, errors), errors=errors) + + +class NotPrimaryError(NotMasterError): + """The server responded "not primary" or "node is recovering". These errors result from a query, write, or command. The operation failed because the client thought it was using the primary but the primary has @@ -105,10 +120,11 @@ class NotMasterError(AutoReconnect): its view of the server as soon as possible after throwing this exception. Subclass of :exc:`~pymongo.errors.AutoReconnect`. + + .. versionadded:: 3.12 """ def __init__(self, message='', errors=None): - super(NotMasterError, self).__init__( - _format_detailed_error(message, errors), errors=errors) + super(NotPrimaryError, self).__init__(message, errors=errors) class ServerSelectionTimeoutError(AutoReconnect): diff --git a/pymongo/helpers.py b/pymongo/helpers.py index 2fb3ce337..1d8005ac3 100644 --- a/pymongo/helpers.py +++ b/pymongo/helpers.py @@ -24,7 +24,7 @@ from pymongo import ASCENDING from pymongo.errors import (CursorNotFound, DuplicateKeyError, ExecutionTimeout, - NotMasterError, + NotPrimaryError, OperationFailure, WriteError, WriteConcernError, @@ -35,15 +35,15 @@ _SHUTDOWN_CODES = frozenset([ 11600, # InterruptedAtShutdown 91, # ShutdownInProgress ]) -# From the SDAM spec, the "not master" error codes are combined with the +# From the SDAM spec, the "not primary" error codes are combined with the # "node is recovering" error codes (of which the "node is shutting down" # errors are a subset). _NOT_MASTER_CODES = frozenset([ - 10058, # LegacyNotPrimary <=3.2 "not master" error code - 10107, # NotMaster - 13435, # NotMasterNoSlaveOk + 10058, # LegacyNotPrimary <=3.2 "not primary" error code + 10107, # NotWritablePrimary + 13435, # NotPrimaryNoSecondaryOk 11602, # InterruptedDueToReplStateChange - 13436, # NotMasterOrSecondary + 13436, # NotPrimaryOrSecondary 189, # PrimarySteppedDown ]) | _SHUTDOWN_CODES # From the retryable writes spec. @@ -148,12 +148,12 @@ def _check_command_response(response, max_wire_version, elif errmsg in allowable_errors: return - # Server is "not master" or "recovering" + # Server is "not primary" or "recovering" if code is not None: if code in _NOT_MASTER_CODES: - raise NotMasterError(errmsg, response) + raise NotPrimaryError(errmsg, response) elif "not master" in errmsg or "node is recovering" in errmsg: - raise NotMasterError(errmsg, response) + raise NotPrimaryError(errmsg, response) # Other errors # findAndModify with upsert can raise duplicate key error @@ -185,7 +185,7 @@ def _check_gle_response(result, max_wire_version): return result if error_msg.startswith("not master"): - raise NotMasterError(error_msg, result) + raise NotPrimaryError(error_msg, result) details = result diff --git a/pymongo/message.py b/pymongo/message.py index 3141c4299..15c564475 100644 --- a/pymongo/message.py +++ b/pymongo/message.py @@ -47,7 +47,7 @@ from pymongo.errors import (ConfigurationError, DocumentTooLarge, ExecutionTimeout, InvalidOperation, - NotMasterError, + NotPrimaryError, OperationFailure, ProtocolError) from pymongo.read_concern import DEFAULT_READ_CONCERN @@ -995,7 +995,7 @@ class _BulkWriteContext(object): if isinstance(exc, OperationFailure): failure = _convert_write_result( self.name, cmd, exc.details) - elif isinstance(exc, NotMasterError): + elif isinstance(exc, NotPrimaryError): failure = exc.details else: failure = _convert_exception(exc) @@ -1020,7 +1020,7 @@ class _BulkWriteContext(object): except Exception as exc: if self.publish: duration = (datetime.datetime.now() - start) + duration - if isinstance(exc, (NotMasterError, OperationFailure)): + if isinstance(exc, (NotPrimaryError, OperationFailure)): failure = exc.details else: failure = _convert_exception(exc) @@ -1514,7 +1514,7 @@ class _OpReply(object): Check the response for errors and unpack. - Can raise CursorNotFound, NotMasterError, ExecutionTimeout, or + Can raise CursorNotFound, NotPrimaryError, ExecutionTimeout, or OperationFailure. :Parameters: @@ -1537,7 +1537,7 @@ class _OpReply(object): # Fake the ok field if it doesn't exist. error_object.setdefault("ok", 0) if error_object["$err"].startswith("not master"): - raise NotMasterError(error_object["$err"], error_object) + raise NotPrimaryError(error_object["$err"], error_object) elif error_object.get("code") == 50: raise ExecutionTimeout(error_object.get("$err"), error_object.get("code"), @@ -1558,7 +1558,7 @@ class _OpReply(object): Check the response for errors and unpack, returning a dictionary containing the response data. - Can raise CursorNotFound, NotMasterError, ExecutionTimeout, or + Can raise CursorNotFound, NotPrimaryError, ExecutionTimeout, or OperationFailure. :Parameters: @@ -1715,7 +1715,7 @@ def _first_batch(sock_info, db, coll, query, ntoreturn, except Exception as exc: if publish: duration = (datetime.datetime.now() - start) + encoding_duration - if isinstance(exc, (NotMasterError, OperationFailure)): + if isinstance(exc, (NotPrimaryError, OperationFailure)): failure = exc.details else: failure = _convert_exception(exc) diff --git a/pymongo/mongo_client.py b/pymongo/mongo_client.py index ad82306b8..af1d0b5cd 100644 --- a/pymongo/mongo_client.py +++ b/pymongo/mongo_client.py @@ -56,7 +56,7 @@ from pymongo.errors import (AutoReconnect, ConfigurationError, ConnectionFailure, InvalidOperation, - NotMasterError, + NotPrimaryError, OperationFailure, PyMongoError, ServerSelectionTimeoutError) @@ -1938,7 +1938,7 @@ def _retryable_error_doc(exc): wces = exc.details['writeConcernErrors'] wce = wces[-1] if wces else None return wce - if isinstance(exc, (NotMasterError, OperationFailure)): + if isinstance(exc, (NotPrimaryError, OperationFailure)): return exc.details return None @@ -1963,10 +1963,10 @@ def _add_retryable_write_error(exc, max_wire_version): if code in helpers._RETRYABLE_ERROR_CODES: exc._add_error_label("RetryableWriteError") - # Connection errors are always retryable except NotMasterError which is + # Connection errors are always retryable except NotPrimaryError which is # handled above. if (isinstance(exc, ConnectionFailure) and - not isinstance(exc, NotMasterError)): + not isinstance(exc, NotPrimaryError)): exc._add_error_label("RetryableWriteError") diff --git a/pymongo/monitor.py b/pymongo/monitor.py index 7a3a4f22b..8a60abce0 100644 --- a/pymongo/monitor.py +++ b/pymongo/monitor.py @@ -20,7 +20,7 @@ import time import weakref from pymongo import common, periodic_executor -from pymongo.errors import (NotMasterError, +from pymongo.errors import (NotPrimaryError, OperationFailure, _OperationCancelled) from pymongo.ismaster import IsMaster @@ -212,7 +212,7 @@ class Monitor(MonitorBase): try: try: return self._check_once() - except (OperationFailure, NotMasterError) as exc: + except (OperationFailure, NotPrimaryError) as exc: # Update max cluster time even when isMaster fails. self._topology.receive_cluster_time( exc.details.get('$clusterTime')) diff --git a/pymongo/network.py b/pymongo/network.py index 813a7cc38..b0e06a717 100644 --- a/pymongo/network.py +++ b/pymongo/network.py @@ -26,7 +26,7 @@ from bson import _decode_all_selective from pymongo import helpers, message from pymongo.common import MAX_MESSAGE_SIZE from pymongo.compression_support import decompress, _NO_COMPRESSION -from pymongo.errors import (NotMasterError, +from pymongo.errors import (NotPrimaryError, OperationFailure, ProtocolError, _OperationCancelled) @@ -160,7 +160,7 @@ def command(sock_info, dbname, spec, slave_ok, is_mongos, except Exception as exc: if publish: duration = (datetime.datetime.now() - start) + encoding_duration - if isinstance(exc, (NotMasterError, OperationFailure)): + if isinstance(exc, (NotPrimaryError, OperationFailure)): failure = exc.details else: failure = message._convert_exception(exc) diff --git a/pymongo/pool.py b/pymongo/pool.py index 335f8d07f..a8a645f89 100644 --- a/pymongo/pool.py +++ b/pymongo/pool.py @@ -47,7 +47,7 @@ from pymongo.errors import (AutoReconnect, InvalidOperation, DocumentTooLarge, NetworkTimeout, - NotMasterError, + NotPrimaryError, OperationFailure, PyMongoError) from pymongo.hello import HelloCompat @@ -736,7 +736,7 @@ class SocketInfo(object): unacknowledged=unacknowledged, user_fields=user_fields, exhaust_allowed=exhaust_allowed) - except (OperationFailure, NotMasterError): + except (OperationFailure, NotPrimaryError): raise # Catch socket.error, KeyboardInterrupt, etc. and close ourselves. except BaseException as error: @@ -770,13 +770,13 @@ class SocketInfo(object): self._raise_connection_failure(error) def _raise_if_not_writable(self, unacknowledged): - """Raise NotMasterError on unacknowledged write if this socket is not + """Raise NotPrimaryError on unacknowledged write if this socket is not writable. """ if unacknowledged and not self.is_writable: - # Write won't succeed, bail as if we'd received a not master error. - raise NotMasterError("not master", { - "ok": 0, "errmsg": "not master", "code": 10107}) + # Write won't succeed, bail as if we'd received a not primary error. + raise NotPrimaryError("not primary", { + "ok": 0, "errmsg": "not primary", "code": 10107}) def legacy_write(self, request_id, msg, max_doc_size, with_last_error): """Send OP_INSERT, etc., optionally returning response as a dict. @@ -811,7 +811,7 @@ class SocketInfo(object): reply = self.receive_message(request_id) result = reply.command_response() - # Raises NotMasterError or OperationFailure. + # Raises NotPrimaryError or OperationFailure. helpers._check_command_response(result, self.max_wire_version) return result diff --git a/pymongo/server.py b/pymongo/server.py index 19529237d..10037cbf9 100644 --- a/pymongo/server.py +++ b/pymongo/server.py @@ -18,7 +18,7 @@ from datetime import datetime from bson import _decode_all_selective -from pymongo.errors import NotMasterError, OperationFailure +from pymongo.errors import NotPrimaryError, OperationFailure from pymongo.helpers import _check_command_response from pymongo.message import _convert_exception, _OpMsg from pymongo.response import Response, PinnedResponse @@ -130,7 +130,7 @@ class Server(object): except Exception as exc: if publish: duration = datetime.now() - start - if isinstance(exc, (NotMasterError, OperationFailure)): + if isinstance(exc, (NotPrimaryError, OperationFailure)): failure = exc.details else: failure = _convert_exception(exc) diff --git a/pymongo/topology.py b/pymongo/topology.py index baa4293dd..3b5437a32 100644 --- a/pymongo/topology.py +++ b/pymongo/topology.py @@ -29,7 +29,7 @@ from pymongo.client_session import _ServerSessionPool from pymongo.errors import (ConnectionFailure, ConfigurationError, NetworkTimeout, - NotMasterError, + NotPrimaryError, OperationFailure, PyMongoError, ServerSelectionTimeoutError, @@ -611,9 +611,9 @@ class Topology(object): elif issubclass(exc_type, WriteError): # Ignore writeErrors. return - elif issubclass(exc_type, (NotMasterError, OperationFailure)): + elif issubclass(exc_type, (NotPrimaryError, OperationFailure)): # As per the SDAM spec if: - # - the server sees a "not master" error, and + # - the server sees a "not primary" error, and # - the server is not shutting down, and # - the server version is >= 4.2, then # we keep the existing connection pool, but mark the server type diff --git a/test/test_connections_survive_primary_stepdown_spec.py b/test/test_connections_survive_primary_stepdown_spec.py index 8690962af..6ed16fa50 100644 --- a/test/test_connections_survive_primary_stepdown_spec.py +++ b/test/test_connections_survive_primary_stepdown_spec.py @@ -20,7 +20,7 @@ sys.path[0:0] = [""] from bson import SON from pymongo import monitoring -from pymongo.errors import NotMasterError +from pymongo.errors import NotPrimaryError from pymongo.write_concern import WriteConcern from test import (client_context, @@ -93,10 +93,10 @@ class TestConnectionsSurvivePrimaryStepDown(IntegrationTest): # Verify pool not cleared. self.verify_pool_not_cleared() # Attempt insertion to mark server description as stale and prevent a - # notMaster error on the subsequent operation. + # NotPrimaryError on the subsequent operation. try: self.coll.insert_one({}) - except NotMasterError: + except NotPrimaryError: pass # Next insert should succeed on the new primary without clearing pool. self.coll.insert_one({}) @@ -109,7 +109,7 @@ class TestConnectionsSurvivePrimaryStepDown(IntegrationTest): "errorCode": error_code}}) self.addCleanup(self.set_fail_point, {"mode": "off"}) # Insert record and verify failure. - with self.assertRaises(NotMasterError) as exc: + with self.assertRaises(NotPrimaryError) as exc: self.coll.insert_one({"test": 1}) self.assertEqual(exc.exception.details['code'], error_code) # Retry before CMAPListener assertion if retry_before=True. diff --git a/test/test_discovery_and_monitoring.py b/test/test_discovery_and_monitoring.py index 0cbc40f1f..fc5a3ae0b 100644 --- a/test/test_discovery_and_monitoring.py +++ b/test/test_discovery_and_monitoring.py @@ -27,7 +27,7 @@ from pymongo import (common, from pymongo.errors import (AutoReconnect, ConfigurationError, NetworkTimeout, - NotMasterError, + NotPrimaryError, OperationFailure) from pymongo.helpers import (_check_command_response, _check_write_command_response) @@ -104,7 +104,7 @@ def got_app_error(topology, app_error): else: raise AssertionError('unknown error type: %s' % (error_type,)) assert False - except (AutoReconnect, NotMasterError, OperationFailure) as e: + except (AutoReconnect, NotPrimaryError, OperationFailure) as e: if when == 'beforeHandshakeCompletes': completed_handshake = False elif when == 'afterHandshakeCompletes': diff --git a/test/test_errors.py b/test/test_errors.py index ecd969acc..6ae8ee69b 100644 --- a/test/test_errors.py +++ b/test/test_errors.py @@ -20,6 +20,7 @@ sys.path[0:0] = [""] from pymongo.errors import (BulkWriteError, EncryptionError, + NotPrimaryError, NotMasterError, OperationFailure) from test import (PyMongoTestCase, @@ -27,12 +28,12 @@ from test import (PyMongoTestCase, class TestErrors(PyMongoTestCase): - def test_not_master_error(self): - exc = NotMasterError("not master test", {"errmsg": "error"}) + def test_not_primary_error(self): + exc = NotPrimaryError("not primary test", {"errmsg": "error"}) self.assertIn("full error", str(exc)) try: raise exc - except NotMasterError: + except NotPrimaryError: self.assertIn("full error", traceback.format_exc()) def test_operation_failure(self): @@ -63,8 +64,8 @@ class TestErrors(PyMongoTestCase): self._test_unicode_strs(exc) def test_unicode_strs_not_master_error(self): - exc = NotMasterError('unicode \U0001f40d', - {"errmsg": 'unicode \U0001f40d'}) + exc = NotPrimaryError('unicode \U0001f40d', + {"errmsg": 'unicode \U0001f40d'}) self._test_unicode_strs(exc) def assertPyMongoErrorEqual(self, exc1, exc2): @@ -79,8 +80,8 @@ class TestErrors(PyMongoTestCase): self.assertEqual(exc1.details, exc2.details) self.assertEqual(exc1._max_wire_version, exc2._max_wire_version) - def test_pickle_NotMasterError(self): - exc = NotMasterError("not master test", {"errmsg": "error"}) + def test_pickle_NotPrimaryError(self): + exc = NotPrimaryError("not primary test", {"errmsg": "error"}) self.assertPyMongoErrorEqual(exc, pickle.loads(pickle.dumps(exc))) def test_pickle_OperationFailure(self): @@ -100,6 +101,11 @@ class TestErrors(PyMongoTestCase): self.assertPyMongoErrorEqual(exc, exc2) self.assertOperationFailureEqual(cause, exc2.cause) + def test_NotMasterError_catches_NotPrimaryError(self): + with self.assertRaises(NotMasterError) as exc: + raise NotPrimaryError("not primary test", {"errmsg": "error"}) + self.assertIn("full error", str(exc.exception)) + if __name__ == "__main__": unittest.main() diff --git a/test/test_gridfs.py b/test/test_gridfs.py index e9c5301e2..b21191cfb 100644 --- a/test/test_gridfs.py +++ b/test/test_gridfs.py @@ -29,7 +29,7 @@ sys.path[0:0] = [""] from bson.binary import Binary from pymongo.mongo_client import MongoClient from pymongo.errors import (ConfigurationError, - NotMasterError, + NotPrimaryError, ServerSelectionTimeoutError) from pymongo.read_preferences import ReadPreference import gridfs @@ -539,7 +539,7 @@ class TestGridfsReplicaSet(IntegrationTest): fs = gridfs.GridFS(secondary_connection.gfsreplica, 'gfssecondarytest') # This won't detect secondary, raises error - self.assertRaises(NotMasterError, fs.put, b'foo') + self.assertRaises(NotPrimaryError, fs.put, b'foo') def test_gridfs_secondary_lazy(self): # Should detect it's connected to secondary and not attempt to @@ -556,7 +556,7 @@ class TestGridfsReplicaSet(IntegrationTest): # Connects, doesn't create index. self.assertRaises(NoFile, fs.get_last_version) - self.assertRaises(NotMasterError, fs.put, 'data') + self.assertRaises(NotPrimaryError, fs.put, 'data') if __name__ == "__main__": diff --git a/test/test_gridfs_bucket.py b/test/test_gridfs_bucket.py index 98478bf7b..f31949f03 100644 --- a/test/test_gridfs_bucket.py +++ b/test/test_gridfs_bucket.py @@ -30,7 +30,7 @@ from bson.son import SON import gridfs from gridfs.errors import NoFile, CorruptGridFile from pymongo.errors import (ConfigurationError, - NotMasterError, + NotPrimaryError, ServerSelectionTimeoutError) from pymongo.mongo_client import MongoClient from pymongo.read_preferences import ReadPreference @@ -536,7 +536,7 @@ class TestGridfsBucketReplicaSet(IntegrationTest): secondary_connection.gfsbucketreplica, 'gfsbucketsecondarytest') # This won't detect secondary, raises error - self.assertRaises(NotMasterError, gfs.upload_from_stream, + self.assertRaises(NotPrimaryError, gfs.upload_from_stream, "test_filename", b'foo') def test_gridfs_secondary_lazy(self): @@ -556,7 +556,7 @@ class TestGridfsBucketReplicaSet(IntegrationTest): # Connects, doesn't create index. self.assertRaises(NoFile, gfs.open_download_stream_by_name, "test_filename") - self.assertRaises(NotMasterError, gfs.upload_from_stream, + self.assertRaises(NotPrimaryError, gfs.upload_from_stream, "test_filename", b'data') diff --git a/test/test_monitoring.py b/test/test_monitoring.py index bdf24e240..464231b52 100644 --- a/test/test_monitoring.py +++ b/test/test_monitoring.py @@ -26,7 +26,7 @@ from bson.son import SON from pymongo import CursorType, monitoring, InsertOne, UpdateOne, DeleteOne from pymongo.command_cursor import CommandCursor from pymongo.errors import (AutoReconnect, - NotMasterError, + NotPrimaryError, OperationFailure) from pymongo.read_preferences import ReadPreference from pymongo.write_concern import WriteConcern @@ -438,7 +438,7 @@ class TestCommandMonitoring(PyMongoTestCase): error = None try: client.pymongo_test.test.find_one_and_delete({}) - except NotMasterError as exc: + except NotPrimaryError as exc: error = exc.errors results = self.listener.results started = results['started'][0] @@ -983,7 +983,7 @@ class TestCommandMonitoring(PyMongoTestCase): }, } with self.fail_point(insert_command_error): - with self.assertRaises(NotMasterError): + with self.assertRaises(NotPrimaryError): coll.bulk_write([InsertOne({'_id': 1})]) failed = self.listener.results['failed'] self.assertEqual(1, len(failed)) diff --git a/test/test_sdam_monitoring_spec.py b/test/test_sdam_monitoring_spec.py index bcd2cbc4d..0e8b28513 100644 --- a/test/test_sdam_monitoring_spec.py +++ b/test/test_sdam_monitoring_spec.py @@ -25,7 +25,7 @@ from bson.json_util import object_hook from pymongo import monitoring from pymongo.common import clean_node from pymongo.errors import (ConnectionFailure, - NotMasterError) + NotPrimaryError) from pymongo.ismaster import IsMaster from pymongo.monitor import Monitor from pymongo.server_description import ServerDescription @@ -333,12 +333,12 @@ class TestSdamMonitoring(IntegrationTest): def test_not_master_error_publishes_events(self): self._test_app_error({'errorCode': 10107, 'closeConnection': False, 'errorLabels': ['RetryableWriteError']}, - NotMasterError) + NotPrimaryError) def test_shutdown_error_publishes_events(self): self._test_app_error({'errorCode': 91, 'closeConnection': False, 'errorLabels': ['RetryableWriteError']}, - NotMasterError) + NotPrimaryError) if __name__ == "__main__": diff --git a/test/unified_format.py b/test/unified_format.py index 3da9df7e8..b4219f647 100644 --- a/test/unified_format.py +++ b/test/unified_format.py @@ -40,7 +40,7 @@ from pymongo.change_stream import ChangeStream from pymongo.collection import Collection from pymongo.database import Database from pymongo.errors import ( - BulkWriteError, ConnectionFailure, InvalidOperation, NotMasterError, + BulkWriteError, ConnectionFailure, InvalidOperation, NotPrimaryError, PyMongoError) from pymongo.monitoring import ( CommandFailedEvent, CommandListener, CommandStartedEvent, @@ -720,7 +720,7 @@ class UnifiedSpecTestMixinV1(IntegrationTest): if is_client_error: # Connection errors are considered client errors. if isinstance(exception, ConnectionFailure): - self.assertNotIsInstance(exception, NotMasterError) + self.assertNotIsInstance(exception, NotPrimaryError) else: self.assertNotIsInstance(exception, PyMongoError)