diff --git a/hooks/lib/openstack-common b/hooks/lib/openstack-common index ecc1616..d5b19c5 100644 --- a/hooks/lib/openstack-common +++ b/hooks/lib/openstack-common @@ -20,6 +20,9 @@ function service_ctl_status { function service_ctl { # control a specific service, or all (as defined by $SERVICES) + # service restarts will only occur depending on global $CONFIG_CHANGED, + # which should be updated in charm's set_or_update(). + local config_changed=${CONFIG_CHANGED:-True} if [[ $1 == "all" ]] ; then ctl="$SERVICES" else @@ -37,12 +40,21 @@ function service_ctl { "stop") service_ctl_status $i && service $i stop || return 0 ;; "restart") - service_ctl_status $i && service $i restart || service $i start ;; + if [[ "$config_changed" == "True" ]] ; then + service_ctl_status $i && service $i restart || service $i start + fi + ;; esac if [[ $? != 0 ]] ; then juju-log "$CHARM: service_ctl ERROR - Service $i failed to $action" fi done + # all configs should have been reloaded on restart of all services, reset + # flag if its being used. + if [[ "$action" == "restart" ]] && [[ -n "$CONFIG_CHANGED" ]] && + [[ "$ctl" == "all" ]]; then + CONFIG_CHANGED="False" + fi } function configure_install_source { @@ -165,8 +177,9 @@ get_os_codename_install_source() { fi # have a guess based on the deb string provided - if [[ "${rel:0:3}" == "deb" ]]; then - CODENAMES="diablo essex folsom grizzly" + if [[ "${rel:0:3}" == "deb" ]] || \ + [[ "${rel:0:3}" == "ppa" ]] ; then + CODENAMES="diablo essex folsom grizzly havana" for cname in $CODENAMES; do if echo $rel | grep -q $cname; then codename=$cname @@ -178,11 +191,13 @@ get_os_codename_install_source() { get_os_codename_package() { local pkg_vers=$(dpkg -l | grep "$1" | awk '{ print $3 }') || echo "none" + pkg_vers=$(echo $pkg_vers | cut -d: -f2) # epochs case "${pkg_vers:0:6}" in "2011.2") echo "diablo" ;; "2012.1") echo "essex" ;; "2012.2") echo "folsom" ;; "2013.1") echo "grizzly" ;; + "2013.2") echo "havana" ;; esac } @@ -191,7 +206,8 @@ get_os_version_codename() { "diablo") echo "2011.2" ;; "essex") echo "2012.1" ;; "folsom") echo "2012.2" ;; - "grizzly") echo "2012.3" ;; + "grizzly") echo "2013.1" ;; + "havana") echo "2013.2" ;; esac } @@ -314,3 +330,452 @@ function get_block_device() { echo "$found" return 0 } + +HAPROXY_CFG=/etc/haproxy/haproxy.cfg +HAPROXY_DEFAULT=/etc/default/haproxy +########################################################################## +# Description: Configures HAProxy services for Openstack API's +# Parameters: +# Space delimited list of service:port:mode combinations for which +# haproxy service configuration should be generated for. The function +# assumes the name of the peer relation is 'cluster' and that every +# service unit in the peer relation is running the same services. +# +# Services that do not specify :mode in parameter will default to http. +# +# Example +# configure_haproxy cinder_api:8776:8756:tcp nova_api:8774:8764:http +########################################################################## +configure_haproxy() { + local address=`unit-get private-address` + local name=${JUJU_UNIT_NAME////-} + cat > $HAPROXY_CFG << EOF +global + log 127.0.0.1 local0 + log 127.0.0.1 local1 notice + maxconn 20000 + user haproxy + group haproxy + spread-checks 0 + +defaults + log global + mode http + option httplog + option dontlognull + retries 3 + timeout queue 1000 + timeout connect 1000 + timeout client 30000 + timeout server 30000 + +listen stats :8888 + mode http + stats enable + stats hide-version + stats realm Haproxy\ Statistics + stats uri / + stats auth admin:password + +EOF + for service in $@; do + local service_name=$(echo $service | cut -d : -f 1) + local haproxy_listen_port=$(echo $service | cut -d : -f 2) + local api_listen_port=$(echo $service | cut -d : -f 3) + local mode=$(echo $service | cut -d : -f 4) + [[ -z "$mode" ]] && mode="http" + juju-log "Adding haproxy configuration entry for $service "\ + "($haproxy_listen_port -> $api_listen_port)" + cat >> $HAPROXY_CFG << EOF +listen $service_name 0.0.0.0:$haproxy_listen_port + balance roundrobin + mode $mode + option ${mode}log + server $name $address:$api_listen_port check +EOF + local r_id="" + local unit="" + for r_id in `relation-ids cluster`; do + for unit in `relation-list -r $r_id`; do + local unit_name=${unit////-} + local unit_address=`relation-get -r $r_id private-address $unit` + if [ -n "$unit_address" ]; then + echo " server $unit_name $unit_address:$api_listen_port check" \ + >> $HAPROXY_CFG + fi + done + done + done + echo "ENABLED=1" > $HAPROXY_DEFAULT + service haproxy restart +} + +########################################################################## +# Description: Query HA interface to determine is cluster is configured +# Returns: 0 if configured, 1 if not configured +########################################################################## +is_clustered() { + local r_id="" + local unit="" + for r_id in $(relation-ids ha); do + if [ -n "$r_id" ]; then + for unit in $(relation-list -r $r_id); do + clustered=$(relation-get -r $r_id clustered $unit) + if [ -n "$clustered" ]; then + juju-log "Unit is haclustered" + return 0 + fi + done + fi + done + juju-log "Unit is not haclustered" + return 1 +} + +########################################################################## +# Description: Return a list of all peers in cluster relations +########################################################################## +peer_units() { + local peers="" + local r_id="" + for r_id in $(relation-ids cluster); do + peers="$peers $(relation-list -r $r_id)" + done + echo $peers +} + +########################################################################## +# Description: Determines whether the current unit is the oldest of all +# its peers - supports partial leader election +# Returns: 0 if oldest, 1 if not +########################################################################## +oldest_peer() { + peers=$1 + local l_unit_no=$(echo $JUJU_UNIT_NAME | cut -d / -f 2) + for peer in $peers; do + echo "Comparing $JUJU_UNIT_NAME with peers: $peers" + local r_unit_no=$(echo $peer | cut -d / -f 2) + if (($r_unit_no<$l_unit_no)); then + juju-log "Not oldest peer; deferring" + return 1 + fi + done + juju-log "Oldest peer; might take charge?" + return 0 +} + +########################################################################## +# Description: Determines whether the current service units is the +# leader within a) a cluster of its peers or b) across a +# set of unclustered peers. +# Parameters: CRM resource to check ownership of if clustered +# Returns: 0 if leader, 1 if not +########################################################################## +eligible_leader() { + if is_clustered; then + if ! is_leader $1; then + juju-log 'Deferring action to CRM leader' + return 1 + fi + else + peers=$(peer_units) + if [ -n "$peers" ] && ! oldest_peer "$peers"; then + juju-log 'Deferring action to oldest service unit.' + return 1 + fi + fi + return 0 +} + +########################################################################## +# Description: Query Cluster peer interface to see if peered +# Returns: 0 if peered, 1 if not peered +########################################################################## +is_peered() { + local r_id=$(relation-ids cluster) + if [ -n "$r_id" ]; then + if [ -n "$(relation-list -r $r_id)" ]; then + juju-log "Unit peered" + return 0 + fi + fi + juju-log "Unit not peered" + return 1 +} + +########################################################################## +# Description: Determines whether host is owner of clustered services +# Parameters: Name of CRM resource to check ownership of +# Returns: 0 if leader, 1 if not leader +########################################################################## +is_leader() { + hostname=`hostname` + if [ -x /usr/sbin/crm ]; then + if crm resource show $1 | grep -q $hostname; then + juju-log "$hostname is cluster leader." + return 0 + fi + fi + juju-log "$hostname is not cluster leader." + return 1 +} + +########################################################################## +# Description: Determines whether enough data has been provided in +# configuration or relation data to configure HTTPS. +# Parameters: None +# Returns: 0 if HTTPS can be configured, 1 if not. +########################################################################## +https() { + local r_id="" + if [[ -n "$(config-get ssl_cert)" ]] && + [[ -n "$(config-get ssl_key)" ]] ; then + return 0 + fi + for r_id in $(relation-ids identity-service) ; do + for unit in $(relation-list -r $r_id) ; do + if [[ "$(relation-get -r $r_id https_keystone $unit)" == "True" ]] && + [[ -n "$(relation-get -r $r_id ssl_cert $unit)" ]] && + [[ -n "$(relation-get -r $r_id ssl_key $unit)" ]] && + [[ -n "$(relation-get -r $r_id ca_cert $unit)" ]] ; then + return 0 + fi + done + done + return 1 +} + +########################################################################## +# Description: For a given number of port mappings, configures apache2 +# HTTPs local reverse proxying using certficates and keys provided in +# either configuration data (preferred) or relation data. Assumes ports +# are not in use (calling charm should ensure that). +# Parameters: Variable number of proxy port mappings as +# $internal:$external. +# Returns: 0 if reverse proxy(s) have been configured, 0 if not. +########################################################################## +enable_https() { + local port_maps="$@" + local http_restart="" + juju-log "Enabling HTTPS for port mappings: $port_maps." + + # allow overriding of keystone provided certs with those set manually + # in config. + local cert=$(config-get ssl_cert) + local key=$(config-get ssl_key) + local ca_cert="" + if [[ -z "$cert" ]] || [[ -z "$key" ]] ; then + juju-log "Inspecting identity-service relations for SSL certificate." + local r_id="" + cert="" + key="" + ca_cert="" + for r_id in $(relation-ids identity-service) ; do + for unit in $(relation-list -r $r_id) ; do + [[ -z "$cert" ]] && cert="$(relation-get -r $r_id ssl_cert $unit)" + [[ -z "$key" ]] && key="$(relation-get -r $r_id ssl_key $unit)" + [[ -z "$ca_cert" ]] && ca_cert="$(relation-get -r $r_id ca_cert $unit)" + done + done + [[ -n "$cert" ]] && cert=$(echo $cert | base64 -di) + [[ -n "$key" ]] && key=$(echo $key | base64 -di) + [[ -n "$ca_cert" ]] && ca_cert=$(echo $ca_cert | base64 -di) + else + juju-log "Using SSL certificate provided in service config." + fi + + [[ -z "$cert" ]] || [[ -z "$key" ]] && + juju-log "Expected but could not find SSL certificate data, not "\ + "configuring HTTPS!" && return 1 + + apt-get -y install apache2 + a2enmod ssl proxy proxy_http | grep -v "To activate the new configuration" && + http_restart=1 + + mkdir -p /etc/apache2/ssl/$CHARM + echo "$cert" >/etc/apache2/ssl/$CHARM/cert + echo "$key" >/etc/apache2/ssl/$CHARM/key + if [[ -n "$ca_cert" ]] ; then + juju-log "Installing Keystone supplied CA cert." + echo "$ca_cert" >/usr/local/share/ca-certificates/keystone_juju_ca_cert.crt + update-ca-certificates --fresh + + # XXX TODO: Find a better way of exporting this? + if [[ "$CHARM" == "nova-cloud-controller" ]] ; then + [[ -e /var/www/keystone_juju_ca_cert.crt ]] && + rm -rf /var/www/keystone_juju_ca_cert.crt + ln -s /usr/local/share/ca-certificates/keystone_juju_ca_cert.crt \ + /var/www/keystone_juju_ca_cert.crt + fi + + fi + for port_map in $port_maps ; do + local ext_port=$(echo $port_map | cut -d: -f1) + local int_port=$(echo $port_map | cut -d: -f2) + juju-log "Creating apache2 reverse proxy vhost for $port_map." + cat >/etc/apache2/sites-available/${CHARM}_${ext_port} < + ServerName $(unit-get private-address) + SSLEngine on + SSLCertificateFile /etc/apache2/ssl/$CHARM/cert + SSLCertificateKeyFile /etc/apache2/ssl/$CHARM/key + ProxyPass / http://localhost:$int_port/ + ProxyPassReverse / http://localhost:$int_port/ + ProxyPreserveHost on + + + Order deny,allow + Allow from all + + + Order allow,deny + Allow from all + +END + a2ensite ${CHARM}_${ext_port} | grep -v "To activate the new configuration" && + http_restart=1 + done + if [[ -n "$http_restart" ]] ; then + service apache2 restart + fi +} + +########################################################################## +# Description: Ensure HTTPS reverse proxying is disabled for given port +# mappings. +# Parameters: Variable number of proxy port mappings as +# $internal:$external. +# Returns: 0 if reverse proxy is not active for all portmaps, 1 on error. +########################################################################## +disable_https() { + local port_maps="$@" + local http_restart="" + juju-log "Ensuring HTTPS disabled for $port_maps." + ( [[ ! -d /etc/apache2 ]] || [[ ! -d /etc/apache2/ssl/$CHARM ]] ) && return 0 + for port_map in $port_maps ; do + local ext_port=$(echo $port_map | cut -d: -f1) + local int_port=$(echo $port_map | cut -d: -f2) + if [[ -e /etc/apache2/sites-available/${CHARM}_${ext_port} ]] ; then + juju-log "Disabling HTTPS reverse proxy for $CHARM $port_map." + a2dissite ${CHARM}_${ext_port} | grep -v "To activate the new configuration" && + http_restart=1 + fi + done + if [[ -n "$http_restart" ]] ; then + service apache2 restart + fi +} + + +########################################################################## +# Description: Ensures HTTPS is either enabled or disabled for given port +# mapping. +# Parameters: Variable number of proxy port mappings as +# $internal:$external. +# Returns: 0 if HTTPS reverse proxy is in place, 1 if it is not. +########################################################################## +setup_https() { + # configure https via apache reverse proxying either + # using certs provided by config or keystone. + [[ -z "$CHARM" ]] && + error_out "setup_https(): CHARM not set." + if ! https ; then + disable_https $@ + else + enable_https $@ + fi +} + +########################################################################## +# Description: Determine correct API server listening port based on +# existence of HTTPS reverse proxy and/or haproxy. +# Paremeters: The standard public port for given service. +# Returns: The correct listening port for API service. +########################################################################## +determine_api_port() { + local public_port="$1" + local i=0 + ( [[ -n "$(peer_units)" ]] || is_clustered >/dev/null 2>&1 ) && i=$[$i + 1] + https >/dev/null 2>&1 && i=$[$i + 1] + echo $[$public_port - $[$i * 10]] +} + +########################################################################## +# Description: Determine correct proxy listening port based on public IP + +# existence of HTTPS reverse proxy. +# Paremeters: The standard public port for given service. +# Returns: The correct listening port for haproxy service public address. +########################################################################## +determine_haproxy_port() { + local public_port="$1" + local i=0 + https >/dev/null 2>&1 && i=$[$i + 1] + echo $[$public_port - $[$i * 10]] +} + +########################################################################## +# Description: Print the value for a given config option in an OpenStack +# .ini style configuration file. +# Parameters: File path, option to retrieve, optional +# section name (default=DEFAULT) +# Returns: Prints value if set, prints nothing otherwise. +########################################################################## +local_config_get() { + # return config values set in openstack .ini config files. + # default placeholders starting (eg, %AUTH_HOST%) treated as + # unset values. + local file="$1" + local option="$2" + local section="$3" + [[ -z "$section" ]] && section="DEFAULT" + python -c " +import ConfigParser +config = ConfigParser.RawConfigParser() +config.read('$file') +try: + value = config.get('$section', '$option') +except: + print '' + exit(0) +if value.startswith('%'): exit(0) +print value +" +} + +########################################################################## +# Description: Creates an rc file exporting environment variables to a +# script_path local to the charm's installed directory. +# Any charm scripts run outside the juju hook environment can source this +# scriptrc to obtain updated config information necessary to perform health +# checks or service changes +# +# Parameters: +# An array of '=' delimited ENV_VAR:value combinations to export. +# If optional script_path key is not provided in the array, script_path +# defaults to scripts/scriptrc +########################################################################## +function save_script_rc { + if [ ! -n "$JUJU_UNIT_NAME" ]; then + echo "Error: Missing JUJU_UNIT_NAME environment variable" + exit 1 + fi + # our default unit_path + unit_path="$CHARM_DIR/scripts/scriptrc" + echo $unit_path + tmp_rc="/tmp/${JUJU_UNIT_NAME/\//-}rc" + + echo "#!/bin/bash" > $tmp_rc + for env_var in "${@}" + do + if `echo $env_var | grep -q script_path`; then + # well then we need to reset the new unit-local script path + unit_path="$CHARM_DIR/${env_var/script_path=/}" + else + echo "export $env_var" >> $tmp_rc + fi + done + chmod 755 $tmp_rc + mv $tmp_rc $unit_path +} diff --git a/hooks/swift-storage-node-common b/hooks/swift-storage-node-common index 9972214..66a0871 100755 --- a/hooks/swift-storage-node-common +++ b/hooks/swift-storage-node-common @@ -2,11 +2,12 @@ set -ue CHARM="swift-storage" +HOOKS_DIR="$CHARM_DIR/hooks" -if [[ -e "$CHARM_DIR/lib/openstack-common" ]] ; then - . $CHARM_DIR/lib/openstack-common +if [[ -e "$HOOKS_DIR/lib/openstack-common" ]] ; then + . $HOOKS_DIR/lib/openstack-common else - juju-log "ERROR: Couldn't load $CHARM_DIR/lib/openstack-common." && exit 1 + juju-log "ERROR: Couldn't load $HOOKS_DIR/lib/openstack-common." && exit 1 fi DEFAULT_ETH=$(ip route | grep default | awk '{ print $5 }') diff --git a/hooks/swift-storage-node-relations b/hooks/swift-storage-node-relations index 0a81996..f6c8f1c 100755 --- a/hooks/swift-storage-node-relations +++ b/hooks/swift-storage-node-relations @@ -2,15 +2,35 @@ # test set -eu -CHARM_DIR=$(dirname $0) +HOOKS_DIR="$CHARM_DIR/hooks" ARG0=${0##*/} -if [[ -e $CHARM_DIR/swift-storage-node-common ]] ; then - . $CHARM_DIR/swift-storage-node-common +if [[ -e $HOOKS_DIR/swift-storage-node-common ]] ; then + . $HOOKS_DIR/swift-storage-node-common else - echo "ERROR: Could not load swift-storage-node-common from $CHARM_DIR" + echo "ERROR: Could not load swift-storage-node-common from $HOOKS_DIR" fi +function config_changed { + + declare -a env_vars=() + for i in account container object ; do + port=$(config-get ${i}-server-port) + local url="http://$STORAGE_LOCAL_NET_IP:$port/recon/diskusage" + # append to env_vars + env_vars+=("OPENSTACK_PORT_${i^^}=$port") + env_vars+=("OPENSTACK_SWIFT_SERVICE_${i^^}=${i}-server") + + # Ensure we have at least one device mounted as reported by swift-recon + env_vars+=('OPENSTACK_URL_'${i^^}'="'$url'|\"mounted\":+true"') + + create_server_conf $i "$port" + done + + # Save our scriptrc env variables for health checks + save_script_rc ${env_vars[@]} +} + function install_hook { apt-get -y --force-yes install python-software-properties || exit 1 @@ -27,10 +47,7 @@ function install_hook { configure_rsyncd swift-init all stop || true setup_storage - for i in account container object ; do - port=$(config-get ${i}-server-port) - create_server_conf $i "$port" - done + config_changed } function storage_joined { @@ -52,15 +69,14 @@ function storage_joined { } function storage_changed { - local www_dir=`relation-get www_dir` + local rings_url=`relation-get rings_url` local swift_hash=`relation-get swift_hash` - [[ -z $www_dir ]] || [[ -z $swift_hash ]] && exit 0 + [[ -z $rings_url ]] || [[ -z $swift_hash ]] && exit 0 set_swift_hash $swift_hash - local url="http://$(relation-get private-address)/$www_dir" for i in account object container ; do - echo "Fetching $www_dir/$i.ring.gz" - wget "$url/$i.ring.gz" -O /etc/swift/$i.ring.gz + echo "Fetching $rings_url/$i.ring.gz" + wget "$rings_url/$i.ring.gz" -O /etc/swift/$i.ring.gz done set_swift_hash $swift_hash chown swift -R /etc/swift @@ -83,6 +99,7 @@ function config_changed { case $ARG0 in "install") install_hook ;; "start"|"stop") exit 0 ;; + "config-changed") config_changed ;; "swift-storage-relation-joined") storage_joined ;; "swift-storage-relation-changed") storage_changed ;; "config-changed") config_changed ;; diff --git a/revision b/revision index 59343b0..8c61d23 100644 --- a/revision +++ b/revision @@ -1 +1 @@ -53 +58