chef/cookbooks/provisioner/templates/suse/crowbar_register.erb
#! /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