From 5b65b54b09bfff3eb90aa106fd33f79ab0329c39 Mon Sep 17 00:00:00 2001 From: Adam Gandelman Date: Fri, 21 Sep 2012 17:53:41 -0700 Subject: [PATCH] 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. --- hooks/cinder-volume-service-relation-changed | 1 + hooks/cinder-volume-service-relation-joined | 1 + hooks/lib/nova-common | 159 ++++++++++++++++ hooks/nova-cloud-controller-common | 189 ++----------------- hooks/nova-cloud-controller-relations | 173 ++++++++++++----- hooks/nova-volume-service-relation-changed | 1 + hooks/nova-volume-service-relation-joined | 1 + metadata.yaml | 4 +- revision | 2 +- 9 files changed, 303 insertions(+), 228 deletions(-) create mode 120000 hooks/cinder-volume-service-relation-changed create mode 120000 hooks/cinder-volume-service-relation-joined create mode 100644 hooks/lib/nova-common create mode 120000 hooks/nova-volume-service-relation-changed create mode 120000 hooks/nova-volume-service-relation-joined diff --git a/hooks/cinder-volume-service-relation-changed b/hooks/cinder-volume-service-relation-changed new file mode 120000 index 00000000..ed780797 --- /dev/null +++ b/hooks/cinder-volume-service-relation-changed @@ -0,0 +1 @@ +nova-cloud-controller-relations \ No newline at end of file diff --git a/hooks/cinder-volume-service-relation-joined b/hooks/cinder-volume-service-relation-joined new file mode 120000 index 00000000..ed780797 --- /dev/null +++ b/hooks/cinder-volume-service-relation-joined @@ -0,0 +1 @@ +nova-cloud-controller-relations \ No newline at end of file diff --git a/hooks/lib/nova-common b/hooks/lib/nova-common new file mode 100644 index 00000000..84b1dd89 --- /dev/null +++ b/hooks/lib/nova-common @@ -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." +} diff --git a/hooks/nova-cloud-controller-common b/hooks/nova-cloud-controller-common index 0fa237c2..b38461bc 100755 --- a/hooks/nova-cloud-controller-common +++ b/hooks/nova-cloud-controller-common @@ -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 - else - juju-log "nova-cloud-controller: 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 "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 <>"$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 { @@ -149,7 +173,7 @@ function keystone_changed { service_username=$(relation-get service_username) service_password=$(relation-get service_password) service_tenant=$(relation-get service_tenant) - [[ -z "$token" ]] || [[ -z "$service_port" ]] || [[ -z "$auth_port" ]] || + [[ -z "$token" ]] || [[ -z "$service_port" ]] || [[ -z "$auth_port" ]] || [[ -z "$service_username" ]] || [[ -z "$service_password" ]] || [[ -z "$service_tenant" ]] && juju-log "keystone_changed: Peer not ready" \ && exit 0 @@ -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 diff --git a/hooks/nova-volume-service-relation-changed b/hooks/nova-volume-service-relation-changed new file mode 120000 index 00000000..ed780797 --- /dev/null +++ b/hooks/nova-volume-service-relation-changed @@ -0,0 +1 @@ +nova-cloud-controller-relations \ No newline at end of file diff --git a/hooks/nova-volume-service-relation-joined b/hooks/nova-volume-service-relation-joined new file mode 120000 index 00000000..ed780797 --- /dev/null +++ b/hooks/nova-volume-service-relation-joined @@ -0,0 +1 @@ +nova-cloud-controller-relations \ No newline at end of file diff --git a/metadata.yaml b/metadata.yaml index 37b9ff4f..ca2f3db0 100644 --- a/metadata.yaml +++ b/metadata.yaml @@ -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 diff --git a/revision b/revision index a862eb84..3b20426c 100644 --- a/revision +++ b/revision @@ -1 +1 @@ -85 +108