You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@solr.apache.org by ja...@apache.org on 2026/05/30 21:11:20 UTC
(solr-orbit) branch main updated: Require SolrCloud mode: enforce at runtime and document clearly (#43)
This is an automated email from the ASF dual-hosted git repository.
janhoy pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/solr-orbit.git
The following commit(s) were added to refs/heads/main by this push:
new e34a7eac Require SolrCloud mode: enforce at runtime and document clearly (#43)
e34a7eac is described below
commit e34a7eac95cb2820991621f038234ae386a623a4
Author: Jan Høydahl <ja...@apache.org>
AuthorDate: Sat May 30 23:11:15 2026 +0200
Require SolrCloud mode: enforce at runtime and document clearly (#43)
---
docs/quickstart.md | 6 ++-
docs/reference/workloads/test-procedures.md | 3 ++
.../working-with-workloads/running-workloads.md | 3 ++
solrorbit/client.py | 35 +++++++++++++++
solrorbit/test_run_orchestrator.py | 36 +++++++++++++++
tests/unit/solr/test_client.py | 51 ++++++++++++++++++++++
6 files changed, 133 insertions(+), 1 deletion(-)
diff --git a/docs/quickstart.md b/docs/quickstart.md
index 247d47e4..2643d889 100644
--- a/docs/quickstart.md
+++ b/docs/quickstart.md
@@ -17,10 +17,14 @@ To perform the quickstart steps, you'll need the following:
## Set up a Solr cluster
+{: .important }
+Solr Orbit requires Solr to run in **SolrCloud mode**. Standalone/user-managed mode is not supported. For Solr 9.x, start Solr with the `-c` flag to enable SolrCloud mode. For Solr 10.0.0 and later, SolrCloud is the default and no extra flag is required.
+
If you don't already have a running Solr cluster, the easiest way to start one is with Docker:
```bash
-docker run -d --name solr-orbit -p 8983:8983 solr:9 solr-demo
+# Solr 9.x — the -c flag enables SolrCloud mode
+docker run -d --name solr-orbit -p 8983:8983 solr:9 -c
```
Verify that Solr is running by opening [http://localhost:8983/solr/](http://localhost:8983/solr/) in your browser, or with:
diff --git a/docs/reference/workloads/test-procedures.md b/docs/reference/workloads/test-procedures.md
index 8cf439b1..a4db6b9e 100644
--- a/docs/reference/workloads/test-procedures.md
+++ b/docs/reference/workloads/test-procedures.md
@@ -60,6 +60,9 @@ Test procedures define named benchmark scenarios within a workload. A workload t
## Selecting a test procedure at run time
+{: .important }
+The target Solr cluster must be running in **SolrCloud (ZooKeeper) mode**. For Solr 9.x, start with `-c` (e.g. `docker run -d -p 8983:8983 solr:9 -c`). For Solr 10.0.0+, SolrCloud is the default.
+
```bash
solr-orbit run \
--pipeline benchmark-only \
diff --git a/docs/user-guide/working-with-workloads/running-workloads.md b/docs/user-guide/working-with-workloads/running-workloads.md
index bb7eb2e2..3af8fd33 100644
--- a/docs/user-guide/working-with-workloads/running-workloads.md
+++ b/docs/user-guide/working-with-workloads/running-workloads.md
@@ -29,6 +29,9 @@ solr-orbit run [--pipeline PIPELINE] [--target-hosts HOSTS] \
[--workload WORKLOAD | --workload-path PATH] [OPTIONS]
```
+{: .important }
+When using the `benchmark-only` pipeline, the target Solr cluster must be running in **SolrCloud (ZooKeeper) mode** — standalone/user-managed mode is not supported. For Solr 9.x, start Solr with the `-c` flag (e.g. `bin/solr start -c` or `docker run -d -p 8983:8983 solr:9 -c`). For Solr 10.0.0 and later, SolrCloud is the default.
+
---
## Using a named workload
diff --git a/solrorbit/client.py b/solrorbit/client.py
index b872003f..73538718 100644
--- a/solrorbit/client.py
+++ b/solrorbit/client.py
@@ -330,6 +330,38 @@ class SolrAdminClient:
return resp.text
return resp.json()
+ def is_cloud_mode(self) -> bool:
+ """
+ Return True if this Solr node is running in SolrCloud (ZooKeeper) mode.
+
+ Calls the Collections CLUSTERSTATUS API, which only succeeds in cloud mode.
+ Returns False if Solr responds with a 400 indicating standalone/user-managed mode.
+ Raises SolrClientError on connection errors or unexpected failures.
+ """
+ try:
+ resp = self._get_session().get(
+ f"{self.base_url}/solr/admin/collections",
+ params={"action": "CLUSTERSTATUS", "wt": "json"},
+ timeout=self.timeout,
+ )
+ except requests.exceptions.RequestException as exc:
+ raise SolrClientError(
+ f"Could not connect to Solr at {self.base_url}: {exc}"
+ ) from exc
+ if resp.ok:
+ return True
+ # HTTP 400 with a message about SolrCloud/user-managed → definitely standalone mode
+ body = self._try_parse_json(resp)
+ msg = str(body.get("error", {}).get("msg", "")).lower()
+ if resp.status_code == 400 and any(
+ kw in msg for kw in ("solrcloud", "user-managed", "collections api")
+ ):
+ return False
+ # Auth error, unexpected status, etc. — surface to caller
+ raise SolrClientError(
+ f"Unexpected response from CLUSTERSTATUS (HTTP {resp.status_code}): {resp.text[:300]}"
+ )
+
# ------------------------------------------------------------------
# Raw request (for the raw-request workload operation)
# ------------------------------------------------------------------
@@ -478,6 +510,9 @@ class SolrClient(RequestContextHolder): # pylint: disable=too-many-public-metho
def wait_for_cluster_ready(self, **kwargs):
return self._admin.wait_for_cluster_ready(**kwargs)
+ def is_cloud_mode(self) -> bool:
+ return self._admin.is_cloud_mode()
+
def raw_request(self, method, path, body=None, headers=None):
return self._admin.raw_request(method, path, body=body, headers=headers)
diff --git a/solrorbit/test_run_orchestrator.py b/solrorbit/test_run_orchestrator.py
index 7ff1af4c..9b6f02c0 100644
--- a/solrorbit/test_run_orchestrator.py
+++ b/solrorbit/test_run_orchestrator.py
@@ -377,9 +377,45 @@ def set_default_hosts(cfg, host="127.0.0.1", port=9200):
cfg.add(config.Scope.benchmark, "client", "hosts", default_host_object)
+def _check_cloud_mode(cfg):
+ """Fail fast with a clear error if the target Solr cluster is not in SolrCloud mode."""
+ from solrorbit.client import SolrAdminClient, SolrClientError # local import to avoid circular
+
+ configured_hosts = cfg.opts("client", "hosts")
+ hosts = configured_hosts.default
+ if not hosts:
+ return
+
+ entry = hosts[0]
+ if isinstance(entry, dict):
+ host = entry.get("host", "localhost")
+ port = int(entry.get("port", 8983))
+ else:
+ parts = str(entry).rsplit(":", 1)
+ host = parts[0]
+ port = int(parts[1]) if len(parts) == 2 and parts[1].isdigit() else 8983
+
+ client = SolrAdminClient(host=host, port=port)
+ try:
+ if not client.is_cloud_mode():
+ raise exceptions.SystemSetupError(
+ f"Solr at {host}:{port} is not running in SolrCloud (ZooKeeper) mode.\n"
+ "Solr Orbit currently only supports SolrCloud mode.\n"
+ " \u2022 For Solr 9.x: start with the '-c' flag, e.g.:\n"
+ " docker run -d -p 8983:8983 solr:9 -c\n"
+ " bin/solr start -c\n"
+ " \u2022 For Solr 10.0.0+: SolrCloud is the default; no extra flag is needed."
+ )
+ except SolrClientError as exc:
+ raise exceptions.SystemSetupError(
+ f"Could not verify Solr mode at {host}:{port}: {exc}"
+ ) from exc
+
+
# Poor man's curry
def benchmark_only(cfg):
set_default_hosts(cfg)
+ _check_cloud_mode(cfg)
return run_test(cfg, external=True)
diff --git a/tests/unit/solr/test_client.py b/tests/unit/solr/test_client.py
index c89ddf88..bf2e2216 100644
--- a/tests/unit/solr/test_client.py
+++ b/tests/unit/solr/test_client.py
@@ -241,5 +241,56 @@ class TestBuildConfigsetZip(unittest.TestCase):
self.assertTrue(any("solrconfig.xml" in n for n in names))
+class TestSolrAdminClientIsCloudMode(unittest.TestCase):
+ def _make_client_with_mock_session(self):
+ client = SolrAdminClient("localhost")
+ client._session = MagicMock()
+ return client
+
+ def test_returns_true_when_clusterstatus_succeeds(self):
+ client = self._make_client_with_mock_session()
+ resp = _make_response(status_code=200, json_data={"cluster": {"collections": {}}})
+ client._session.get.return_value = resp
+ self.assertTrue(client.is_cloud_mode())
+
+ def test_returns_false_when_solrcloud_not_running(self):
+ client = self._make_client_with_mock_session()
+ resp = _make_response(
+ status_code=400,
+ json_data={"error": {"msg": "Solr instance is not running in SolrCloud mode."}},
+ text='{"error": {"msg": "Solr instance is not running in SolrCloud mode."}}',
+ )
+ client._session.get.return_value = resp
+ self.assertFalse(client.is_cloud_mode())
+
+ def test_returns_false_when_user_managed_mode(self):
+ client = self._make_client_with_mock_session()
+ resp = _make_response(
+ status_code=400,
+ json_data={"error": {"msg": "Collections API is disabled in user-managed mode."}},
+ text='{"error": {"msg": "Collections API is disabled in user-managed mode."}}',
+ )
+ client._session.get.return_value = resp
+ self.assertFalse(client.is_cloud_mode())
+
+ def test_raises_on_unexpected_error_status(self):
+ client = self._make_client_with_mock_session()
+ resp = _make_response(
+ status_code=503,
+ json_data={},
+ text="Service Unavailable",
+ )
+ client._session.get.return_value = resp
+ with self.assertRaises(SolrClientError):
+ client.is_cloud_mode()
+
+ def test_raises_on_connection_error(self):
+ import requests as _requests
+ client = self._make_client_with_mock_session()
+ client._session.get.side_effect = _requests.exceptions.ConnectionError("refused")
+ with self.assertRaises(SolrClientError):
+ client.is_cloud_mode()
+
+
if __name__ == "__main__":
unittest.main()