From e42026e37ca509f4d75545675ccef15fd542ec18 Mon Sep 17 00:00:00 2001 From: Alex Petenchea Date: Mon, 27 Jul 2026 19:44:24 +0800 Subject: [PATCH] Removing stale replication applier tests and updating docs. Also removed the long-deprecated make_slave API. --- arango/replication.py | 199 ++++++-------------------------------- docs/replication.rst | 39 +------- tests/test_replication.py | 137 -------------------------- 3 files changed, 29 insertions(+), 346 deletions(-) diff --git a/arango/replication.py b/arango/replication.py index 308f9c2e..7dd1323f 100644 --- a/arango/replication.py +++ b/arango/replication.py @@ -18,7 +18,6 @@ ReplicationInventoryError, ReplicationLoggerFirstTickError, ReplicationLoggerStateError, - ReplicationMakeSlaveError, ReplicationServerIDError, ReplicationSyncError, ) @@ -342,12 +341,17 @@ def response_handler(resp: Response) -> str: return self._execute(request, response_handler) def applier_config(self) -> Result[Json]: - """Return the configuration of the replication applier. + """**Removed** in ArangoDB v3.12.10. + + Return the configuration of the replication applier. :return: Configuration of the replication applier. :rtype: dict :raise arango.exceptions.ReplicationApplierConfigError: If retrieval fails. """ + m = "/_api/replication/applier-config was removed in ArangoDB v3.12.10." + warn(m, DeprecationWarning, stacklevel=2) + request = Request( method="get", endpoint="/_api/replication/applier-config", @@ -384,7 +388,9 @@ def set_applier_config( restrict_type: Optional[str] = None, restrict_collections: Optional[Sequence[str]] = None, ) -> Result[Json]: - """Set configuration values of the replication applier. + """**Removed** in ArangoDB v3.12.10. + + Set configuration values of the replication applier. :param endpoint: Server endpoint (e.g. "tcp://192.168.173.13:8529"). :type endpoint: str @@ -477,6 +483,9 @@ def set_applier_config( :rtype: dict :raise arango.exceptions.ReplicationApplierConfigSetError: If update fails. """ + m = "/_api/replication/applier-config was removed in ArangoDB v3.12.10." + warn(m, DeprecationWarning, stacklevel=2) + data: Json = {"endpoint": endpoint} if database is not None: @@ -532,12 +541,17 @@ def response_handler(resp: Response) -> Json: return self._execute(request, response_handler) def applier_state(self) -> Result[Json]: - """Return the state of the replication applier + """**Removed** in ArangoDB v3.12.10. + + Return the state of the replication applier :return: Applier state and details. :rtype: dict :raise arango.exceptions.ReplicationApplierStateError: If retrieval fails. """ + m = "/_api/replication/applier-state was removed in ArangoDB v3.12.10." + warn(m, DeprecationWarning, stacklevel=2) + request = Request( method="get", endpoint="/_api/replication/applier-state", @@ -551,7 +565,9 @@ def response_handler(resp: Response) -> Json: return self._execute(request, response_handler) def start_applier(self, last_tick: Optional[str] = None) -> Result[Json]: - """Start the replication applier. + """**Removed** in ArangoDB v3.12.10. + + Start the replication applier. :param last_tick: The remote last log tick value from which to start applying replication. If not specified, the last saved tick from @@ -563,6 +579,9 @@ def start_applier(self, last_tick: Optional[str] = None) -> Result[Json]: :rtype: dict :raise arango.exceptions.ReplicationApplierStartError: If operation fails. """ + m = "/_api/replication/applier-start was removed in ArangoDB v3.12.10." + warn(m, DeprecationWarning, stacklevel=2) + request = Request( method="put", endpoint="/_api/replication/applier-start", @@ -577,12 +596,15 @@ def response_handler(resp: Response) -> Json: return self._execute(request, response_handler) def stop_applier(self) -> Result[Json]: - """Stop the replication applier. + """**Removed** in ArangoDB v3.12.10. :return: Applier state and details. :rtype: dict :raise arango.exceptions.ReplicationApplierStopError: If operation fails. """ + m = "/_api/replication/applier-stop was removed in ArangoDB v3.12.10." + warn(m, DeprecationWarning, stacklevel=2) + request = Request( method="put", endpoint="/_api/replication/applier-stop", @@ -595,171 +617,6 @@ def response_handler(resp: Response) -> Json: return self._execute(request, response_handler) - def make_slave( - self, - endpoint: str, - database: Optional[str] = None, - username: Optional[str] = None, - password: Optional[str] = None, - restrict_type: Optional[str] = None, - restrict_collections: Optional[Sequence[str]] = None, - include_system: Optional[bool] = None, - max_connect_retries: Optional[int] = None, - connect_timeout: Optional[int] = None, - request_timeout: Optional[int] = None, - chunk_size: Optional[int] = None, - adaptive_polling: Optional[bool] = None, - auto_resync: Optional[bool] = None, - auto_resync_retries: Optional[int] = None, - initial_sync_max_wait_time: Optional[int] = None, - connection_retry_wait_time: Optional[int] = None, - idle_min_wait_time: Optional[int] = None, - idle_max_wait_time: Optional[int] = None, - require_from_present: Optional[bool] = None, - verbose: Optional[bool] = None, - ) -> Result[Json]: # pragma: no cover - """Change the server role to slave. - - :param endpoint: Master endpoint (e.g. "tcp://192.168.173.13:8529"). - :type endpoint: str - :param database: Database name. - :type database: str | None - :param username: Username. - :type username: str | None - :param password: Password. - :type password: str | None - :param restrict_type: Optional string value for collection filtering. - Allowed values are "include" or "exclude". - :type restrict_type: str | None - :param restrict_collections: Optional list of collections for use with - argument **restrict_type**. If **restrict_type** set to "include", - only the specified collections are included. Otherwise, only the - specified collections are excluded. - :type restrict_collections: [str] | None - :param include_system: Whether system collection operations are - applied. - :type include_system: bool | None - :param max_connect_retries: Maximum number of connection attempts the - applier makes in a row before stopping itself. - :type max_connect_retries: int | None - :param connect_timeout: Timeout in seconds when attempting to connect - to the endpoint. This value is used for each connection attempt. - :type connect_timeout: int | None - :param request_timeout: Timeout in seconds for individual requests to - the endpoint. - :type request_timeout: int | None - :param chunk_size: Requested maximum size in bytes for log transfer - packets when the endpoint is contacted. - :type chunk_size: int | None - :param adaptive_polling: If set to True, replication applier sleeps - for an increasingly long period in case the logger server at the - endpoint has no replication events to apply. Using adaptive polling - reduces the amount of work done by both the applier and the logger - server when there are infrequent changes. The downside is that it - might take longer for the replication applier to detect new events. - :type adaptive_polling: bool | None - :param auto_resync: Whether the slave should perform a full automatic - resynchronization with the master in case the master cannot serve - log data requested by the slave, or when the replication is started - and no tick value can be found. - :type auto_resync: bool | None - :param auto_resync_retries: Max number of resynchronization retries. - Setting this to 0 disables it. - :type auto_resync_retries: int | None - :param initial_sync_max_wait_time: Max wait time in seconds the initial - synchronization waits for master on collection data. This value - is relevant even for continuous replication when **auto_resync** is - set to True because this may restart the initial synchronization - when master cannot provide log data slave requires. This value is - ignored if set to 0. - :type initial_sync_max_wait_time: int | None - :param connection_retry_wait_time: Time in seconds the applier idles - before trying to connect to master in case of connection problems. - This value is ignored if set to 0. - :type connection_retry_wait_time: int | None - :param idle_min_wait_time: Minimum wait time in seconds the applier - idles before fetching more log data from the master in case the - master has already sent all its log data. This wait time can be - used to control the frequency with which the replication applier - sends HTTP log fetch requests to the master in case there is no - write activity on the master. This value is ignored if set to 0. - :type idle_min_wait_time: int | None - :param idle_max_wait_time: Maximum wait time in seconds the applier - idles before fetching more log data from the master in case the - master has already sent all its log data. This wait time can be - used to control the maximum frequency with which the replication - applier sends HTTP log fetch requests to the master in case there - is no write activity on the master. Applies only when argument - **adaptive_polling** is set to True. This value is ignored if set - to 0. - :type idle_max_wait_time: int | None - :param require_from_present: If set to True, replication applier checks - at start whether the start tick from which it starts or resumes - replication is still present on the master. If not, then there - would be data loss. If set to True, the replication applier aborts - with an appropriate error message. If set to False, the applier - still starts and ignores the data loss. - :type require_from_present: bool | None - :param verbose: If set to True, a log line is emitted for all - operations performed by the replication applier. This should be - used for debugging replication problems only. - :type verbose: bool | None - :return: Replication details. - :rtype: dict - :raise arango.exceptions.ReplicationApplierStopError: If operation fails. - """ - data: Json = {"endpoint": endpoint} - - if database is not None: - data["database"] = database - if username is not None: - data["username"] = username - if password is not None: - data["password"] = password - if restrict_type is not None: - data["restrictType"] = restrict_type - if restrict_collections is not None: - data["restrictCollections"] = restrict_collections - if include_system is not None: - data["includeSystem"] = include_system - if max_connect_retries is not None: - data["maxConnectRetries"] = max_connect_retries - if connect_timeout is not None: - data["connectTimeout"] = connect_timeout - if request_timeout is not None: - data["requestTimeout"] = request_timeout - if chunk_size is not None: - data["chunkSize"] = chunk_size - if adaptive_polling is not None: - data["adaptivePolling"] = adaptive_polling - if auto_resync is not None: - data["autoResync"] = auto_resync - if auto_resync_retries is not None: - data["autoResyncRetries"] = auto_resync_retries - if initial_sync_max_wait_time is not None: - data["initialSyncMaxWaitTime"] = initial_sync_max_wait_time - if connection_retry_wait_time is not None: - data["connectionRetryWaitTime"] = connection_retry_wait_time - if idle_min_wait_time is not None: - data["idleMinWaitTime"] = idle_min_wait_time - if idle_max_wait_time is not None: - data["idleMaxWaitTime"] = idle_max_wait_time - if require_from_present is not None: - data["requireFromPresent"] = require_from_present - if verbose is not None: - data["verbose"] = verbose - - request = Request( - method="put", endpoint="/_api/replication/make-slave", data=data - ) - - def response_handler(resp: Response) -> Json: - if resp.is_success: - return format_replication_applier_state(resp.body) - raise ReplicationMakeSlaveError(resp, request) - - return self._execute(request, response_handler) - def server_id(self) -> Result[str]: """Return this server's ID. diff --git a/docs/replication.rst b/docs/replication.rst index 290ca194..9aaf9496 100644 --- a/docs/replication.rst +++ b/docs/replication.rst @@ -5,7 +5,7 @@ Replication basis of all disaster recovery and failover features ArangoDB offers. For more information, refer to `ArangoDB manual`_. -.. _ArangoDB manual: https://www.arangodb.com/docs/stable/architecture-replication.html +.. _ArangoDB manual: https://docs.arango.ai/arangodb/stable/deploy/architecture/replication/ **Example:** @@ -60,43 +60,6 @@ information, refer to `ArangoDB manual`_. # Get the logger first tick value. replication.logger_first_tick() - # Get the replication applier configuration. - replication.applier_config() - - # Update the replication applier configuration. - result = replication.set_applier_config( - endpoint='http://127.0.0.1:8529', - database='test', - username='root', - password='passwd', - max_connect_retries=120, - connect_timeout=15, - request_timeout=615, - chunk_size=0, - auto_start=True, - adaptive_polling=False, - include_system=True, - auto_resync=True, - auto_resync_retries=3, - initial_sync_max_wait_time=405, - connection_retry_wait_time=25, - idle_min_wait_time=2, - idle_max_wait_time=3, - require_from_present=False, - verbose=True, - restrict_type='include', - restrict_collections=['students'] - ) - - # Get the replication applier state. - replication.applier_state() - - # Start the replication applier. - replication.start_applier() - - # Stop the replication applier. - replication.stop_applier() - # Get the server ID. replication.server_id() diff --git a/tests/test_replication.py b/tests/test_replication.py index c89ee80e..302076dd 100644 --- a/tests/test_replication.py +++ b/tests/test_replication.py @@ -8,11 +8,6 @@ HTTP_UNAUTHORIZED, ) from arango.exceptions import ( - ReplicationApplierConfigError, - ReplicationApplierConfigSetError, - ReplicationApplierStartError, - ReplicationApplierStateError, - ReplicationApplierStopError, ReplicationClusterInventoryError, ReplicationDumpBatchCreateError, ReplicationDumpBatchDeleteError, @@ -20,7 +15,6 @@ ReplicationDumpError, ReplicationInventoryError, ReplicationLoggerStateError, - ReplicationMakeSlaveError, ReplicationServerIDError, ReplicationSyncError, ) @@ -105,137 +99,6 @@ def test_replication_logger_state(sys_db, bad_db, cluster): assert err.value.error_code in {FORBIDDEN, DATABASE_NOT_FOUND} -def test_replication_applier(sys_db, bad_db, url, cluster): - if cluster: - pytest.skip("Not tested in a cluster setup") - - # Test replication applier state - state = sys_db.replication.applier_state() - assert "server" in state - assert "state" in state - - with assert_raises(ReplicationApplierStateError) as err: - bad_db.replication.applier_state() - assert err.value.error_code in {FORBIDDEN, DATABASE_NOT_FOUND} - - # Test replication get applier config - result = sys_db.replication.applier_config() - assert "verbose" in result - assert "incremental" in result - assert "include_system" in result - - with assert_raises(ReplicationApplierConfigError) as err: - bad_db.replication.applier_config() - assert err.value.error_code in {FORBIDDEN, DATABASE_NOT_FOUND} - - # Test replication stop applier - result = sys_db.replication.stop_applier() - assert "server" in result - assert "state" in result - - with assert_raises(ReplicationApplierStopError) as err: - bad_db.replication.stop_applier() - assert err.value.error_code in {FORBIDDEN, DATABASE_NOT_FOUND} - - # We need a tcp endpoint - tcp_endpoint = url.replace("http", "tcp") - - # Test replication set applier config - result = sys_db.replication.set_applier_config( - endpoint=tcp_endpoint, - database="_system", - username="root", - password="passwd", - max_connect_retries=120, - connect_timeout=15, - request_timeout=615, - chunk_size=0, - auto_start=True, - adaptive_polling=False, - include_system=True, - auto_resync=True, - auto_resync_retries=3, - initial_sync_max_wait_time=405, - connection_retry_wait_time=25, - idle_min_wait_time=2, - idle_max_wait_time=3, - require_from_present=False, - verbose=True, - restrict_type="exclude", - restrict_collections=["students"], - ) - assert result["endpoint"] == tcp_endpoint - assert result["database"] == "_system" - assert result["username"] == "root" - assert result["max_connect_retries"] == 120 - assert result["connect_timeout"] == 15 - assert result["request_timeout"] == 615 - assert result["chunk_size"] == 0 - assert result["auto_start"] is True - assert result["adaptive_polling"] is False - assert result["include_system"] is True - assert result["auto_resync"] is True - assert result["auto_resync_retries"] == 3 - assert result["initial_sync_max_wait_time"] == 405 - assert result["connection_retry_wait_time"] == 25 - assert result["idle_min_wait_time"] == 2 - assert result["idle_max_wait_time"] == 3 - assert result["require_from_present"] is False - assert result["verbose"] is True - assert result["restrict_type"] == "exclude" - assert result["restrict_collections"] == ["students"] - - with assert_raises(ReplicationApplierConfigSetError) as err: - bad_db.replication.set_applier_config(tcp_endpoint) - assert err.value.error_code in {FORBIDDEN, DATABASE_NOT_FOUND} - - # Test replication start applier - result = sys_db.replication.start_applier() - assert "server" in result - assert "state" in result - sys_db.replication.stop_applier() - - with assert_raises(ReplicationApplierStartError) as err: - bad_db.replication.start_applier() - assert err.value.error_code in {FORBIDDEN, DATABASE_NOT_FOUND} - - -def test_replication_make_slave(sys_db, bad_db, url, replication): - if not replication: - pytest.skip("Only tested for replication") - - sys_db.replication.stop_applier() - - result = sys_db.replication.make_slave( - endpoint="tcp://192.168.1.65:8500", - database="test", - username="root", - password="passwd", - restrict_type="include", - restrict_collections=["test"], - include_system=False, - max_connect_retries=5, - connect_timeout=500, - request_timeout=500, - chunk_size=0, - adaptive_polling=False, - auto_resync=False, - auto_resync_retries=0, - initial_sync_max_wait_time=0, - connection_retry_wait_time=0, - idle_min_wait_time=0, - idle_max_wait_time=0, - require_from_present=False, - verbose=True, - ) - assert "endpoint" in result - assert "database" in result - - with assert_raises(ReplicationMakeSlaveError) as err: - bad_db.replication.make_slave(endpoint=url) - assert err.value.error_code in {FORBIDDEN, DATABASE_NOT_FOUND} - - def test_replication_cluster_inventory(sys_db, bad_db, cluster): if cluster: result = sys_db.replication.cluster_inventory(include_system=True)