multiSSH3 5.40__py3-none-any.whl → 5.42__py3-none-any.whl
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.
Potentially problematic release.
This version of multiSSH3 might be problematic. Click here for more details.
- {multiSSH3-5.40.dist-info → multiSSH3-5.42.dist-info}/METADATA +377 -376
- multiSSH3-5.42.dist-info/RECORD +6 -0
- multiSSH3.py +42 -14
- multiSSH3-5.40.dist-info/LICENSE +0 -674
- multiSSH3-5.40.dist-info/RECORD +0 -7
- {multiSSH3-5.40.dist-info → multiSSH3-5.42.dist-info}/WHEEL +0 -0
- {multiSSH3-5.40.dist-info → multiSSH3-5.42.dist-info}/entry_points.txt +0 -0
- {multiSSH3-5.40.dist-info → multiSSH3-5.42.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
multiSSH3.py,sha256=Xs05pxUY2WXSF-o98K87wKbIwNxtkKZlDwrthtzQf1Q,138824
|
|
2
|
+
multiSSH3-5.42.dist-info/METADATA,sha256=xtXbj1Q5ezo_UxPdqOp9b2vcBnkywdQY1gHeTwAARYQ,18001
|
|
3
|
+
multiSSH3-5.42.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
|
4
|
+
multiSSH3-5.42.dist-info/entry_points.txt,sha256=xi2rWWNfmHx6gS8Mmx0rZL2KZz6XWBYP3DWBpWAnnZ0,143
|
|
5
|
+
multiSSH3-5.42.dist-info/top_level.txt,sha256=tUwttxlnpLkZorSsroIprNo41lYSxjd2ASuL8-EJIJw,10
|
|
6
|
+
multiSSH3-5.42.dist-info/RECORD,,
|
multiSSH3.py
CHANGED
|
@@ -45,7 +45,7 @@ except AttributeError:
|
|
|
45
45
|
# If neither is available, use a dummy decorator
|
|
46
46
|
def cache_decorator(func):
|
|
47
47
|
return func
|
|
48
|
-
version = '5.
|
|
48
|
+
version = '5.42'
|
|
49
49
|
VERSION = version
|
|
50
50
|
|
|
51
51
|
CONFIG_FILE = '/etc/multiSSH3.config.json'
|
|
@@ -361,12 +361,13 @@ if True:
|
|
|
361
361
|
__curses_color_table = {}
|
|
362
362
|
__curses_current_color_index = 10
|
|
363
363
|
__max_connections_nofile_limit_supported = 0
|
|
364
|
+
__thread_start_delay = 0
|
|
364
365
|
if __resource_lib_available:
|
|
365
366
|
# Get the current limits
|
|
366
367
|
_, __system_nofile_limit = resource.getrlimit(resource.RLIMIT_NOFILE)
|
|
367
368
|
# Set the soft limit to the hard limit
|
|
368
369
|
resource.setrlimit(resource.RLIMIT_NOFILE, (__system_nofile_limit, __system_nofile_limit))
|
|
369
|
-
__max_connections_nofile_limit_supported = int((__system_nofile_limit -
|
|
370
|
+
__max_connections_nofile_limit_supported = int((__system_nofile_limit - 10) / 3)
|
|
370
371
|
|
|
371
372
|
# Mapping of ANSI 4-bit colors to curses colors
|
|
372
373
|
if __curses_available:
|
|
@@ -1155,7 +1156,7 @@ def __handle_writing_stream(stream,stop_event,host):
|
|
|
1155
1156
|
sentInput += 1
|
|
1156
1157
|
host.lastUpdateTime = time.time()
|
|
1157
1158
|
else:
|
|
1158
|
-
time.sleep(0.
|
|
1159
|
+
time.sleep(0.01) # sleep for 10ms
|
|
1159
1160
|
if sentInput < len(__keyPressesIn) - 1 :
|
|
1160
1161
|
eprint(f"Warning: {len(__keyPressesIn)-sentInput} key presses are not sent before the process is terminated!")
|
|
1161
1162
|
# # send the last line
|
|
@@ -1166,7 +1167,7 @@ def __handle_writing_stream(stream,stop_event,host):
|
|
|
1166
1167
|
# host.stdout.append(' $ ' + ''.join(__keyPressesIn[-1]).encode().decode().replace('\n', '↵'))
|
|
1167
1168
|
return sentInput
|
|
1168
1169
|
|
|
1169
|
-
def run_command(host, sem, timeout=60,passwds=None):
|
|
1170
|
+
def run_command(host, sem, timeout=60,passwds=None, retry_limit = 5):
|
|
1170
1171
|
'''
|
|
1171
1172
|
Run the command on the host. Will format the commands accordingly. Main execution function.
|
|
1172
1173
|
|
|
@@ -1184,6 +1185,11 @@ def run_command(host, sem, timeout=60,passwds=None):
|
|
|
1184
1185
|
global __ipmiiInterfaceIPPrefix
|
|
1185
1186
|
global _binPaths
|
|
1186
1187
|
global __DEBUG_MODE
|
|
1188
|
+
if retry_limit < 0:
|
|
1189
|
+
host.output.append('Error: Retry limit reached!')
|
|
1190
|
+
host.stderr.append('Error: Retry limit reached!')
|
|
1191
|
+
host.returncode = 1
|
|
1192
|
+
return
|
|
1187
1193
|
try:
|
|
1188
1194
|
localExtraArgs = []
|
|
1189
1195
|
|
|
@@ -1260,7 +1266,7 @@ def run_command(host, sem, timeout=60,passwds=None):
|
|
|
1260
1266
|
host.command = 'ipmitool power status'
|
|
1261
1267
|
else:
|
|
1262
1268
|
host.command = 'ipmitool '+host.command if not host.command.startswith('ipmitool ') else host.command
|
|
1263
|
-
run_command(host,sem,timeout,passwds)
|
|
1269
|
+
run_command(host,sem,timeout,passwds,retry_limit=retry_limit - 1)
|
|
1264
1270
|
return
|
|
1265
1271
|
else:
|
|
1266
1272
|
host.output.append('Ipmitool not found on the local machine! Please install ipmitool to use ipmi mode.')
|
|
@@ -1279,7 +1285,7 @@ def run_command(host, sem, timeout=60,passwds=None):
|
|
|
1279
1285
|
host.stderr.append('shell not found on the local machine! Using ssh localhost instead...')
|
|
1280
1286
|
host.shell = False
|
|
1281
1287
|
host.name = 'localhost'
|
|
1282
|
-
run_command(host,sem,timeout,passwds)
|
|
1288
|
+
run_command(host,sem,timeout,passwds,retry_limit=retry_limit - 1)
|
|
1283
1289
|
else:
|
|
1284
1290
|
if host.files:
|
|
1285
1291
|
if host.scp:
|
|
@@ -1358,6 +1364,7 @@ def run_command(host, sem, timeout=60,passwds=None):
|
|
|
1358
1364
|
# Monitor the subprocess and terminate it after the timeout
|
|
1359
1365
|
host.lastUpdateTime = time.time()
|
|
1360
1366
|
timeoutLineAppended = False
|
|
1367
|
+
sleep_interval = 1.0e-8 # 10 nanoseconds
|
|
1361
1368
|
while proc.poll() is None: # while the process is still running
|
|
1362
1369
|
if timeout > 0:
|
|
1363
1370
|
if time.time() - host.lastUpdateTime > timeout:
|
|
@@ -1365,7 +1372,6 @@ def run_command(host, sem, timeout=60,passwds=None):
|
|
|
1365
1372
|
host.output.append('Timeout!')
|
|
1366
1373
|
proc.send_signal(signal.SIGINT)
|
|
1367
1374
|
time.sleep(0.1)
|
|
1368
|
-
|
|
1369
1375
|
proc.terminate()
|
|
1370
1376
|
break
|
|
1371
1377
|
elif time.time() - host.lastUpdateTime > max(1, timeout // 2):
|
|
@@ -1389,7 +1395,11 @@ def run_command(host, sem, timeout=60,passwds=None):
|
|
|
1389
1395
|
time.sleep(0.1)
|
|
1390
1396
|
proc.terminate()
|
|
1391
1397
|
break
|
|
1392
|
-
time.sleep(
|
|
1398
|
+
time.sleep(sleep_interval) # avoid busy-waiting
|
|
1399
|
+
if sleep_interval < 0.001:
|
|
1400
|
+
sleep_interval *= 2
|
|
1401
|
+
elif sleep_interval < 0.01:
|
|
1402
|
+
sleep_interval *= 1.1
|
|
1393
1403
|
stdin_stop_event.set()
|
|
1394
1404
|
# Wait for output processing to complete
|
|
1395
1405
|
stdout_thread.join(timeout=1)
|
|
@@ -1420,6 +1430,16 @@ def run_command(host, sem, timeout=60,passwds=None):
|
|
|
1420
1430
|
if host.stderr:
|
|
1421
1431
|
# filter out the error messages that we want to ignore
|
|
1422
1432
|
host.stderr = [line for line in host.stderr if not __ERROR_MESSAGES_TO_IGNORE_REGEX.search(line)]
|
|
1433
|
+
# except os error too many open files
|
|
1434
|
+
except OSError as e:
|
|
1435
|
+
if e.errno == 24: # Errno 24 corresponds to "Too many open files"
|
|
1436
|
+
host.output.append("Warning: Too many open files. retrying...")
|
|
1437
|
+
# Handle the error, e.g., clean up, retry logic, or exit
|
|
1438
|
+
time.sleep(0.1)
|
|
1439
|
+
run_command(host,sem,timeout,passwds,retry_limit=retry_limit - 1)
|
|
1440
|
+
else:
|
|
1441
|
+
# Re-raise the exception if it's not the specific one
|
|
1442
|
+
raise
|
|
1423
1443
|
except Exception as e:
|
|
1424
1444
|
import traceback
|
|
1425
1445
|
host.stderr.extend(str(e).split('\n'))
|
|
@@ -1436,7 +1456,7 @@ def run_command(host, sem, timeout=60,passwds=None):
|
|
|
1436
1456
|
host.ipmi = False
|
|
1437
1457
|
host.interface_ip_prefix = None
|
|
1438
1458
|
host.command = 'ipmitool '+host.command if not host.command.startswith('ipmitool ') else host.command
|
|
1439
|
-
run_command(host,sem,timeout,passwds)
|
|
1459
|
+
run_command(host,sem,timeout,passwds,retry_limit=retry_limit - 1)
|
|
1440
1460
|
# If transfering files, we will try again using scp if rsync connection is not successful
|
|
1441
1461
|
if host.files and not host.scp and not useScp and host.returncode != 0 and host.stderr:
|
|
1442
1462
|
host.stderr = []
|
|
@@ -1445,7 +1465,7 @@ def run_command(host, sem, timeout=60,passwds=None):
|
|
|
1445
1465
|
if __DEBUG_MODE:
|
|
1446
1466
|
host.stderr.append('Rsync connection failed! Trying SCP connection...')
|
|
1447
1467
|
host.scp = True
|
|
1448
|
-
run_command(host,sem,timeout,passwds)
|
|
1468
|
+
run_command(host,sem,timeout,passwds,retry_limit=retry_limit - 1)
|
|
1449
1469
|
|
|
1450
1470
|
# ------------ Start Threading Block ----------------
|
|
1451
1471
|
def start_run_on_hosts(hosts, timeout=60,password=None,max_connections=4 * os.cpu_count()):
|
|
@@ -1461,12 +1481,14 @@ def start_run_on_hosts(hosts, timeout=60,password=None,max_connections=4 * os.cp
|
|
|
1461
1481
|
Returns:
|
|
1462
1482
|
list: A list of threads that get started
|
|
1463
1483
|
'''
|
|
1484
|
+
global __thread_start_delay
|
|
1464
1485
|
if len(hosts) == 0:
|
|
1465
1486
|
return []
|
|
1466
1487
|
sem = threading.Semaphore(max_connections) # Limit concurrent SSH sessions
|
|
1467
1488
|
threads = [threading.Thread(target=run_command, args=(host, sem,timeout,password), daemon=True) for host in hosts]
|
|
1468
1489
|
for thread in threads:
|
|
1469
1490
|
thread.start()
|
|
1491
|
+
time.sleep(__thread_start_delay)
|
|
1470
1492
|
return threads
|
|
1471
1493
|
|
|
1472
1494
|
# ------------ Display Block ----------------
|
|
@@ -2240,7 +2262,7 @@ def processRunOnHosts(timeout, password, max_connections, hosts, returnUnfinishe
|
|
|
2240
2262
|
if not returnUnfinished:
|
|
2241
2263
|
# wait until all hosts have a return code
|
|
2242
2264
|
while any([host.returncode is None for host in hosts]):
|
|
2243
|
-
time.sleep(0.
|
|
2265
|
+
time.sleep(0.01)
|
|
2244
2266
|
for thread in threads:
|
|
2245
2267
|
thread.join(timeout=3)
|
|
2246
2268
|
# update the unavailable hosts and global unavailable hosts
|
|
@@ -2421,6 +2443,8 @@ def run_command_on_hosts(hosts = DEFAULT_HOSTS,commands = None,oneonone = DEFAUL
|
|
|
2421
2443
|
global _no_env
|
|
2422
2444
|
global _emo
|
|
2423
2445
|
global __DEBUG_MODE
|
|
2446
|
+
global __thread_start_delay
|
|
2447
|
+
global __max_connections_nofile_limit_supported
|
|
2424
2448
|
_emo = False
|
|
2425
2449
|
_no_env = no_env
|
|
2426
2450
|
if os.path.exists(os.path.join(tempfile.gettempdir(),'__multiSSH3_UNAVAILABLE_HOSTS.csv')):
|
|
@@ -2456,9 +2480,13 @@ def run_command_on_hosts(hosts = DEFAULT_HOSTS,commands = None,oneonone = DEFAUL
|
|
|
2456
2480
|
max_connections = __max_connections_nofile_limit_supported
|
|
2457
2481
|
elif max_connections < 0:
|
|
2458
2482
|
max_connections = (-max_connections) * os.cpu_count()
|
|
2459
|
-
if __max_connections_nofile_limit_supported > 0
|
|
2460
|
-
|
|
2461
|
-
|
|
2483
|
+
if __max_connections_nofile_limit_supported > 0:
|
|
2484
|
+
if max_connections > __max_connections_nofile_limit_supported:
|
|
2485
|
+
eprint(f"Warning: The number of maximum connections {max_connections} is larger than estimated limit {__max_connections_nofile_limit_supported} from ulimit nofile limit {__system_nofile_limit}, setting the maximum connections to {__max_connections_nofile_limit_supported}.")
|
|
2486
|
+
max_connections = __max_connections_nofile_limit_supported
|
|
2487
|
+
if max_connections > __max_connections_nofile_limit_supported * 2:
|
|
2488
|
+
# we need to throttle thread start to avoid hitting the nofile limit
|
|
2489
|
+
__thread_start_delay = 0.001
|
|
2462
2490
|
if not commands:
|
|
2463
2491
|
commands = []
|
|
2464
2492
|
else:
|