Repository: ClearcodeHQ/pytest-rabbitmq (language: python)

Issue:
RabbitMQ will not start if selected port value is too large
### What action do you want to perform

Start the rabbitmq server reliably. The RabbitMQ server randomly fails to start up. I dug in and noticed an `erl_crash.dump` file gets created. Looking into this and with some googling, this is the 'Slogan', which supposedly is the error message:

```
Slogan: Kernel pid terminated (application_controller) ({application_start_failure,rabbitmq_prelaunch,{{shutdown,{failed_to_start_child,prelaunch,{invalid_dist_port_range,65958}}},{rabbit_prelaunch_app,start,[normal,[]]}}})
```

The important part of this is `{invalid_dist_port_range,65958}`. It turns out that RabbitMQ has a maximum port number of 65535 [as defined here](https://github.com/rabbitmq/rabbitmq-server/blob/1a6f81b584b3723870dec0ed1e0f714aa55fb80c/deps/rabbit/apps/rabbitmq_prelaunch/src/rabbit_prelaunch_dist.erl#L99-L102).

Not sure if this happens regularly on Linux but it happens a lot on MacOS (2019 Intel, Ventura 13.3.1).

### What are the results

Not started reliably.

### What are the expected results

Should be a simple fix to limit the `port_for` call in this part if not otherwise specified by the user:

https://github.com/ClearcodeHQ/pytest-rabbitmq/blob/dcd96a4ddce881b16c63a0794c152b1a3a36d001/src/pytest_rabbitmq/factories/process.py#LL96C32-L96C36




Discussion / hints:
~The fix was pretty easy:~


~from pytest_rabbitmq import factories~

~factories.rabbitmq_proc(port=(1, 65535))~

The fix is slightly more nuanced. [According to the RabbitMQ networking docs](https://www.rabbitmq.com/networking.html#epmd-inet-dist-port-range):

> RabbitMQ nodes communicate with CLI tools and other nodes using a port known as the distribution port. It is dynamically allocated from a range of values. For RabbitMQ, the default range is limited to a single value computed as RABBITMQ_NODE_PORT (AMQP 0-9-1 and AMQP 1.0 port) + 20000, which results in using port 25672

The distribution port is the problem, that's what is too high. Since the limit is 65535 and it's computed by adding 20000 to the default port, the port we select for rabbitmq then cannot exceed `45535`. This is the fix:

```python
from pytest_rabbitmq import factories

factories.rabbitmq_proc(port=(1, 45535))
```
Linux also is affected by something similar, it actually states that the distribution port is already used:
https://github.com/ClearcodeHQ/pytest-rabbitmq/actions/runs/5187634162/jobs/9350236844

🤔 is there a way to manually set that for RabbitMQ?

Fix patch:
diff --git a/Pipfile b/Pipfile
index 75faf6c..e0d3e7f 100644
--- a/Pipfile
+++ b/Pipfile
@@ -6,7 +6,7 @@ name = "pypi"
 [packages]
 pytest = "==7.3.2"
 pika = "==1.3.2"
-port-for = "==0.6.3"
+port-for = "==0.7.0"
 mirakuru = "==2.5.1"
 
 [dev-packages]
diff --git a/Pipfile.lock b/Pipfile.lock
index bf4672a..5f67b33 100644
--- a/Pipfile.lock
+++ b/Pipfile.lock
@@ -1,7 +1,7 @@
 {
     "_meta": {
         "hash": {
-            "sha256": "33fcb8f8efceee4d996639d88a236b04358f60d59711053c645bf4afb1b45291"
+            "sha256": "4df559d2bcb1dee6ff8de68baea82951b3ce35632f79be2b7444065fb7a371cb"
         },
         "pipfile-spec": 6,
         "requires": {},
@@ -64,11 +64,11 @@
         },
         "port-for": {
             "hashes": [
-                "sha256:232bd999015b7fbdf19f90f3a9298cc742252d67650108123940bfc75c6f4d4e",
-                "sha256:31860afde6cb552e1830c927def3288350c8fbbe9aea8aed8150ed9d1aa0de81"
+                "sha256:2cdd35f12919f48a3c2a78df1f64d5e0791a0bac34172253bd52a7e5a0c676c8",
+                "sha256:c8e948fd833b45af3814603fb11107ca2e58022ad6e6e24ae7428f8cd75cdd29"
             ],
             "index": "pypi",
-            "version": "==0.6.3"
+            "version": "==0.7.0"
         },
         "psutil": {
             "hashes": [
diff --git a/README.rst b/README.rst
index f04399c..44e1839 100644
--- a/README.rst
+++ b/README.rst
@@ -86,11 +86,16 @@ You can pick which you prefer, but remember that these settings are handled in t
      - --rabbitmq-host
      - rabbitmq_host
      - 127.0.0.1
-   * - port
+   * - RABBITMQ_NODE_PORT
      - port
      - --rabbitmq-port
      - rabbitmq_port
      - random
+   * - RABBITMQ_DIST_PORT
+     - distribution_port
+     - --rabbitmq-distribution-port
+     - rabbitmq_distribution_port
+     - random
    * - rabbitmqctl path
      - ctl
      - --rabbitmq-ctl
diff --git a/newsfragments/317.feature.rst b/newsfragments/317.feature.rst
new file mode 100644
index 0000000..a358593
--- /dev/null
+++ b/newsfragments/317.feature.rst
@@ -0,0 +1,6 @@
+Define RABBITMQ_DIST_PORT for rabbitmq.
+Added `--rabbitmq-distribution-port` to commandline and `rabbitmq_distribution_port` to ini configuration options.
+
+This will help both with macos port number limit (as by default Rabbitmk adds 20000 to the Node port to determine the port), and the port being already used error.
+
+This port has to be different that rabbitmq port.
\ No newline at end of file


Test patch:
diff --git a/pytest_rabbitmq/factories/executor.py b/pytest_rabbitmq/factories/executor.py
index 3a4e988..118dae1 100644
--- a/pytest_rabbitmq/factories/executor.py
+++ b/pytest_rabbitmq/factories/executor.py
@@ -17,6 +17,7 @@ def __init__(
         command: str,
         host: str,
         port: int,
+        distribution_port: int,
         rabbit_ctl: str,
         logpath: Path,
         path: Path,
@@ -38,6 +39,7 @@ def __init__(
             "RABBITMQ_MNESIA_BASE": str(path / "mnesia"),
             "RABBITMQ_ENABLED_PLUGINS_FILE": str(plugin_path / "plugins"),
             "RABBITMQ_NODE_PORT": str(port),
+            "RABBITMQ_DIST_PORT": str(distribution_port),
             # Use the port number in node name, so multiple instances started
             # at different ports will work separately instead of clustering.
             "RABBITMQ_NODENAME": node_name or f"rabbitmq-test-{port}",
diff --git a/pytest_rabbitmq/factories/process.py b/pytest_rabbitmq/factories/process.py
index f68f2eb..874cd00 100644
--- a/pytest_rabbitmq/factories/process.py
+++ b/pytest_rabbitmq/factories/process.py
@@ -34,6 +34,7 @@ class RabbitMQConfig(TypedDict):
 
     host: str
     port: Optional[int]
+    distribution_port: Optional[int]
     logsdir: Optional[Path]
     server: str
     ctl: str
@@ -49,9 +50,11 @@ def get_conf_option(option: str) -> Any:
         return request.config.getoption(option_name) or request.config.getini(option_name)
 
     port = get_conf_option("port")
+    distribution_port = get_conf_option("distribution_port")
     config: RabbitMQConfig = {
         "host": get_conf_option("host"),
         "port": int(port) if port else None,
+        "distribution_port": int(distribution_port) if distribution_port else None,
         "logsdir": Path(get_conf_option("logsdir")),
         "server": get_conf_option("server"),
         "ctl": get_conf_option("ctl"),
@@ -65,6 +68,7 @@ def rabbitmq_proc(
     server: Optional[str] = None,
     host: Optional[str] = None,
     port: Optional[int] = -1,
+    distribution_port: Optional[int] = -1,
     node: Optional[str] = None,
     ctl: Optional[str] = None,
     logsdir: Optional[Path] = None,
@@ -80,6 +84,12 @@ def rabbitmq_proc(
         [(2000,3000)] or (2000,3000) - random available port from a given range
         [{4002,4003}] or {4002,4003} - random of 4002 or 4003 ports
         [(2000,3000), {4002,4003}] -random of given range and set
+    :param distribution_port:
+        exact port (e.g. '8000', 8000)
+        randomly selected port (None) - any random available port
+        [(2000,3000)] or (2000,3000) - random available port from a given range
+        [{4002,4003}] or {4002,4003} - random of 4002 or 4003 ports
+        [(2000,3000), {4002,4003}] -random of given range and set
     :param node: RabbitMQ node name used for setting environment
                           variable RABBITMQ_NODENAME (the default depends
                           on the port number, so multiple nodes are not
@@ -103,6 +113,7 @@ def rabbitmq_proc_fixture(
         #.  * RABBITMQ_MNESIA_BASE
         #.  * RABBITMQ_ENABLED_PLUGINS_FILE
         #.  * RABBITMQ_NODE_PORT
+        #.  * RABBITMQ_DIST_PORT
         #.  * RABBITMQ_NODENAME
         #. Start a rabbit server
             `<http://www.rabbitmq.com/man/rabbitmq-server.1.man.html>`_
@@ -118,6 +129,13 @@ def rabbitmq_proc_fixture(
         rabbit_host = host or config["host"]
         rabbit_port = get_port(port) or get_port(config["port"])
         assert rabbit_port
+        rabbit_distribution_port = get_port(distribution_port, [rabbit_port]) or get_port(
+            config["distribution_port"], [rabbit_port]
+        )
+        assert rabbit_distribution_port
+        assert (
+            rabbit_distribution_port != rabbit_port
+        ), "rabbit_port and distribution_port can not be the same!"
 
         tmpdir = tmp_path_factory.mktemp(f"pytest-rabbitmq-{request.fixturename}")
 
@@ -138,6 +156,7 @@ def rabbitmq_proc_fixture(
             rabbit_server,
             rabbit_host,
             rabbit_port,
+            rabbit_distribution_port,
             rabbit_ctl,
             logpath=rabbit_logpath,
             path=tmpdir,
diff --git a/pytest_rabbitmq/plugin.py b/pytest_rabbitmq/plugin.py
index ff09857..a2a4398 100644
--- a/pytest_rabbitmq/plugin.py
+++ b/pytest_rabbitmq/plugin.py
@@ -29,6 +29,7 @@
 _help_plugindir = "Directory where 'plugin' file is located"
 _help_host = "Host at which RabbitMQ will accept connections"
 _help_port = "Port at which RabbitMQ will accept connections"
+_help_distribution_port = "Port at which RabbitMQ nodes will communicate with each other"
 _help_node = "Node name for rabbitmq instance"
 
 
@@ -40,6 +41,11 @@ def pytest_addoption(parser: Parser) -> None:
         help=_help_port,
         default=None,
     )
+    parser.addini(
+        name="rabbitmq_distribution_port",
+        help=_help_distribution_port,
+        default=None,
+    )
     parser.addini(
         name="rabbitmq_ctl",
         help=_help_ctl,
@@ -73,6 +79,12 @@ def pytest_addoption(parser: Parser) -> None:
         help=_help_host,
     )
     parser.addoption("--rabbitmq-port", action="store", dest="rabbitmq_port", help=_help_port)
+    parser.addoption(
+        "--rabbitmq-distribution-port",
+        action="store",
+        dest="rabbitmq_distribution_port",
+        help=_help_distribution_port,
+    )
     parser.addoption("--rabbitmq-ctl", action="store", dest="rabbitmq_ctl", help=_help_ctl)
     parser.addoption("--rabbitmq-server", action="store", dest="rabbitmq_server", help=_help_server)
     parser.addoption(