diff --git a/arango/cluster.py b/arango/cluster.py index a272f50c..6ebdb69b 100644 --- a/arango/cluster.py +++ b/arango/cluster.py @@ -11,6 +11,7 @@ ClusterServerCountError, ClusterServerEngineError, ClusterServerIDError, + ClusterServerModeError, ClusterServerRoleError, ClusterServerStatisticsError, ClusterServerVersionError, @@ -57,6 +58,27 @@ def response_handler(resp: Response) -> str: return self._execute(request, response_handler) + def server_mode(self) -> Result[str]: + """Return the server mode. + + In a read-only server, all write operations will fail + with an error code of 1004 (ERROR_READ_ONLY). Creating or dropping + databases and collections will also fail with error code 11 (ERROR_FORBIDDEN). + + :return: Server mode. Possible values are "default" or "readonly". + :rtype: str + :raise arango.exceptions.ClusterServerModeError: If retrieval fails. + """ + request = Request(method="get", endpoint="/_admin/server/mode") + + def response_handler(resp: Response) -> str: + if resp.is_success: + return str(resp.body["mode"]) + + raise ClusterServerModeError(resp, request) + + return self._execute(request, response_handler) + def server_version(self, server_id: str) -> Result[Json]: """Return the version of the given server. diff --git a/arango/database.py b/arango/database.py index 9faafa66..aedeaba6 100644 --- a/arango/database.py +++ b/arango/database.py @@ -52,6 +52,8 @@ ServerLogSettingError, ServerLogSettingSetError, ServerMetricsError, + ServerModeError, + ServerModeSetError, ServerReadLogError, ServerReloadRoutingError, ServerRequiredDBVersionError, @@ -554,6 +556,56 @@ def response_handler(resp: Response) -> str: return self._execute(request, response_handler) + def mode(self) -> Result[str]: + """Return the server mode (default or read-only) + + In a read-only server, all write operations will fail + with an error code of 1004 (ERROR_READ_ONLY). Creating or dropping + databases and collections will also fail with error code 11 (ERROR_FORBIDDEN). + + :return: Server mode. Possible values are "default" or "readonly". + :rtype: str + :raise arango.exceptions.ServerModeError: If retrieval fails. + """ + request = Request(method="get", endpoint="/_admin/server/mode") + + def response_handler(resp: Response) -> str: + if resp.is_success: + return str(resp.body["mode"]) + + raise ServerModeError(resp, request) + + return self._execute(request, response_handler) + + def set_mode(self, mode: str) -> Result[Json]: + """Set the server mode to read-only or default. + + Update mode information about a server. The JSON response will + contain a field mode with the value readonly or default. + In a read-only server all write operations will fail with an error + code of 1004 (ERROR_READ_ONLY). Creating or dropping of databases + and collections will also fail with error code 11 (ERROR_FORBIDDEN). + + This is a protected API. It requires authentication and administrative + server rights. + + :param mode: Server mode. Possible values are "default" or "readonly". + :type mode: str + :return: Server mode. + :rtype: str + :raise arango.exceptions.ServerModeSetError: If set fails. + """ + request = Request( + method="put", endpoint="/_admin/server/mode", data={"mode": mode} + ) + + def response_handler(resp: Response) -> Json: + if resp.is_success: + return format_body(resp.body) + raise ServerModeSetError(resp, request) + + return self._execute(request, response_handler) + def time(self) -> Result[datetime]: """Return server system time. diff --git a/arango/exceptions.py b/arango/exceptions.py index f3f4d264..0c0aca26 100644 --- a/arango/exceptions.py +++ b/arango/exceptions.py @@ -695,7 +695,15 @@ class ServerMetricsError(ArangoServerError): class ServerRoleError(ArangoServerError): - """Failed to retrieve server role in a cluster.""" + """Failed to retrieve server role.""" + + +class ServerModeError(ArangoServerError): + """Failed to retrieve server mode.""" + + +class ServerModeSetError(ArangoServerError): + """Failed to set server mode.""" class ServerTLSError(ArangoServerError): @@ -980,7 +988,11 @@ class ClusterServerIDError(ArangoServerError): class ClusterServerRoleError(ArangoServerError): - """Failed to retrieve server role.""" + """Failed to retrieve server role in a cluster.""" + + +class ClusterServerModeError(ArangoServerError): + """Failed to retrieve server mode in a cluster.""" class ClusterServerStatisticsError(ArangoServerError): diff --git a/docs/admin.rst b/docs/admin.rst index dc3dc030..e1c1efeb 100644 --- a/docs/admin.rst +++ b/docs/admin.rst @@ -32,9 +32,22 @@ database. # Retrieve the server time. sys_db.time() - # Retrieve the server role in a cluster. + # Retrieve the server role. sys_db.role() + # Retrieve the server role in a cluster. + sys_db.cluster.server_role() + + # Retrieve the server mode. + sys_db.mode() + + # Retrieve the server mode in a cluster. + sys_db.cluster.server_mode() + + # Set the server mode. + sys_db.set_mode('readonly') + sys_db.set_mode('default') + # Retrieve the server statistics. sys_db.statistics() diff --git a/tests/test_cluster.py b/tests/test_cluster.py index bbc31778..3eda0bb0 100644 --- a/tests/test_cluster.py +++ b/tests/test_cluster.py @@ -12,6 +12,7 @@ ClusterServerCountError, ClusterServerEngineError, ClusterServerIDError, + ClusterServerModeError, ClusterServerRoleError, ClusterServerStatisticsError, ClusterServerVersionError, @@ -43,6 +44,18 @@ def test_cluster_server_role(sys_db, bad_db, cluster): assert err.value.error_code in {FORBIDDEN, DATABASE_NOT_FOUND} +def test_cluster_server_mode(sys_db, bad_db, cluster): + if not cluster: + pytest.skip("Only tested in a cluster setup") + + result = sys_db.cluster.server_mode() + assert result == "default" + + with assert_raises(ClusterServerModeError) as err: + bad_db.cluster.server_mode() + assert err.value.error_code in {FORBIDDEN, DATABASE_NOT_FOUND} + + def test_cluster_health(sys_db, bad_db, cluster): if not cluster: pytest.skip("Only tested in a cluster setup") diff --git a/tests/test_database.py b/tests/test_database.py index 12d14a7d..8645b909 100644 --- a/tests/test_database.py +++ b/tests/test_database.py @@ -25,6 +25,7 @@ ServerLogLevelError, ServerLogLevelSetError, ServerMetricsError, + ServerModeSetError, ServerReadLogError, ServerReloadRoutingError, ServerRequiredDBVersionError, @@ -138,6 +139,19 @@ def test_database_misc_methods(client, sys_db, db, bad_db, cluster, secret): bad_db.role() assert err.value.error_code in {11, 1228} + # Test get/set server mode + assert sys_db.mode() == "default" + with assert_raises(ServerModeSetError): + sys_db.set_mode("badmode") + assert err.value.error_code in {11, 1228} + + with assert_raises(ServerModeSetError): + db.set_mode("readonly") + assert err.value.error_code in {11, 1228} + + result = sys_db.set_mode("default") + assert result == {"mode": "default"} + # Test get server status status = db.status() assert "host" in status