Skip to content

Add proxy gateways #1171

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Jan 18, 2025
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions changelog/1170.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Add the `--px` arg to create proxy gateways.

Proxy gateways are passed to additional gateways using the `via` keyword.
They can serve as a way to run multiple workers on remote machines.
14 changes: 14 additions & 0 deletions docs/remote.rst
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,20 @@ new socket host with something like this::
pytest -d --tx socket=192.168.1.102:8888 --rsyncdir mypkg


Using proxies to run multiple workers on remote machines
---------------------------------------

In case you want to run multiple workers on a remote machine,
you can create a proxy gateway for the machine, and run multiple
workers using the `via` attribute.::

pytest -d --px id=my_proxy//socket=192.168.1.102:8888 --tx 5*popen//via=my_proxy

Here we declare a proxy gateway using the `--px` arg, and
create 5 workers that run on the remote server using the proxy.
Note that the proxy gateway does not run a worker, thus only 5
workers are created.


Running tests on many platforms at once
---------------------------------------
Expand Down
11 changes: 11 additions & 0 deletions src/xdist/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,17 @@ def pytest_addoption(parser: pytest.Parser) -> None:
"--tx ssh=user@codespeak.net//chdir=testcache"
),
)
group.addoption(
"--px",
dest="px",
action="append",
default=[],
metavar="xspec",
help=(
"Add a proxy gateway to pass to test execution environments using `via`. Example:\n"
"--px id=my_proxy//socket=192.168.1.102:8888 --tx 5*popen//via=my_proxy"
),
)
group._addoption(
"-d",
action="store_true",
Expand Down
4 changes: 2 additions & 2 deletions src/xdist/scheduler/each.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

from xdist.remote import Producer
from xdist.report import report_collection_diff
from xdist.workermanage import parse_spec_config
from xdist.workermanage import parse_tx_spec_config
from xdist.workermanage import WorkerController


Expand All @@ -26,7 +26,7 @@ class EachScheduling:

def __init__(self, config: pytest.Config, log: Producer | None = None) -> None:
self.config = config
self.numnodes = len(parse_spec_config(config))
self.numnodes = len(parse_tx_spec_config(config))
self.node2collection: dict[WorkerController, list[str]] = {}
self.node2pending: dict[WorkerController, list[int]] = {}
self._started: list[WorkerController] = []
Expand Down
4 changes: 2 additions & 2 deletions src/xdist/scheduler/load.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

from xdist.remote import Producer
from xdist.report import report_collection_diff
from xdist.workermanage import parse_spec_config
from xdist.workermanage import parse_tx_spec_config
from xdist.workermanage import WorkerController


Expand Down Expand Up @@ -58,7 +58,7 @@ class LoadScheduling:
"""

def __init__(self, config: pytest.Config, log: Producer | None = None) -> None:
self.numnodes = len(parse_spec_config(config))
self.numnodes = len(parse_tx_spec_config(config))
self.node2collection: dict[WorkerController, list[str]] = {}
self.node2pending: dict[WorkerController, list[int]] = {}
self.pending: list[int] = []
Expand Down
4 changes: 2 additions & 2 deletions src/xdist/scheduler/loadscope.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

from xdist.remote import Producer
from xdist.report import report_collection_diff
from xdist.workermanage import parse_spec_config
from xdist.workermanage import parse_tx_spec_config
from xdist.workermanage import WorkerController


Expand Down Expand Up @@ -91,7 +91,7 @@ class LoadScopeScheduling:
"""

def __init__(self, config: pytest.Config, log: Producer | None = None) -> None:
self.numnodes = len(parse_spec_config(config))
self.numnodes = len(parse_tx_spec_config(config))
self.collection: list[str] | None = None

self.workqueue: OrderedDict[str, dict[str, bool]] = OrderedDict()
Expand Down
4 changes: 2 additions & 2 deletions src/xdist/scheduler/worksteal.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

from xdist.remote import Producer
from xdist.report import report_collection_diff
from xdist.workermanage import parse_spec_config
from xdist.workermanage import parse_tx_spec_config
from xdist.workermanage import WorkerController


Expand Down Expand Up @@ -65,7 +65,7 @@ class WorkStealingScheduling:
"""

def __init__(self, config: pytest.Config, log: Producer | None = None) -> None:
self.numnodes = len(parse_spec_config(config))
self.numnodes = len(parse_tx_spec_config(config))
self.node2collection: dict[WorkerController, list[str]] = {}
self.node2pending: dict[WorkerController, list[int]] = {}
self.pending: list[int] = []
Expand Down
20 changes: 16 additions & 4 deletions src/xdist/workermanage.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
from xdist.remote import WorkerInfo


def parse_spec_config(config: pytest.Config) -> list[str]:
def parse_tx_spec_config(config: pytest.Config) -> list[str]:
xspeclist = []
tx: list[str] = config.getvalue("tx")
for xspec in tx:
Expand Down Expand Up @@ -57,8 +57,17 @@ def __init__(
if self.testrunuid is None:
self.testrunuid = uuid.uuid4().hex
self.group = execnet.Group(execmodel="main_thread_only")
for proxy_spec in self._getpxspecs():
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I need to crosscheck in execnet on how proxy startup is managed

I vaguely recall that main thread only may cause trouble when more than one worker is behind a proxy

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tested locally using 5 workers behind a local socket proxy, and it worked.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I realized my mistake

Proxy doesn't get thr default value of main thread only as its using a own spec

Thanks for validation

# Proxy gateways do not run workers, and are meant to be passed with the `via` attribute
# to additional gateways.
# They are useful for running multiple workers on remote machines.
if getattr(proxy_spec, "id", None) is None:
raise pytest.UsageError(
f"Proxy gateway {proxy_spec} must include an id"
)
self.group.makegateway(proxy_spec)
if specs is None:
specs = self._getxspecs()
specs = self._gettxspecs()
self.specs: list[execnet.XSpec] = []
for spec in specs:
if not isinstance(spec, execnet.XSpec):
Expand Down Expand Up @@ -107,8 +116,11 @@ def setup_node(
def teardown_nodes(self) -> None:
self.group.terminate(self.EXIT_TIMEOUT)

def _getxspecs(self) -> list[execnet.XSpec]:
return [execnet.XSpec(x) for x in parse_spec_config(self.config)]
def _gettxspecs(self) -> list[execnet.XSpec]:
return [execnet.XSpec(x) for x in parse_tx_spec_config(self.config)]

def _getpxspecs(self) -> list[execnet.XSpec]:
return [execnet.XSpec(x) for x in self.config.getoption("px")]

def _getrsyncdirs(self) -> list[Path]:
for spec in self.specs:
Expand Down
4 changes: 2 additions & 2 deletions testing/test_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -295,15 +295,15 @@ class TestDistOptions:
def test_getxspecs(self, pytester: pytest.Pytester) -> None:
config = pytester.parseconfigure("--tx=popen", "--tx", "ssh=xyz")
nodemanager = NodeManager(config)
xspecs = nodemanager._getxspecs()
xspecs = nodemanager._gettxspecs()
assert len(xspecs) == 2
print(xspecs)
assert xspecs[0].popen
assert xspecs[1].ssh == "xyz"

def test_xspecs_multiplied(self, pytester: pytest.Pytester) -> None:
config = pytester.parseconfigure("--tx=3*popen")
xspecs = NodeManager(config)._getxspecs()
xspecs = NodeManager(config)._gettxspecs()
assert len(xspecs) == 3
assert xspecs[1].popen

Expand Down
Loading