Configuration generalised so that all options can be set via
authorMartin Schwenke <martin@meltin.net>
Fri, 15 Aug 2008 05:34:22 +0000 (15:34 +1000)
committerMartin Schwenke <martin@meltin.net>
Fri, 15 Aug 2008 05:34:22 +0000 (15:34 +1000)
environment variables, in configuration files or on the command-line.
Changed format of config.default to use defconf rather than
assignment, and include documentation for usage message.  All
configuration variables now have an equivalent command-line option.
For example, $NBD_DEVICE can be set on the command-line using the
--nbd-device option.  Options specified on the command-line override
options set in any other way.  New configuration option $WITH_TSM_NODE
(defaults to 1).  Removed -n option - this is implicitly replaced by
--with-tsm-node=0.  The -c option can now be specified multiple times
and interspersed with setting individual options - order is important.
New option --dump displays the current configuration settings and then
exits.  It can be inserted anywhere on the command-line and takes
immediate effect, so it should be at the end of the command-line to
display all specified settings.  Configuration settings can also be
set in autocluster's environment, where they override the settings in
config.default (but not those specified on the command-line, including
via -c).  Error trapping moved to after usage processing, so an
invalid command-line option causes the desired usage message to
appear.

Signed-off-by: Martin Schwenke <martin@meltin.net>
autocluster
config.default
functions

index b2e228b92a5cf2b2b61881169b34e91d544928fc..6adccfcf172b5674948fd5731156f096f78b4477 100755 (executable)
@@ -24,15 +24,6 @@ else
     installdir="`dirname \"$autocluster\"`"
 fi
 
-config="config"
-
-# catch errors
-set -e
-set -E
-trap 'es=$?; 
-      echo ERROR: failed in function \"${FUNCNAME}\" at line ${LINENO} of ${BASH_SOURCE[0]} with code $es; 
-      exit $es' ERR
-
 ####################
 # show program usage
 usage ()
@@ -41,9 +32,15 @@ usage ()
 Usage: autocluster [OPTION] ... <COMMAND>
   options:
     -c <file>    specify config file (default is "config")
-    --firstip N  override FIRSTIP from config
-    -n           don't create a TSM node
     -x           enable script debugging
+    --dump       dump config settings and exit
+
+  configuration options:
+EOF
+
+    usage_config_options
+
+    cat >&2 <<EOF
 
   commands:
      create base
@@ -182,13 +179,13 @@ create_cluster() {
        create_node "$CLUSTER" $i
     done
 
-    if [ $withtsmnode -eq 1 ]; then
+    if [ $WITH_TSM_NODE -eq 1 ]; then
       echo "Creating TSM server node"
       create_tsm "$CLUSTER"
     fi
 
     echo "# autocluster $CLUSTER" > hosts.$CLUSTER
-    [ $withtsmnode -eq 1 ] && {
+    [ $WITH_TSM_NODE -eq 1 ] && {
        echo "$IPBASE.0.$FIRSTIP ${CLUSTER}tsm.$LOWDOMAIN ${CLUSTER}tsm" >> hosts.$CLUSTER
     }
     for i in `seq 1 $NUMNODES`; do
@@ -327,35 +324,65 @@ test_proxy() {
 
 ######################################################################
 
+. "$installdir/functions"
+. "$installdir/config.default"
 
 ############################
 # parse command line options
-temp=$(getopt -n "$prog" -o "c:xhn" -l help,firstip: -- "$@")
-
+long_opts=$(getopt_config_options)
+getopt_output=$(getopt -n autocluster -o "c:xh" -l help,dump -l "$long_opts" -- "$@")
 [ $? != 0 ] && usage
 
-eval set -- "$temp"
+use_default_config=true
 
-withtsmnode=1
+# We 2 passes of the options.  The first time we just handle usageor the first pass
+eval set -- "$getopt_output"
+while true ; do
+    case "$1" in
+       -c) shift 2 ; use_default_config=false ;;
+       --) shift ; break ;;
+       --dump|-x) shift ;;
+       -h|--help) usage ;; # Usage should be shown here for real defaults.
+       --*) shift 2 ;; # Assume other long opts are valid and take an arg.
+       *) usage ;; # shouldn't happen, so this is reasonable.
+    esac
+done
+
+config="./config"
+$use_default_config && [ -r "$config" ] && . "$config"
+
+eval set -- "$getopt_output"
 
 while true ; do
     case "$1" in
-       -c) config="$2" ; shift; shift ;;
-       --firstip) firstip="$2"; shift; shift; ;;
-        -n) withtsmnode=0; shift; ;;
+       -c) . "`dirname $2`/$2" ; shift 2 ; conf_done=true ;;
        -x) set -x; shift ;;
+       --dump) dump_config ;;
        --) shift ; break ;;
-       -h|--help|*) usage ;; # Shouldn't happen, so this is reasonable.
+       -h|--help) usage ;; # Redundant.
+       --*)
+           # Putting --opt1|opt2|... into a variable and having case
+           # match against it as a pattern doesn't work.  The | is
+           # part of shell syntax, so we need to do this.  Look away
+           # now to stop your eyes from bleeding! :-)
+           x=",${long_opts}" # Now each option is surrounded by , and :
+           if [ "$x" != "${x#*,${1#--}:}" ] ; then
+               # Our option, $1, surrounded by , and : was in $x, so is legal.
+               setconf_longopt "$1" "$2"; shift 2
+           else
+               usage
+           fi
+           ;;
+       *) usage ;; # shouldn't happen, so this is reasonable.
     esac
 done
 
-. "$installdir/config.default"
-. "`dirname $config`/$config"
-. "$installdir/functions"
-
-[ -n "$firstip" ] && {
-    FIRSTIP="$firstip"
-}
+# catch errors
+set -e
+set -E
+trap 'es=$?; 
+      echo ERROR: failed in function \"${FUNCNAME}\" at line ${LINENO} of ${BASH_SOURCE[0]} with code $es; 
+      exit $es' ERR
 
 LOWDOMAIN=`echo $DOMAIN | tr A-Z a-z`
 
index 06d711846fad49c6e0003f8f45de081ed752d55c..105388b9476782e6a3de9f102bb6ef8fb5a85b25 100644 (file)
 # please override with your own options in your own config file
 
 # where virtual machines are stored on this host
-VIRTBASE=/virtual
+defconf VIRTBASE /virtual \
+       "<path>" "virtual machine directory for this host"
 
 # where to find virsh
-VIRSH="virsh -c qemu:///system"
+defconf VIRSH "virsh -c qemu:///system" \
+       "<cmd>" "how to invoke virsh"
 
 # the name of the base RHEL install image that the nodes will be based on
 # a kvm image called $VIRTBASE/$BASENAME.img will be created
 # that will form the basis file for the images for each of the nodes
-BASENAME="SoFS-1.5-base"
+defconf BASENAME "SoFS-1.5-base" \
+       "<file>" "filename for cluster base image"
 
 # the install server where we will get SoFS packages from
-INSTALL_SERVER="http://9.155.61.11/mediasets"
+defconf INSTALL_SERVER "http://9.155.61.11/mediasets" \
+       "<url>" "URL of install server"
 
 # what timezone to put the nodes in
 # leave this empty to base the timezone on the zone that
 # this host is in
-TIMEZONE=
+defconf TIMEZONE "" \
+       "<tz>" "timezone for each node"
 
 # what keyboard type to setup
-KEYBOARD="us"
+defconf KEYBOARD "us" \
+       "<kbd>" "keyboard layout for each node"
 
 # the base ISO to install from
-ISO="/virtual/ISO/RHEL5.2-Server-20080430.0-x86_64-DVD.iso"
+defconf ISO "/virtual/ISO/RHEL5.2-Server-20080430.0-x86_64-DVD.iso" \
+       "<file>" "location of ISO image for base image creation"
 
 # which template kickstart file to use. There are separate templates
 # for each version of SoFS
-KICKSTART="$installdir/templates/kickstart-1.5.cfg"
+defconf KICKSTART "$installdir/templates/kickstart-1.5.cfg" \
+       "<file>" "choice of kickstart file"
 
 # the yum repositories to use. Choose the one appropriate for the
 # version of SoFS you are installing
-YUM_TEMPLATE="$installdir/templates/SoFS-1.5.repo"
-
+defconf YUM_TEMPLATE "$installdir/templates/SoFS-1.5.repo" \
+       "<file>" "location of template for yum repositories"
 
 # any extra packages to install. List one on each line. To force a package
 # not to be installed, list it with a leading - 
-EXTRA_PACKAGES='
+defconf EXTRA_PACKAGES '
 emacs
-'
+' \
+       "<list>" "extra packages for kickstart to install"
 
 # RHEL install key
-INSTALLKEY="--skip"
+defconf INSTALLKEY "--skip" \
+       "<key>" "RHEL install key"
 
 # the kvm binary to use - should be a very recent version
 # I am using a git snapshot from http://kvm.qumranet.com/kvmwiki/Code
-KVM="/usr/local/bin/qemu-system-x86_64"
+defconf KVM "/usr/local/bin/qemu-system-x86_64" \
+       "<file>"  "location of KVM executable"
 
 # memory for each node
-MEM=250000
+defconf MEM 250000 \
+       "<n>" "memory allocated for each node"
 
 # memory for the node that will run the SoFS GUI - used if it is
 # greater than $MEM
-GUIMEM=700000
+defconf GUIMEM 700000 \
+       "<n>" "memory allocated for the management node"
 
 # a directory on the host which will be mounted via NFS onto the 
 # nodes as /root/SOFS, giving a nice common area independent of GPFS
 # This is useful for compiles, RPMs, devel scripts etc
 # you need to add this to your /etc/exports and run exportfs -av yourself
 # on the host
-NFSSHARE="10.0.0.1:/home/SOFS"
+defconf NFSSHARE "10.0.0.1:/home/SOFS" \
+       "<mnt>" "NFS share to mount on each node"
 
 # windows domain name the nodes will be part of
-DOMAIN="VSOFS1.COM"
+defconf DOMAIN "VSOFS1.COM" \
+       "<dom>" "Windows(TM) domain name for each node"
 
 # windows workgroup name the nodes will be part of
-WORKGROUP="VSOFS1"
+defconf WORKGROUP "VSOFS1" \
+       "<grp>" "Windows(TM) workgroup for node"
 
 # DNS name server. Usually set this to the
 # kvm host, then setup DNS on the kvm host to direct 
 # queries for the windows domain name to the w2003 server
-NAMESERVER="10.0.0.1"
+defconf NAMESERVER "10.0.0.1" \
+       "<ip>" "DNS server for each node"
 
 # any extra domains to add to the search list
-DNSSEARCH="$DOMAIN"
+defconf DNSSEARCH "$DOMAIN" \
+       "<dom>" "extra domains for DNS search list"
 
 # set the first two octets of the IPs we will use
 # the 3rd and 4th octets are controlled by the node setup scripts
-IPBASE="10.0"
+defconf IPBASE "10.0" \
+       "<n>.<n>" "first 2 octets of IP for each node"
+
+# should we create a TSM node?  If this is not 1 then no TSM node is
+# created and all other TSM options are ignored.
+defconf WITH_TSM_NODE 1 \
+       "<0|1>" "1 if a TSM node should be created"
 
 # the nodes will get IPs starting at this number 
 # the TSM server will get $FIRSTIP, then the first node will get
@@ -89,7 +113,8 @@ IPBASE="10.0"
 #     1st node    10.0.0.21
 #     2nd node    10.0.0.22
 # etc etc
-FIRSTIP="20"
+defconf FIRSTIP "20" \
+       "<n>" "final octet for the 1st IP of the cluster"
 
 # a caching web proxy that can get to the install server from the
 # nodes. If you don't have one on the local network then look in
@@ -98,88 +123,116 @@ FIRSTIP="20"
 # several G of cache You can choose to have no web proxy at all, in
 # which case set it to the empty string, and hope you have a fast
 # network connection to the install server
-WEBPROXY="http://10.0.0.1:3128/"
+defconf WEBPROXY "http://10.0.0.1:3128/" \
+       "<url>" "URL of a caching web proxy"
 
 # IP gateway (the IP of the kvm host for the clients)
-GATEWAY="10.0.0.1"
+defconf GATEWAY "10.0.0.1" \
+       "<ip>" "IP gateway for cluster hosts, usually KVM host"
 
 # how many nodes to build
 # this doesn't include the TSM server
-NUMNODES=4
+defconf NUMNODES 4 \
+       "<n>" "number of nodes to build, not including TSM server"
 
 # how much disk space to use on each node
 # note that it will only use what is actually occupied,
 # so start this larger than you think you'll need
-DISKSIZE="20G"
+defconf DISKSIZE "20G" \
+       "<n>G" "maximum disk size for each node"
 
 # size of root partition in megabytes
-ROOTSIZE=15000
+defconf ROOTSIZE 15000 \
+       "<n>" "size of root partition in MB"
 
 # size of swap partition in megabytes
-SWAPSIZE=2000
+defconf SWAPSIZE 2000 \
+       "<n>" "size of swap partition in MB"
 
 # the size of the 3 GPFS shared disks
-SHAREDDISKSIZE="10G"
+defconf SHAREDDISKSIZE "10G" \
+       "<n>G" "size of the 3 GPFS shared disks"
 
 # the size of the TSM storage disk
-TSMDISKSIZE="50G"
+defconf TSMDISKSIZE "50G" \
+       "<n>G" "size of the TSM storage disk"
 
 # what network adapter to use
-NICMODEL="e1000"
+defconf NICMODEL "e1000" \
+       "<module>" "choice of KVM network adapter"
 
 # where we will log serial consoles to
-KVMLOG="/var/log/kvm"
+defconf KVMLOG "/var/log/kvm" \
+       "<dir>" "directory for serial logs"
 
 # initial root password
-ROOTPASSWORD="password"
+defconf ROOTPASSWORD "password" \
+       "<string>" "initial root password for each node"
 
 # install language - make it the same as the installers by default
-LANGUAGE="${LANG:-en_US.UTF-8}"
+defconf LANGUAGE "${LANG:-en_US.UTF-8}" \
+       "<locale>" "locale for installer to use"
 
 # name of the system in CIFS protocol
-CIFSNAME="Samba01"
+defconf CIFSNAME "Samba01" \
+       "<name>" "name of the system in CIFS protocol"
 
 # the name that the nodes will use to talk to the TSM server
-TSMNAME="SOFS01"
+defconf TSMNAME "SOFS01" \
+       "<name>" "name used by nodes to talk to TSM server"
 
 # how big should the TSM database be
-TSM_DB_SIZE=100
+defconf TSM_DB_SIZE 100 \
+       "<n>" "size of TSM database"
 
 # how big should the TSM space management pool be
-TSM_SPACE_MGMT_SIZE=1024
+defconf TSM_SPACE_MGMT_SIZE 1024 \
+       "<n>" "size of TSM space management pool"
 
 # how big should the TSM backup pool be
-TSM_BACKUP_POOL_SIZE=100
+defconf TSM_BACKUP_POOL_SIZE 100 \
+       "<n>" "size of TSM backup pool"
 
 # how big should the TSM archive pool be
-TSM_ACRHIVE_POOL_SIZE=100
+defconf TSM_ACRHIVE_POOL_SIZE 100 \
+       "<n>" "size of TSM archive pool"
 
 # the min size of the java heap for the SoFS GUI
-JAVA_MIN_SIZE="200M"
+defconf JAVA_MIN_SIZE "200M" \
+       "<n>M" "minimum size of Java heap for SoFS GUI"
 
 # the max size of the java heap for the SoFS GUI
-JAVA_MAX_SIZE="400M"
+defconf JAVA_MAX_SIZE "400M" \
+       "<n>M" "maximum size of Java heap for SoFS GUI"
 
 # how many virtual CPUs per node?
-NUMCPUS=2
+defconf NUMCPUS 2 \
+       "<n>" "number of virtual CPUs per node"
 
 # libvirt template to use for nodes
-NODE_TEMPLATE="$installdir/templates/node.xml"
+defconf NODE_TEMPLATE "$installdir/templates/node.xml" \
+       "<file>" "libvirt template for nodes"
 
 # libvirt template to use for TSM server
-TSM_TEMPLATE="$installdir/templates/tsmserver.xml"
+defconf TSM_TEMPLATE "$installdir/templates/tsmserver.xml" \
+       "<file>" "libvirt template for TSM server"
 
 # libvirt template to use for initial install
-INSTALL_TEMPLATE="$installdir/templates/install.xml"
+defconf INSTALL_TEMPLATE "$installdir/templates/install.xml" \
+       "<file>" "libvirt template for initial install"
 
 # libvirt template to use for boot_base.sh
-BOOT_TEMPLATE="$installdir/templates/bootbase.xml"
+defconf BOOT_TEMPLATE "$installdir/templates/bootbase.xml" \
+       "<file>" "libvirt template for \"bootbase\" command"
 
 # where to get the base templates from
-BASE_TEMPLATES="$installdir/base"
+defconf BASE_TEMPLATES "$installdir/base" \
+       "<dir>" "directory containing base templates"
 
 # nbd device to use
-NBD_DEVICE="/dev/nbd0"
+defconf NBD_DEVICE "/dev/nbd0" \
+       "<dev>" "NBD device node to use"
 
-# the format to use the for qemu base images
-BASE_FORMAT="qcow2"
+# the format to use for qemu base images
+defconf BASE_FORMAT "qcow2" \
+       "<fmt>" "format to use for the qemu base images"
index 80f76c50961766562687e310a95dd69fd83bf9b7..f37d3bc221f403df65ff89d10ea86a664ab4bd22 100644 (file)
--- a/functions
+++ b/functions
@@ -1,4 +1,4 @@
-# common functions for autocluster
+# common functions for autocluster (-*- shell-script -*-)
 
 # create a MAC address based on a hash of the cluster name
 # plus the adapter and node number
@@ -198,3 +198,161 @@ check_command() {
     fi
 }
 
+# Set a variable if it isn't already set.  This allows environment
+# variables to override default config settings.
+defconf() {
+    local v="$1"
+    local e="$2"
+
+    [ "${!v+x}" ] || eval "$v=\"$e\""
+}
+
+# Print the list of config variables defined in config.default.
+get_config_options() {( # sub-shell for local declaration of defconf()
+       local options=
+       defconf() { options="$options $1" ; }
+       . "$installdir/config.default"
+       echo $options
+)}
+
+# Produce a list of long options, suitable for use with getopt, that
+# represent the config variables defined in config.default.
+getopt_config_options() {
+    local x=$(get_config_options | tr 'A-Z_' 'a-z-')
+    echo "${x// /:,}:"
+}
+
+# Unconditionally set the config variable associated with the given
+# long option.
+setconf_longopt() {
+    local longopt="$1"
+    local e="$2"
+
+    local v=$(echo "${longopt#--}" | tr 'a-z-' 'A-Z_')
+    # unset so defconf will set it
+    eval "unset $v"
+    defconf "$v" "$e"
+}
+
+# Dump all of the current config variables.
+dump_config() {
+    local o
+    for o in $(get_config_options) ; do
+       echo "${o}=\"${!o}\""
+    done
+    exit 0
+}
+
+# Print text assuming it starts after other text in $startcol and
+# needs to wrap before $fillcol.  Subsequent lines start at $startcol.
+# Long "words" will extend past $fillcol.
+fill_text() {
+    local startcol="$1"
+    local fillcol="$2"
+    local text="$3"
+
+    local width=$(($fillcol - $startcol))
+    [ $width -lt 0 ] && width=$((78 - $startcol))
+
+    local out=""
+
+    local padding=$(printf "\n%${startcol}s" " ")
+
+    while [ -n "$text" ] ; do
+       local orig="$text"
+
+       # If we already have output then arrange padding on the next line.
+       [ -n "$out" ] && out="${out}${padding}"
+
+       # Break the text at $width.
+       out="${out}${text:0:${width}}"
+       text="${text:${width}}"
+
+       # If we have left over text then the line break may be ugly,
+       # so let's check and try to break it on a space.
+       if [ -n "$text" ] ; then
+           if [ "${text:0:1}" != " " -a "${text: -1:1}" != " " ] ; then
+               # We didn't break on a space.  Arrange for the
+               # beginning of the broken "word" to appear on the next
+               # line but not if it will make us loop infinitely.
+               if [ "${orig}" != "${out##* }${text}" ] ; then
+                   text="${out##* }${text}"
+                   out="${out% *}"
+               else
+                   # Hmmm, doing that would make us loop, so add the
+                   # rest of the word from the remainder of the text
+                   # to this line and let it extend past $fillcol.
+                   out="${out}${text%% *}"
+                   if [ "${text# *}" != "$text" ] ; then
+                       # Remember the text after the next space for next time.
+                       text="${text# *}"
+                   else
+                       # No text after next space.
+                       text=""
+                   fi
+               fi
+           else
+               # We broke on a space.  If it will be at the beginning
+               # of the next line then remove it.
+               text="${text# }"
+           fi
+       fi
+    done
+
+    echo "$out"
+}
+
+# Display usage text, trying these approaches in order.
+# 1. See if it all fits on one line before $fillcol.
+# 2. See if splitting before the default value and indenting it
+#    to $startcol means that nothing passes $fillcol.
+# 3. Treat the message and default value as a string and just us fill_text()
+#    to format it. 
+usage_display_text() {
+    local startcol="$1"
+    local fillcol="$2"
+    local desc="$3"
+    local default="$4"
+    
+    local width=$(($fillcol - $startcol))
+
+    # To avoid clutter, only quote values that need it.
+    #local q=
+    #[ -z "$default" -o "$(echo $default)" = "${default// /}" ] || q="\""
+    default="(default \"$default\")"
+
+    if [ $((${#desc} + 1 + ${#default})) -le $width ] ; then
+       echo "${desc} ${default}"
+    else
+       local padding=$(printf "%${startcol}s" " ")
+
+       if [ ${#desc} -lt $width -a ${#default} -lt $width ] ; then
+           echo "$desc"
+           echo "${padding}${default}"
+       else
+           fill_text $startcol $fillcol "${desc} ${default}"
+       fi
+    fi
+}
+
+# Display usage information for long config options.
+usage_config_options(){( # sub-shell for local declaration of defconf()
+       local def_fillcol=78
+       local fillcol=$def_fillcol
+       local rows=$(stty size | sed -e 's@.* @@')
+       [ -n "$rows" ] && fillcol=$(($rows - 2))
+
+       local startcol=33
+
+       # We need to have at least one column... negative is also a problem.
+       [ $fillcol -le $startcol ] && fillcol=$def_fillcol
+
+       defconf() {
+           local local longopt=$(echo "$1" | tr 'A-Z_' 'a-z-')
+
+           printf "     --%-25s " "${longopt}=${3}" >&2
+
+           usage_display_text $startcol $fillcol "$4" "$2" >&2
+       }
+       . "$installdir/config.default"
+)}