distribute.sh 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. #!/bin/sh
  2. readonly DEFAULT_OUT_FMT="%s.out"
  3. readonly CMD_FEED="$(mktemp -u /tmp/distributer-XXX.fifo)"
  4. readonly PROC_BUFFER=10
  5. readonly PROCS_PER_SERVER=6
  6. readonly MAX_PROCS=$(expr $(ulimit -u) / $PROCS_PER_SERVER - $PROC_BUFFER)
  7. readonly MAX_CMD_SIZE=512
  8. readonly READER="$(dirname $(realpath $0))/fixed-read"
  9. readonly TIMEOUT=600 # 10 minutes
  10. readonly MK_DIR_CMD="if [ ! -d \"$(dirname "$OUT_FMT")\" ]; then
  11. mkdir -p \"$(dirname "$OUT_FMT")\" > /dev/null
  12. fi"
  13. readonly S_SPACE="s/[[:space:].\/]/_/g"
  14. readonly R_DASH="s/-//g"
  15. readonly S_QUOTE="s/'/\\\\'/g"
  16. help() {
  17. printf "Usage: $(basename $0) [options] cmd_list server_list [out_file_fmt]\n" >&2
  18. printf " cmd_list: A text file containing a list of commands to run.\n" >&2
  19. printf " server_list: A text file containing a list of servers to\n" >&2
  20. printf " connect to and run commands on.\n" >&2
  21. printf " out_file_fmt: File name format to write the output of each\n" >&2
  22. printf " command to (default: %s).\n\n" "$DEFAULT_OUT_FMT" >&2
  23. printf "Options:.\n" >&2
  24. printf " -h -?: Print this help mesage and exit. \n" >&2
  25. printf " -d destination: Move files to destination as space becomes\n" >&2
  26. printf " unavailable. (Requires quota to be available)\n" >&2
  27. printf " -c processor: If present, instead of directly executing\n\n" >&2
  28. printf " cmd_list, will launch a processor on each server and \n" >&2
  29. printf " distribute cmds to the processors.\n\n" >&2
  30. printf "All commands will be allocated to the first available server.\n" >&2
  31. printf "Each command must be valid on every server.\n" >&2
  32. printf "The output will be saved to a text file on the remote systems.\n" >&2
  33. exit $1
  34. }
  35. clean_server() {
  36. cmd_feed="$1"
  37. rm "$cmd_feed"
  38. exit 2
  39. }
  40. run_processor() {
  41. server="$1"
  42. loop="$2"
  43. out_file="$3"
  44. {
  45. # Test that ssh is ready to receive.
  46. printf "echo\n"
  47. read -t $TIMEOUT line < "$loop"
  48. printf "Running processor: '$PROCESSOR'\n" "$out_file" >&2
  49. printf "$PROCESSOR\n" "$out_file"
  50. # Block until command completes
  51. while read -t $TIMEOUT line < "$loop" > /dev/null && \
  52. cmd=$("$READER" $MAX_CMD_SIZE)
  53. do
  54. cmd=$(printf "%s" "$cmd" |
  55. sed -e 's/^[[:space:]]*//' \
  56. -e 's/[[:space:]]*$//')
  57. printf "%s\n" "$cmd" >&2
  58. printf "%s\n" "$cmd"
  59. done
  60. } | ssh -oBatchMode=yes -oStrictHostKeyChecking=no "$server" "sh" > "$loop"
  61. }
  62. run_cmd_list() {
  63. server="$1"
  64. loop="$2"
  65. out_file="$3"
  66. {
  67. # Test that ssh is ready to receive.
  68. printf "echo\n"
  69. # Block until command completes
  70. while read -t $TIMEOUT line < "$loop" > /dev/null && \
  71. cmd=$("$READER" $MAX_CMD_SIZE)
  72. do
  73. cmd=$(printf "%s" "$cmd" |
  74. sed -e 's/^[[:space:]]*//' \
  75. -e 's/[[:space:]]*$//')
  76. printf "%s\n" "$cmd" >&2
  77. printf "printf \"%s: \" >> %s\n" "$cmd" "$out_file"
  78. printf "%s >> %s\n" "$cmd" "$out_file"
  79. printf "echo\n"
  80. done
  81. } | ssh -oBatchMode=yes -oStrictHostKeyChecking=no "$server" "sh" > "$loop"
  82. }
  83. run_server() {
  84. server="$1"
  85. loop="$(mktemp -u /tmp/$(basename $CMD_FEED .fifo)-$server-XXX.fifo)"
  86. out_file=$(printf "$OUT_FMT" "$(basename "$loop" .fifo)")
  87. mkfifo "$loop"
  88. trap "clean_server $loop" 2 15
  89. if [ -n "$PROCESSOR" ]; then
  90. run_processor "$server" "$loop" "$out_file"
  91. else
  92. run_cmd_list "$server" "$loop" "$out_file"
  93. fi # > "$loop"
  94. #| ssh -oBatchMode=yes -oStrictHostKeyChecking=no "$server" "sh" > "$loop"
  95. clean_server "$loop"
  96. echo "Finished!" >&2
  97. }
  98. clean_up() {
  99. pkill -P $$
  100. rm "$CMD_FEED"
  101. exit 2
  102. }
  103. prepend() {
  104. while read -r line; do
  105. printf "%s: %s\n" "$1" "$line"
  106. done
  107. }
  108. promote_subordinate_master() {
  109. server="$(head -1 -)"
  110. until server="$(head -1 -)";\
  111. ssh -oBatchMode=yes -oStrictHostKeyChecking=no "$server" \
  112. "$(realpath $0) -t"; do
  113. sleep 1
  114. done
  115. }
  116. launch_children() {
  117. for server in $(head -n$MAX_PROCS -); do
  118. run_server "$server" < "$CMD_FEED" 2>&1 | prepend "$server" &
  119. done
  120. # promote_subordinate_master &
  121. while pgrep -P $$ > /dev/null; do
  122. wait
  123. done
  124. }
  125. main() {
  126. mkfifo "$CMD_FEED"
  127. trap clean_up 2 15
  128. trap "" 10
  129. cat "$CONF_LIST" | sed '/^[[:space:]]*$/d' | \
  130. xargs -d'\n' printf "%-$MAX_CMD_SIZE.${MAX_CMD_SIZE}s" > "$CMD_FEED" &
  131. cat "$SERVER_LIST" | launch_children
  132. clean_up
  133. echo "All jobs finished!"
  134. }
  135. OPTIND=1
  136. while getopts "h?c:t" opt; do
  137. case "$opt" in
  138. h|\?)
  139. help 0
  140. ;;
  141. c) readonly PROCESSOR="$OPTARG"
  142. ;;
  143. t) launch_children
  144. ;;
  145. *) echo "Unknown option '$opt'" >&2
  146. help 1
  147. ;;
  148. esac
  149. done
  150. shift $(expr $OPTIND - 1)
  151. if [ "$#" -lt 2 -o "$#" -gt 3 ]; then
  152. help 1
  153. fi
  154. readonly CONF_LIST="$1"
  155. readonly SERVER_LIST="$2"
  156. readonly OUT_FMT="${3:-$DEFAULT_OUT_FMT}"
  157. main