PageRenderTime 48ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/scripts/ha-cluster-join

https://github.com/tserong/sleha-bootstrap
#! | 295 lines | 248 code | 47 blank | 0 comment | 0 complexity | 0bd5bb9be5834f824e178cb7581cb92f MD5 | raw file
  1. #!/bin/bash
  2. #
  3. # Copyright (c) 2010-2013 SUSE LLC, All Rights Reserved.
  4. #
  5. # Author: Tim Serong <tserong@suse.com>
  6. #
  7. # This program is free software; you can redistribute it and/or modify
  8. # it under the terms of version 2 of the GNU General Public License as
  9. # published by the Free Software Foundation.
  10. #
  11. # This program is distributed in the hope that it would be useful, but
  12. # WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  14. #
  15. # Further, this software is distributed without any warranty that it is
  16. # free of the rightful claim of any third person regarding infringement
  17. # or the like. Any license provided herein, whether implied or
  18. # otherwise, applies only to this software file. Patent licenses, if
  19. # any, provided herein do not apply to combinations of this program with
  20. # other software, or any other product whatsoever.
  21. #
  22. # You should have received a copy of the GNU General Public License
  23. # along with this program; if not, write the Free Software Foundation,
  24. # Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA.
  25. #
  26. . /usr/lib/ha-cluster-functions
  27. declare SEED_HOST
  28. usage()
  29. {
  30. cat <<END
  31. Usage: $0 [options] [stage]
  32. Options:
  33. -c <host> IP address or hostname of existing cluster node
  34. -i <if> Default to IP address on interface 'if' instead of eth0
  35. -h,--help Display this usage information
  36. -q Be quiet (don't describe what's happening, just do it)
  37. -y Answer "yes" to all prompts (use with care)
  38. Stage can be one of:
  39. ssh Obtain SSH keys from existing cluster node (requires -c <ip>)
  40. csync2 Configure csync2 (requires -c <ip>)
  41. ssh_merge Merge root's SSH known_hosts across all nodes (csync2 must
  42. already be configured).
  43. cluster Start the cluster on this node
  44. If stage is not specified, each stage will be invoked in sequence.
  45. END
  46. exit 0
  47. }
  48. join_ssh()
  49. {
  50. [ -n "$SEED_HOST" ] || error "No existing IP/hostname specified (use -c option)"
  51. local -i got_keys=0
  52. start_service sshd.service
  53. invoke mkdir -m 700 -p /root/.ssh
  54. local tmp_dir=/tmp/ha-cluster-ssh.$$
  55. invoke mkdir -p $tmp_dir || error "Can't create temporary directory $tmp_dir"
  56. invoke rm -f $tmp_dir/*
  57. status "Retrieving SSH keys from $SEED_HOST"
  58. invoke scp -oStrictHostKeyChecking=no \
  59. root@$SEED_HOST:'/root/.ssh/id_*' $tmp_dir/ \
  60. || error "Can't retrieve SSH keys from $SEED_HOST"
  61. # This supports all SSH key types, for the case where ha-cluster-init
  62. # wasn't used to set up the seed node, and the user has manually
  63. # created, for example, DSA keys (bnc#878080)
  64. for key in id_rsa id_dsa id_ecdsa id_ed25519 ; do
  65. [ -f $tmp_dir/$key ] || continue
  66. if [ -f /root/.ssh/$key ]; then
  67. confirm \
  68. "/root/.ssh/$key already exists - overwrite?" || continue
  69. fi
  70. invoke mv $tmp_dir/$key* /root/.ssh/
  71. grep -q -s "$(cat /root/.ssh/$key.pub)" /root/.ssh/authorized_keys \
  72. || append /root/.ssh/$key.pub /root/.ssh/authorized_keys
  73. let got_keys=$got_keys+1
  74. done
  75. invoke rm -r $tmp_dir
  76. [ $got_keys -eq 0 ] && status "No new SSH keys installed"
  77. [ $got_keys -eq 1 ] && status "One new SSH key installed"
  78. [ $got_keys -gt 1 ] && status "$got_keys new SSH keys installed"
  79. # This makes sure the seed host has its own SSH keys in its own
  80. # authorized_keys file (again, to help with the case where the
  81. # user has done manual initial setup without the assistance of
  82. # ha-cluster-init).
  83. invoke ssh root@$SEED_HOST ha-cluster-init ssh_remote \
  84. || error "Can't invoke ha-cluster-init ssh_remote on $SEED_HOST"
  85. }
  86. join_csync2()
  87. {
  88. [ -n "$SEED_HOST" ] || error "No existing IP/hostname specified (use -c option)"
  89. status "Configuring csync2"
  90. # Necessary if re-running join on a node that's been configured before.
  91. invoke rm -f /var/lib/csync2/$(hostname).db3
  92. # Not automatically updating /etc/hosts - risky in the general case.
  93. #etc_hosts_add_me
  94. #local hosts_line=$(etc_hosts_get_me)
  95. #[ -n "$hosts_line" ] || error "No valid entry for $(hostname) in /etc/hosts - csync2 can't work"
  96. # If we *were* updating /etc/hosts, the next line would have "\"$hosts_line\"" as
  97. # the last arg (but this requires re-enabling this functionality in ha-cluster-init)
  98. invoke ssh root@$SEED_HOST ha-cluster-init csync2_remote $(hostname) \
  99. || error "Can't invoke ha-cluster-init csync2_remote on $SEED_HOST"
  100. # This is necessary if syncing /etc/hosts (to ensure everyone's got the
  101. # same list of hosts)
  102. #local tmp_conf=/etc/hosts.$$
  103. #invoke scp root@$SEED_HOST:/etc/hosts $tmp_conf \
  104. # || error "Can't retrieve /etc/hosts from $SEED_HOST"
  105. #install_tmp $tmp_conf /etc/hosts
  106. invoke scp root@$SEED_HOST:'/etc/csync2/{csync2.cfg,key_hagroup}' \
  107. /etc/csync2 \
  108. || error "Can't retrieve csync2 config from $SEED_HOST"
  109. start_service csync2.socket
  110. # Sync new config out. This goes to all hosts; csync2.cfg definitely
  111. # needs to go to all hosts (else hosts other than the seed and the
  112. # joining host won't have the joining host in their config yet).
  113. # Strictly, the rest of the files need only go to the new host which
  114. # could theoretically be effected using `csync2 -xv -P $(hostname)`,
  115. # but this still leaves all the other files in dirty state (becuase
  116. # they haven't gone to all nodes in the cluster, which means a
  117. # subseqent join of another node can fail its sync of corosync.conf
  118. # when it updates expected_votes. Grrr...
  119. invoke ssh root@$SEED_HOST "csync2 -mr / ; csync2 -fr / ; csync2 -xv" \
  120. || warn "csync2 run failed - some files may not be sync'd"
  121. }
  122. join_ssh_merge()
  123. {
  124. status "Merging known_hosts"
  125. local host_args=$(awk '/^[[:space:]]*host[[:space:]]/ { print "-H", $2 }' \
  126. $CSYNC2_CFG | sed -e 's/;//')
  127. [ -z "$host_args" ] && error "Unable to extract host list from $CSYNC2_CFG"
  128. local tmp_dir=/tmp/ha-cluster-pssh.$$
  129. invoke mkdir -p $tmp_dir || error "Can't create temporary directory $tmp_dir"
  130. invoke rm -f $tmp_dir/*
  131. # The act of using pssh to connect to every host (without strict host key
  132. # checking) ensures that at least *this* host has every other host in its
  133. # known_hosts
  134. invoke pssh $host_args -O StrictHostKeyChecking=no -o $tmp_dir \
  135. cat ~/.ssh/known_hosts
  136. local rc=$?
  137. # 0 == success, 5 == ssh OK but remote command failed - we're ignoring
  138. # this because it's most likely known_hosts doesn't exist.
  139. [ $rc -ne 0 -a $rc -ne 5 ] && warn "known_hosts collection may be incomplete"
  140. # Sort combines everything we grabbed with our latest local known_hosts
  141. sort -u $tmp_dir/* ~/.ssh/known_hosts > ~/.ssh/known_hosts.new
  142. invoke pscp $host_args -O StrictHostKeyChecking=no \
  143. ~/.ssh/known_hosts.new ~/.ssh/known_hosts \
  144. || warn "known_hosts merge may be incomplete"
  145. invoke rm ~/.ssh/known_hosts.new
  146. invoke rm -r $tmp_dir
  147. }
  148. join_cluster()
  149. {
  150. # Need to do this if second (or subsequent) node happens to be up and
  151. # connected to storage while it's being repartitioned on the first node.
  152. probe_partitions
  153. # It would be massively useful at this point if new nodes could come
  154. # up in standby mode, so we could query the CIB locally to see if
  155. # there was any further local setup that needed doing, e.g.: creating
  156. # mountpoints for clustered filesystems. Unfortunately we don't have
  157. # that yet, so the following crawling horror takes a punt on the seed
  158. # node being up, then asks it for a list of mountpoints...
  159. if [ -n "$SEED_HOST" ]; then
  160. local m
  161. local mtpts=$(ssh root@$SEED_HOST 'cibadmin -Q -A '\''//primitive[@class="ocf" and @provider="heartbeat" and @type="Filesystem"]/instance_attributes/nvpair[@name="directory"]'\'' 2>/dev/null | grep '\''<nvpair'\'' | sed '\''s/.*value="\([^"]*\)".*/\1/'\''')
  162. for m in $mtpts; do
  163. invoke mkdir -p $m
  164. done
  165. else
  166. status "No existing IP/hostname specified - skipping mountpoint detection/creation"
  167. fi
  168. # Bump expected_votes in corosync.conf
  169. # TODO(must): this is rather fragile (see related code in ha-cluster-remove)
  170. local tmp_conf=${COROSYNC_CONF}.$$
  171. local new_quorum=$(awk -F[^0-9] '/^[[:space:]]*expected_votes/ { print $NF + 1 };' $COROSYNC_CONF)
  172. local two_node=0
  173. [ $new_quorum -eq 2 ] && two_node=1
  174. sed \
  175. -e 's/^\([[:space:]]*expected_votes:[[:space:]]*\).*/\1'$new_quorum'/' \
  176. -e 's/^\([[:space:]]*two_node:[[:space:]]*\).*/\1'$two_node'/' \
  177. $COROSYNC_CONF > $tmp_conf
  178. install_tmp $tmp_conf $COROSYNC_CONF
  179. invoke csync2 -m $COROSYNC_CONF
  180. invoke csync2 -f $COROSYNC_CONF
  181. invoke csync2 -xv $COROSYNC_CONF
  182. # ...now that that's out of the way, let's initialize the cluster.
  183. init_cluster_local
  184. # Trigger corosync config reload to ensure expected_votes is propagated
  185. invoke corosync-cfgtool -R
  186. # Ditch no-quorum-policy=ignore if we're going over two nodes
  187. if [ $new_quorum -gt 2 ] && crm configure show | grep -q 'no-quorum-policy=.*ignore' ; then
  188. invoke crm_attribute --attr-name no-quorum-policy --delete-attr
  189. fi
  190. }
  191. #------------------------------------------------------------------------------
  192. # for --help option
  193. [ "$1" == "--help" ] && usage
  194. while getopts 'c:i:hqy' o; do
  195. case $o in
  196. c) SEED_HOST=$OPTARG;;
  197. i) NET_IF=$OPTARG;;
  198. h) usage;;
  199. q) BE_QUIET=true;;
  200. y) YES_TO_ALL=true;;
  201. esac
  202. done
  203. shift $(($OPTIND - 1))
  204. if [ "$1" == "ssh_test" ]; then
  205. # Check whether passwordless ssh works or not. Not a "regular" stage (i.e.
  206. # never executed automatically, intended for use by external programs), so
  207. # currently undocumented in usage text or man page. Requires -c <host>.
  208. [ -n "$SEED_HOST" ] || _die "No existing IP/hostname specified (use -c option)"
  209. ssh -oBatchmode=yes -oStrictHostKeyChecking=no \
  210. root@$SEED_HOST true 2>/dev/null
  211. exit $?
  212. fi
  213. if [ "$1" != "ssh" -a "$1" != "csync2" -a "$1" != "ssh_merge" ]; then
  214. systemctl -q is-active corosync.service
  215. rc=$?
  216. [ $rc -eq 0 ] && error "Cluster is currently active - can't run"
  217. fi
  218. # Need hostname resolution to work, want NTP
  219. check_prereqs
  220. case $1 in
  221. ssh|csync2|ssh_merge|cluster)
  222. init
  223. join_$1
  224. ;;
  225. "")
  226. init
  227. if ! $YES_TO_ALL && [ -z "$SEED_HOST" ]; then
  228. status "
  229. Join This Node to Cluster:
  230. You will be asked for the IP address of an existing node, from which
  231. configuration will be copied. If you have not already configured
  232. passwordless ssh between nodes, you will be prompted for the root
  233. password of the existing node.
  234. "
  235. SEED_HOST=$(prompt_for_string \
  236. 'IP address or hostname of existing node (e.g.: 192.168.1.1)' \
  237. '.+')
  238. fi
  239. join_ssh
  240. join_csync2
  241. join_ssh_merge
  242. join_cluster
  243. ;;
  244. *) echo -e "Invalid stage ($1)\n"
  245. usage
  246. esac
  247. status "Done (log saved to $LOG_FILE)"
  248. exit 0