# There are better ways of doing this but not if you still want to be
# able to run straight out of a git tree. :-)
if [ -f "$0" ]; then
- installdir="`dirname \"$0\"`"
+ autocluster="$0"
else
- autocluster=`which $0`
- installdir="`dirname \"$autocluster\"`"
+ autocluster=$(which "$0")
fi
+if [ -L "$autocluster" ] ; then
+ autocluster=$(readlink "$autocluster")
+fi
+installdir=$(dirname "$autocluster")
##END-INSTALLDIR-MAGIC##
####################
Usage: autocluster [OPTION] ... <COMMAND>
options:
-c <file> specify config file (default is "config")
-EOF
-
- releases=$(list_releases)
-
- usage_smart_display \
- defconf "WITH_RELEASE" "" \
- "<string>" "specify preset options for a release using a version string. Possible values are: ${releases}."
-
-cat <<EOF
- -e <expr> execute <expr> and exit (advanced debugging)
+ -e <expr> execute <expr> and exit
+ -E <expr> execute <expr> and continue
-x enable script debugging
--dump dump config settings and exit
cat <<EOF
commands:
+ base [ create | boot ] ...
+
+ cluster [ build | destroy | create | update_hosts | boot | configure ] ...
+
create base
create a base image
- create cluster CLUSTERNAME
+ create cluster [ CLUSTERNAME ]
create a full cluster
create node CLUSTERNAME IP_OFFSET
mount DISK
mount a qemu disk on mnt/
- unmount
+ unmount | umount
unmount a qemu disk from mnt/
bootbase
boot the base image
-
- testproxy
- test your proxy setup
EOF
exit 1
}
###############################
die () {
- fill_text 0 "ERROR: $*" >&2
- exit 1
+ if [ "$no_sanity" = 1 ] ; then
+ fill_text 0 "WARNING: $*" >&2
+ else
+ fill_text 0 "ERROR: $*" >&2
+ exit 1
+ fi
+}
+
+announce ()
+{
+ echo "######################################################################"
+ printf "# %-66s #\n" "$*"
+ echo "######################################################################"
+ echo ""
+}
+
+waitfor ()
+{
+ local file="$1"
+ local msg="$2"
+ local timeout="$3"
+
+ local tmpfile=$(mktemp)
+
+ cat <<EOF >"$tmpfile"
+spawn tail -n 10000 -f $file
+expect -timeout $timeout -re "$msg"
+EOF
+
+ export LANG=C
+ expect "$tmpfile"
+ rm -f "$tmpfile"
+
+ if ! grep -E "$msg" "$file" > /dev/null; then
+ echo "Failed to find \"$msg\" in \"$file\""
+ return 1
+ fi
+
+ return 0
}
###############################
run_hooks ()
{
local hook_var="$1"
+ shift
local i
for i in ${!hook_var} ; do
- $i
+ $i "$@"
done
}
+# Use with care, since this may clear some autocluster defaults.!
+clear_hooks ()
+{
+ local hook_var="$1"
+
+ eval "$hook_var=\"\""
+}
+
##############################
+# These hooks are intended to customise the value of $DISK. They have
+# access to 1 argument ("base", "system", "shared") and the variables
+# $VIRTBASE, $CLUSTER, $BASENAME (for "base"), $NAME (for "system"),
+# $SHARED_DISK_NUM (for "shared"). A hook must be deterministic and
+# should not be stateful, since they can be called multiple times for
+# the same disk.
+hack_disk_hooks=""
+
# common node creation stuff
create_node_COMMON ()
{
local type="$3"
local template_file="${4:-$NODE_TEMPLATE}"
- if [ "$SYSTEM_DISK_FORMAT" = "raw" -a "$BASE_FORMAT" != "raw" ] ; then
- die "Error: if SYSTEM_DISK_FORMAT is \"raw\" then BASE_FORMAT must also be \"raw\"."
+ if [ "$SYSTEM_DISK_FORMAT" != "qcow2" -a "$BASE_FORMAT" = "qcow2" ] ; then
+ die "Error: if BASE_FORMAT is \"qcow2\" then SYSTEM_DISK_FORMAT must also be \"qcow2\"."
+ fi
+
+ local IPNUM=$(($FIRSTIP + $ip_offset))
+ make_network_map
+
+ # Determine base image name. We use $DISK temporarily to allow
+ # the path to be hacked.
+ local DISK="${VIRTBASE}/${BASENAME}.${BASE_FORMAT}"
+ if [ "$BASE_PER_NODE_TYPE" = "yes" ] ; then
+ DISK="${VIRTBASE}/${BASENAME}-${type}.${BASE_FORMAT}"
fi
+ run_hooks hack_disk_hooks "base"
+ local base_disk="$DISK"
- IPNUM=$(($FIRSTIP + $ip_offset))
+ # Determine the system disk image name.
DISK="${VIRTBASE}/${CLUSTER}/${NAME}.${SYSTEM_DISK_FORMAT}"
- local base_disk="${VIRTBASE}/${BASENAME}.${BASE_FORMAT}"
+ run_hooks hack_disk_hooks "system"
- mkdir -p $VIRTBASE/$CLUSTER tmp
+ local di="$DISK"
+ if [ "$DISK_FOLLOW_SYMLINKS" = "yes" -a -L "$DISK" ] ; then
+ di=$(readlink "$DISK")
+ fi
+ rm -f "$di"
+ local di_dirname="${di%/*}"
+ mkdir -p "$di_dirname"
- echo "Creating the disk"
- rm -f "$DISK"
case "$SYSTEM_DISK_FORMAT" in
qcow2)
- qemu-img create -b "$base_disk" -f qcow2 "$DISK"
+ echo "Creating the disk..."
+ qemu-img create -b "$base_disk" -f qcow2 "$di"
+ create_node_configure_image "$DISK" "$type"
;;
raw)
- cp -v --sparse=always "$base_disk" "$DISK"
+ echo "Creating the disk..."
+ cp -v --sparse=always "$base_disk" "$di"
+ create_node_configure_image "$DISK" "$type"
+ ;;
+ reflink)
+ echo "Creating the disk..."
+ cp -v --reflink=always "$base_disk" "$di"
+ create_node_configure_image "$DISK" "$type"
+ ;;
+ mmclone)
+ echo "Creating the disk (using mmclone)..."
+ local base_snap="${base_disk}.snap"
+ [ -f "$base_snap" ] || mmclone snap "$base_disk" "$base_snap"
+ mmclone copy "$base_snap" "$di"
+ create_node_configure_image "$DISK" "$type"
+ ;;
+ none)
+ echo "Skipping disk image creation as requested"
;;
*)
die "Error: unknown SYSTEM_DISK_FORMAT=\"${SYSTEM_DISK_FORMAT}\"."
esac
- mount_disk $DISK
- setup_base "$type"
- setup_network
- unmount_disk
-
- set_macaddrs $CLUSTER $ip_offset
- UUID=`uuidgen`
+ # Pull the UUID for this node out of the map.
+ UUID=$(awk "\$1 == $ip_offset {print \$2}" $uuid_map)
+ mkdir -p tmp
+
echo "Creating $NAME.xml"
substitute_vars $template_file tmp/$NAME.xml
$VIRSH define tmp/$NAME.xml
}
+create_node_configure_image ()
+{
+ local disk="$1"
+ local type="$2"
+
+ diskimage mount "$disk"
+ setup_base "$type"
+ diskimage unmount
+}
+
# Provides an easy way of removing nodes from $NODE.
create_node_null () {
:
}
+hack_network_map_hooks=""
+
+# Uses: CLUSTER, NAME, NETWORKS, FIRSTIP, ip_offset
+make_network_map ()
+{
+ network_map="tmp/network_map.$NAME"
+
+ if [ -n "$CLUSTER" ] ; then
+ local md5=$(echo "$CLUSTER" | md5sum)
+ local nh=$(printf "%02x" $ip_offset)
+ local mac_prefix="02:${md5:0:2}:${md5:2:2}:00:${nh}:"
+ else
+ local mac_prefix="02:42:42:00:00:"
+ fi
+
+ local n
+ local count=1
+ for n in $NETWORKS ; do
+ local ch=$(printf "%02x" $count)
+ local mac="${mac_prefix}${ch}"
+
+ set -- ${n//,/ }
+ local ip_bits="$1" ; shift
+ local dev="$1" ; shift
+ local opts="$*"
+
+ local net="${ip_bits%/*}"
+ local netname="acnet_${net//./_}"
+
+ local ip="${net%.*}.${IPNUM}"
+ local mask="255.255.255.0"
+
+ # This can be used to override the variables in the echo
+ # statement below. The hook can use any other variables
+ # available in this function.
+ run_hooks hack_network_map_hooks
+
+ echo "${netname} ${dev} ${ip} ${mask} ${mac} ${opts}"
+ count=$(($count + 1))
+ done >"$network_map"
+}
+
##############################
hack_nodes_functions=
Some cluster filesystems have problems with other characters."
}
+hosts_file=
+
common_nodelist_hacking ()
{
# Rework the NODES list
local sname=""
local hosts_line
- local ip_addr="$IPBASE.0.$(($FIRSTIP + $ip_offset))"
+ local ip_addr="${NETWORK_PRIVATE_PREFIX}.$(($FIRSTIP + $ip_offset))"
if [ "$ctdb_node" = 1 ] ; then
num_ctdb_nodes=$(($num_ctdb_nodes + 1))
ctdb_nodes_line ()
{
[ "$ctdb_node" = 1 ] || return 0
- echo "$IPBASE.0.$(($FIRSTIP + $ip_offset))"
+ echo "${NETWORK_PRIVATE_PREFIX}.$(($FIRSTIP + $ip_offset))"
num_nodes=$(($num_nodes + 1))
}
nodes_file="tmp/nodes.$CLUSTER"
local num_nodes=0
hack_all_nodes_with ctdb_nodes_line >$nodes_file
: "${NUMNODES:=${num_nodes}}" # Set $NUMNODES if necessary
+
+ # Build UUID map
+ uuid_map="tmp/uuid_map.$CLUSTER"
+ uuid_map_line ()
+ {
+ echo "${ip_offset} $(uuidgen) ${node_type}"
+ }
+ hack_all_nodes_with uuid_map_line >$uuid_map
}
create_cluster_hooks=
cluster_created_hooks=
-create_cluster ()
+cluster_create ()
{
- CLUSTER="$1"
+ # Use $1. If not set then use value from configuration file.
+ CLUSTER="${1:-${CLUSTER}}"
+ announce "cluster create \"${CLUSTER}\""
+ [ -n "$CLUSTER" ] || die "\$CLUSTER not set"
sanity_check_cluster_name
for_each_node call_func create_node
echo "Cluster $CLUSTER created"
+ echo ""
+
+ run_hooks cluster_created_hooks
+}
+
+cluster_created_hosts_message ()
+{
echo "You may want to add this to your /etc/hosts file:"
cat $hosts_file
+}
- run_hooks cluster_created_hooks
+register_hook cluster_created_hooks cluster_created_hosts_message
+
+cluster_destroy ()
+{
+ announce "cluster destroy \"${CLUSTER}\""
+ [ -n "$CLUSTER" ] || die "\$CLUSTER not set"
+
+ vircmd destroy "$CLUSTER" || true
+}
+
+cluster_update_hosts ()
+{
+ announce "cluster update_hosts \"${CLUSTER}\""
+ [ -n "$CLUSTER" ] || die "\$CLUSTER not set"
+
+ [ -n "$hosts_file" ] || hosts_file="tmp/hosts.${CLUSTER}"
+ [ -r "$hosts_file" ] || die "Missing hosts file \"${hosts_file}\""
+
+ local pat="# autocluster ${CLUSTER}\$|[[:space:]]${CLUSTER}(n|base)[[:digit:]]+"
+
+ local t="/etc/hosts.${CLUSTER}"
+ grep -E "$pat" /etc/hosts >"$t" || true
+ if diff -B "$t" "$hosts_file" >/dev/null ; then
+ rm "$t"
+ return
+ fi
+
+ local old=/etc/hosts.old.autocluster
+ cp /etc/hosts "$old"
+ local new=/etc/hosts.new
+ grep -Ev "$pat" "$old" |
+ cat -s - "$hosts_file" >"$new"
+
+ mv "$new" /etc/hosts
+
+ echo "Made these changes to /etc/hosts:"
+ diff -u "$old" /etc/hosts || true
+}
+
+cluster_boot ()
+{
+ [ -n "$CLUSTER_PATTERN" ] || CLUSTER_PATTERN="$CLUSTER"
+ announce "cluster boot \"${CLUSTER_PATTERN}\""
+ [ -n "$CLUSTER_PATTERN" ] || die "\$CLUSTER_PATTERN not set"
+
+ vircmd start "$CLUSTER_PATTERN"
+
+ local nodes=$(vircmd dominfo "$CLUSTER_PATTERN" 2>/dev/null | \
+ sed -n -e 's/Name: *//p')
+
+ # Wait for each node
+ local i
+ for i in $nodes ; do
+ waitfor "${KVMLOG}/serial.$i" "login:" 300 || {
+ vircmd destroy "$CLUSTER_PATTERN"
+ die "Failed to create cluster"
+ }
+ done
+
+ # Move past the last line of log output
+ echo ""
+}
+
+cluster_configure ()
+{
+ announce "cluster configure \"${CLUSTER}\""
+ [ -n "$CLUSTER" ] || die "\$CLUSTER not set"
+
+ local n1="${CLUSTER}n1"
+ local ssh="ssh -o StrictHostKeyChecking=no"
+
+ case "$CLUSTER_TYPE" in
+ "build")
+ $ssh "$n1" ./scripts/install_packages.sh clusterfs build
+ $ssh "$n1" ./scripts/setup_cluster.sh build
+ ;;
+
+ "ad")
+ $ssh "$n1" ./scripts/install_packages.sh ad_server
+ $ssh "$n1" ./scripts/configure_cluster.sh ad_server
+ ;;
+
+ "samba")
+ [ -n "$CLUSTER_PATTERN" ] || CLUSTER_PATTERN="$CLUSTER"
+
+ local nodes=$(vircmd dominfo "$CLUSTER_PATTERN" 2>/dev/null | \
+ sed -n -e 's/Name: *//p')
+
+ for i in $nodes ; do
+ $ssh "$i" ./scripts/install_packages.sh clusterfs nas
+ done
+
+ $ssh "$n1" ./scripts/setup_cluster.sh clusterfs nas
+ ;;
+ esac
}
create_one_node ()
call_func create_node "$@"
echo "Requested node created"
+ echo ""
echo "You may want to update your /etc/hosts file:"
cat $hosts_file
}
###################
-# create base image
-create_base() {
- NAME="$BASENAME"
- DISK="${VIRTBASE}/${NAME}.${BASE_FORMAT}"
+kickstart_floppy_create_hooks=
+
+guess_install_network ()
+{
+ # Figure out IP address to use during base install. Default to
+ # the IP address of the 1st (private) network. If a gateway is
+ # specified then use the IP address associated with it.
+ INSTALL_IP=""
+ INSTALL_GW=""
+ local netname dev ip mask mac opts
+ while read netname dev ip mask mac opts; do
+ local o
+ for o in $opts ; do
+ case "$o" in
+ gw\=*)
+ INSTALL_GW="${o#gw=}"
+ INSTALL_IP="${ip}${FIRSTIP}"
+ esac
+ done
+ [ -n "$INSTALL_IP" ] || INSTALL_IP="$ip"
+ done <"$network_map"
+}
+
+# create base image
+base_create()
+{
+ local NAME="$BASENAME"
+ local DISK="${VIRTBASE}/${NAME}.${BASE_FORMAT}"
+ run_hooks hack_disk_hooks "base"
mkdir -p $KVMLOG
echo "Testing WEBPROXY $WEBPROXY"
test_proxy
+ local di="$DISK"
+ if [ "$DISK_FOLLOW_SYMLINKS" = "yes" -a -L "$DISK" ] ; then
+ di=$(readlink "$DISK")
+ fi
+ rm -f "$di"
+ local di_dirname="${di%/*}"
+ mkdir -p "$di_dirname"
+
echo "Creating the disk"
- qemu-img create -f $BASE_FORMAT "$DISK" $DISKSIZE
+ qemu-img create -f $BASE_FORMAT "$di" $DISKSIZE
rm -rf tmp
mkdir -p mnt tmp tmp/ISO
setup_timezone
- echo "Creating kickstart file from template"
- substitute_vars "$KICKSTART" "tmp/ks.cfg"
-
- if [ $INSTALLKEY = "--skip" ]; then
- cat <<EOF
---------------------------------------------------------------------------------------
-WARNING: You have not entered an install key. Some RHEL packages will not be installed.
-
-Please enter a valid RHEL install key in your config file like this:
+ make_network_map
- INSTALLKEY="1234-5678-0123-4567"
+ guess_install_network
-The install will continue without an install key in 5 seconds
---------------------------------------------------------------------------------------
-EOF
- sleep 5
- fi
+ echo "Creating kickstart file from template"
+ substitute_vars "$KICKSTART" "tmp/ks.cfg"
# $ISO gets $ISO_DIR prepended if it doesn't start with a leading '/'.
case "$ISO" in
echo "Creating kickstart floppy"
dd if=/dev/zero of=tmp/floppy.img bs=1024 count=1440
- mkdosfs tmp/floppy.img
+ mkdosfs -n KICKSTART tmp/floppy.img
mount -o loop -t msdos tmp/floppy.img mnt
cp tmp/ks.cfg mnt
mount -o loop,ro $ISO tmp/ISO
cp tmp/ISO/isolinux/isolinux.bin tmp
cp tmp/ISO/isolinux/vmlinuz tmp
cp tmp/ISO/isolinux/initrd.img tmp
+
+ run_hooks kickstart_floppy_create_hooks
+
umount tmp/ISO
umount mnt
sleep 2
# wait for the install to finish
- if ! waitfor $KVMLOG/serial.$NAME "you may safely reboot your system" $CREATE_BASE_TIMEOUT ; then
+ if ! waitfor $KVMLOG/serial.$NAME "$KS_DONE_MESSAGE" $CREATE_BASE_TIMEOUT ; then
$VIRSH destroy $NAME
- die "Failed to create base image $DISK"
+ die "Failed to create base image ${DISK} after waiting for ${CREATE_BASE_TIMEOUT} seconds.
+You may need to increase the value of CREATE_BASE_TIMEOUT.
+Alternatively, the install might have completed but KS_DONE_MESSAGE
+(currently \"${KS_DONE_MESSAGE}\")
+may not have matched anything at the end of the kickstart output."
fi
$VIRSH destroy $NAME
Install finished, base image $DISK created
You may wish to run
+ chcon -t virt_content_t $DISK
chattr +i $DISK
To ensure that this image does not change
###############################
# boot the base disk
-boot_base() {
- CLUSTER="$1"
+base_boot() {
+ rm -rf tmp
+ mkdir -p tmp
NAME="$BASENAME"
DISK="${VIRTBASE}/${NAME}.${BASE_FORMAT}"
- rm -rf tmp
- mkdir -p tmp
-
IPNUM=$FIRSTIP
+
+ make_network_map
+
CLUSTER="base"
- mount_disk $DISK
+ diskimage mount $DISK
setup_base
- unmount_disk
+ diskimage unmount
UUID=`uuidgen`
######################################################################
-# various functions...
-
-# Set some MAC address variables based on a hash of the cluster name
-# plus the node number and each adapter number.
-set_macaddrs () {
- local cname="$1"
- local ip_offset="$2"
-
- local md5=$(echo $cname | md5sum)
- local nh=$(printf "%02x" $ip_offset)
- local mac_prefix="02:${md5:0:2}:${md5:2:2}:00:${nh}:"
-
- MAC1="${mac_prefix}01"
- MAC2="${mac_prefix}02"
- MAC3="${mac_prefix}03"
- MAC4="${mac_prefix}04"
- MAC5="${mac_prefix}05"
- MAC6="${mac_prefix}06"
-}
-
-# mount a qemu image via nbd
-connect_nbd() {
- echo "Connecting nbd to $1"
- mkdir -p mnt
- modprobe nbd
- killall -9 -q $QEMU_NBD || true
- $QEMU_NBD -p 1300 $1 &
- sleep 1
- [ -r $NBD_DEVICE ] || {
- mknod $NBD_DEVICE b 43 0
- }
- umount mnt 2> /dev/null || true
- nbd-client -d $NBD_DEVICE > /dev/null 2>&1 || true
- killall -9 -q nbd-client || true
- nbd-client localhost 1300 $NBD_DEVICE > /dev/null 2>&1 || true &
- sleep 1
-}
-
-# disconnect nbd
-disconnect_nbd() {
- echo "Disconnecting nbd"
- sync; sync
- nbd-client -d $NBD_DEVICE > /dev/null 2>&1 || true
- killall -9 -q nbd-client || true
- killall -q $QEMU_NBD || true
-}
-
-setup_image ()
-{
- local disk="$1"
-
- case "$SYSTEM_DISK_FORMAT" in
- qcow2)
- connect_nbd "$disk"
- device=$NBD_DEVICE
- extra_mount_options=""
- ;;
- raw)
- device="$disk"
- extra_mount_options=",loop"
- ;;
- *)
- die "Error: unknown SYSTEM_DISK_FORMAT=${SYSTEM_DISK_FORMAT}."
- esac
-}
-
-cleanup_image ()
-{
- case "$SYSTEM_DISK_FORMAT" in
- qcow2)
- disconnect_nbd
- ;;
- raw)
- :
- ;;
- *)
- die "Error: unknown SYSTEM_DISK_FORMAT=${SYSTEM_DISK_FORMAT}."
- esac
-}
+# Updating a disk image...
-# mount a qemu image via nbd
-mount_disk()
+diskimage ()
{
- local disk="$1"
-
- local device extra_mount_options
-
- setup_image "$disk"
-
- echo "Mounting disk $disk"
- local mount_ok=0
- local i
- for i in $(seq 1 5); do
- mount -o offset=32256${extra_mount_options} $device mnt && {
- mount_ok=1
- break
- }
- umount mnt 2>/dev/null || true
- sleep 1
- done
- [ $mount_ok = 1 ] || die "Failed to mount $disk"
-
- [ -d mnt/root ] || {
- echo "Mounted directory does not look like a root filesystem"
- ls -latr mnt
- exit 1
- }
-}
-
-# unmount a qemu image
-unmount_disk() {
- echo "Unmounting disk"
- sync; sync;
- umount mnt || umount mnt || true
- cleanup_image
+ local func="$1"
+ shift
+ call_func diskimage_"$func" "$SYSTEM_DISK_ACCESS_METHOD" "$@"
}
# setup the files from $BASE_TEMPLATES/, substituting any variables
[ -d "$d" ] || return 0
local f
- for f in $(cd "$d" && find . \! -name '*~') ; do
+ for f in $(cd "$d" && find . \! -name '*~' \( -type d -name .svn -prune -o -print \) ) ; do
+ f="${f#./}" # remove leading "./" for clarity
if [ -d "$d/$f" ]; then
- mkdir -p mnt/"$f"
- else
- substitute_vars "$d/$f" "mnt/$f"
+ # Don't chmod existing directory
+ if diskimage is_directory "/$f" ; then
+ continue
+ fi
+ diskimage mkdir_p "/$f"
+ else
+ echo " Install: $f"
+ diskimage substitute_vars "$d/$f" "/$f"
fi
- chmod --reference="$d/$f" "mnt/$f"
+ diskimage chmod_reference "$d/$f" "/$f"
done
}
setup_base_hooks=
-setup_base()
+setup_base_ssh_keys ()
{
- local type="$1"
-
- umask 022
- echo "Copy base files"
- copy_base_dir_substitute_templates "all"
- if [ -n "$type" ] ; then
- copy_base_dir_substitute_templates "$type"
- fi
-
# this is needed as git doesn't store file permissions other
# than execute
- chmod 600 mnt/etc/ssh/*key mnt/root/.ssh/*
- chmod 700 mnt/etc/ssh mnt/root/.ssh mnt/root
+ # Note that we protect the wildcards from the local shell.
+ diskimage chmod 600 "/etc/ssh/*key" "/root/.ssh/*"
+ diskimage chmod 700 "/etc/ssh" "/root/.ssh" "/root"
if [ -r "$HOME/.ssh/id_rsa.pub" ]; then
echo "Adding $HOME/.ssh/id_rsa.pub to ssh authorized_keys"
- cat "$HOME/.ssh/id_rsa.pub" >> mnt/root/.ssh/authorized_keys
+ diskimage append_text_file "$HOME/.ssh/id_rsa.pub" "/root/.ssh/authorized_keys"
fi
if [ -r "$HOME/.ssh/id_dsa.pub" ]; then
echo "Adding $HOME/.ssh/id_dsa.pub to ssh authorized_keys"
- cat "$HOME/.ssh/id_dsa.pub" >> mnt/root/.ssh/authorized_keys
+ diskimage append_text_file "$HOME/.ssh/id_dsa.pub" "/root/.ssh/authorized_keys"
fi
+}
+
+register_hook setup_base_hooks setup_base_ssh_keys
+
+setup_base_grub_conf ()
+{
echo "Adjusting grub.conf"
local o="$EXTRA_KERNEL_OPTIONS" # For readability.
- sed -e "s/console=ttyS0,19200/console=ttyS0,115200/" \
- -e "s/ nodmraid//" -e "s/ nompath//" \
- -e "s/quiet/noapic divider=10${o:+ }${o}/g" mnt/boot/grub/grub.conf -i.org
+ local grub_configs="/boot/grub/grub.conf"
+ if ! diskimage is_file "$grub_configs" ; then
+ grub_configs="/etc/default/grub /boot/grub2/grub.cfg"
+ fi
+ local c
+ for c in $grub_configs ; do
+ diskimage sed "$c" \
+ -e "s/console=ttyS0,19200/console=ttyS0,115200/" \
+ -e "s/ console=tty1//" -e "s/ rhgb/ norhgb/" \
+ -e "s/ nodmraid//" -e "s/ nompath//" \
+ -e "s/quiet/noapic divider=10${o:+ }${o}/g"
+ done
+}
+
+register_hook setup_base_hooks setup_base_grub_conf
+
+setup_base()
+{
+ local type="$1"
+
+ umask 022
+ echo "Copy base files"
+ copy_base_dir_substitute_templates "all"
+ if [ -n "$type" ] ; then
+ copy_base_dir_substitute_templates "$type"
+ fi
+
run_hooks setup_base_hooks
}
# setup various networking components
-setup_network() {
- echo "Setting up networks"
+setup_network()
+{
+ # This avoids doing anything when we're called from boot_base().
+ if [ -z "$hosts_file" ] ; then
+ echo "Skipping network-related setup"
+ return
+ fi
- cat $hosts_file >>mnt/etc/hosts
+ echo "Setting up networks"
+ diskimage append_text_file "$hosts_file" "/etc/hosts"
echo "Setting up /etc/ctdb/nodes"
- mkdir -p mnt/etc/ctdb
- cp $nodes_file mnt/etc/ctdb/nodes
+ diskimage mkdir_p "/etc/ctdb"
+ diskimage put "$nodes_file" "/etc/ctdb/nodes"
[ "$WEBPROXY" = "" ] || {
- echo "export http_proxy=$WEBPROXY" >> mnt/etc/bashrc
+ diskimage append_text "export http_proxy=$WEBPROXY" "/etc/bashrc"
}
if [ -n "$NFSSHARE" -a -n "$NFS_MOUNTPOINT" ] ; then
echo "Enabling nfs mount of $NFSSHARE"
- mkdir -p "mnt$NFS_MOUNTPOINT"
- echo "$NFSSHARE $NFS_MOUNTPOINT nfs intr" >> mnt/etc/fstab
+ diskimage mkdir_p "$NFS_MOUNTPOINT"
+ diskimage append_text "$NFSSHARE $NFS_MOUNTPOINT nfs nfsvers=3,intr 0 0" "/etc/fstab"
fi
- mkdir -p mnt/etc/yum.repos.d
- echo '@@@YUM_TEMPLATE@@@' | substitute_vars - > mnt/etc/yum.repos.d/autocluster.repo
+ diskimage mkdir_p "/etc/yum.repos.d"
+ echo '@@@YUM_TEMPLATE@@@' | diskimage substitute_vars - "/etc/yum.repos.d/autocluster.repo"
+
+ diskimage rm_rf "/etc/udev/rules.d/70-persistent-net.rules"
+
+ echo "Setting up network interfaces: "
+ local netname dev ip mask mac opts
+ while read netname dev ip mask mac opts; do
+ echo " $dev"
+
+ local o gw
+ gw=""
+ for o in $opts ; do
+ case "$o" in
+ gw\=*)
+ gw="${o#gw=}"
+ esac
+ done
+
+ cat <<EOF | \
+ diskimage put - "/etc/sysconfig/network-scripts/ifcfg-${dev}"
+DEVICE=$dev
+ONBOOT=yes
+TYPE=Ethernet
+IPADDR=$ip
+NETMASK=$mask
+HWADDR=$mac
+${gw:+GATEWAY=}${gw}
+EOF
+
+ # This goes to 70-persistent-net.rules
+ cat <<EOF
+# Generated by autocluster
+SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", ATTR{address}=="${mac}", ATTR{type}=="1", KERNEL=="eth*", NAME="${dev}"
+
+EOF
+ done <"$network_map" |
+ diskimage put - "/etc/udev/rules.d/70-persistent-net.rules"
}
+register_hook setup_base_hooks setup_network
+
setup_timezone() {
[ -z "$TIMEZONE" ] && {
[ -r /etc/timezone ] && {
infile="${1:-/dev/null}" # if empty then default to /dev/null
outfile="$2" # optional
- instring=$(cat $infile)
+ tmp_out=$(mktemp)
+ cat "$infile" >"$tmp_out"
# Handle any indirects by looping until nothing changes.
# However, only handle 10 levels of recursion.
count=0
while : ; do
- outstring=$(_substitute_vars "$instring" "@@@")
- [ $? -eq 0 ] || die "Failed to expand template $infile"
+ if ! _substitute_vars "$tmp_out" "@@@" ; then
+ rm -f "$tmp_out"
+ die "Failed to expand template $infile"
+ fi
+
+ # No old version of file means no changes made.
+ if [ ! -f "${tmp_out}.old" ] ; then
+ break
+ fi
- [ "$instring" = "$outstring" ] && break
+ rm -f "${tmp_out}.old"
count=$(($count + 1))
- [ $count -lt 10 ] || \
+ if [ $count -ge 10 ] ; then
+ rm -f "$tmp_out"
die "Recursion too deep in $infile - only 10 levels allowed!"
-
- instring="$outstring"
+ fi
done
# Now regular variables.
- outstring=$(_substitute_vars "$instring" "@@")
- [ $? -eq 0 ] || die "Failed to expand template $infile"
+ if ! _substitute_vars "$tmp_out" "@@" ; then
+ rm -f "$tmp_out"
+ die "Failed to expand template $infile"
+ fi
+ rm -f "${tmp_out}.old"
if [ -n "$outfile" ] ; then
- echo "$outstring" > "$outfile"
+ mv "$tmp_out" "$outfile"
else
- echo "$outstring"
+ cat "$tmp_out"
+ rm -f "$tmp_out"
fi
)}
# @@@ supports leading '|' in variable value, which means to excute a
# command.
_substitute_vars() {(
- instring="$1"
+ tmp_out="$1"
delimiter="${2:-@@}"
- # get the list of variables used in the template
- VARS=`echo "$instring" |
- tr -cs "A-Z0-9_$delimiter" '\012' |
- sort -u |
- sed -n -e "s#^${delimiter}\(.*\)${delimiter}\\$#\1#p"`
+ # Get the list of variables used in the template. The grep
+ # gets rid of any blank lines and lines with extraneous '@'s
+ # next to template substitutions.
+ VARS=$(sed -n -e "s#[^@]*${delimiter}\([A-Z0-9_][A-Z0-9_]*\)${delimiter}[^@]*#\1\n#gp" "$tmp_out" |
+ grep '^[A-Z0-9_][A-Z0-9_]*$' |
+ sort -u)
tmp=$(mktemp)
for v in $VARS; do
fi
# escape some pesky chars
- s=${s//
-/\\n}
+ # This first one can be too slow if done using a bash
+ # variable pattern subsitution.
+ s=$(echo -n "$s" | tr '\n' '\001' | sed -e 's/\o001/\\n/g')
s=${s//#/\\#}
s=${s//&/\\&}
echo "s#${delimiter}${v}${delimiter}#${s}#g"
done > $tmp
- echo "$instring" | sed -f $tmp
+ # Get the in-place sed to make a backup of the old file.
+ # Remove the backup if it is the same as the resulting file -
+ # this acts as a flag to the caller that no changes were made.
+ sed -i.old -f $tmp "$tmp_out"
+ if cmp -s "${tmp_out}.old" "$tmp_out" ; then
+ rm -f "${tmp_out}.old"
+ fi
rm -f $tmp
)}
usage_smart_display load_config
}
-list_releases () {
- local releases=$(cd $installdir/releases && echo *.release)
- releases="${releases//.release}"
- releases="${releases// /\", \"}"
- echo "\"$releases\""
-}
-
-with_release () {
- local release="$1"
- shift # subsequent args are passed to release file
-
- # This simply loads an extra config file from $installdir/releases
- f="${installdir}/releases/${release}.release"
- if [ -r "$f" ] ; then
- . "$f"
- else
- echo "Unknown release \"${release}\" specified to --with-release"
- printf "%-25s" "Supported releases are: "
- # The 70 is lazy but it will do.
- fill_text 25 "$(list_releases)"
- exit 1
- fi
-
+actions_init ()
+{
+ actions=""
}
-has_public_addresses_DEFAULT ()
+actions_add ()
{
- false
+ actions="${actions}${actions:+ }$*"
}
-# Build public address configuration.
-# * 1st public IP: unless specified, last octet is $FIRSTIP + $PUBLIC_IP_OFFSET
-# * Excluded nodes: unless specified via comma-separated list of IP offsets,
-# nodes are excluded via their node types
-# * Number of public addresses per interface is either specified or $NUMNODES.
-make_public_addresses () {
- local firstip="${1:-$(($FIRSTIP + $PUBLIC_IP_OFFSET))}"
- local excluded_nodes="$2"
- local num_addrs="${3:-${NUMNODES}}"
-
- # For delimiting matches.
- excluded_nodes="${excluded_nodes:+,}${excluded_nodes}${excluded_nodes:+,}"
- # Avoid spaces
- excluded_nodes="${excluded_nodes// /}"
-
- make_public_addresses_for_node ()
- {
- [ "$ctdb_node" = 1 ] || return 0
+actions_run ()
+{
+ [ -n "$actions" ] || usage
- echo "[/etc/ctdb/public_addresses:${name}.${DOMAIN}]"
+ local a
+ for a in $actions ; do
+ $a
+ done
+}
- if [ -n "$excluded_nodes" -a \
- "${excluded_nodes/,${ip_offset},}" = "$excluded_nodes" ] ||
- ([ -z "$excluded_nodes" ] &&
- call_func has_public_addresses "$node_type") ; then
+######################################################################
- local e i
- for e in "1" "2" ; do
- for i in $(seq $firstip $(($firstip + $num_addrs - 1))) ; do
- if [ $i -gt 254 ] ; then
- die "make_public_addresses: octet > 254 - consider setting PUBLIC_IP_OFFSET"
- fi
- printf "\t${IPBASE}.${e}.${i}/24 eth${e}\n"
- done
- done
- fi
- echo
- }
- hack_all_nodes_with make_public_addresses_for_node
-}
+post_config_hooks=
######################################################################
############################
# parse command line options
long_opts=$(getopt_config_options)
-getopt_output=$(getopt -n autocluster -o "c:e:xh" -l help,dump,with-release: -l "$long_opts" -- "$@")
+getopt_output=$(getopt -n autocluster -o "c:e:E:xh" -l help,dump -l "$long_opts" -- "$@")
[ $? != 0 ] && usage
use_default_config=true
case "$1" in
-c) shift 2 ; use_default_config=false ;;
-e) shift 2 ;;
+ -E) shift 2 ;;
--) shift ; break ;;
- --with-release) shift 2 ;; # Don't set use_default_config=false!!!
--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.
while true ; do
case "$1" in
- # force at least ./local_file to avoid accidental file from $PATH
- -c) . "$(dirname $2)/$(basename $2)" ; shift 2 ;;
- -e) eval "$2" ; exit ;;
- --with-release)
- with_release "$2"
+ -c)
+ b=$(basename $2)
+ # force at least ./local_file to avoid accidental file
+ # from $PATH
+ . "$(dirname $2)/${b}"
+ # If $CLUSTER is unset then try to base it on the filename
+ if [ ! -n "$CLUSTER" ] ; then
+ case "$b" in
+ *.autocluster)
+ CLUSTER="${b%.autocluster}"
+ esac
+ fi
shift 2
;;
+ -e) no_sanity=1 ; run_hooks post_config_hooks ; eval "$2" ; exit ;;
+ -E) eval "$2" ; shift 2 ;;
-x) set -x; shift ;;
- --dump) dump_config ;;
+ --dump) no_sanity=1 ; run_hooks post_config_hooks ; dump_config ;;
--) shift ; break ;;
-h|--help) usage ;; # Redundant.
--*)
esac
done
+run_hooks post_config_hooks
+
# catch errors
set -e
set -E
exit $es' ERR
# check for needed programs
-check_command nbd-client
check_command expect
-[ "$SYSTEM_DISK_FORMAT" = "raw" ] || check_command $QEMU_NBD
[ $# -lt 1 ] && usage
-command="$1"
+t="$1"
shift
-case $command in
+case "$t" in
+ base)
+ actions_init
+ for t in "$@" ; do
+ case "$t" in
+ create|boot) actions_add "base_${t}" ;;
+ *) usage ;;
+ esac
+ done
+ actions_run
+ ;;
+
+ cluster)
+ actions_init
+ for t in "$@" ; do
+ case "$t" in
+ destroy|create|update_hosts|boot|configure)
+ actions_add "cluster_${t}" ;;
+ build)
+ for t in destroy create update_hosts boot configure ; do
+ actions_add "cluster_${t}"
+ done
+ ;;
+ *) usage ;;
+ esac
+ done
+ actions_run
+ ;;
+
create)
- type=$1
+ t="$1"
shift
- case $type in
+ case "$t" in
base)
[ $# != 0 ] && usage
- create_base
+ base_create
;;
cluster)
[ $# != 1 ] && usage
- create_cluster "$1"
+ cluster_create "$1"
;;
node)
[ $# != 2 ] && usage
;;
mount)
[ $# != 1 ] && usage
- mount_disk "$1"
+ diskimage mount "$1"
;;
- unmount)
+ unmount|umount)
[ $# != 0 ] && usage
- unmount_disk
+ diskimage unmount
;;
bootbase)
- boot_base;
- ;;
- testproxy)
- test_proxy;
+ base_boot;
;;
*)
usage;