boot-cluster.sh 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  1. #!/bin/bash
  2. #
  3. # A script to boot a dev swarm cluster on a Linux host (typically in a Docker
  4. # container started with swarm/dev/run.sh).
  5. #
  6. # The cluster contains a bootnode, a geth node and multiple swarm nodes, with
  7. # each node having its own data directory in a base directory passed with the
  8. # --dir flag (default is swarm/dev/cluster).
  9. #
  10. # To avoid using different ports for each node and to make networking more
  11. # realistic, each node gets its own network namespace with IPs assigned from
  12. # the 192.168.33.0/24 subnet:
  13. #
  14. # bootnode: 192.168.33.2
  15. # geth: 192.168.33.3
  16. # swarm: 192.168.33.10{1,2,...,n}
  17. set -e
  18. ROOT="$(cd "$(dirname "$0")/../../.." && pwd)"
  19. source "${ROOT}/swarm/dev/scripts/util.sh"
  20. # DEFAULT_BASE_DIR is the default base directory to store node data
  21. DEFAULT_BASE_DIR="${ROOT}/swarm/dev/cluster"
  22. # DEFAULT_CLUSTER_SIZE is the default swarm cluster size
  23. DEFAULT_CLUSTER_SIZE=3
  24. # Linux bridge configuration for connecting the node network namespaces
  25. BRIDGE_NAME="swarmbr0"
  26. BRIDGE_IP="192.168.33.1"
  27. # static bootnode configuration
  28. BOOTNODE_IP="192.168.33.2"
  29. BOOTNODE_PORT="30301"
  30. BOOTNODE_KEY="32078f313bea771848db70745225c52c00981589ad6b5b49163f0f5ee852617d"
  31. BOOTNODE_PUBKEY="760c4460e5336ac9bbd87952a3c7ec4363fc0a97bd31c86430806e287b437fd1b01abc6e1db640cf3106b520344af1d58b00b57823db3e1407cbc433e1b6d04d"
  32. BOOTNODE_URL="enode://${BOOTNODE_PUBKEY}@${BOOTNODE_IP}:${BOOTNODE_PORT}"
  33. # static geth configuration
  34. GETH_IP="192.168.33.3"
  35. GETH_RPC_PORT="8545"
  36. GETH_RPC_URL="http://${GETH_IP}:${GETH_RPC_PORT}"
  37. usage() {
  38. cat >&2 <<USAGE
  39. usage: $0 [options]
  40. Boot a dev swarm cluster.
  41. OPTIONS:
  42. -d, --dir DIR Base directory to store node data [default: ${DEFAULT_BASE_DIR}]
  43. -s, --size SIZE Size of swarm cluster [default: ${DEFAULT_CLUSTER_SIZE}]
  44. -h, --help Show this message
  45. USAGE
  46. }
  47. main() {
  48. local base_dir="${DEFAULT_BASE_DIR}"
  49. local cluster_size="${DEFAULT_CLUSTER_SIZE}"
  50. parse_args "$@"
  51. local pid_dir="${base_dir}/pids"
  52. local log_dir="${base_dir}/logs"
  53. mkdir -p "${base_dir}" "${pid_dir}" "${log_dir}"
  54. stop_cluster
  55. create_network
  56. start_bootnode
  57. start_geth_node
  58. start_swarm_nodes
  59. }
  60. parse_args() {
  61. while true; do
  62. case "$1" in
  63. -h | --help)
  64. usage
  65. exit 0
  66. ;;
  67. -d | --dir)
  68. if [[ -z "$2" ]]; then
  69. fail "--dir flag requires an argument"
  70. fi
  71. base_dir="$2"
  72. shift 2
  73. ;;
  74. -s | --size)
  75. if [[ -z "$2" ]]; then
  76. fail "--size flag requires an argument"
  77. fi
  78. cluster_size="$2"
  79. shift 2
  80. ;;
  81. *)
  82. break
  83. ;;
  84. esac
  85. done
  86. if [[ $# -ne 0 ]]; then
  87. usage
  88. fail "ERROR: invalid arguments: $@"
  89. fi
  90. }
  91. stop_cluster() {
  92. info "stopping existing cluster"
  93. "${ROOT}/swarm/dev/scripts/stop-cluster.sh" --dir "${base_dir}"
  94. }
  95. # create_network creates a Linux bridge which is used to connect the node
  96. # network namespaces together
  97. create_network() {
  98. local subnet="${BRIDGE_IP}/24"
  99. info "creating ${subnet} network on ${BRIDGE_NAME}"
  100. ip link add name "${BRIDGE_NAME}" type bridge
  101. ip link set dev "${BRIDGE_NAME}" up
  102. ip address add "${subnet}" dev "${BRIDGE_NAME}"
  103. }
  104. # start_bootnode starts a bootnode which is used to bootstrap the geth and
  105. # swarm nodes
  106. start_bootnode() {
  107. local key_file="${base_dir}/bootnode.key"
  108. echo -n "${BOOTNODE_KEY}" > "${key_file}"
  109. local args=(
  110. --addr "${BOOTNODE_IP}:${BOOTNODE_PORT}"
  111. --nodekey "${key_file}"
  112. --verbosity "6"
  113. )
  114. start_node "bootnode" "${BOOTNODE_IP}" "$(which bootnode)" ${args[@]}
  115. }
  116. # start_geth_node starts a geth node with --datadir pointing at <base-dir>/geth
  117. # and a single, unlocked account with password "geth"
  118. start_geth_node() {
  119. local dir="${base_dir}/geth"
  120. mkdir -p "${dir}"
  121. local password="geth"
  122. echo "${password}" > "${dir}/password"
  123. # create an account if necessary
  124. if [[ ! -e "${dir}/keystore" ]]; then
  125. info "creating geth account"
  126. create_account "${dir}" "${password}"
  127. fi
  128. # get the account address
  129. local address="$(jq --raw-output '.address' ${dir}/keystore/*)"
  130. if [[ -z "${address}" ]]; then
  131. fail "failed to get geth account address"
  132. fi
  133. local args=(
  134. --datadir "${dir}"
  135. --networkid "321"
  136. --bootnodes "${BOOTNODE_URL}"
  137. --unlock "${address}"
  138. --password "${dir}/password"
  139. --rpc
  140. --rpcaddr "${GETH_IP}"
  141. --rpcport "${GETH_RPC_PORT}"
  142. --verbosity "6"
  143. )
  144. start_node "geth" "${GETH_IP}" "$(which geth)" ${args[@]}
  145. }
  146. start_swarm_nodes() {
  147. for i in $(seq 1 ${cluster_size}); do
  148. start_swarm_node "${i}"
  149. done
  150. }
  151. # start_swarm_node starts a swarm node with a name like "swarmNN" (where NN is
  152. # a zero-padded integer like "07"), --datadir pointing at <base-dir>/<name>
  153. # (e.g. <base-dir>/swarm07) and a single account with <name> as the password
  154. start_swarm_node() {
  155. local num=$1
  156. local name="swarm$(printf '%02d' ${num})"
  157. local ip="192.168.33.1$(printf '%02d' ${num})"
  158. local dir="${base_dir}/${name}"
  159. mkdir -p "${dir}"
  160. local password="${name}"
  161. echo "${password}" > "${dir}/password"
  162. # create an account if necessary
  163. if [[ ! -e "${dir}/keystore" ]]; then
  164. info "creating account for ${name}"
  165. create_account "${dir}" "${password}"
  166. fi
  167. # get the account address
  168. local address="$(jq --raw-output '.address' ${dir}/keystore/*)"
  169. if [[ -z "${address}" ]]; then
  170. fail "failed to get swarm account address"
  171. fi
  172. local args=(
  173. --bootnodes "${BOOTNODE_URL}"
  174. --datadir "${dir}"
  175. --identity "${name}"
  176. --ens-api "${GETH_RPC_URL}"
  177. --bzznetworkid "321"
  178. --bzzaccount "${address}"
  179. --password "${dir}/password"
  180. --verbosity "6"
  181. )
  182. start_node "${name}" "${ip}" "$(which swarm)" ${args[@]}
  183. }
  184. # start_node runs the node command as a daemon in a network namespace
  185. start_node() {
  186. local name="$1"
  187. local ip="$2"
  188. local path="$3"
  189. local cmd_args=${@:4}
  190. info "starting ${name} with IP ${ip}"
  191. create_node_network "${name}" "${ip}"
  192. # add a marker to the log file
  193. cat >> "${log_dir}/${name}.log" <<EOF
  194. >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
  195. Starting ${name} node - $(date)
  196. >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
  197. EOF
  198. # run the command in the network namespace using start-stop-daemon to
  199. # daemonise the process, sending all output to the log file
  200. local daemon_args=(
  201. --start
  202. --background
  203. --no-close
  204. --make-pidfile
  205. --pidfile "${pid_dir}/${name}.pid"
  206. --exec "${path}"
  207. )
  208. if ! ip netns exec "${name}" start-stop-daemon ${daemon_args[@]} -- $cmd_args &>> "${log_dir}/${name}.log"; then
  209. fail "could not start ${name}, check ${log_dir}/${name}.log"
  210. fi
  211. }
  212. # create_node_network creates a network namespace and connects it to the Linux
  213. # bridge using a veth pair
  214. create_node_network() {
  215. local name="$1"
  216. local ip="$2"
  217. # create the namespace
  218. ip netns add "${name}"
  219. # create the veth pair
  220. local veth0="veth${name}0"
  221. local veth1="veth${name}1"
  222. ip link add name "${veth0}" type veth peer name "${veth1}"
  223. # add one end to the bridge
  224. ip link set dev "${veth0}" master "${BRIDGE_NAME}"
  225. ip link set dev "${veth0}" up
  226. # add the other end to the namespace, rename it eth0 and give it the ip
  227. ip link set dev "${veth1}" netns "${name}"
  228. ip netns exec "${name}" ip link set dev "${veth1}" name "eth0"
  229. ip netns exec "${name}" ip link set dev "eth0" up
  230. ip netns exec "${name}" ip address add "${ip}/24" dev "eth0"
  231. }
  232. create_account() {
  233. local dir=$1
  234. local password=$2
  235. geth --datadir "${dir}" --password /dev/stdin account new <<< "${password}"
  236. }
  237. main "$@"