Beginning of a major refactor.

This paves the way for better folsom support and upgrade ability.

    * Move functions that can be shared across all nova charms
      to hooks/lib/nova-common to allow easier managing / syncing
      among charms.
    * Add cinder-volume relations.
    * Make nova-volume API an optional service.  It is only installed
      and configured upon relation to a nova-volume service.
    * nova-volume relations trigger a new endpoint to be configured in
      keystone via identity-service relation, if it exists.  This is not
      necessary for cinder.
This commit is contained in:
Adam Gandelman 2012-09-21 17:53:41 -07:00
parent 52bb3c51db
commit 5b65b54b09
9 changed files with 303 additions and 228 deletions

View File

@ -0,0 +1 @@
nova-cloud-controller-relations

View File

@ -0,0 +1 @@
nova-cloud-controller-relations

159
hooks/lib/nova-common Normal file
View File

@ -0,0 +1,159 @@
#!/bin/bash
# Common utility functions used across all nova charms.
# Assumes $CHARM is set to charm name (for logging).
function set_or_update {
# Set a config option in nova.conf or api-paste.ini, depending
# Defaults to updating nova.conf
local key="$1"
local value="$2"
local conf_file="$3"
local nova_conf=${NOVA_CONF:-/etc/nova/nova.conf}
local api_conf=${API_CONF:-/etc/nova/api-paste.ini}
[[ -z $key ]] && juju-log "$CHARM: set_or_update: value $value missing key" && exit 1
[[ -z $value ]] && juju-log "$CHARM: set_or_update: key $key missing value" && exit 1
[[ -z "$conf_file" ]] && conf_file=$nova_conf
local pattern=""
case "$conf_file" in
"$nova_conf") match="^$key="
pattern="$key="
out=$pattern
;;
"$api_conf") match="^$key = "
pattern="$match"
out="$key = "
;;
*) juju-log "$CHARM ERROR: set_or_update: Invalid conf_file ($conf_file)"
esac
cat $conf_file | grep "$match$value" >/dev/null &&
juju-log "$CHARM: $key=$value already in set in $conf_file" \
&& return 0
if cat $conf_file | grep "$match" >/dev/null ; then
juju-log "$CHARM: Updating $conf_file, $key=$value"
sed -i "s|\($pattern\).*|\1$value|" $conf_file
else
juju-log "$CHARM: Setting new option $key=$value in $conf_file"
echo "$out$value" >>$conf_file
fi
}
function set_config_flags() {
# Set user-defined nova.conf flags from deployment config
juju-log "$CHARM: Processing config-flags."
flags=$(config-get config-flags)
if [[ "$flags" != "None" && -n "$flags" ]] ; then
for f in $(echo $flags | sed -e 's/,/ /g') ; do
k=$(echo $f | cut -d= -f1)
v=$(echo $f | cut -d= -f2)
set_or_update "$k" "$v"
done
fi
}
function nova_ctl_status {
# Return 0 if a service is running, 1 otherwise.
local svc="$1"
local status=$(service $svc status | cut -d/ -f1 | awk '{ print $2 }')
case $status in
"start") return 0 ;;
"stop") return 1 ;;
*) echo "ERROR: Unexpected status of service $svc: $status" && exit 1 ;;
esac
}
function nova_ctl {
# control a specific service, or all (as defined by $SERVICES)
if [[ $1 == "all" ]] ; then
ctl="$SERVICES"
else
ctl="$1"
fi
action=$2
if [[ -z $ctl ]] || [[ -z $action ]] ; then
juju-log "ERROR nova_ctl: Not enough arguments"
exit 1
fi
for i in $ctl ; do
case $action in
"start")
nova_ctl_status $i || service $i start ;;
"stop")
nova_ctl_status $i && service $i stop || return 0 ;;
"restart")
nova_ctl_status $i && service $i restart || service $i start ;;
esac
if [[ $? != 0 ]] ; then
juju-log "$CHARM: nova_ctl ERROR - Service $i failed to $action"
fi
done
}
configure_volume_service() {
local svc="$1"
case "$svc" in
"cinder") set_or_update "volume_api_class" "nova.volume.cinder.API" ;;
"nova-volume") set_or_update "volume_api_class" "nova.volume.api.API" ;;
*) juju-log "$CHARM ERROR - configure_volume_service: Invalid service $svc"
return 1 ;;
esac
}
function configure_network_manager {
local manager="$1"
echo "$CHARM: configuring $manager network manager"
case $1 in
"FlatManager")
set_or_update "network_manager" "nova.network.manager.FlatManager"
;;
"FlatDHCPManager")
set_or_update "network_manager" "nova.network.manager.FlatDHCPManager"
;;
*) echo "ERROR: Invalid network manager $1" && exit 1 ;;
esac
}
function configure_install_source {
# Setup and configure installation source based on a config flag.
local src="$1"
# Default to installing from the main Ubuntu archive.
[[ $src == "distro" ]] || [[ -z "$src" ]] && return 0
. /etc/lsb-release
# standard 'ppa:someppa/name' format.
if [[ "${src:0:4}" == "ppa:" ]] ; then
juju-log "$CHARM: Configuring installation from custom src ($src)"
add-apt-repository -y "$src" || exit 1
return
fi
# standard 'deb http://url/ubuntu main' entries. gpg key ids must
# be appended to the end of url after a |, ie:
# 'deb http://url/ubuntu main|$GPGKEYID'
if [[ "${src:0:3}" == "deb" ]] ; then
juju-log "$CHARM: Configuring installation from custom src URL ($src)"
if echo "$src" | grep -q "|" ; then
# gpg key id tagged to end of url folloed by a |
url=$(echo $src | cut -d'|' -f1)
key=$(echo $src | cut -d'|' -f2)
if [[ -n "$key" ]] ; then
juju-log "$CHARM: Importing repository key: $key"
apt-key adv --keyserver keyserver.ubuntu.com --recv-keys "$key" || \
juju-log "$CHARM WARN: Could not import key from keyserver: $key"
else
juju-log "$CHARM No repository key specified"
url="$src"
fi
add-apt-repository -y "$url"
fi
return
fi
juju-log "nova: No PPA specified. Falling back to installation from Ubuntu archive."
}

View File

@ -1,189 +1,24 @@
#!/bin/bash
CHARM="nova-cloud-controller"
SERVICES="nova-api-ec2 nova-api-os-compute nova-objectstore nova-cert"
PACKAGES="$SERVICES python-mysqldb python-keystone"
NOVA_CONF=$(config-get nova-config)
API_CONF="/etc/nova/api-paste.ini"
# we'll request credentials via the amqp relation for this user
RABBIT_USER=$(config-get rabbit-user)
RABBIT_VHOST=$(config-get rabbit-vhost)
# the database we'll be requesting via shared-db relations
DB_USER=$(config-get db-user)
NOVA_DB=$(config-get nova-db)
# The specific flavor of volume service that exists (if any) is tracked
# in this file. It's updated by volume-service hooks and used by controller
# hooks to inform backend nova services of how volume service is configured.
VOLUME_SERVICE_FLAG_FILE=/var/lib/juju/volume_service.txt
NETWORK_MANAGER=$(config-get network-manager)
PPA=$(config-get nova-release)
function set_or_update {
# Set a config option in nova.conf or api-paste.ini, depending
# Defaults to updating nova.conf
local KEY=$1
local VALUE=$2
local CONF_FILE=$3
local pattern=""
[[ -z $KEY ]] && juju-log "set_or_update: value $VALUE missing KEY" && exit 1
[[ -z $VALUE ]] && juju-log "set_or_update: key $KEY missing VALUE" && exit 1
[[ -z "$CONF_FILE" ]] && CONF_FILE=$NOVA_CONF
case "$CONF_FILE" in
"$NOVA_CONF") match="^$KEY="
pattern="$KEY="
out=$pattern
;;
"$API_CONF") match="^$KEY = "
pattern="$match"
out="$KEY = "
;;
*) juju-log "ERROR: set_or_update: Invalid CONF_FILE ($CONF_FILE)"
esac
cat $CONF_FILE | grep "$match$VALUE" >/dev/null &&
juju-log "nova-cloud-controller: $KEY=$VALUE already in set in $CONF_FILE" \
&& return 0
if cat $CONF_FILE | grep "$match" >/dev/null ; then
juju-log "nova-cloud-controller: Updating $CONF_FILE, $KEY=$VALUE"
sed -i "s|\($pattern\).*|\1$VALUE|" $CONF_FILE
if [[ -e $CHARM_DIR/lib/nova-common ]] ; then
. $CHARM_DIR/lib/nova-common
else
juju-log "nova-cloud-controller: Setting new option $KEY=$VALUE in $CONF_FILE"
echo "$out$VALUE" >>$CONF_FILE
juju-log "Couldn't load $CHARM_DIR/lib/nova-common" && exit 1
fi
}
function set_config_flags() {
# Set user-defined nova.conf flags from deployment config
juju-log "Processing config-flags."
flags=$(config-get config-flags)
if [[ "$flags" != "None" && -n "$flags" ]] ; then
for f in $(echo $flags | sed -e 's/,/ /g') ; do
k=$(echo $f | cut -d= -f1)
v=$(echo $f | cut -d= -f2)
set_or_update "$k" "$v"
done
fi
}
function nova_ctl_status {
SERVICE=$1
# workaround upstarts lack of scriptable return codes
STATUS=$(service $SERVICE status | cut -d/ -f1 | awk '{ print $2 }')
case $STATUS in
"start") return 0 ;;
"stop") return 1 ;;
*) echo "ERROR: Unexpected status of service $SERVICE: $STATUS" && exit 1 ;;
esac
}
function nova_ctl {
if [[ $1 == "all" ]] ; then
CTL=$SERVICES
else
CTL=$1
fi
ACTION=$2
if [[ -z $CTL ]] || [[ -z $ACTION ]] ; then
juju-log "ERROR nova_ctl: Not enough arguments"
exit 1
fi
for i in $CTL ; do
case $ACTION in
"start")
nova_ctl_status $i || service $i start ;;
"stop")
nova_ctl_status $i && service $i stop || return 0 ;;
"restart")
nova_ctl_status $i && service $i restart || service $i start ;;
esac
if [[ $? != 0 ]] ; then
juju-log "nova_ctl: ERROR - Service $i failed to $ACTION"
fi
done
}
function setup_bridge {
# XXX This is required by nova-network and will likely move somewhere else
# once we can split these services up into seperate formulas.
br=$1
ip=$2
netmask=$3
[[ -z $br ]] && br="br100"
[[ -z $ip ]] && ip="11.0.0.1"
[[ -z $netmask ]] && netmask="255.255.255.0"
apt-get -y install bridge-utils augeas-lenses augeas-tools
echo "Configuring bridge $br ($ip $netmask)"
context="/files/etc/network/interfaces"
augtool <<EOF
set $context/auto[child::1 = "$br"]/1 $br
set $context/iface[. = '$br'] $br
set $context/iface[. = '$br']/family inet
set $context/iface[. = '$br']/method static
set $context/iface[. = '$br']/address $ip
set $context/iface[. = '$br']/netmask $netmask
set $context/iface[. = '$br']/bridge_ports none
save
EOF
ifdown -a ; ifup -a
}
function configure_network_manager {
# needed by the nova-network bits
# to be expanded later to cover flatDhcp and VLAN
echo "$0: configuring $1 network manager"
case $1 in
"FlatManager")
NETWORK_BRIDGE=$(config-get bridge-interface)
BRIDGE_IP=$(config-get bridge-ip)
BRIDGE_NETMASK=$(config-get bridge-netmask)
setup_bridge $NETWORK_BRIDGE $BRIDGE_IP $BRIDGE_NETMASK
set_or_update network_manager nova.network.manager.FlatManager
set_or_update flat_network_bridge $NETWORK_BRIDGE
;;
"FlatDHCPManager")
set_or_update network_manager nova.network.manager.FlatDHCPManager
;;
*) echo "ERROR: Invalid network manager $1" && exit 1 ;;
esac
}
function add_ppa {
# Install from archive instead of PPA.
[[ $PPA == "distro" ]] && return 0
. /etc/lsb-release
[[ -z $PPA ]] && return 0
# if referenced by name, setup ppa to upstream PPAs
if [[ "$PPA" == "trunk" ]] ||
[[ "$PPA" == "milestone" ]] ||
[[ "$PPA" == "milestone-proposed" ]] ; then
juju-log "nova: Configuring installation from upstream PPA ($PPA)"
PPA_URL="deb http://ppa.launchpad.net/nova-core/$PPA/ubuntu $DISTRIB_CODENAME main"
add-apt-repository "$PPA_URL" || exit 1
return
fi
if [[ "${PPA:0:4}" == "ppa:" ]] ; then
juju-log "nova: Configuring installation from custom PPA ($PPA)"
add-apt-repository -y "$PPA" || exit 1
return
fi
if [[ "${PPA:0:3}" == "deb" ]] ; then
juju-log "nova: Configuring installation from custom PPA URL ($PPA)"
if echo "$PPA" | grep -q "|" ; then
# gpg key id tagged to end of url folloed by a |
url=$(echo $PPA | cut -d'|' -f1)
key=$(echo $PPA | cut -d'|' -f2)
if [[ -n "$key" ]] ; then
juju-log "Importing repository key: $key"
apt-key adv --keyserver keyserver.ubuntu.com --recv-keys "$key" || \
juju-log "WARN: Could not import key from keyserver: $key"
else
juju-log "No repository key specified"
url="$PPA"
fi
add-apt-repository -y "$url"
fi
return
fi
juju-log "nova: No PPA specified. Falling back to installation from Ubuntu archive."
}

View File

@ -1,41 +1,43 @@
#!/bin/bash
FORMULA_DIR=$(dirname $0)
ARG0=${0##*/}
if [[ -e $FORMULA_DIR/nova-cloud-controller-common ]] ; then
. $FORMULA_DIR/nova-cloud-controller-common
CHARM_DIR=$(dirname $0)
arg0=$(basename $0)
if [[ -e $CHARM_DIR/nova-cloud-controller-common ]] ; then
. $CHARM_DIR/nova-cloud-controller-common
else
echo "ERROR: Could not load nova-cloud-controller-common from $FORMULA_DIR"
echo "ERROR: Could not load nova-cloud-controller-common from $CHARM_DIR"
fi
function install_hook {
juju-log "Installing nova packages"
apt-get -y install python-software-properties || exit 1
add_ppa
configure_install_source $(config-get nova-release)
apt-get update || exit 1
DEBIAN_FRONTEND=noninteractive apt-get -y \
install --no-install-recommends $PACKAGES || exit 1
configure_network_manager $NETWORK_MANAGER
# setup osapi extensions required for dashboard
# these are the required middleware extensions as of 12/20/2011
extensions="nova.api.openstack.compute.contrib.standard_extensions"
for e in $extensions ; do
grep -q "^--osapi_compute_extension=$e" "$NOVA_CONF" ||
echo "--osapi_compute_extension=$e" >>"$NOVA_CONF"
done
# # setup osapi extensions required for dashboard
# # these are the required middleware extensions as of 12/20/2011
# extensions="nova.api.openstack.compute.contrib.standard_extensions"
#
# for e in $extensions ; do
# grep -q "^--osapi_compute_extension=$e" "$NOVA_CONF" ||
# echo "--osapi_compute_extension=$e" >>"$NOVA_CONF"
# done
# Configure any flags specified in deployment config
set_config_flags
# Open up the various API endpoints
# EC2
open-port 8774
open-port 8773
# osapi-compute
open-port 8774
# object-store / s3
open-port 3333
@ -51,57 +53,69 @@ function amqp_joined {
# we request a username on the rabbit queue
# and store it in nova.conf. our response is its IP + PASSWD
# but we configure that in _changed
juju-log "amqp_joined: requesting credentials for $RABBIT_USER"
echo "amqp_joined: requesting credentials for $RABBIT_USER"
relation-set username=$RABBIT_USER
relation-set vhost=$RABBIT_VHOST
local rabbit_user=$(config-get rabbit-user)
local rabbit_vhost=$(config-get rabbit-vhost)
juju-log "amqp_joined: requesting credentials for $rabbit_user"
echo "amqp_joined: requesting credentials for $rabbit_user"
relation-set username=$rabbit_user
relation-set vhost=$rabbit_vhost
}
function amqp_changed {
# server creates our credentials and tells us where
# to connect. for now, using default vhost '/'
RABBIT_HOST=`relation-get private-address`
RABBIT_PASSWORD=`relation-get password`
if [[ -z $RABBIT_HOST ]] || \
[[ -z $RABBIT_PASSWORD ]] ; then
echo "amqp_changed: RABBIT_HOST||RABBIT_PASSWORD not set."
local rabbit_host=$(relation-get private-address)
local rabbit_password=$(relation-get password)
if [[ -z $rabbit_host ]] || \
[[ -z $rabbit_password ]] ; then
echo "amqp_changed: rabbit_host||rabbit_password not set."
exit 0
fi
echo "amqp_changed: Setting rabbit config in nova.conf: $RABBIT_HOST $RABBIT_USER $RABBIT_PASSWORD"
set_or_update rabbit_host $RABBIT_HOST
set_or_update rabbit_userid $RABBIT_USER
set_or_update rabbit_password $RABBIT_PASSWORD
set_or_update rabbit_virtual_host $RABBIT_VHOST
local rabbit_user=$(config-get rabbit-user)
local rabbit_vhost=$(config-get rabbit-vhost)
echo "amqp_changed: Setting rabbit config in nova.conf: $rabbit_host $rabbit_user $rabbit_password"
set_or_update rabbit_host $rabbit_host
set_or_update rabbit_userid $rabbit_user
set_or_update rabbit_password $rabbit_password
set_or_update rabbit_virtual_host $rabbit_vhost
nova_ctl all restart
}
function db_joined {
# tell mysql provider which database we want. it will create it and give us
# credentials
juju-log "db_joined: requesting database access to $NOVA_DB for $DB_USER@$HOSTNAME"
relation-set database=$NOVA_DB
relation-set username=$DB_USER
relation-set hostname=`unit-get private-address`
local nova_db=$(config-get nova-db)
local db_user=$(config-get db-user)
local hostname=$(unit-get private-address)
juju-log "db_joined: requesting database access to $nova_db for $db_user@$hostname"
relation-set database=$nova_db username=$db_user hostname=$hostname
}
function db_changed {
DB_HOST=`relation-get private-address`
DB_PASSWORD=`relation-get password`
if [[ -z $DB_HOST ]] || [[ -z $DB_PASSWORD ]] ; then
echo "db_changed: DB_HOST || DB_PASSWORD not yet set. Exit 0 and retry"
local db_host=`relation-get private-address`
local db_password=`relation-get password`
if [[ -z $db_host ]] || [[ -z $db_password ]] ; then
echo "db_changed: db_host || db_password not yet set. Exit 0 and retry"
exit 0
fi
echo "db_changed: Configuring nova.conf for access to $NOVA_DB"
set_or_update sql_connection "mysql://$DB_USER:$DB_PASSWORD@$DB_HOST/$NOVA_DB"
nova_ctl all restart
sleep 1
local nova_db=$(config-get nova-db)
local db_user=$(config-get db-user)
echo "db_changed: Configuring nova.conf for access to $nova_db"
set_or_update sql_connection "mysql://$db_user:$db_password@$db_host/$nova_db"
nova_ctl all stop
/usr/bin/nova-manage db sync
nova_ctl all start
}
function image-service_changed {
API_SERVER=`relation-get glance-api-server`
[[ -z $API_SERVER ]] && echo "image-service_changed: Peer not ready?" && exit 0
set_or_update glance_api_servers $API_SERVER
local api_server=$(relation-get glance-api-server)
[[ -z $api_server ]] && echo "image-service_changed: Peer not ready?" && exit 0
set_or_update glance_api_servers $api_server
set_or_update image_service "nova.image.glance.GlanceImageService"
nova_ctl all restart
}
@ -140,6 +154,16 @@ function keystone_joined {
s3_public_url="$s3_url" \
s3_admin_url="$s3_url" \
s3_internal_url="$s3_url"
# tack on an endpoint for nova-volume a relation exists.
if [[ -n "$(relation-ids nova-volume-service)" ]] ; then
nova_vol_url="http://$(unit-get private-address):8776/v1/\$(tenant_id)s"
relation-set nova-volume_service="nova-volume" \
nova-volume_region="RegionOne" \
nova-volume_public_url="$nova_vol_url" \
nova-volume_admin_url="$nova_vol_url" \
nova-volume_internal_url="$nova_vol_url"
fi
}
function keystone_changed {
@ -178,11 +202,58 @@ function keystone_changed {
set_or_update "admin_tenant_name" "$service_tenant" "$API_CONF"
set_or_update "admin_user" "$service_username" "$API_CONF"
set_or_update "admin_password" "$service_password" "$API_CONF"
nova_ctl nova-api restart
nova_ctl all restart
}
case $ARG0 in
"start"|"stop") nova_ctl all $ARG0 ;;
volume_joined() {
local svc=""
case "$arg0" in
"cinder-volume-service-relation-joined") svc="cinder" ;;
"nova-volume-service-relation-joined") svc="nova-volume" ;;
*) svc="nova-volume" ;;
esac
configure_volume_service "$svc"
nova_ctl all restart
# The nova-volume API can be hosted here alongside the other
# nova API services, but there needs to be a new endpoint
# configured in keystone.
if [[ "$svc" == "nova-volume" ]] ; then
apt-get -y install nova-api-os-volume
nova_vol_url="http://$(unit-get private-address):8776/v1/\$(tenant_id)s"
r_ids=$(relation-ids identity-service)
for id in $r_ids ; do
juju-log "$CHARM: Registering new endpoint for nova-volume API on "\
"existing identity-service relation: $id"
nova_vol_url="http://$(unit-get private-address):8776/v1/\$(tenant_id)s"
relation-set -r $id nova-volume_service="nova-volume" \
nova-volume_region="RegionOne" \
nova-volume_public_url="$nova_vol_url" \
nova-volume_admin_url="$nova_vol_url" \
nova-volume_internal_url="$nova_vol_url"
done
fi
}
volume_changed() {
# nothing to do here, yet.
exit 0
}
controller_joined() {
# This interface serves to synchronize global configuration to other
# nova services. For example, reconfiguring all nova services to use
# a new auth strategy after keystone has been introduced, a new
# volume driver after cinder, or a new network manager after quantum.
# This function is able to target existing relations to allow updating
# existing settings.
local relation_id="$1"
}
arg0=$(basename $0)
case $arg0 in
"start"|"stop") nova_ctl all $arg0 ;;
"install") install_hook ;;
"config-changed") config_changed ;;
"amqp-relation-joined") amqp_joined ;;
@ -194,5 +265,9 @@ case $ARG0 in
"network-manager-relation-joined") nova-network_joined ;;
"identity-service-relation-joined") keystone_joined ;;
"identity-service-relation-changed") keystone_changed ;;
"cloud-controller-relation-joined") controller_joined ;;
"cloud-controller-relation-changed") controller_changed ;;
"cinder-volume-service-relation-joined") volume_joined ;;
"nova-volume-service-relation-joined") volume_joined ;;
*) exit 0 ;;
esac

View File

@ -0,0 +1 @@
nova-cloud-controller-relations

View File

@ -0,0 +1 @@
nova-cloud-controller-relations

View File

@ -20,5 +20,7 @@ requires:
interface: keystone
cloud-compute:
interface: nova-compute
instance-storage:
cinder-volume-service:
interface: cinder
nova-volume-service:
interface: nova-volume

View File

@ -1 +1 @@
85
108