Skip to content

Commit 54d6105

Browse files
authored
Merge pull request #30 from romecode/main
SSH fixture reliability patch
2 parents 88af7b1 + e18de12 commit 54d6105

File tree

2 files changed

+72
-40
lines changed

2 files changed

+72
-40
lines changed

src/pytest_netdut/factories.py

Lines changed: 71 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# -------------------------------------------------------------------------------
2-
# - Copyright (c) 2021 Arista Networks, Inc. All rights reserved.
2+
# - Copyright (c) 2021-2024 Arista Networks, Inc. All rights reserved.
33
# -------------------------------------------------------------------------------
44
# - Author:
55
@@ -19,6 +19,7 @@
1919
import pytest
2020
from packaging import version
2121
from .wrappers import CLI, xapi
22+
from functools import wraps
2223

2324
logger = logging.getLogger(__name__)
2425

@@ -108,36 +109,39 @@ def skipper(node):
108109
min_chg_num = None
109110

110111
# Restrict SKUs
111-
for marker in node.iter_markers():
112-
if marker.name in {"mos", "eos"}:
113-
allowed_os.add(marker.name)
114-
min_chg_num = marker.kwargs.get("min_change_number", None)
115-
min_version = marker.kwargs.get("min_version", None)
116-
117-
elif marker.name == "only_device_type":
118-
pattern = marker.args[0]
119-
sku = request.getfixturevalue(f"{name}_sku")
120-
if not re.search(pattern, sku):
121-
pytest.skip(f"Skipped on this SKU: {sku} (only runs on {pattern})")
122-
123-
elif marker.name == "skip_device_type":
124-
pattern = marker.args[0]
125-
sku = request.getfixturevalue(f"{name}_sku")
126-
if re.search(pattern, sku):
127-
pytest.skip(f"Skipped on this SKU: {sku}")
128-
129-
if allowed_os:
130-
dut_ssh = request.getfixturevalue(f"{name}_ssh")
131-
dut_os_version = request.getfixturevalue(f"{name}_os_version")
132-
if dut_ssh.cli_flavor not in allowed_os:
133-
pytest.skip(f"Cannot run on platform {dut_ssh.cli_flavor}")
134-
if min_version or min_chg_num:
135-
# matches the pattern X.XX.X.XX with digits only
136-
release, change_number = parse_version(dut_os_version)
137-
if min_chg_num:
138-
version_skipper(change_number, min_chg_num)
139-
else:
140-
version_skipper(release, min_version)
112+
try:
113+
for marker in node.iter_markers():
114+
if marker.name in {"mos", "eos"}:
115+
allowed_os.add(marker.name)
116+
min_chg_num = marker.kwargs.get("min_change_number", None)
117+
min_version = marker.kwargs.get("min_version", None)
118+
119+
elif marker.name == "only_device_type":
120+
pattern = marker.args[0]
121+
sku = request.getfixturevalue(f"{name}_sku")
122+
if not re.search(pattern, sku):
123+
pytest.skip(f"Skipped on this SKU: {sku} (only runs on {pattern})")
124+
125+
elif marker.name == "skip_device_type":
126+
pattern = marker.args[0]
127+
sku = request.getfixturevalue(f"{name}_sku")
128+
if re.search(pattern, sku):
129+
pytest.skip(f"Skipped on this SKU: {sku}")
130+
131+
if allowed_os:
132+
dut_ssh = request.getfixturevalue(f"{name}_ssh")
133+
dut_os_version = request.getfixturevalue(f"{name}_os_version")
134+
if dut_ssh.cli_flavor not in allowed_os:
135+
pytest.skip(f"Cannot run on platform {dut_ssh.cli_flavor}")
136+
if min_version or min_chg_num:
137+
# matches the pattern X.XX.X.XX with digits only
138+
release, change_number = parse_version(dut_os_version)
139+
if min_chg_num:
140+
version_skipper(change_number, min_chg_num)
141+
else:
142+
version_skipper(release, min_version)
143+
except pytest.FixtureLookupError:
144+
logger.debug("Pytest fixture recursion caught.")
141145

142146
return skipper
143147

@@ -196,6 +200,32 @@ def _softened(dut_ssh):
196200
return _softened
197201

198202

203+
def retry(limit):
204+
def decorator(fn):
205+
@wraps(fn)
206+
def wrapper(*args, **kwargs):
207+
attempt, limit_ = 0, limit
208+
result = None
209+
while attempt < limit_:
210+
try:
211+
result = fn(*args, **kwargs)
212+
break
213+
except Exception as e:
214+
attempt += 1
215+
logging.error(
216+
"An error occurred in %s, attempt %d of %d: %s",
217+
fn.__name__,
218+
attempt,
219+
limit_,
220+
e,
221+
)
222+
return result
223+
224+
return wrapper
225+
226+
return decorator
227+
228+
199229
class _CLI_wrapper:
200230
_cli = None
201231

@@ -213,6 +243,7 @@ def close_and_re_init(self):
213243
def close(self, *args, **kwargs):
214244
return self._cli.close(*args, **kwargs)
215245

246+
@retry(3)
216247
def login(self, *args, **kwargs):
217248
return self._cli.login(*args, **kwargs)
218249

@@ -294,15 +325,16 @@ def args(self):
294325
def create_ssh_fixture(name):
295326
@pytest.fixture(scope="session", name=f"{name}_ssh")
296327
def _ssh(request):
328+
ssh = _CLI_wrapper(f"ssh://{request.getfixturevalue(f'{name}_hostname')}")
329+
# do not break the fixture if SSH failed
330+
# pass the failure into the test
297331
try:
298-
ssh = _CLI_wrapper(f"ssh://{request.getfixturevalue(f'{name}_hostname')}")
299-
except Exception as exc:
300-
logging.error("Failed to create ssh fixture and log in")
301-
raise exc
302-
if ssh.cli_flavor == "mos":
303-
ssh.sendcmd("enable")
304-
# Disable pagination
305-
ssh.sendcmd("terminal length 0")
332+
if ssh.cli_flavor == "mos":
333+
ssh.sendcmd("enable")
334+
# Disable pagination
335+
ssh.sendcmd("terminal length 0")
336+
except AttributeError:
337+
pass
306338
yield ssh
307339

308340
return _ssh

src/pytest_netdut/px.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ def __init__( # pylint: disable=dangerous-default-value,too-many-arguments
150150
self.cmd = o.scheme
151151
if self.cmd == "ssh":
152152
self.args = ["%s@%s" % (username, o.hostname)]
153-
self.args += ["-o LogLevel ERROR"]
153+
self.args += ["-o LogLevel DEBUG3"]
154154
self.args += ["-o ConnectionAttempts 10"]
155155
self.args += ["-o StrictHostKeyChecking no"]
156156
self.args += ["-o UserKnownHostsFile /dev/null"]

0 commit comments

Comments
 (0)