Created
October 29, 2023 16:31
-
-
Save andrewrcollins/49f787c1a2b1df7e3fbb5e9d56b2dae0 to your computer and use it in GitHub Desktop.
Implements connection pool for SSH connections.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/sh | |
# | |
# ssh-pool.sh | |
# | |
# Implements connection pool for SSH connections. | |
# | |
##### start script name | |
if [ -z "${script}" ] | |
then | |
script=ssh-pool.sh | |
fi | |
##### end script name | |
# get script start time | |
start_dt=$( date "+%s" ) | |
# make temporary folder | |
tmp=/tmp/tmp.${script}.$$ | |
mkdir -m 777 ${tmp} >> /dev/null 2>&1 | |
# log messages to terminal and file | |
log_file=/tmp/${script}.log | |
log() { | |
now=$( date "+[%Y-%m-%d %H:%M:%S]" ) | |
pid=[$$] | |
message="${1}" | |
echo ${now} ${pid} "${message}" | tee -a ${log_file} | |
} | |
passthru() { | |
tee -a ${log_file} | |
} | |
# logger pipe and pid | |
logger_pipe=${tmp}/logger | |
logger_pid_file=${tmp}/logger.pid | |
# make named pipe (FIFO) | |
mkfifo ${logger_pipe} | |
# keep pipe open using sleep | |
sleep 1d > ${logger_pipe} & | |
# save sleep pid | |
echo $! > ${logger_pid_file} | |
# run logger | |
while read message | |
do | |
# indent message | |
log " ${message}" | |
done < ${logger_pipe} & | |
# cleanup | |
finished="no" | |
cleanup() { | |
if [ "${finished}" = "no" ] | |
then | |
normal_exit=${1} | |
# kill logger | |
xargs -a ${logger_pid_file} kill -KILL >> /dev/null 2>&1 | |
# remove logger pid and pipe | |
rm -f ${logger_pid_file} ${logger_pipe} | |
##### start script cleanup | |
teardown_connection_pool | |
log "wait for connections to finish ..." | |
wait | |
cleanup_connection_pool | |
##### end script cleanup | |
# remove temporary folder | |
rm -rf ${tmp} | |
# get script end time | |
end_dt=$( date "+%s" ) | |
seconds=$(( ${end_dt} - ${start_dt} )) | |
h=$(( ${seconds} / 3600 )) | |
m=$(( ( ${seconds} % 3600 ) / 60 )) | |
s=$(( ${seconds} % 60 )) | |
if [ ${h} -gt 0 ] | |
then | |
if [ ${h} -eq 1 ] | |
then | |
hour="1 hour" | |
else | |
hour="${h} hours" | |
fi | |
fi | |
if [ ${m} -gt 0 ] | |
then | |
if [ ${m} -eq 1 ] | |
then | |
minute="1 minute" | |
else | |
minute="${m} minutes" | |
fi | |
fi | |
if [ ${s} -gt 0 ] | |
then | |
if [ ${s} -eq 1 ] | |
then | |
second="1 second" | |
else | |
second="${s} seconds" | |
fi | |
fi | |
# use xargs to trim leading, trailing, and internal whitespace | |
runtime=$( echo ${hour} ${minute} ${second} | xargs ) | |
if [ -z "${runtime}" ] | |
then | |
runtime="0 seconds" | |
fi | |
# display script runtime | |
log "script runtime: ${script} ${runtime}" | |
finished="yes" | |
# handle abnormal exit | |
if [ "${normal_exit}" = "no" ] | |
then | |
# indicate failure | |
exit 1 | |
fi | |
fi | |
} | |
# abnormal cleanup | |
# normal exit = no | |
trap "cleanup no" INT | |
trap "cleanup no" TERM | |
# normal cleanup | |
# normal exit = yes | |
trap "cleanup yes" EXIT | |
# display running script | |
log "running script: ${script}" | |
##### start script action | |
# SSH identity file | |
ssh_identity_file="THE_SSH_KEY" | |
# SSH user | |
ssh_user="THE_SSH_USER" | |
# SSH hostname | |
ssh_hostname="THE_SSH_HOSTNAME" | |
# connections | |
connections=10 | |
ssh="${ssh_user}@${ssh_hostname}" | |
setup_connection_pool() { | |
log "setup connection pool ..." | |
# connections = 1, 2, 3, ..., connections | |
all_list=$( seq 1 ${connections} ) | |
# timeout, 300 seconds = 5 minutes | |
timeout=300 | |
# timeout = 1, 2, 3, ..., timeout | |
timeout_list=$( seq 1 ${timeout} ) | |
# internal connection id | |
connection_id=0 | |
# setup all connections | |
set -- ${all_list} | |
for index | |
do | |
config_file=${tmp}/ssh_config.${index} | |
master_config_file=${tmp}/ssh_config.master.${index} | |
socket_file=${tmp}/socket.${index} | |
ready_file=${tmp}/ready.${index} | |
# just in case | |
rm -f ${config_file} ${master_config_file} ${socket_file} ${ready_file} | |
# config file | |
cat > ${config_file} <<config_file | |
User ${ssh_user} | |
HostName ${ssh_hostname} | |
IdentityFile ${ssh_identity_file} | |
BatchMode yes | |
ControlPath ${socket_file} | |
LogLevel QUIET | |
config_file | |
# master config file | |
cat > ${master_config_file} <<master_config_file | |
User ${ssh_user} | |
HostName ${ssh_hostname} | |
IdentityFile ${ssh_identity_file} | |
BatchMode yes | |
ControlPath ${socket_file} | |
LogLevel QUIET | |
ControlMaster yes | |
ControlPersist yes | |
master_config_file | |
# open SSH connection, creates socket file | |
ssh -f -F ${master_config_file} ${ssh} "sleep 1d" >> /dev/null 2>&1 | |
# connection ready | |
touch ${ready_file} | |
done | |
} | |
teardown_connection_pool() { | |
log "teardown connection pool ..." | |
# teardown all connections | |
set -- ${all_list} | |
for index | |
do | |
socket_file=${tmp}/socket.${index} | |
config_file=${tmp}/ssh_config.${index} | |
ready_file=${tmp}/ready.${index} | |
if [ -S ${socket_file} ] | |
then | |
# "stop" = request master to stop accepting further multiplexing requests | |
ssh -F ${config_file} -O "stop" ${ssh} >> /dev/null 2>&1 | |
fi | |
if [ -f ${ready_file} ] | |
then | |
rm -f ${ready_file} | |
fi | |
done | |
} | |
cleanup_connection_pool() { | |
log "cleanup connection pool ..." | |
# cleanup all connections | |
set -- ${all_list} | |
for index | |
do | |
socket_file=${tmp}/socket.${index} | |
config_file=${tmp}/ssh_config.${index} | |
if [ -S ${socket_file} ] | |
then | |
# "exit" = request master to exit | |
ssh -F ${config_file} -O "exit" ${ssh} >> /dev/null 2>&1 | |
# remove socket file | |
rm -f ${socket_file} | |
fi | |
# remove config file | |
rm -f ${config_file} | |
done | |
} | |
get_connection() { | |
ready=-1 | |
# timeout, 300 seconds = 5 minutes | |
set -- ${timeout_list} | |
for time | |
do | |
set -- ${all_list} | |
for index | |
do | |
ready_file=${tmp}/ready.${index} | |
if [ -f ${ready_file} ] | |
then | |
rm -f ${ready_file} | |
ready=${index} | |
break | |
fi | |
done | |
if [ ${ready} -lt 0 ] | |
then | |
sleep 1 | |
else | |
break | |
fi | |
done | |
connection=${ready} | |
# increment internal connection id | |
connection_id=$(( connection_id + 1 )) | |
} | |
ssh_command() { | |
command=${1} | |
output=${2} | |
background=${3} | |
before_message="${4}" | |
after_message="${5}" | |
# get connection | |
get_connection | |
if [ -n "${before_message}" ] | |
then | |
# log before message | |
log "${before_message}" | |
fi | |
config_file=${tmp}/ssh_config.${connection} | |
ready_file=${tmp}/ready.${connection} | |
if [ "${background}" = "yes" ] | |
then | |
# run in background | |
{ | |
ssh -F ${config_file} ${ssh_hostname} ${command} 2>> /dev/null > ${output} ; | |
if [ -n "${after_message}" ] | |
then | |
# log after message | |
log "${after_message}" ; | |
fi | |
# touch ready file | |
touch ${ready_file} ; | |
} & | |
else | |
# run in foreground | |
ssh -F ${config_file} ${ssh_hostname} ${command} 2>> /dev/null > ${output} | |
if [ -n "${after_message}" ] | |
then | |
# log after message | |
log "${after_message}" | |
fi | |
# touch ready file | |
touch ${ready_file} | |
fi | |
} | |
##### end script action |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment