Merge "exercices: aggregates needs to be more flexible"
diff --git a/README.md b/README.md
index 66e36b2..514786c 100644
--- a/README.md
+++ b/README.md
@@ -215,6 +215,10 @@
     $ cd /opt/stack/tempest
     $ nosetests tempest/scenario/test_network_basic_ops.py
 
+# Additional Projects
+
+DevStack has a hook mechanism to call out to a dispatch script at specific points in the execution if `stack.sh`, `unstack.sh` and `clean.sh`.  This allows higher-level projects, especially those that the lower level projects have no dependency on, to be added to DevStack without modifying the scripts.  Tempest is built this way as an example of how to structure the dispatch script, see `extras.d/80-tempest.sh`.  See `extras.d/README.md` for more information.
+
 # Multi-Node Setup
 
 A more interesting setup involves running multiple compute nodes, with Neutron networks connecting VMs on different compute nodes.
diff --git a/clean.sh b/clean.sh
index 6ceb5a4..395941a 100755
--- a/clean.sh
+++ b/clean.sh
@@ -47,6 +47,15 @@
 source $TOP_DIR/lib/baremetal
 source $TOP_DIR/lib/ldap
 
+# Extras Source
+# --------------
+
+# Phase: source
+if [[ -d $TOP_DIR/extras.d ]]; then
+    for i in $TOP_DIR/extras.d/*.sh; do
+        [[ -r $i ]] && source $i source
+    done
+fi
 
 # See if there is anything running...
 # need to adapt when run_service is merged
@@ -56,6 +65,16 @@
     $TOP_DIR/unstack.sh --all
 fi
 
+# Run extras
+# ==========
+
+# Phase: clean
+if [[ -d $TOP_DIR/extras.d ]]; then
+    for i in $TOP_DIR/extras.d/*.sh; do
+        [[ -r $i ]] && source $i clean
+    done
+fi
+
 # Clean projects
 cleanup_oslo
 cleanup_cinder
diff --git a/exercises/neutron-adv-test.sh b/exercises/neutron-adv-test.sh
index abb29cf..e0c37ef 100755
--- a/exercises/neutron-adv-test.sh
+++ b/exercises/neutron-adv-test.sh
@@ -102,6 +102,7 @@
 # and save it.
 
 TOKEN=`keystone token-get | grep ' id ' | awk '{print $4}'`
+die_if_not_set $LINENO TOKEN "Keystone fail to get token"
 
 # Various functions
 # -----------------
diff --git a/extras.d/80-tempest.sh b/extras.d/80-tempest.sh
index f159955..75b702c 100644
--- a/extras.d/80-tempest.sh
+++ b/extras.d/80-tempest.sh
@@ -1,21 +1,29 @@
 # tempest.sh - DevStack extras script
 
-source $TOP_DIR/lib/tempest
-
-if [[ "$1" == "stack" ]]; then
-    # Configure Tempest last to ensure that the runtime configuration of
-    # the various OpenStack services can be queried.
-    if is_service_enabled tempest; then
-        echo_summary "Configuring Tempest"
+if is_service_enabled tempest; then
+    if [[ "$1" == "source" ]]; then
+        # Initial source
+        source $TOP_DIR/lib/tempest
+    elif [[ "$1" == "stack" && "$2" == "install" ]]; then
+        echo_summary "Installing Tempest"
         install_tempest
+    elif [[ "$1" == "stack" && "$2" == "post-config" ]]; then
+        # Tempest config must come after layer 2 services are running
+        :
+    elif [[ "$1" == "stack" && "$2" == "extra" ]]; then
+        echo_summary "Initializing Tempest"
         configure_tempest
         init_tempest
     fi
-fi
 
-if [[ "$1" == "unstack" ]]; then
-    # no-op
-    :
-fi
+    if [[ "$1" == "unstack" ]]; then
+        # no-op
+        :
+    fi
 
+    if [[ "$1" == "clean" ]]; then
+        # no-op
+        :
+    fi
+fi
 
diff --git a/extras.d/README b/extras.d/README
deleted file mode 100644
index ffc6793..0000000
--- a/extras.d/README
+++ /dev/null
@@ -1,14 +0,0 @@
-The extras.d directory contains project initialization scripts to be
-sourced by stack.sh at the end of its run.  This is expected to be
-used by external projects that want to be configured, started and
-stopped with DevStack.
-
-Order is controlled by prefixing the script names with the a two digit
-sequence number.  Script names must end with '.sh'.  This provides a
-convenient way to disable scripts by simoy renaming them.
-
-DevStack reserves the sequence numbers 00 through 09 and 90 through 99
-for its own use.
-
-The scripts are called with an argument of 'stack' by stack.sh and
-with an argument of 'unstack' by unstack.sh.
diff --git a/extras.d/README.md b/extras.d/README.md
new file mode 100644
index 0000000..591e438
--- /dev/null
+++ b/extras.d/README.md
@@ -0,0 +1,31 @@
+# Extras Hooks
+
+The `extras.d` directory contains project dispatch scripts that are called
+at specific times by `stack.sh`, `unstack.sh` and `clean.sh`.  These hooks are
+used to install, configure and start additional projects during a DevStack run
+without any modifications to the base DevStack scripts.
+
+When `stack.sh` reaches one of the hook points it sources the scripts in `extras.d`
+that end with `.sh`.  To control the order that the scripts are sourced their
+names start with a two digit sequence number.  DevStack reserves the sequence
+numbers 00 through 09 and 90 through 99 for its own use.
+
+The scripts are sourced at each hook point so they should not declare anything
+at the top level that would cause a problem, specifically, functions.  This does
+allow the entire `stack.sh` variable space to be available.  The scripts are
+sourced with one or more arguments, the first of which defines the hook phase:
+
+arg 1: source | stack | unstack | clean
+
+    source: always called first in any of the scripts, used to set the
+        initial defaults in a lib/* script or similar
+
+    stack: called by stack.sh.  There are three possible values for
+        the second arg to distinguish the phase stack.sh is in:
+
+        arg 2:  install | post-config | extra
+
+    unstack: called by unstack.sh
+
+    clean: called by clean.sh.  Remember, clean.sh also calls unstack.sh
+        so that work need not be repeated.
diff --git a/functions b/functions
index 9e99cb2..d969677 100644
--- a/functions
+++ b/functions
@@ -264,7 +264,7 @@
 # - ``# dist:DISTRO`` or ``dist:DISTRO1,DISTRO2`` limits the selection
 #   of the package to the distros listed.  The distro names are case insensitive.
 function get_packages() {
-    local services=$1
+    local services=$@
     local package_dir=$(_get_package_dir)
     local file_to_parse
     local service
@@ -276,7 +276,7 @@
     if [[ -z "$DISTRO" ]]; then
         GetDistro
     fi
-    for service in general ${services//,/ }; do
+    for service in ${services//,/ }; do
         # Allow individual services to specify dependencies
         if [[ -e ${package_dir}/${service} ]]; then
             file_to_parse="${file_to_parse} $service"
diff --git a/lib/baremetal b/lib/baremetal
index 52af420..f4d8589 100644
--- a/lib/baremetal
+++ b/lib/baremetal
@@ -449,8 +449,10 @@
        "$mac_1" \
        | grep ' id ' | get_field 2 )
     [ $? -eq 0 ] || [ "$id" ] || die $LINENO "Error adding baremetal node"
-    id2=$(nova baremetal-interface-add "$id" "$mac_2" )
-    [ $? -eq 0 ] || [ "$id2" ] || die $LINENO "Error adding interface to barmetal node $id"
+    if [ -n "$mac_2" ]; then
+        id2=$(nova baremetal-interface-add "$id" "$mac_2" )
+        [ $? -eq 0 ] || [ "$id2" ] || die $LINENO "Error adding interface to barmetal node $id"
+    fi
 }
 
 
diff --git a/lib/ceilometer b/lib/ceilometer
index 1b04319..cd4c4d8 100644
--- a/lib/ceilometer
+++ b/lib/ceilometer
@@ -134,12 +134,12 @@
 
 # start_ceilometer() - Start running processes, including screen
 function start_ceilometer() {
-    screen_it ceilometer-acompute "sg $LIBVIRT_GROUP \"ceilometer-agent-compute --config-file $CEILOMETER_CONF\""
-    screen_it ceilometer-acentral "ceilometer-agent-central --config-file $CEILOMETER_CONF"
-    screen_it ceilometer-collector "ceilometer-collector --config-file $CEILOMETER_CONF"
-    screen_it ceilometer-api "ceilometer-api -d -v --log-dir=$CEILOMETER_API_LOG_DIR --config-file $CEILOMETER_CONF"
-    screen_it ceilometer-alarm-notifier "ceilometer-alarm-notifier --config-file $CEILOMETER_CONF"
-    screen_it ceilometer-alarm-evaluator "ceilometer-alarm-evaluator --config-file $CEILOMETER_CONF"
+    screen_it ceilometer-acompute "cd ; sg $LIBVIRT_GROUP \"ceilometer-agent-compute --config-file $CEILOMETER_CONF\""
+    screen_it ceilometer-acentral "cd ; ceilometer-agent-central --config-file $CEILOMETER_CONF"
+    screen_it ceilometer-collector "cd ; ceilometer-collector --config-file $CEILOMETER_CONF"
+    screen_it ceilometer-api "cd ; ceilometer-api -d -v --log-dir=$CEILOMETER_API_LOG_DIR --config-file $CEILOMETER_CONF"
+    screen_it ceilometer-alarm-notifier "cd ; ceilometer-alarm-notifier --config-file $CEILOMETER_CONF"
+    screen_it ceilometer-alarm-evaluator "cd ; ceilometer-alarm-evaluator --config-file $CEILOMETER_CONF"
 }
 
 # stop_ceilometer() - Stop running processes
diff --git a/lib/cinder b/lib/cinder
index 220488a..f6f137c 100644
--- a/lib/cinder
+++ b/lib/cinder
@@ -202,15 +202,25 @@
     sudo mv $TEMPFILE /etc/sudoers.d/cinder-rootwrap
 
     cp $CINDER_DIR/etc/cinder/api-paste.ini $CINDER_API_PASTE_INI
-    iniset $CINDER_API_PASTE_INI filter:authtoken auth_host $KEYSTONE_AUTH_HOST
-    iniset $CINDER_API_PASTE_INI filter:authtoken auth_port $KEYSTONE_AUTH_PORT
-    iniset $CINDER_API_PASTE_INI filter:authtoken auth_protocol $KEYSTONE_AUTH_PROTOCOL
-    iniset $CINDER_API_PASTE_INI filter:authtoken admin_tenant_name $SERVICE_TENANT_NAME
-    iniset $CINDER_API_PASTE_INI filter:authtoken admin_user cinder
-    iniset $CINDER_API_PASTE_INI filter:authtoken admin_password $SERVICE_PASSWORD
-    iniset $CINDER_API_PASTE_INI filter:authtoken signing_dir $CINDER_AUTH_CACHE_DIR
+
+    inicomment $CINDER_API_PASTE_INI filter:authtoken auth_host
+    inicomment $CINDER_API_PASTE_INI filter:authtoken auth_port
+    inicomment $CINDER_API_PASTE_INI filter:authtoken auth_protocol
+    inicomment $CINDER_API_PASTE_INI filter:authtoken admin_tenant_name
+    inicomment $CINDER_API_PASTE_INI filter:authtoken admin_user
+    inicomment $CINDER_API_PASTE_INI filter:authtoken admin_password
+    inicomment $CINDER_API_PASTE_INI filter:authtoken signing_dir
 
     cp $CINDER_DIR/etc/cinder/cinder.conf.sample $CINDER_CONF
+
+    iniset $CINDER_CONF keystone_authtoken auth_host $KEYSTONE_AUTH_HOST
+    iniset $CINDER_CONF keystone_authtoken auth_port $KEYSTONE_AUTH_PORT
+    iniset $CINDER_CONF keystone_authtoken auth_protocol $KEYSTONE_AUTH_PROTOCOL
+    iniset $CINDER_CONF keystone_authtoken admin_tenant_name $SERVICE_TENANT_NAME
+    iniset $CINDER_CONF keystone_authtoken admin_user cinder
+    iniset $CINDER_CONF keystone_authtoken admin_password $SERVICE_PASSWORD
+    iniset $CINDER_CONF keystone_authtoken signing_dir $CINDER_AUTH_CACHE_DIR
+
     iniset $CINDER_CONF DEFAULT auth_strategy keystone
     iniset $CINDER_CONF DEFAULT debug $ENABLE_DEBUG_LOG_LEVEL
     iniset $CINDER_CONF DEFAULT verbose True
diff --git a/lib/ironic b/lib/ironic
index f3b4a72..89d0edc 100644
--- a/lib/ironic
+++ b/lib/ironic
@@ -11,6 +11,7 @@
 # ``stack.sh`` calls the entry points in this order:
 #
 # install_ironic
+# install_ironicclient
 # configure_ironic
 # init_ironic
 # start_ironic
@@ -27,6 +28,7 @@
 
 # Set up default directories
 IRONIC_DIR=$DEST/ironic
+IRONICCLIENT_DIR=$DEST/python-ironicclient
 IRONIC_AUTH_CACHE_DIR=${IRONIC_AUTH_CACHE_DIR:-/var/cache/ironic}
 IRONIC_CONF_DIR=${IRONIC_CONF_DIR:-/etc/ironic}
 IRONIC_CONF_FILE=$IRONIC_CONF_DIR/ironic.conf
@@ -45,6 +47,18 @@
 # Functions
 # ---------
 
+# install_ironic() - Collect source and prepare
+function install_ironic() {
+    git_clone $IRONIC_REPO $IRONIC_DIR $IRONIC_BRANCH
+    setup_develop $IRONIC_DIR
+}
+
+# install_ironicclient() - Collect sources and prepare
+function install_ironicclient() {
+    git_clone $IRONICCLIENT_REPO $IRONICCLIENT_DIR $IRONICCLIENT_BRANCH
+    setup_develop $IRONICCLIENT_DIR
+}
+
 # cleanup_ironic() - Remove residual data files, anything left over from previous
 # runs that would need to clean up.
 function cleanup_ironic() {
@@ -170,12 +184,6 @@
     create_ironic_accounts
 }
 
-# install_ironic() - Collect source and prepare
-function install_ironic() {
-    git_clone $IRONIC_REPO $IRONIC_DIR $IRONIC_BRANCH
-    setup_develop $IRONIC_DIR
-}
-
 # start_ironic() - Start running processes, including screen
 function start_ironic() {
     # Start Ironic API server, if enabled.
diff --git a/lib/neutron_plugins/midonet b/lib/neutron_plugins/midonet
index 193055f..074f847 100644
--- a/lib/neutron_plugins/midonet
+++ b/lib/neutron_plugins/midonet
@@ -37,6 +37,18 @@
     iniset $Q_DHCP_CONF_FILE DEFAULT interface_driver $DHCP_INTERFACE_DRIVER
     iniset $Q_DHCP_CONF_FILE DEFAULT use_namespaces True
     iniset $Q_DHCP_CONF_FILE DEFAULT enable_isolated_metadata True
+    if [[ "$MIDONET_API_URI" != "" ]]; then
+        iniset $Q_DHCP_CONF_FILE MIDONET midonet_uri "$MIDONET_API_URI"
+    fi
+    if [[ "$MIDONET_USERNAME" != "" ]]; then
+        iniset $Q_DHCP_CONF_FILE MIDONET username "$MIDONET_USERNAME"
+    fi
+    if [[ "$MIDONET_PASSWORD" != "" ]]; then
+        iniset $Q_DHCP_CONF_FILE MIDONET password "$MIDONET_PASSWORD"
+    fi
+    if [[ "$MIDONET_PROJECT_ID" != "" ]]; then
+        iniset $Q_DHCP_CONF_FILE MIDONET project_id "$MIDONET_PROJECT_ID"
+    fi
 }
 
 function neutron_plugin_configure_l3_agent() {
diff --git a/lib/neutron_thirdparty/nicira b/lib/neutron_thirdparty/nicira
index 5a20934..3f2a5af 100644
--- a/lib/neutron_thirdparty/nicira
+++ b/lib/neutron_thirdparty/nicira
@@ -18,22 +18,38 @@
 # to an network that allows it to talk to the gateway for
 # testing purposes
 NVP_GATEWAY_NETWORK_INTERFACE=${NVP_GATEWAY_NETWORK_INTERFACE:-eth2}
+# Re-declare floating range as it's needed also in stop_nicira, which
+# is invoked by unstack.sh
+FLOATING_RANGE=${FLOATING_RANGE:-172.24.4.224/28}
 
 function configure_nicira() {
     :
 }
 
 function init_nicira() {
-    die_if_not_set $LINENO NVP_GATEWAY_NETWORK_CIDR "Please, specify CIDR for the gateway network interface."
+    if ! is_set NVP_GATEWAY_NETWORK_CIDR; then
+        NVP_GATEWAY_NETWORK_CIDR=$PUBLIC_NETWORK_GATEWAY/${FLOATING_RANGE#*/}
+        echo "The IP address to set on br-ex was not specified. "
+        echo "Defaulting to "$NVP_GATEWAY_NETWORK_CIDR
+    fi
     # Make sure the interface is up, but not configured
-    sudo ifconfig $NVP_GATEWAY_NETWORK_INTERFACE up
+    sudo ip link dev $NVP_GATEWAY_NETWORK_INTERFACE set up
+    # Save and then flush the IP addresses on the interface
+    addresses=$(ip addr show dev $NVP_GATEWAY_NETWORK_INTERFACE | grep inet | awk {'print $2'})
     sudo ip addr flush $NVP_GATEWAY_NETWORK_INTERFACE
     # Use the PUBLIC Bridge to route traffic to the NVP gateway
     # NOTE(armando-migliaccio): if running in a nested environment this will work
     # only with mac learning enabled, portsecurity and security profiles disabled
+    # The public bridge might not exist for the NVP plugin if Q_USE_DEBUG_COMMAND is off
+    # Try to create it anyway
+    sudo ovs-vsctl --no-wait -- --may-exist add-br $PUBLIC_BRIDGE
     sudo ovs-vsctl -- --may-exist add-port $PUBLIC_BRIDGE $NVP_GATEWAY_NETWORK_INTERFACE
     nvp_gw_net_if_mac=$(ip link show $NVP_GATEWAY_NETWORK_INTERFACE | awk '/ether/ {print $2}')
-    sudo ifconfig $PUBLIC_BRIDGE $NVP_GATEWAY_NETWORK_CIDR hw ether $nvp_gw_net_if_mac
+    sudo ip link dev $PUBLIC_BRIDGE set address $nvp_gw_net_if_mac
+    for address in $addresses; do
+        sudo ip addr add dev $PUBLIC_BRIDGE $address
+    done
+    sudo ip addr add dev $PUBLIC_BRIDGE $NVP_GATEWAY_NETWORK_CIDR
 }
 
 function install_nicira() {
@@ -45,7 +61,21 @@
 }
 
 function stop_nicira() {
-    :
+    if ! is_set NVP_GATEWAY_NETWORK_CIDR; then
+        NVP_GATEWAY_NETWORK_CIDR=$PUBLIC_NETWORK_GATEWAY/${FLOATING_RANGE#*/}
+        echo "The IP address expected on br-ex was not specified. "
+        echo "Defaulting to "$NVP_GATEWAY_NETWORK_CIDR
+    fi
+    sudo ip addr del $NVP_GATEWAY_NETWORK_CIDR dev $PUBLIC_BRIDGE
+    # Save and then flush remaining addresses on the interface
+    addresses=$(ip addr show dev $PUBLIC_BRIDGE | grep inet | awk {'print $2'})
+    sudo ip addr flush $PUBLIC_BRIDGE
+    # Try to detach physical interface from PUBLIC_BRIDGE
+    sudo ovs-vsctl del-port $NVP_GATEWAY_NETWORK_INTERFACE
+    # Restore addresses on NVP_GATEWAY_NETWORK_INTERFACE
+    for address in $addresses; do
+        sudo ip addr add dev $NVP_GATEWAY_NETWORK_INTERFACE $address
+    done
 }
 
 # Restore xtrace
diff --git a/lib/nova b/lib/nova
index 8deb3a0..5ff5099 100644
--- a/lib/nova
+++ b/lib/nova
@@ -212,26 +212,24 @@
     configure_nova_rootwrap
 
     if is_service_enabled n-api; then
-        # Use the sample http middleware configuration supplied in the
-        # Nova sources.  This paste config adds the configuration required
-        # for Nova to validate Keystone tokens.
-
         # Remove legacy paste config if present
         rm -f $NOVA_DIR/bin/nova-api-paste.ini
 
         # Get the sample configuration file in place
         cp $NOVA_DIR/etc/nova/api-paste.ini $NOVA_CONF_DIR
 
-        iniset $NOVA_API_PASTE_INI filter:authtoken auth_host $KEYSTONE_AUTH_HOST
+        # Comment out the keystone configs in Nova's api-paste.ini.
+        # We are using nova.conf to configure this instead.
+        inicomment $NOVA_API_PASTE_INI filter:authtoken auth_host
         if is_service_enabled tls-proxy; then
-            iniset $NOVA_API_PASTE_INI filter:authtoken auth_protocol $KEYSTONE_AUTH_PROTOCOL
+            inicomment $NOVA_API_PASTE_INI filter:authtoken auth_protocol
         fi
-        iniset $NOVA_API_PASTE_INI filter:authtoken admin_tenant_name $SERVICE_TENANT_NAME
-        iniset $NOVA_API_PASTE_INI filter:authtoken admin_user nova
-        iniset $NOVA_API_PASTE_INI filter:authtoken admin_password $SERVICE_PASSWORD
+        inicomment $NOVA_API_PASTE_INI filter:authtoken admin_tenant_name
+        inicomment $NOVA_API_PASTE_INI filter:authtoken admin_user
+        inicomment $NOVA_API_PASTE_INI filter:authtoken admin_password
     fi
 
-    iniset $NOVA_API_PASTE_INI filter:authtoken signing_dir $NOVA_AUTH_CACHE_DIR
+    inicomment $NOVA_API_PASTE_INI filter:authtoken signing_dir
 
     if is_service_enabled n-cpu; then
         # Force IP forwarding on, just on case
@@ -394,7 +392,20 @@
             # Set the service port for a proxy to take the original
             iniset $NOVA_CONF DEFAULT osapi_compute_listen_port "$NOVA_SERVICE_PORT_INT"
         fi
+
+        # Add keystone authtoken configuration
+
+        iniset $NOVA_CONF keystone_authtoken auth_host $KEYSTONE_AUTH_HOST
+        if is_service_enabled tls-proxy; then
+            iniset $NOVA_CONF keystone_authtoken auth_protocol $KEYSTONE_AUTH_PROTOCOL
+        fi
+        iniset $NOVA_CONF keystone_authtoken admin_tenant_name $SERVICE_TENANT_NAME
+        iniset $NOVA_CONF keystone_authtoken admin_user nova
+        iniset $NOVA_CONF keystone_authtoken admin_password $SERVICE_PASSWORD
     fi
+
+    iniset $NOVA_CONF keystone_authtoken signing_dir $NOVA_AUTH_CACHE_DIR
+
     if is_service_enabled cinder; then
         iniset $NOVA_CONF DEFAULT volume_api_class "nova.volume.cinder.API"
     fi
diff --git a/lib/rpc_backend b/lib/rpc_backend
index 63edc07..44c1e44 100644
--- a/lib/rpc_backend
+++ b/lib/rpc_backend
@@ -63,7 +63,7 @@
     if is_service_enabled rabbit; then
         # Obliterate rabbitmq-server
         uninstall_package rabbitmq-server
-        sudo killall epmd
+        sudo killall epmd || sudo killall -9 epmd
         if is_ubuntu; then
             # And the Erlang runtime too
             sudo aptitude purge -y ~nerlang
@@ -86,10 +86,6 @@
         else
             exit_distro_not_supported "zeromq installation"
         fi
-
-        # Necessary directory for socket location.
-        sudo mkdir -p /var/run/openstack
-        sudo chown $STACK_USER /var/run/openstack
     fi
 }
 
diff --git a/lib/tempest b/lib/tempest
index bc0b18d..9f41608 100644
--- a/lib/tempest
+++ b/lib/tempest
@@ -266,7 +266,7 @@
     iniset $TEMPEST_CONF boto ssh_user ${DEFAULT_INSTANCE_USER:-cirros}
 
     # Orchestration test image
-    if [ $HEAT_CREATE_TEST_IMAGE == "True" ]; then
+    if [[ "$HEAT_CREATE_TEST_IMAGE" = "True" ]]; then
         disk_image_create /usr/share/tripleo-image-elements "vm fedora heat-cfntools" "i386" "fedora-vm-heat-cfntools-tempest"
         iniset $TEMPEST_CONF orchestration image_ref "fedora-vm-heat-cfntools-tempest"
     fi
diff --git a/stack.sh b/stack.sh
index 14ec023..5bb26f4 100755
--- a/stack.sh
+++ b/stack.sh
@@ -313,6 +313,16 @@
 source $TOP_DIR/lib/ironic
 source $TOP_DIR/lib/trove
 
+# Extras Source
+# --------------
+
+# Phase: source
+if [[ -d $TOP_DIR/extras.d ]]; then
+    for i in $TOP_DIR/extras.d/*.sh; do
+        [[ -r $i ]] && source $i source
+    done
+fi
+
 # Set the destination directories for other OpenStack projects
 OPENSTACKCLIENT_DIR=$DEST/python-openstackclient
 
@@ -722,9 +732,20 @@
 
 if is_service_enabled ir-api ir-cond; then
     install_ironic
+    install_ironicclient
     configure_ironic
 fi
 
+# Extras Install
+# --------------
+
+# Phase: install
+if [[ -d $TOP_DIR/extras.d ]]; then
+    for i in $TOP_DIR/extras.d/*.sh; do
+        [[ -r $i ]] && source $i stack install
+    done
+fi
+
 if [[ $TRACK_DEPENDS = True ]]; then
     $DEST/.venv/bin/pip freeze > $DEST/requires-post-pip
     if ! diff -Nru $DEST/requires-pre-pip $DEST/requires-post-pip > $DEST/requires.diff; then
@@ -1000,6 +1021,17 @@
 fi
 
 
+# Extras Configuration
+# ====================
+
+# Phase: post-config
+if [[ -d $TOP_DIR/extras.d ]]; then
+    for i in $TOP_DIR/extras.d/*.sh; do
+        [[ -r $i ]] && source $i stack post-config
+    done
+fi
+
+
 # Local Configuration
 # ===================
 
@@ -1143,6 +1175,7 @@
 
 if is_service_enabled g-reg; then
     TOKEN=$(keystone token-get | grep ' id ' | get_field 2)
+    die_if_not_set $LINENO TOKEN "Keystone fail to get token"
 
     if is_baremetal; then
        echo_summary "Creating and uploading baremetal images"
@@ -1214,9 +1247,10 @@
 # Run extras
 # ==========
 
+# Phase: extra
 if [[ -d $TOP_DIR/extras.d ]]; then
     for i in $TOP_DIR/extras.d/*.sh; do
-        [[ -r $i ]] && source $i stack
+        [[ -r $i ]] && source $i stack extra
     done
 fi
 
diff --git a/stackrc b/stackrc
index 3f740b5..0151672 100644
--- a/stackrc
+++ b/stackrc
@@ -104,6 +104,10 @@
 IRONIC_REPO=${IRONIC_REPO:-${GIT_BASE}/openstack/ironic.git}
 IRONIC_BRANCH=${IRONIC_BRANCH:-master}
 
+# ironic client
+IRONICCLIENT_REPO=${IRONICCLIENT_REPO:-${GIT_BASE}/openstack/python-ironicclient.git}
+IRONICCLIENT_BRANCH=${IRONICCLIENT_BRANCH:-master}
+
 # unified auth system (manages accounts/tokens)
 KEYSTONE_REPO=${KEYSTONE_REPO:-${GIT_BASE}/openstack/keystone.git}
 KEYSTONE_BRANCH=${KEYSTONE_BRANCH:-master}
diff --git a/tools/create-stack-user.sh b/tools/create-stack-user.sh
old mode 100644
new mode 100755
diff --git a/tools/fixup_stuff.sh b/tools/fixup_stuff.sh
index f3c0f98..9e65b7c 100755
--- a/tools/fixup_stuff.sh
+++ b/tools/fixup_stuff.sh
@@ -35,25 +35,35 @@
 # Python Packages
 # ---------------
 
+# get_package_path python-package    # in import notation
+function get_package_path() {
+    local package=$1
+    echo $(python -c "import os; import $package; print(os.path.split(os.path.realpath($package.__file__))[0])")
+}
+
+
 # Pre-install affected packages so we can fix the permissions
+# These can go away once we are confident that pip 1.4.1+ is available everywhere
+
+# Fix prettytable 0.7.2 permissions
+# Don't specify --upgrade so we use the existing package if present
 pip_install prettytable
+PACKAGE_DIR=$(get_package_path prettytable)
+# Only fix version 0.7.2
+dir=$(echo $PACKAGE_DIR/prettytable-0.7.2*)
+if [[ -d $dir ]]; then
+    sudo chmod +r $dir/*
+fi
+
+# Fix httplib2 0.8 permissions
+# Don't specify --upgrade so we use the existing package if present
 pip_install httplib2
-
-SITE_DIRS=$(python -c "import site; import os; print os.linesep.join(site.getsitepackages())")
-for dir in $SITE_DIRS; do
-
-    # Fix prettytable 0.7.2 permissions
-    if [[ -r $dir/prettytable.py ]]; then
-        sudo chmod +r $dir/prettytable-0.7.2*/*
-    fi
-
-    # Fix httplib2 0.8 permissions
-    httplib_dir=httplib2-0.8.egg-info
-    if [[ -d $dir/$httplib_dir ]]; then
-        sudo chmod +r $dir/$httplib_dir/*
-    fi
-
-done
+PACKAGE_DIR=$(get_package_path httplib2)
+# Only fix version 0.8
+dir=$(echo $PACKAGE_DIR-0.8*)
+if [[ -d $dir ]]; then
+    sudo chmod +r $dir/*
+fi
 
 
 # RHEL6
diff --git a/tools/install_prereqs.sh b/tools/install_prereqs.sh
index 68f11ce..0c65fd9 100755
--- a/tools/install_prereqs.sh
+++ b/tools/install_prereqs.sh
@@ -55,7 +55,7 @@
 # ================
 
 # Install package requirements
-install_package $(get_packages $ENABLED_SERVICES)
+install_package $(get_packages general $ENABLED_SERVICES)
 
 if [[ -n "$SYSLOG" && "$SYSLOG" != "False" ]]; then
     if is_ubuntu || is_fedora; then
diff --git a/tools/upload_image.sh b/tools/upload_image.sh
index dd21c9f..d81a5c8 100755
--- a/tools/upload_image.sh
+++ b/tools/upload_image.sh
@@ -33,6 +33,7 @@
 
 # Get a token to authenticate to glance
 TOKEN=$(keystone token-get | grep ' id ' | get_field 2)
+die_if_not_set $LINENO TOKEN "Keystone fail to get token"
 
 # Glance connection info.  Note the port must be specified.
 GLANCE_HOSTPORT=${GLANCE_HOSTPORT:-$GLANCE_HOST:9292}
diff --git a/unstack.sh b/unstack.sh
index c944ccc..67c8b7c 100755
--- a/unstack.sh
+++ b/unstack.sh
@@ -42,6 +42,16 @@
 source $TOP_DIR/lib/ironic
 source $TOP_DIR/lib/trove
 
+# Extras Source
+# --------------
+
+# Phase: source
+if [[ -d $TOP_DIR/extras.d ]]; then
+    for i in $TOP_DIR/extras.d/*.sh; do
+        [[ -r $i ]] && source $i source
+    done
+fi
+
 # Determine what system we are running on.  This provides ``os_VENDOR``,
 # ``os_RELEASE``, ``os_UPDATE``, ``os_PACKAGE``, ``os_CODENAME``
 GetOSVersion
@@ -53,6 +63,7 @@
 # Run extras
 # ==========
 
+# Phase: unstack
 if [[ -d $TOP_DIR/extras.d ]]; then
     for i in $TOP_DIR/extras.d/*.sh; do
         [[ -r $i ]] && source $i unstack