multiSSH3 6.24__tar.gz → 6.25__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {multissh3-6.24 → multissh3-6.25}/PKG-INFO +1 -1
- {multissh3-6.24 → multissh3-6.25}/multiSSH3.egg-info/PKG-INFO +1 -1
- {multissh3-6.24 → multissh3-6.25}/multiSSH3.egg-info/SOURCES.txt +1 -0
- {multissh3-6.24 → multissh3-6.25}/multiSSH3.egg-info/dependency_links.txt +0 -0
- {multissh3-6.24 → multissh3-6.25}/multiSSH3.egg-info/entry_points.txt +0 -0
- {multissh3-6.24 → multissh3-6.25}/multiSSH3.egg-info/requires.txt +0 -0
- {multissh3-6.24 → multissh3-6.25}/multiSSH3.egg-info/top_level.txt +0 -0
- {multissh3-6.24 → multissh3-6.25}/multiSSH3.py +55 -2
- multissh3-6.25/tests/test_ipmi_lockout_wait.py +32 -0
- {multissh3-6.24 → multissh3-6.25}/README.md +0 -0
- {multissh3-6.24 → multissh3-6.25}/setup.cfg +0 -0
- {multissh3-6.24 → multissh3-6.25}/setup.py +0 -0
- {multissh3-6.24 → multissh3-6.25}/tests/test_cli_branches.py +0 -0
- {multissh3-6.24 → multissh3-6.25}/tests/test_cli_processing.py +0 -0
- {multissh3-6.24 → multissh3-6.25}/tests/test_color_helpers.py +0 -0
- {multissh3-6.24 → multissh3-6.25}/tests/test_compact_hostnames.py +0 -0
- {multissh3-6.24 → multissh3-6.25}/tests/test_config_cli.py +0 -0
- {multissh3-6.24 → multissh3-6.25}/tests/test_config_validate.py +0 -0
- {multissh3-6.24 → multissh3-6.25}/tests/test_curses_branches.py +0 -0
- {multissh3-6.24 → multissh3-6.25}/tests/test_curses_event_loop.py +0 -0
- {multissh3-6.24 → multissh3-6.25}/tests/test_curses_tui.py +0 -0
- {multissh3-6.24 → multissh3-6.25}/tests/test_expand_hostnames.py +0 -0
- {multissh3-6.24 → multissh3-6.25}/tests/test_harness_smoke.py +0 -0
- {multissh3-6.24 → multissh3-6.25}/tests/test_helpers_extra.py +0 -0
- {multissh3-6.24 → multissh3-6.25}/tests/test_host.py +0 -0
- {multissh3-6.24 → multissh3-6.25}/tests/test_main_lifecycle.py +0 -0
- {multissh3-6.24 → multissh3-6.25}/tests/test_ordered_multiset.py +0 -0
- {multissh3-6.24 → multissh3-6.25}/tests/test_output_merge.py +0 -0
- {multissh3-6.24 → multissh3-6.25}/tests/test_run_command.py +0 -0
- {multissh3-6.24 → multissh3-6.25}/tests/test_run_command_branches.py +0 -0
- {multissh3-6.24 → multissh3-6.25}/tests/test_run_on_hosts.py +0 -0
- {multissh3-6.24 → multissh3-6.25}/tests/test_run_on_hosts_modes.py +0 -0
- {multissh3-6.24 → multissh3-6.25}/tests/test_runtime_branch_matrix.py +0 -0
- {multissh3-6.24 → multissh3-6.25}/tests/test_smoke_optional.py +0 -0
- {multissh3-6.24 → multissh3-6.25}/tests/test_unavailable_hosts.py +0 -0
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -36,7 +36,7 @@ from itertools import count, product
|
|
|
36
36
|
from pprint import pformat
|
|
37
37
|
|
|
38
38
|
|
|
39
|
-
version = '6.
|
|
39
|
+
version = '6.25'
|
|
40
40
|
VERSION = version
|
|
41
41
|
__version__ = version
|
|
42
42
|
COMMIT_DATE = '2026-08-11'
|
|
@@ -1861,6 +1861,56 @@ def __handle_writing_stream(stream,stop_event,host):
|
|
|
1861
1861
|
def _emfile_backoff_seconds(attempt):
|
|
1862
1862
|
return min(0.1 * (2 ** attempt), 1.0)
|
|
1863
1863
|
|
|
1864
|
+
def _ipmi_failure_should_wait(host):
|
|
1865
|
+
'''Return True when the next IPMI password should wait out retry_lockout_time.
|
|
1866
|
+
|
|
1867
|
+
The wait protects a controller that actually rejected a login. Timeouts, routing
|
|
1868
|
+
failures, and a socket that never opened did not reach a BMC, so they skip it.
|
|
1869
|
+
An unrecognized BMC error still waits.
|
|
1870
|
+
'''
|
|
1871
|
+
chunks = []
|
|
1872
|
+
for attr in ('stdout', 'stderr', 'output'):
|
|
1873
|
+
value = getattr(host, attr, None) or []
|
|
1874
|
+
chunks.extend(str(line) for line in value)
|
|
1875
|
+
text = '\n'.join(chunks).lower()
|
|
1876
|
+
auth_markers = (
|
|
1877
|
+
'insufficient resources',
|
|
1878
|
+
'rakp',
|
|
1879
|
+
'unauthorized',
|
|
1880
|
+
'authentication failed',
|
|
1881
|
+
'invalid password',
|
|
1882
|
+
'invalid user',
|
|
1883
|
+
'bad password',
|
|
1884
|
+
'account locked',
|
|
1885
|
+
'user locked',
|
|
1886
|
+
'session limit',
|
|
1887
|
+
'privilege level',
|
|
1888
|
+
)
|
|
1889
|
+
if any(marker in text for marker in auth_markers):
|
|
1890
|
+
return True
|
|
1891
|
+
no_contact_markers = (
|
|
1892
|
+
'could not open socket',
|
|
1893
|
+
'no route to host',
|
|
1894
|
+
'network is unreachable',
|
|
1895
|
+
'connection timed out',
|
|
1896
|
+
'connection refused',
|
|
1897
|
+
'host is down',
|
|
1898
|
+
'destination host unreachable',
|
|
1899
|
+
'name or service not known',
|
|
1900
|
+
'could not resolve',
|
|
1901
|
+
'permission denied',
|
|
1902
|
+
'unable to establish lan session',
|
|
1903
|
+
'unable to establish ipmi v1.5',
|
|
1904
|
+
'timeout!',
|
|
1905
|
+
)
|
|
1906
|
+
if any(marker in text for marker in no_contact_markers):
|
|
1907
|
+
return False
|
|
1908
|
+
if getattr(host, 'returncode', None) == 124:
|
|
1909
|
+
return False
|
|
1910
|
+
if 'unable to establish ipmi v2' in text or 'rmcp+' in text:
|
|
1911
|
+
return True
|
|
1912
|
+
return True
|
|
1913
|
+
|
|
1864
1914
|
def run_command(host, sem, timeout=60,passwds=None, retry_limit = 7 + len(DEFAULT_IPMI_DEFINITIONS),ipmi_definitions_list = ...):
|
|
1865
1915
|
'''
|
|
1866
1916
|
Run the command on the host. Will format the commands accordingly. Main execution function.
|
|
@@ -2199,6 +2249,7 @@ def run_command(host, sem, timeout=60,passwds=None, retry_limit = 7 + len(DEFAUL
|
|
|
2199
2249
|
retry_limit -= 1
|
|
2200
2250
|
# If using ipmi, we will try again using next ipmi definition if ipmi connection is not successful
|
|
2201
2251
|
if host.ipmi and host.returncode != 0:
|
|
2252
|
+
should_wait = _ipmi_failure_should_wait(host)
|
|
2202
2253
|
host.returncode = None
|
|
2203
2254
|
host.output.append('IPMI connection failed! Trying next ipmi definition...')
|
|
2204
2255
|
if __DEBUG_MODE:
|
|
@@ -2209,11 +2260,13 @@ def run_command(host, sem, timeout=60,passwds=None, retry_limit = 7 + len(DEFAUL
|
|
|
2209
2260
|
if ipmi_definition.get('retry_lockout_time'):
|
|
2210
2261
|
try:
|
|
2211
2262
|
sleep_seconds = int(ipmi_definition.get('retry_lockout_time'))
|
|
2212
|
-
if sleep_seconds >= 0:
|
|
2263
|
+
if sleep_seconds >= 0 and should_wait:
|
|
2213
2264
|
host.output.append(f'Sleeping for {sleep_seconds} seconds...')
|
|
2214
2265
|
if __DEBUG_MODE:
|
|
2215
2266
|
host.stderr.append(f'Sleeping for {sleep_seconds} seconds...')
|
|
2216
2267
|
time.sleep(sleep_seconds)
|
|
2268
|
+
elif sleep_seconds >= 0:
|
|
2269
|
+
host.output.append('Skipping IPMI lockout wait; the controller was not reached.')
|
|
2217
2270
|
except Exception as e:
|
|
2218
2271
|
host.output.append(f'Error: Failed to sleep for {ipmi_definition.get("retry_lockout_time")} seconds! {e}')
|
|
2219
2272
|
if __DEBUG_MODE:
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import multiSSH3
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class _Host:
|
|
5
|
+
def __init__(self, stderr, returncode=1):
|
|
6
|
+
self.stdout = []
|
|
7
|
+
self.stderr = stderr
|
|
8
|
+
self.output = []
|
|
9
|
+
self.returncode = returncode
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def test_ipmi_lockout_wait_skips_when_bmc_was_not_reached():
|
|
13
|
+
cases = [
|
|
14
|
+
["Could not open socket!", "Error: Unable to establish IPMI v2 / RMCP+ session"],
|
|
15
|
+
["Error: Unable to establish LAN session", "Error: Unable to establish IPMI v1.5 / RMCP session"],
|
|
16
|
+
["Timeout!"],
|
|
17
|
+
["ssh: connect to host 10.251.5.57 port 22: Connection timed out"],
|
|
18
|
+
["Permission denied, please try again."],
|
|
19
|
+
]
|
|
20
|
+
for stderr in cases:
|
|
21
|
+
assert multiSSH3._ipmi_failure_should_wait(_Host(stderr, 124 if stderr == ["Timeout!"] else 1)) is False
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def test_ipmi_lockout_wait_runs_after_bmc_auth_failure():
|
|
25
|
+
cases = [
|
|
26
|
+
["RAKP 2 HMAC is invalid", "Error: Unable to establish IPMI v2 / RMCP+ session"],
|
|
27
|
+
["RAKP 2 message indicates an error : insufficient resources for session"],
|
|
28
|
+
["Error: Unable to establish IPMI v2 / RMCP+ session"],
|
|
29
|
+
["Set Session Privilege Level to ADMINISTRATOR failed"],
|
|
30
|
+
]
|
|
31
|
+
for stderr in cases:
|
|
32
|
+
assert multiSSH3._ipmi_failure_should_wait(_Host(stderr)) is True
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|