#!/bin/bash # Copyright (c) 2010-2011 OpenStack, LLC. # 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. # nova-install - configure a fully functioning OpenStack Nova component # For detailed usage instructions, run: # nova-install --help # This script is intended to be ran on a fresh install on Ubuntu 10.04 64-bit. # Once ran with the appropiate variables, it will produce a fully functioning # Nova Cloud Contoller or Nova Compute Node. Eventually other Ubuntu releases, # as well as RPM-based distributions, will be supported. # Written by Wayne A. Walls (dubsquared) with the amazing help of Jordan Rinke # (JordanRinke), Vish Ishaya (vishy), Aaron Bull Schaefer (elasticdog), and # a lot of input from the fine folks in #openstack on irc.freenode.net! # Please contact script maintainers for questions, comments, or concerns: # Wayne -> wayne@openstack.org # Jordan -> jordan@openstack.org # You can also get assistance by stopping by irc.freenode.net - #openstack, # sending a message to the OpenStack mailing list - openstack@lists.launchpad.net, # or posting at https://answers.launchpad.net/openstack ##### Constants readonly VERSION=1.2.1 # release version of this script readonly BASENAME="${0##*/}" # name of this script for error output readonly CREDS_DIR='/root/creds' # directory to store nova credentials readonly LOG_FILE='/var/log/nova/install.log' # location of script log file readonly PARAMETERS="$*" # all of the specified input parameters readonly PPA='ppa:nova-core/release' # which ppa to install packages from readonly ROOT_UID=0 # users with $UID 0 have root privileges # Exit codes readonly EX_OK=0 # successful termination readonly EX_USAGE=64 # command line usage error readonly EX_OSFILE=72 # critical OS file missing readonly EX_NOPERM=77 # permission denied ##### Helper Functions # Print message to stderr and exit with the given status code, if one was supplied error_exit() { local message="$1" local status="$2" local max_status=255 # highest legal status code set -o xtrace echo "ERROR: $message" 1>&2 set +o xtrace finalize_log 'installation process aborted due to error' if is_integer "$status" "$max_status"; then exit $status else exit 1 fi } # Remove clutter from log and append a timestamp finalize_log() { local message="$1" sed -i'' -e '/^+ set +o xtrace$/d' $LOG_FILE echo "$(date) $BASENAME: $message ($PARAMETERS)" >>$LOG_FILE } # Prompt user for an IP address range, validate it, and then assign it to the given variable get_cidr_range() { local prompt="$1" local variable="$2" local max_prefix=32 # only 32 bits in an address while true; do read -e -p "${prompt}: " range local address=${range%/*} local prefix=${range##*/} if is_valid_ipv4 $address && is_integer "$prefix" "$max_prefix"; then # much safer than assigning with eval printf -v $variable %s "$range" break else echo -e "That IP address range is invalid...try again\n" fi done } # Prompt user for an IP address, validate it, and then assign it to the given # variable; a default is offered and required when calling this function get_host_ip() { local prompt="$1" local variable="$2" local default="$3" while true; do read -e -p "$prompt [$default]: " address # fall back to the default IP if none was input by user address=${address:-$default} if is_valid_ipv4 $address; then # much safer than assigning with eval printf -v $variable %s "$address" break else echo -e "That IP address is invalid...try again\n" fi done } # Prompt user for input, and do not accept an empty string get_input() { local prompt="$1" local variable="$2" while true; do read -e -p "$prompt: " if [[ -n $REPLY ]]; then # much safer than assigning with eval printf -v $variable %s "$REPLY" break fi done } # Prompt user for a number, validate that it is an integer, optionally checking # if it's less than or equal to the supplied maximum, and then assign it to the # given variable get_integer() { local prompt="$1" local variable="$2" # maximum might not be set set +o nounset local maximum="$3" set -o nounset while true; do read -e -p "$prompt: " number if is_integer "$number" "$maximum"; then # much safer than assigning with eval printf -v $variable %s "$number" break else if [[ -z $maximum ]]; then echo -e "The input must be an unsigned integer...try again\n" else echo -e "The input must be an unsigned integer and <= $maximum...try again\n" fi fi done } # Install each given package, checking first if it's already installed install_packages() { for package in "$@"; do echo -n "Installing package '$package' ... " set -o xtrace # skip if the package is already installed if is_installed $package; then set +o xtrace echo 'already installed' continue else apt-get install --quiet --assume-yes $package &>>$LOG_FILE set +o xtrace echo 'done' fi done } # Return whether or not the provided input is an integer, and optionally, less # than or equal to the given maximum is_integer() { local input="$1" # maximum might not be set set +o nounset local maximum="$2" set -o nounset case "$input" in # reject the following: # empty strings # anything other than digits ""|*[!0-9]*) return 1 ;; esac if [[ -n $maximum ]]; then (( $input <= $maximum )) fi } # Return whether or not the given package is already installed on the system is_installed() { set +o xtrace local package="$1" set -o xtrace dpkg -l $package 2>>$LOG_FILE | grep -q 'ii' } # Return whether or not the provided component is supported by the script is_supported_component() { local component="$1" case "$component" in cloud|compute) ;; *) return 1 ;; esac } # Return whether or not the provided input is a valid IPv4 address...assumes # only dotted quads are valid, rejecting addresses like 127.1 is_valid_ipv4() { local input="$1" case "$input" in # reject the following: # empty strings # anything other than digits and dots # anything not ending in a digit ""|*[!0-9.]*|*[!0-9]) return 1 ;; esac # change IFS to a dot, only for this function local IFS=. # split the address into positional parameters set -- $* # there must be four parameters, each less than or equal to 255 (( $# == 4 )) && (( $1 <= 255 )) && (( $2 <= 255 )) && (( $3 <= 255 )) && (( $4 <= 255 )) } # Display a section separator with the provided message section_banner() { local border='################################################################' echo -e "\n${border}" echo " $*" echo -e "${border}\n" } # Display the script's help message show_help() { cat <<-EOF NAME $BASENAME -- configure a fully functioning OpenStack Nova component SYNOPSIS $BASENAME [-t type] [-V] [-h] DESCRIPTION The $BASENAME command will gather information, install dependencies, and then configure one of a number of different OpenStack component types as specified on the command line. A cloud controller will be configured to run all five Nova services (api, compute, network, objectstor, and scheduler), while a compute node will only run Nova's compute service. OPTIONS -t, --type Changes the type of component to install. Currently implemented types are 'cloud' (default) for installing a new cloud controller, and 'compute' for installing a compute node for an existing cloud. -V, --version Print the $BASENAME script's version information and exit. -h, --help View this help message about the available command-line options. EOF } # Display a subsection separator with the provided message subsection_banner() { echo -e "\n$*" echo -e "${*//?/#}\n" } ##### Workflow Functions # Ensure user has root privileges and that we're on a supported linux box check_safety() { # root check if (( $UID != $ROOT_UID )); then error_exit 'Permission denied - you must have root privileges to run this script' $EX_NOPERM fi # linux check if [[ $OSTYPE != linux* ]]; then error_exit 'Not compatible - you must run this script on a Linux system' $EX_OSFILE fi # distribution check if [[ -f /etc/lsb-release ]]; then distro='ubuntu' else error_exit 'Not compatible - this script only supports Ubuntu at this time' $EX_OSFILE fi } # Initialize package dependencies based on OS and component type initialize_dependencies() { case "$component_type" in cloud) required_packages=" euca2ools mysql-server nova-api nova-compute nova-network nova-objectstore nova-scheduler unzip " ;; compute) required_packages=" nova-compute " ;; esac } # Initializes the local network interface variables using the first listed # interface; this could be improved later as it might not work as expected on # all systems initialize_network_variables() { local interface=$(/sbin/ifconfig -a | grep 'inet ' | grep -v '127.0.0.1' | head -n 1) local_address=$(echo "$interface" | cut -d: -f2 | awk '{ print $1 }') local_broadcast=$(echo "$interface" | cut -d: -f3 | awk '{ print $1 }') local_netmask=$(echo "$interface" | cut -d: -f4 | awk '{ print $1 }') local_gateway=$(/sbin/ip route | awk '/default/{ print $3 }') local_nameserver=$(awk '/nameserver/{ print $2 }' /etc/resolv.conf) } # Create the log file (if necessary) and append a timestamp start_log() { set -o xtrace mkdir -p "${LOG_FILE%/*}" set +o xtrace echo "$(date) $BASENAME: installation process initiated ($PARAMETERS)" >>$LOG_FILE } # Display installer introduction show_intro() { local wording if [[ $component_type == cloud ]]; then wording='Cloud Controller' elif [[ $component_type == compute ]]; then wording="Compute Node" fi cat <<-EOF Nova Installation Script v${VERSION} Setting up a Nova $wording is a multi-step process. After you seed information, the script will take over and finish off the installation for you. A complete log of commands will be availible at $LOG_FILE EOF read -s -n 1 -p 'Press any key to continue...' echo } # Prompt user for host IPs for various Nova services get_service_hosts() { section_banner 'Nova Services Configuration' cat <<-EOF This section includes setting the host IP addresses for the Nova Cloud Controller, S3, RabbitMQ, and MySQL services...these are typically hosted on the same machine. EOF get_host_ip 'Cloud Controller host IP address' cc_host_ip $local_address get_host_ip 'S3 host IP address' s3_host_ip $cc_host_ip get_host_ip 'RabbitMQ host IP address' rabbit_host_ip $cc_host_ip get_host_ip 'MySQL host IP address' mysql_host_ip $cc_host_ip } # Prompt user for database root password...MySQL only for now, will update to # include others later get_database_creds() { local wording if [[ $component_type == cloud ]]; then wording='Desired' else wording="The Cloud Controller's" fi subsection_banner 'Database credentials' while true; do local mysql_pass2 read -s -p "$wording MySQL root password: " mysql_pass echo read -s -p 'Verify MySQL root password: ' mysql_pass2 echo if [[ $mysql_pass == $mysql_pass2 ]]; then if [[ -n $mysql_pass ]]; then break else echo -e "Password cannot be empty...try again\n" fi else echo -e "Passwords do not match...try again\n" fi done if [[ $component_type == cloud ]]; then set -o xtrace # preseed the password in preparation for package installation cat <<-MYSQL_PRESEED | debconf-set-selections mysql-server-5.1 mysql-server/root_password password $mysql_pass mysql-server-5.1 mysql-server/root_password_again password $mysql_pass mysql-server-5.1 mysql-server/start_on_boot boolean true MYSQL_PRESEED set +o xtrace fi } # Prompt user for the local machine's network interface settings get_bridge_info() { section_banner 'Local Network Settings' cat <<-EOF In order to set up the network bridge, we'll need some information on your local network interface configuration. The settings will default to the first known device as listed by 'ifconfig'. EOF get_host_ip "Enter this machine's IP address" bridge_address $local_address get_host_ip "Enter this machine's broadcast address" bridge_broadcast $local_broadcast get_host_ip "Enter this machine's netmask" bridge_netmask $local_netmask get_host_ip "Enter this machine's gateway" bridge_gateway $local_gateway get_host_ip "Enter this machine's DNS nameserver address" bridge_nameserver $local_nameserver } # Prompt user for desired project network configuration get_project_info() { section_banner 'Project Network Configuration' cat <<-EOF Here you will set the network range that ALL of your projects will reside in, and the number of IP addresses in that block that should be available for use. After that, you'll create a project administrator and a new project. EOF get_cidr_range 'Network range for ALL projects (normally x.x.x.x/12)' fixed_range get_integer 'Total amount of usable IPs for ALL projects' network_size 999999999 subsection_banner 'New project creation' get_input 'User name for the project administrator' project_user get_input 'Name for the project' project_name echo get_cidr_range "Desired network range for the '$project_name' project (normally x.x.x.x/24)" project_cidr get_integer "How many networks for the '$project_name' project" project_network_count 999 get_integer "How many available IPs per '$project_name' project network" project_ips_per_network 999999999 } # We need the user to manually bypass the RabbitMQ installation splash screen bypass_rabbit_splash() { local package='rabbitmq-server' # skip if the package is already installed if ! is_installed $package; then cat <<-EOF ######## One last thing...there is currently no way to background/preeseed the RabbitMQ package's installation splash screen, so we'll need you to manually go through it. EOF read -s -n 1 -p 'Press any key to continue...' echo -e "\n" set -o xtrace apt-get install --quiet --assume-yes "$package" set +o xtrace fi } # Display auto-pilot notice show_auto_pilot() { section_banner 'ENTERING AUTO-PILOT MODE' cat <<-EOF At this point, you've entered all the information needed to finish the installation of this Nova component. Feel free to get some coffee, you have earned it! EOF sleep 4 } # Install all required packages install_dependencies() { subsection_banner 'Installing packages' install_packages 'python-software-properties' echo -n "Adding '$PPA' repository ... " set -o xtrace add-apt-repository $PPA &>>$LOG_FILE apt-get update &>>$LOG_FILE set +o xtrace echo 'done' install_packages $required_packages } # Create nova.conf file, initialize the database, and generate user credentials setup_nova_config() { subsection_banner 'Setting up the Nova configuration file' echo -n 'Generating nova.conf ... ' if [[ $component_type == cloud ]]; then set -o xtrace cat >> /etc/nova/nova.conf <<-EOF --s3_host=$s3_host_ip --rabbit_host=$rabbit_host_ip --cc_host=$cc_host_ip --ec2_url=http://$s3_host_ip:8773/services/Cloud --fixed_range=$fixed_range --network_size=$network_size --FAKE_subdomain=ec2 --routing_source_ip=$cc_host_ip --verbose --sql_connection=mysql://root:$mysql_pass@$mysql_host_ip/nova --network_manager=nova.network.manager.FlatManager EOF set +o xtrace else set -o xtrace cat >> /etc/nova/nova.conf <<-EOF --s3_host=$s3_host_ip --rabbit_host=$rabbit_host_ip --cc_host=$cc_host_ip --ec2_url=http://$s3_host_ip:8773/services/Cloud --sql_connection=mysql://root:$mysql_pass@$mysql_host_ip/nova --network_manager=nova.network.manager.FlatManager EOF set +o xtrace fi echo 'done' echo -n 'Setting proper permissions for nova.conf ... ' if ! grep -q '^nova' /etc/group; then set -o xtrace groupadd nova &>>$LOG_FILE set +o xtrace fi if ! groups nova | grep -q ' nova'; then set -o xtrace usermod -a -G nova nova &>>$LOG_FILE set +o xtrace fi set -o xtrace chown -R root:nova /etc/nova &>>$LOG_FILE chmod 640 /etc/nova/nova.conf &>>$LOG_FILE set +o xtrace echo 'done' } # Create a new database and initialize it with the project information finalize_database() { local mysql_config='/etc/mysql/my.cnf' subsection_banner 'Finalizing MySQL setup' echo -n 'Adjusting MySQL network configuration ... ' set -o xtrace # back up original my.cnf file [[ ! -f $mysql_config.bak ]] && cp $mysql_config{,.bak} sed -i 's/127.0.0.1/0.0.0.0/g' $mysql_config service mysql restart &>>$LOG_FILE set +o xtrace echo 'done' echo -n 'Creating database ... ' set -o xtrace mysql --user='root' --password="$mysql_pass" --execute "CREATE DATABASE nova;" mysql --user='root' --password="$mysql_pass" --execute "GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' WITH GRANT OPTION;" mysql --user='root' --password="$mysql_pass" --execute "SET PASSWORD FOR 'root'@'%' = PASSWORD('$mysql_pass');" set +o xtrace echo 'done' echo -n 'Initializing database ... ' set -o xtrace nova-manage db sync &>>$LOG_FILE sleep 1 /usr/bin/python /usr/bin/nova-manage user admin $project_user &>>$LOG_FILE /usr/bin/python /usr/bin/nova-manage project create $project_name $project_user &>>$LOG_FILE /usr/bin/python /usr/bin/nova-manage network create $project_cidr $project_network_count $project_ips_per_network &>>$LOG_FILE set +o xtrace echo 'done' } # Generate user credentials and environment settings generate_credentials() { local novacreds="${CREDS_DIR}/novacreds.zip" local novarc="${CREDS_DIR}/novarc" subsection_banner 'Generate Nova user credentials' echo -n 'Generating and extracting novacreds.zip ... ' mkdir -p $CREDS_DIR set -o xtrace /usr/bin/python /usr/bin/nova-manage project zipfile $project_name $project_user $novacreds &>>$LOG_FILE sleep 3 unzip -d $CREDS_DIR $novacreds &>>$LOG_FILE set +o xtrace echo 'done' echo -n 'Appending novarc environment settings to your ~/.bashrc ... ' set -o xtrace source $novarc echo -e '\n# OpenStack Nova Credentials' >> $HOME/.bashrc cat $novarc >> $HOME/.bashrc # back up original novarc file [[ ! -f $novarc.bak ]] && cp $novarc{,.bak} sed -i "s/127.0.0.1/$cc_host_ip/g" $novarc set +o xtrace echo 'done' } # Use a bridged network interface and adjust firewall if necessary configure_network() { subsection_banner 'Updating network interfaces' echo -n 'Adding bridge device ... ' set -o xtrace # back up original interfaces file [[ ! -f /etc/network/interaces.bak ]] && cp /etc/network/interfaces{,.bak} cat > /etc/network/interfaces << EOF # The loopback network interface auto lo iface lo inet loopback auto br100 iface br100 inet static bridge_ports eth0 bridge_stp off bridge_maxwait 0 bridge_fd 0 address $bridge_address netmask $bridge_netmask broadcast $bridge_broadcast gateway $bridge_gateway dns-nameservers $bridge_nameserver EOF set +o xtrace echo 'done' echo -n 'Restarting networking service ... ' set -o xtrace /etc/init.d/networking restart &>>$LOG_FILE set +o xtrace echo 'done' # any server that does /NOT/ have nova-api running on it will need this # firewall rule for UEC images to be able to fetch metadata info if [[ $component_type != cloud ]]; then echo -n 'Allowing image metadata traffic ... ' set -o xtrace iptables -t nat -A PREROUTING -d 169.254.169.254/32 -p tcp -m tcp --dport 80 -j DNAT --to-destination $cc_host_ip:8773 set +o xtrace echo 'done' fi } # Start bring services back up with the new configurations restart_services() { subsection_banner 'Bringing your Nova node online' echo -n 'Restarting Nova services ... ' if [[ $component_type == cloud ]]; then set -o xtrace source ${CREDS_DIR}/novarc restart libvirt-bin &>>$LOG_FILE restart nova-network &>>$LOG_FILE restart nova-compute &>>$LOG_FILE restart nova-api &>>$LOG_FILE restart nova-objectstore &>>$LOG_FILE restart nova-scheduler &>>$LOG_FILE set +o xtrace else set -o xtrace restart libvirt-bin &>>$LOG_FILE restart nova-compute &>>$LOG_FILE set +o xtrace fi sleep 4 echo 'done' if [[ $component_type == cloud ]]; then echo -e 'Ensure all five Nova services are running:\n' else echo -e 'Ensure the Nova compute service is running:\n' fi set -o xtrace ps -ef c --sort=cmd | egrep 'PID|nova-[acnos]' set +o xtrace sleep 2 } # Ensure proper initialization of KVM if it's available; # otherwise VMs run in the much slower QEMU mode fix_kvm_permissions() { if [[ -f /dev/kvm ]]; then set -o xtrace chgrp kvm /dev/kvm chmod g+rwx /dev/kvm set +o xtrace fi } # Authorize ICMP and SSH traffic between VMs by default allow_ping_and_ssh() { echo -n -e "\nAllowing ICMP and SSH access to all VMs ... " set -o xtrace euca-authorize -P icmp -t -1:-1 default &>>$LOG_FILE euca-authorize -P tcp -p 22 default &>>$LOG_FILE set +o xtrace echo 'done' } # Only one dnsmasq process starts, supposed to be two running at different # priorities. This fixes that...possible bug? fix_dnsmasq_bug() { set -o xtrace killall dnsmasq service nova-network restart &>>$LOG_FILE set +o xtrace } # Display closing message after a successful installation show_closing() { if [[ $component_type == cloud ]]; then cat <<-EOF ######################################################################## # You /MUST/ re-source your 'novarc' to use the API commands since the # # script cannot pass the source information out of its own process... # # the variables have been appended to your ~/.bashrc file for sourcing # ######################################################################## For reference, the generated credentials can be found in ${CREDS_DIR}/ The next thing you are going to want to do it get a VM to test with. You can find a test VM how-to and read about custom image creation here: http://nova.openstack.org/adminguide/multi.node.install.html http://wiki.openstack.org/GettingImages Enjoy your new private cloud! EOF else cat <<-EOF ######## That's it, your new Nova Compute Node is up and running! EOF fi } ##### Main # Default options component_type='cloud' # which nova component to install # Read command-line arguments while [[ $1 == -* ]]; do case "$1" in -t|--type) if (( $# > 1 )); then if is_supported_component "$2"; then component_type="$2" shift 2 else echo "Unsupported component type: $2" 1>&2 show_help exit $EX_USAGE fi else echo "The $1 option requires an argument: " 1>&2 show_help exit $EX_USAGE fi ;; -h|--help|-\?) show_help exit $EX_OK ;; -V|--version) echo "$BASENAME $VERSION" exit $EX_OK ;; -*) echo "Invalid option: $1" 1>&2 show_help exit $EX_USAGE ;; esac done # Saftey measures to fail early before problems snowball set -o errexit # exit if any command in this script has a non-zero status code set -o nounset # exit if an uninitialized variable is accidentally used check_safety initialize_dependencies initialize_network_variables start_log # Log xtrace to the log file when enabled exec 3>>$LOG_FILE BASH_XTRACEFD=3 # User interaction required show_intro get_service_hosts get_database_creds get_bridge_info if [[ $component_type == cloud ]]; then get_project_info bypass_rabbit_splash fi # Automated steps show_auto_pilot install_dependencies setup_nova_config if [[ $component_type == cloud ]]; then finalize_database generate_credentials fi configure_network restart_services if [[ $component_type == cloud ]]; then allow_ping_and_ssh fix_dnsmasq_bug fi fix_kvm_permissions show_closing finalize_log 'installation process complete' exit $EX_OK