crowbar/crowbar-core

View on GitHub
chef/cookbooks/provisioner/templates/suse/crowbar_register.erb

Summary

Maintainability
Test Coverage
#! /bin/bash -e
# vim: sw=4 et
#
# Copyright 2013, SUSE
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#  http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

# TODO:
#   change timezeone?


usage () {
    cat <<EOF
<% if !node[:provisioner][:keep_existing_hostname] -%>
`basename $0` [-h|--help] [-v|--verbose] [-f|--force] [--gpg-auto-import-keys] [--keep-existing-hostname] [--no-gpg-checks] [--interface IF]
<% else -%>
`basename $0` [-h|--help] [-v|--verbose] [-f|--force] [--gpg-auto-import-keys] [--no-gpg-checks] [--interface IF]
<% end -%>

Register node in Crowbar.
EOF
    exit
}

# Variables for options
CROWBAR_AUTO_IMPORT_KEYS=0
CROWBAR_NO_GPG_CHECKS=0
CROWBAR_FORCE=0
CROWBAR_VERBOSE=0
<% if !node[:provisioner][:keep_existing_hostname] -%>
KEEP_EXISTING_HOSTNAME=0
<% else -%>
KEEP_EXISTING_HOSTNAME=1
<% end -%>
DEFINEDDEV=

while test $# -gt 0; do
    case "$1" in
        -h|--help|--usage|-\?) usage ;;
        -v|--verbose) CROWBAR_VERBOSE=1 ;;
        -f|--force) CROWBAR_FORCE=1 ;;
        <% if !node[:provisioner][:keep_existing_hostname] -%>
        --keep-existing-hostname) KEEP_EXISTING_HOSTNAME=1 ;;
        <% end -%>
        --gpg-auto-import-keys) CROWBAR_AUTO_IMPORT_KEYS=1 ;;
        --no-gpg-checks) CROWBAR_NO_GPG_CHECKS=1 ;;
        --interface)
            if test $# -eq 1; then
                echo "Option --interface requires an argument."
                exit 1
            else
                shift
                DEFINEDDEV="$1"
            fi
            ;;
        *) ;;
    esac
    shift
done

if [ $(id -u) -gt 0 ]; then
    echo "$0 needs to be run as root user."
    echo ""
    exit 1
fi

if test -n "$SSH_CONNECTION" -a -z "$STY"; then
    echo "Not running in screen. Please run $0 inside screen to avoid problems during network re-configuration."
    echo ""
    exit 1
fi

if test $CROWBAR_FORCE -ne 1; then
    echo    "Running this tool will alter the system for integration with Crowbar."
    echo -n "Continue? [y/N]: "
    read ANSWER
    if test "x$ANSWER" != xy -a "x$ANSWER" != xY; then
        exit 0
    fi
fi


# Helper functions
# ----------------

add_group() {
  gid=$(getent group $1 | cut -f 3 -d ":")
  if [[ -z "$gid" ]]; then
    groupadd --system --gid $2 $1
  elif [[ "$gid" != "$2" ]]; then
    groupmod -g $2 $1
    if [[ -n "$3" ]] && rpm -q $3 >/dev/null; then
      zypper --non-interactive install --force $3
    fi
  fi
}

add_user() {
  uid=$(getent passwd $1 | cut -f 3 -d ":")
  gid=$(getent passwd $1 | cut -f 4 -d ":")
  if [[ -z "$uid" ]]; then
    useradd --system --shell /sbin/nologin -d / --gid $2 --uid $2 --groups $3 $1
  elif [[ "$uid" != $2 ]] || [[ "$gid" != $2 ]]; then
    if [[ -n "$gid" ]]; then
      echo "Group $1 for the same username doesn't exist. Please clean up manually."
      exit 1
    fi
    usermod -u $2 $1
    groupmod -g $2 $1
    if [[ -n "$4" ]] && rpm -q $4 >/dev/null; then
      zypper --non-interactive install --force $4
    fi
  fi
}


# Variables that are templated
# ----------------------------

ADMIN_IP="<%= @admin_ip %>"
ADMIN_BROADCAST="<%= @admin_broadcast %>"
WEB_PORT="<%= @web_port %>"
HTTP_SERVER="http://${ADMIN_IP}:${WEB_PORT}"
CROWBAR_OS="<%= @os %>"
CROWBAR_ARCH="<%= @arch %>"
DOMAIN="<%= @domain %>"
NTP_SERVERS="<%= @ntp_servers_ips.join(" ") %>"

# we need to know an architecture we are running on
ARCH=`uname -m`


# Make sure we know which interface to use as a basis
# ---------------------------------------------------

NIC_CANDIDATES=
DEFINEDDEV_FOUND=0
MULTIPLE_NICS=0
for nic in /sys/class/net/*; do
    [[ -f $nic/address && -f $nic/type && \
        $(cat "$nic/type") = 1 ]] || continue
    NICDEV="${nic##*/}"

    if ip addr show $NICDEV | grep -q " brd $ADMIN_BROADCAST "; then
        test "x$NICDEV" == "x$DEFINEDDEV" && DEFINEDDEV_FOUND=1
        test -n "$NIC_CANDIDATES" && MULTIPLE_NICS=1
        NIC_CANDIDATES="$NIC_CANDIDATES $NICDEV"
    fi
done

# remove leading space
NIC_CANDIDATES=${NIC_CANDIDATES## }

if test $DEFINEDDEV_FOUND -ne 1 -a -n "$DEFINEDDEV"; then
    if test -f "/sys/class/net/$DEFINEDDEV/address"; then
        echo "Defined interface to use ($DEFINEDDEV) does not seem to be on the admin network."
        echo "Is DHCP used for it?"
    else
        echo "Defined interface to use ($DEFINEDDEV) was not detected."
    fi
    exit 1
elif test "x$NIC_CANDIDATES" == "x"; then
    echo "Cannot find any good interface that would be on the admin network."
    echo "Did the node boot with DHCP on the admin network?"
    exit 1
elif test $DEFINEDDEV_FOUND -ne 1 -a $MULTIPLE_NICS -ne 0; then
    echo "More than one potential interface can be used:"
    echo "   $NIC_CANDIDATES"
    echo ""
    echo "Please define the one to use with the --interface option."
    exit 1
fi

if test -n "$DEFINEDDEV"; then
    BOOTDEV="$DEFINEDDEV"
else
    BOOTDEV="$NIC_CANDIDATES"
fi

# Setup groups and users
# ---------------

# for making HA on shared NFS backend storage work
add_group glance 200 openstack-glance
add_group qemu 201 qemu
add_group kvm 202 qemu
add_group cinder 203 openstack-cinder
add_user glance 200 glance openstack-glance
add_user qemu 201 kvm qemu
add_user cinder 203 cinder openstack-cinder

# Check that we're really on the admin network
# --------------------------------------------

MD5_ADMIN=$(curl -s $HTTP_SERVER/$CROWBAR_OS/$CROWBAR_ARCH/crowbar_register | md5sum | awk -F " " '{print $1}')
MD5_LOCAL=$(md5sum $0 | awk -F " " '{print $1}')

if test "x$MD5_ADMIN" != "x$MD5_LOCAL"; then
    echo "This script does not match the one from the administration server."
    echo "Please download $HTTP_SERVER/$CROWBAR_OS/$CROWBAR_ARCH/crowbar_register and use it."
    exit 1
fi

# Setup the repos
# ---------------

<% @repos.keys.sort.each do |name| %>
zypper -n ar "<%= @repos[name][:url] %>" "<%= name %>"
  <% unless @repos[name][:priority] == 99 -%>
zypper -n mr -p "<%= @repos[name][:priority] %>" "<%= name %>"
  <% end -%>
<% end %>
<% if @repos.keys.include? "PTF" -%>

# PTF has an unknown key, and this is expected
zypper -n --gpg-auto-import-keys refresh -r PTF
<% end -%>

ZYPPER_REF_OPT=
test $CROWBAR_AUTO_IMPORT_KEYS -eq 1 && ZYPPER_REF_OPT=--gpg-auto-import-keys
test $CROWBAR_NO_GPG_CHECKS -eq 1 && ZYPPER_REF_OPT=--no-gpg-checks
zypper -n $ZYPPER_REF_OPT refresh


# Install packages that are needed
# --------------------------------

PATTERNS_INSTALL=
PACKAGES_INSTALL=

# Obvious dependencies
PACKAGES_INSTALL="$PACKAGES_INSTALL openssh"

# From autoyast profile
<% if @platform == "suse" -%>
PATTERNS_INSTALL="$PATTERNS_INSTALL Minimal base"
<% elsif @platform == "opensuse" -%>
PATTERNS_INSTALL="$PATTERNS_INSTALL base enhanced_base sw_management"
<% end -%>
PACKAGES_INSTALL="$PACKAGES_INSTALL netcat-openbsd ruby2.1-rubygem-chef ruby2.1-rubygem-crowbar-client"

# We also need ntp for this script
PACKAGES_INSTALL="$PACKAGES_INSTALL ntp"

case $ARCH in
    x86_64) PACKAGES_INSTALL+=" biosdevname";;
esac

# Also install relevant microcode packages
case $(grep -m 1 'model name' /proc/cpuinfo) in
    *Intel\(R\)*)
        PACKAGES_INSTALL+=" ucode-intel";;
    *AuthenticAMD*)
        PACKAGES_INSTALL+=" ucode-amd";;
esac

zypper --non-interactive install -t pattern $PATTERNS_INSTALL
# Auto-agree with the license since it was already agreed on for the admin server
<% if @platform == "suse" && @target_platform_version.to_f >= 12.1 -%>
zypper --non-interactive install --auto-agree-with-licenses suse-openstack-cloud-crowbar-release supportutils-plugin-suse-openstack-cloud
<% end -%>
zypper --non-interactive install $PACKAGES_INSTALL<%= " '#{@packages.join("' '")}'" unless @packages.empty? %>

<% if @platform == "opensuse" -%>
# we need rsyslog, not systemd-logger
zypper --non-interactive remove systemd-logger
<% end -%>

# Fail early if we know we can't succeed because of a missing chef-client
if ! which chef-client &> /dev/null; then
  echo "chef-client is not available."
  exit 1
fi

# Set up /etc/crowbarrc with crowbarctl credentials
# -------------------------------------------
cat <<EOF > /etc/crowbarrc
[default]
server = <%= @crowbar_protocol %>://<%= @admin_ip %>
username = <%= @crowbar_client_username %>
password = <%= @crowbar_client_password %>
<% unless @crowbar_verify_ssl %>
verify_ssl = 0
<% end %>
EOF


# Check that we can really register this node
# -------------------------------------------

if ! crowbarctl restricted ping &> /dev/null; then
  echo "Failed to contact the administration server."
  echo "Is the administration server up?"
  exit 1
fi

# Copied from sledgehammer
MAC=$(cat /sys/class/net/$BOOTDEV/address)
if test $KEEP_EXISTING_HOSTNAME -ne 1; then
  HOSTNAME="d${MAC//:/-}.${DOMAIN}"
else
  HOSTNAME=$(cat /etc/HOSTNAME)
  if ! echo "$HOSTNAME" | grep -q "\.$DOMAIN$"; then
    echo "The fully qualified domain name did not contain the $DOMAIN cloud domain."
    exit 1
  elif echo "${HOSTNAME%%.$DOMAIN}" | grep -q "\."; then
    echo "The hostname is in a subdomain of the $DOMAIN cloud domain."
    exit 1
  fi
fi

if crowbarctl restricted show $HOSTNAME &> /dev/null; then
  echo "This node seems to be already registered."
  exit 1
fi



# Some initial setup
# ------------------

# Disable firewall
if [ -x /sbin/SuSEfirewall2 ] ; then
    /sbin/SuSEfirewall2 off
fi

# SSH setup
systemctl enable sshd.service
systemctl start sshd.service

mkdir -p /var/log/crowbar

# Taken from autoyast profile
mkdir -p /root/.ssh
chmod 700 /root/.ssh
if ! curl -s -o /root/.ssh/authorized_keys.wget \
    $HTTP_SERVER/authorized_keys ||\
    grep -q "Error 404" /root/.ssh/authorized_keys.wget; then
    rm -f /root/.ssh/authorized_keys.wget
else
    test -f /root/.ssh/authorized_keys && chmod 644 /root/.ssh/authorized_keys
    cat /root/.ssh/authorized_keys.wget >> /root/.ssh/authorized_keys
    rm -f /root/.ssh/authorized_keys.wget
fi


# Steps from sledgehammer
# -----------------------

if test $KEEP_EXISTING_HOSTNAME -ne 1; then
  echo "$HOSTNAME" > /etc/HOSTNAME
fi
sed -i -e "s/\(127\.0\.0\.1.*\)/127.0.0.1 $HOSTNAME ${HOSTNAME%%.*} localhost.localdomain localhost/" /etc/hosts
hostname "$HOSTNAME"
export DOMAIN
export HOSTNAME

ntp="/usr/sbin/ntpdate -u $NTP_SERVERS"
# Make sure date is up-to-date
until $ntp; do
  echo "Waiting for NTP server(s) $NTP_SERVERS"
  sleep 1
done

for retry in $(seq 1 30); do
    curl -f --retry 2 -o /etc/chef/validation.pem \
        --connect-timeout 60 -s -L \
        "$HTTP_SERVER/validation.pem"
    [ -f /etc/chef/validation.pem ] && break
    sleep $retry
done

TMP_ATTRIBUTES=$(mktemp --suffix .json)

# Make sure that we have the right target platform
echo "{ \"target_platform\": \"$CROWBAR_OS\", \"crowbar_wall\": { \"registering\": true } }" > "$TMP_ATTRIBUTES"

crowbarctl restricted transition $HOSTNAME "discovering"
chef-client -S http://$ADMIN_IP:4000/ -N "$HOSTNAME" --json-attributes "$TMP_ATTRIBUTES"
crowbarctl restricted transition $HOSTNAME "discovered"
# TODO need to find way of knowing that chef run is over on server side
sleep 30

crowbarctl restricted allocate $HOSTNAME

# Cheat to make sure that the bootdisk finder attribute will claim the right disks
echo '{ "crowbar_wall": { "registering": true } }' > "$TMP_ATTRIBUTES"

rm -f /etc/chef/client.pem
crowbarctl restricted transition $HOSTNAME "hardware-installing"
chef-client -S http://$ADMIN_IP:4000/ -N "$HOSTNAME" --json-attributes "$TMP_ATTRIBUTES"
crowbarctl restricted transition $HOSTNAME "hardware-installed"
#TODO
#wait_for_pxe_state ".*_install"
sleep 30

rm -f "$TMP_ATTRIBUTES"

crowbarctl restricted transition $HOSTNAME "installing"

# Obviously, on reboot from sledgehammer, we lose the client key
rm -f /etc/chef/client.pem


# Revert bits from sledgehammer that should not persist
# -----------------------------------------------------

# Now setup the hostname with the short name; we couldn't do that earlier
# because we do like in sledgehammer (which sets the hostname to the FQDN one)
hostname "${HOSTNAME%%.*}"

# Remove the resolution for hostname in /etc/hosts; this shouldn't be needed anymore
sed -i -e "s/\(127\.0\.0\.1.*\)/127.0.0.1 localhost.localdomain localhost/" /etc/hosts


# Steps from autoyast profile
# ---------------------------
# normally done by crowbar_join, but only when chef is installed by crowbar_join
systemctl enable chef-client.service

curl -s -o /usr/sbin/crowbar_join $HTTP_SERVER/$CROWBAR_OS/$CROWBAR_ARCH/crowbar_join.sh
chmod +x /usr/sbin/crowbar_join

crowbarctl restricted transition $HOSTNAME "installed"
#TODO
# Wait for DHCP to update
sleep 30

<% if @enable_pxe -%>
# Make sure we can always resolve our hostname; we use DHCP to find what's our
# admin IP
DHCP_VARS=$(mktemp)
/usr/lib/wicked/bin/wickedd-dhcp4 --test --test-output $DHCP_VARS $BOOTDEV
if test $? -eq 0; then
    eval $(grep ^IPADDR= "$DHCP_VARS")
    ADMIN_IP=${IPADDR%%/*}
    echo "$ADMIN_IP $HOSTNAME ${HOSTNAME%%.*}" >> /etc/hosts
fi
rm -f "$DHCP_VARS"
<% end -%>

/usr/sbin/crowbar_join --setup --verbose