Skip to content

Commit bf1301e

Browse files
committed
ssh: use ssh multiplexing to speed up the tests
on my small 2 tests experiment, I've gone from 187s to 113s Signed-off-by: Gaëtan Lehmann <gaetan.lehmann@vates.tech>
1 parent 7beb4fe commit bf1301e

2 files changed

Lines changed: 32 additions & 18 deletions

File tree

lib/commands.py

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import base64
22
import logging
3+
import os
4+
import platform
35
import shlex
46
import subprocess
57

@@ -63,7 +65,7 @@ def _ellide_log_lines(log):
6365
return "\n{}".format("\n".join(reduced_message))
6466

6567
def _ssh(hostname_or_ip, cmd, check, simple_output, suppress_fingerprint_warnings,
66-
background, decode, options) -> Union[SSHResult, SSHCommandFailed, str, bytes, None]:
68+
background, decode, options, multiplexing) -> Union[SSHResult, SSHCommandFailed, str, bytes, None]:
6769
opts = list(options)
6870
opts.append('-o "BatchMode yes"')
6971
opts.append('-o "PubkeyAcceptedAlgorithms +ssh-rsa"')
@@ -74,6 +76,18 @@ def _ssh(hostname_or_ip, cmd, check, simple_output, suppress_fingerprint_warning
7476
opts.append('-o "StrictHostKeyChecking no"')
7577
opts.append('-o "LogLevel ERROR"')
7678
opts.append('-o "UserKnownHostsFile /dev/null"')
79+
# ssh multiplexing is not always well supported on windows, so we disable it on that platform.
80+
# It could work with git bash—we might want to check that instead.
81+
# We use the pid in the control path to avoid a race condition on the master socket creation
82+
# when running the tests in parallel. The socket is removed by the ssh client olding the master
83+
# connection when it reaches the timeout.
84+
if multiplexing and platform.system() != "Windows":
85+
opts.append('-o "ControlMaster auto"')
86+
opts.append(f'-o "ControlPath ~/.ssh/control-{os.getpid()}:%h:%p:%r"')
87+
opts.append('-o "ControlPersist 10m"')
88+
opts.append('-o "ServerAliveInterval 10s"')
89+
else:
90+
opts.append('-o "ControlMaster no"')
7791

7892
if isinstance(cmd, str):
7993
command = cmd
@@ -143,47 +157,47 @@ def _ssh(hostname_or_ip, cmd, check, simple_output, suppress_fingerprint_warning
143157
def ssh(hostname_or_ip: HostAddress, cmd: Union[str, List[str]], *, check: bool = True,
144158
simple_output: Literal[True] = True,
145159
suppress_fingerprint_warnings: bool = True, background: Literal[False] = False,
146-
decode: Literal[True] = True, options: List[str] = []) -> str:
160+
decode: Literal[True] = True, options: List[str] = [], multiplexing=True) -> str:
147161
...
148162
@overload
149163
def ssh(hostname_or_ip: HostAddress, cmd: Union[str, List[str]], *, check: bool = True,
150164
simple_output: Literal[True] = True,
151165
suppress_fingerprint_warnings: bool = True, background: Literal[False] = False,
152-
decode: Literal[False], options: List[str] = []) -> bytes:
166+
decode: Literal[False], options: List[str] = [], multiplexing=True) -> bytes:
153167
...
154168
@overload
155169
def ssh(hostname_or_ip: HostAddress, cmd: Union[str, List[str]], *, check: bool = True,
156170
simple_output: Literal[False],
157171
suppress_fingerprint_warnings: bool = True, background: Literal[False] = False,
158-
decode: bool = True, options: List[str] = []) -> SSHResult:
172+
decode: bool = True, options: List[str] = [], multiplexing=True) -> SSHResult:
159173
...
160174
@overload
161175
def ssh(hostname_or_ip: HostAddress, cmd: Union[str, List[str]], *, check: bool = True,
162176
simple_output: Literal[False],
163177
suppress_fingerprint_warnings: bool = True, background: Literal[True],
164-
decode: bool = True, options: List[str] = []) -> None:
178+
decode: bool = True, options: List[str] = [], multiplexing=True) -> None:
165179
...
166180
@overload
167181
def ssh(hostname_or_ip: HostAddress, cmd: Union[str, List[str]], *, check=True,
168182
simple_output: bool = True,
169183
suppress_fingerprint_warnings=True, background: bool = False,
170-
decode: bool = True, options: List[str] = []) \
184+
decode: bool = True, options: List[str] = [], multiplexing=True) \
171185
-> Union[str, bytes, SSHResult, None]:
172186
...
173187
def ssh(hostname_or_ip, cmd, *, check=True, simple_output=True,
174188
suppress_fingerprint_warnings=True,
175-
background=False, decode=True, options=[]):
189+
background=False, decode=True, options=[], multiplexing=True):
176190
result_or_exc = _ssh(hostname_or_ip, cmd, check, simple_output, suppress_fingerprint_warnings,
177-
background, decode, options)
191+
background, decode, options, multiplexing)
178192
if isinstance(result_or_exc, SSHCommandFailed):
179193
raise result_or_exc
180194
else:
181195
return result_or_exc
182196

183197
def ssh_with_result(hostname_or_ip, cmd, suppress_fingerprint_warnings=True,
184-
background=False, decode=True, options=[]) -> SSHResult:
198+
background=False, decode=True, options=[], multiplexing=True) -> SSHResult:
185199
result_or_exc = _ssh(hostname_or_ip, cmd, False, False, suppress_fingerprint_warnings,
186-
background, decode, options)
200+
background, decode, options, multiplexing)
187201
if isinstance(result_or_exc, SSHCommandFailed):
188202
raise result_or_exc
189203
elif isinstance(result_or_exc, SSHResult):

lib/host.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@
2929
strtobool,
3030
to_xapi_bool,
3131
wait_for,
32-
wait_for_not,
3332
)
3433
from lib.netutil import wrap_ip
3534
from lib.pif import PIF
@@ -92,38 +91,39 @@ def __str__(self):
9291
@overload
9392
def ssh(self, cmd: Union[str, List[str]], *, check: bool = True, simple_output: Literal[True] = True,
9493
suppress_fingerprint_warnings: bool = True, background: Literal[False] = False,
95-
decode: Literal[True] = True) -> str:
94+
decode: Literal[True] = True, multiplexing=True) -> str:
9695
...
9796

9897
@overload
9998
def ssh(self, cmd: Union[str, List[str]], *, check: bool = True, simple_output: Literal[True] = True,
10099
suppress_fingerprint_warnings: bool = True, background: Literal[False] = False,
101-
decode: Literal[False]) -> bytes:
100+
decode: Literal[False], multiplexing=True) -> bytes:
102101
...
103102

104103
@overload
105104
def ssh(self, cmd: Union[str, List[str]], *, check: bool = True, simple_output: Literal[False],
106105
suppress_fingerprint_warnings: bool = True, background: Literal[False] = False,
107-
decode: bool = True) -> commands.SSHResult:
106+
decode: bool = True, multiplexing=True) -> commands.SSHResult:
108107
...
109108

110109
@overload
111110
def ssh(self, cmd: Union[str, List[str]], *, check: bool = True, simple_output: bool = True,
112111
suppress_fingerprint_warnings: bool = True, background: Literal[True],
113-
decode: bool = True) -> None:
112+
decode: bool = True, multiplexing=True) -> None:
114113
...
115114

116115
@overload
117116
def ssh(self, cmd: Union[str, List[str]], *, check: bool = True, simple_output: bool = True,
118-
suppress_fingerprint_warnings: bool = True, background: bool = False, decode: bool = True) \
117+
suppress_fingerprint_warnings: bool = True, background: bool = False, decode: bool = True,
118+
multiplexing=True) \
119119
-> Union[str, bytes, commands.SSHResult, None]:
120120
...
121121

122122
def ssh(self, cmd, *, check=True, simple_output=True, suppress_fingerprint_warnings=True,
123-
background=False, decode=True):
123+
background=False, decode=True, multiplexing=True):
124124
return commands.ssh(self.hostname_or_ip, cmd, check=check, simple_output=simple_output,
125125
suppress_fingerprint_warnings=suppress_fingerprint_warnings,
126-
background=background, decode=decode)
126+
background=background, decode=decode, multiplexing=multiplexing)
127127

128128
def ssh_with_result(self, cmd) -> commands.SSHResult:
129129
# doesn't raise if the command's return is nonzero, unless there's a SSH error

0 commit comments

Comments
 (0)