Merge "Move libvirt install + setup to functions-libvirt"
diff --git a/README.md b/README.md
index 9914b1e..89e3855 100644
--- a/README.md
+++ b/README.md
@@ -73,7 +73,7 @@
 This is a recent change (Oct 2013) from the previous behaviour of
 automatically creating a ``stack`` user.  Automatically creating
 user accounts is not the right response to running as root, so
-that bit is now an explicit step using ``tools/create-stack-user.sh``. 
+that bit is now an explicit step using ``tools/create-stack-user.sh``.
 Run that (as root!) or just check it out to see what DevStack's
 expectations are for the account it runs under.  Many people simply
 use their usual login (the default 'ubuntu' login on a UEC image
@@ -163,7 +163,7 @@
 Basic Setup
 
 In order to enable Neutron a single node setup, you'll need the
-following settings in your `localrc` section:
+following settings in your `local.conf`:
 
     disable_service n-net
     enable_service q-svc
@@ -172,7 +172,6 @@
     enable_service q-l3
     enable_service q-meta
     enable_service q-metering
-    enable_service neutron
     # Optional, to enable tempest configuration as part of DevStack
     enable_service tempest
 
@@ -180,24 +179,44 @@
 
 DevStack supports setting specific Neutron configuration flags to the
 service, Open vSwitch plugin and LinuxBridge plugin configuration files.
-To make use of this feature, the following variables are defined and can
-be configured in your `localrc` section:
+To make use of this feature, the settings can be added to ``local.conf``.
+The old ``Q_XXX_EXTRA_XXX_OPTS`` variables are deprecated and will be removed
+in the near future.  The ``local.conf`` headers for the replacements are:
 
-    Variable Name             Config File  Section Modified
-    -------------------------------------------------------------------------------------
-    Q_SRV_EXTRA_OPTS          Plugin       `OVS` (for Open Vswitch) or `LINUX_BRIDGE` (for LinuxBridge)
-    Q_AGENT_EXTRA_AGENT_OPTS  Plugin       AGENT
-    Q_AGENT_EXTRA_SRV_OPTS    Plugin       `OVS` (for Open Vswitch) or `LINUX_BRIDGE` (for LinuxBridge)
-    Q_SRV_EXTRA_DEFAULT_OPTS  Service      DEFAULT
+* ``Q_SRV_EXTRA_OPTS``:
 
-An example of using the variables in your `localrc` section is below:
+    [[post-config|/$Q_PLUGIN_CONF_FILE]]
+    [linuxbridge]   # or [ovs]
 
-    Q_AGENT_EXTRA_AGENT_OPTS=(tunnel_type=vxlan vxlan_udp_port=8472)
-    Q_SRV_EXTRA_OPTS=(tenant_network_type=vxlan)
+* ``Q_AGENT_EXTRA_AGENT_OPTS``:
+
+    [[post-config|/$Q_PLUGIN_CONF_FILE]]
+    [agent]
+
+* ``Q_AGENT_EXTRA_SRV_OPTS``:
+
+    [[post-config|/$Q_PLUGIN_CONF_FILE]]
+    [linuxbridge]   # or [ovs]
+
+* ``Q_SRV_EXTRA_DEFAULT_OPTS``:
+
+    [[post-config|$NEUTRON_CONF]]
+    [DEFAULT]
+
+Example extra config in `local.conf`:
+
+    [[post-config|/$Q_PLUGIN_CONF_FILE]]
+    [agent]
+    tunnel_type=vxlan
+    vxlan_udp_port=8472
+
+    [[post-config|$NEUTRON_CONF]]
+    [DEFAULT]
+    tenant_network_type=vxlan
 
 DevStack also supports configuring the Neutron ML2 plugin. The ML2 plugin
-can run with the OVS, LinuxBridge, or Hyper-V agents on compute hosts. A
-simple way to configure the ml2 plugin is shown below:
+can run with the OVS, LinuxBridge, or Hyper-V agents on compute hosts. This
+is a simple way to configure the ml2 plugin:
 
     # VLAN configuration
     Q_PLUGIN=ml2
@@ -223,7 +242,6 @@
     Q_ML2_PLUGIN_GRE_TYPE_OPTIONS    GRE TypeDriver options. Defaults to none.
     Q_ML2_PLUGIN_VXLAN_TYPE_OPTIONS  VXLAN TypeDriver options. Defaults to none.
     Q_ML2_PLUGIN_VLAN_TYPE_OPTIONS   VLAN TypeDriver options. Defaults to none.
-    Q_AGENT_EXTRA_AGENT_OPTS         Extra configuration options to pass to the OVS or LinuxBridge Agent.
 
 # Heat
 
@@ -253,10 +271,6 @@
 
 If you would like to use Xenserver as the hypervisor, please refer to the instructions in `./tools/xen/README.md`.
 
-# DevStack on Docker
-
-If you would like to use Docker as the hypervisor, please refer to the instructions in `./tools/docker/README.md`.
-
 # Additional Projects
 
 DevStack has a hook mechanism to call out to a dispatch script at specific
diff --git a/exercises/boot_from_volume.sh b/exercises/boot_from_volume.sh
index f679669..dff8e7a 100755
--- a/exercises/boot_from_volume.sh
+++ b/exercises/boot_from_volume.sh
@@ -44,9 +44,6 @@
 # the exercise is skipped
 is_service_enabled cinder || exit 55
 
-# Also skip if the hypervisor is Docker
-[[ "$VIRT_DRIVER" == "docker" ]] && exit 55
-
 # Instance type to create
 DEFAULT_INSTANCE_TYPE=${DEFAULT_INSTANCE_TYPE:-m1.tiny}
 
diff --git a/exercises/euca.sh b/exercises/euca.sh
index ad852a4..3768b56 100755
--- a/exercises/euca.sh
+++ b/exercises/euca.sh
@@ -40,9 +40,6 @@
 # the exercise is skipped
 is_service_enabled n-api || exit 55
 
-# Skip if the hypervisor is Docker
-[[ "$VIRT_DRIVER" == "docker" ]] && exit 55
-
 # Instance type to create
 DEFAULT_INSTANCE_TYPE=${DEFAULT_INSTANCE_TYPE:-m1.tiny}
 
diff --git a/exercises/floating_ips.sh b/exercises/floating_ips.sh
index 8b7b961..1416d4d 100755
--- a/exercises/floating_ips.sh
+++ b/exercises/floating_ips.sh
@@ -40,9 +40,6 @@
 # the exercise is skipped
 is_service_enabled n-api || exit 55
 
-# Skip if the hypervisor is Docker
-[[ "$VIRT_DRIVER" == "docker" ]] && exit 55
-
 # Instance type to create
 DEFAULT_INSTANCE_TYPE=${DEFAULT_INSTANCE_TYPE:-m1.tiny}
 
diff --git a/exercises/sec_groups.sh b/exercises/sec_groups.sh
index d71a1e0..5f8b0a4 100755
--- a/exercises/sec_groups.sh
+++ b/exercises/sec_groups.sh
@@ -37,9 +37,6 @@
 # the exercise is skipped
 is_service_enabled n-api || exit 55
 
-# Skip if the hypervisor is Docker
-[[ "$VIRT_DRIVER" == "docker" ]] && exit 55
-
 
 # Testing Security Groups
 # =======================
diff --git a/exercises/volumes.sh b/exercises/volumes.sh
index 83d25c7..0d556df 100755
--- a/exercises/volumes.sh
+++ b/exercises/volumes.sh
@@ -41,9 +41,6 @@
 # exercise is skipped.
 is_service_enabled cinder || exit 55
 
-# Also skip if the hypervisor is Docker
-[[ "$VIRT_DRIVER" == "docker" ]] && exit 55
-
 # Instance type to create
 DEFAULT_INSTANCE_TYPE=${DEFAULT_INSTANCE_TYPE:-m1.tiny}
 
diff --git a/extras.d/50-ironic.sh b/extras.d/50-ironic.sh
index 9e61dc5..3b8e3d5 100644
--- a/extras.d/50-ironic.sh
+++ b/extras.d/50-ironic.sh
@@ -24,10 +24,17 @@
         # Start the ironic API and ironic taskmgr components
         echo_summary "Starting Ironic"
         start_ironic
+
+        if [[ "$IRONIC_BAREMETAL_BASIC_OPS" = "True" ]]; then
+            prepare_baremetal_basic_ops
+        fi
     fi
 
     if [[ "$1" == "unstack" ]]; then
         stop_ironic
+        if [[ "$IRONIC_BAREMETAL_BASIC_OPS" = "True" ]]; then
+            cleanup_baremetal_basic_ops
+        fi
     fi
 
     if [[ "$1" == "clean" ]]; then
diff --git a/files/apts/ironic b/files/apts/ironic
new file mode 100644
index 0000000..a749ad7
--- /dev/null
+++ b/files/apts/ironic
@@ -0,0 +1,10 @@
+libguestfs0
+libvirt-bin
+openssh-client
+openvswitch-switch
+openvswitch-datapath-dkms
+python-libguestfs
+python-libvirt
+syslinux
+tftpd-hpa
+xinetd
diff --git a/files/rpms/ironic b/files/rpms/ironic
new file mode 100644
index 0000000..54b9829
--- /dev/null
+++ b/files/rpms/ironic
@@ -0,0 +1,9 @@
+libguestfs
+libvirt
+libvirt-python
+openssh-clients
+openvswitch
+python-libguestfs
+syslinux
+tftp-server
+xinetd
diff --git a/functions b/functions
index 1d30922..e0d2b01 100644
--- a/functions
+++ b/functions
@@ -122,7 +122,7 @@
             flat_fname="$(head -25 $IMAGE | { grep -G 'RW\|RDONLY [0-9]+ FLAT\|VMFS' $IMAGE || true; })"
             flat_fname="${flat_fname#*\"}"
             flat_fname="${flat_fname%?}"
-            if [[ -z "$flat_name" ]]; then
+            if [[ -z "$flat_fname" ]]; then
                 flat_fname="$IMAGE_NAME-flat.vmdk"
             fi
             path_len=`expr ${#image_url} - ${#IMAGE_FNAME}`
@@ -133,27 +133,16 @@
                 if [[ ! -f $FILES/$flat_fname || \
                 "$(stat -c "%s" $FILES/$flat_fname)" = "0" ]]; then
                     wget -c $flat_url -O $FILES/$flat_fname
-                    if [[ $? -ne 0 ]]; then
-                        echo "Flat disk not found: $flat_url"
-                        flat_found=false
-                    fi
                 fi
-                if $flat_found; then
-                    IMAGE="$FILES/${flat_fname}"
-                fi
+                IMAGE="$FILES/${flat_fname}"
             else
                 IMAGE=$(echo $flat_url | sed "s/^file:\/\///g")
                 if [[ ! -f $IMAGE || "$(stat -c "%s" $IMAGE)" == "0" ]]; then
                     echo "Flat disk not found: $flat_url"
-                    flat_found=false
-                fi
-                if ! $flat_found; then
-                    IMAGE=$(echo $image_url | sed "s/^file:\/\///g")
+                    return 1
                 fi
             fi
-            if $flat_found; then
-                IMAGE_NAME="${flat_fname}"
-            fi
+            IMAGE_NAME="${flat_fname}"
             vmdk_disktype="preallocated"
         elif [[ "$vmdk_create_type" = "streamOptimized" ]]; then
             vmdk_disktype="streamOptimized"
@@ -163,33 +152,27 @@
             if [[ ${IMAGE_NAME: -5} != "-flat" ]]; then
                 warn $LINENO "Expected filename suffix: '-flat'."`
                             `" Filename provided: ${IMAGE_NAME}"
-            fi
-
-            descriptor_fname="${IMAGE_NAME:0:${#IMAGE_NAME} - 5}.vmdk"
-            path_len=`expr ${#image_url} - ${#IMAGE_FNAME}`
-            flat_path="${image_url:0:$path_len}"
-            descriptor_url=$flat_path$descriptor_fname
-            warn $LINENO "$descriptor_data_pair_msg"`
-                            `" Attempt to retrieve the descriptor *.vmdk: $descriptor_url"
-            if [[ $flat_path != file* ]]; then
-                if [[ ! -f $FILES/$descriptor_fname || \
-                "$(stat -c "%s" $FILES/$descriptor_fname)" = "0" ]]; then
-                    wget -c $descriptor_url -O $FILES/$descriptor_fname
-                    if [[ $? -ne 0 ]]; then
-                        warn $LINENO "Descriptor not found $descriptor_url"
-                        descriptor_found=false
+            else
+                descriptor_fname="${IMAGE_NAME:0:${#IMAGE_NAME} - 5}.vmdk"
+                path_len=`expr ${#image_url} - ${#IMAGE_FNAME}`
+                flat_path="${image_url:0:$path_len}"
+                descriptor_url=$flat_path$descriptor_fname
+                warn $LINENO "$descriptor_data_pair_msg"`
+                                `" Attempt to retrieve the descriptor *.vmdk: $descriptor_url"
+                if [[ $flat_path != file* ]]; then
+                    if [[ ! -f $FILES/$descriptor_fname || \
+                    "$(stat -c "%s" $FILES/$descriptor_fname)" = "0" ]]; then
+                        wget -c $descriptor_url -O $FILES/$descriptor_fname
+                    fi
+                    descriptor_url="$FILES/$descriptor_fname"
+                else
+                    descriptor_url=$(echo $descriptor_url | sed "s/^file:\/\///g")
+                    if [[ ! -f $descriptor_url || \
+                    "$(stat -c "%s" $descriptor_url)" == "0" ]]; then
+                        echo "Descriptor not found: $descriptor_url"
+                        return 1
                     fi
                 fi
-                descriptor_url="$FILES/$descriptor_fname"
-            else
-                descriptor_url=$(echo $descriptor_url | sed "s/^file:\/\///g")
-                if [[ ! -f $descriptor_url || \
-                "$(stat -c "%s" $descriptor_url)" == "0" ]]; then
-                    warn $LINENO "Descriptor not found $descriptor_url"
-                    descriptor_found=false
-                fi
-            fi
-            if $descriptor_found; then
                 vmdk_adapter_type="$(head -25 $descriptor_url | { grep -a -F -m 1 'ddb.adapterType =' $descriptor_url || true; })"
                 vmdk_adapter_type="${vmdk_adapter_type#*\"}"
                 vmdk_adapter_type="${vmdk_adapter_type%?}"
diff --git a/functions-common b/functions-common
index 90cd3df..c6fd5c7 100644
--- a/functions-common
+++ b/functions-common
@@ -517,12 +517,14 @@
     GIT_DEST=$2
     GIT_REF=$3
     RECLONE=$(trueorfalse False $RECLONE)
+    local orig_dir=`pwd`
 
     if [[ "$OFFLINE" = "True" ]]; then
         echo "Running in offline mode, clones already exist"
         # print out the results so we know what change was used in the logs
         cd $GIT_DEST
         git show --oneline | head -1
+        cd $orig_dir
         return
     fi
 
@@ -572,6 +574,7 @@
     # print out the results so we know what change was used in the logs
     cd $GIT_DEST
     git show --oneline | head -1
+    cd $orig_dir
 }
 
 # git can sometimes get itself infinitely stuck with transient network
diff --git a/lib/baremetal b/lib/baremetal
index 1d02e1e..eda92f9 100644
--- a/lib/baremetal
+++ b/lib/baremetal
@@ -140,7 +140,10 @@
 
 # If you need to add any extra flavors to the deploy ramdisk image
 # eg, specific network drivers, specify them here
-BM_DEPLOY_FLAVOR=${BM_DEPLOY_FLAVOR:-}
+#
+# NOTE(deva): this will be moved to lib/ironic in a future patch
+#             for now, set the default to a suitable value for Ironic's needs
+BM_DEPLOY_FLAVOR=${BM_DEPLOY_FLAVOR:--a amd64 ubuntu deploy-ironic}
 
 # set URL and version for google shell-in-a-box
 BM_SHELL_IN_A_BOX=${BM_SHELL_IN_A_BOX:-http://shellinabox.googlecode.com/files/shellinabox-2.14.tar.gz}
@@ -220,7 +223,7 @@
         BM_DEPLOY_KERNEL=bm-deploy.kernel
         BM_DEPLOY_RAMDISK=bm-deploy.initramfs
         if [ ! -e "$TOP_DIR/files/$BM_DEPLOY_KERNEL" -o ! -e "$TOP_DIR/files/$BM_DEPLOY_RAMDISK" ]; then
-            $BM_IMAGE_BUILD_DIR/bin/ramdisk-image-create $BM_DEPLOY_FLAVOR deploy \
+            $BM_IMAGE_BUILD_DIR/bin/ramdisk-image-create $BM_DEPLOY_FLAVOR \
                 -o $TOP_DIR/files/bm-deploy
         fi
     fi
diff --git a/lib/heat b/lib/heat
index 2d9d863..902333e 100644
--- a/lib/heat
+++ b/lib/heat
@@ -45,6 +45,13 @@
 # Functions
 # ---------
 
+# Test if any Heat services are enabled
+# is_heat_enabled
+function is_heat_enabled {
+    [[ ,${ENABLED_SERVICES} =~ ,"h-" ]] && return 0
+    return 1
+}
+
 # cleanup_heat() - Remove residual data files, anything left over from previous
 # runs that a clean run would need to clean up
 function cleanup_heat {
diff --git a/lib/ironic b/lib/ironic
index b346de1..c6fa563 100644
--- a/lib/ironic
+++ b/lib/ironic
@@ -18,16 +18,19 @@
 # - stop_ironic
 # - cleanup_ironic
 
-# Save trace setting
+# Save trace and pipefail settings
 XTRACE=$(set +o | grep xtrace)
+PIPEFAIL=$(set +o | grep pipefail)
 set +o xtrace
-
+set +o pipefail
 
 # Defaults
 # --------
 
 # Set up default directories
 IRONIC_DIR=$DEST/ironic
+IRONIC_DATA_DIR=$DATA_DIR/ironic
+IRONIC_STATE_PATH=/var/lib/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}
@@ -35,6 +38,28 @@
 IRONIC_ROOTWRAP_CONF=$IRONIC_CONF_DIR/rootwrap.conf
 IRONIC_POLICY_JSON=$IRONIC_CONF_DIR/policy.json
 
+# Set up defaults for functional / integration testing
+IRONIC_SCRIPTS_DIR=${IRONIC_SCRIPTS_DIR:-$TOP_DIR/tools/ironic/scripts}
+IRONIC_TEMPLATES_DIR=${IRONIC_TEMPLATES_DIR:-$TOP_DIR/tools/ironic/templates}
+IRONIC_BAREMETAL_BASIC_OPS=$(trueorfalse False $IRONIC_BAREMETAL_BASIC_OPS)
+IRONIC_SSH_USERNAME=${IRONIC_SSH_USERNAME:-`whoami`}
+IRONIC_SSH_KEY_DIR=${IRONIC_SSH_KEY_DIR:-$IRONIC_DATA_DIR/ssh_keys}
+IRONIC_SSH_KEY_FILENAME=${IRONIC_SSH_KEY_FILENAME:-ironic_key}
+IRONIC_KEY_FILE=$IRONIC_SSH_KEY_DIR/$IRONIC_SSH_KEY_FILENAME
+IRONIC_SSH_VIRT_TYPE=${IRONIC_SSH_VIRT_TYPE:-virsh}
+IRONIC_TFTPBOOT_DIR=${IRONIC_TFTPBOOT_DIR:-$IRONIC_DATA_DIR/tftpboot}
+IRONIC_VM_SSH_PORT=${IRONIC_VM_SSH_PORT:-2222}
+IRONIC_VM_SSH_ADDRESS=${IRONIC_VM_SSH_ADDRESS:-$HOST_IP}
+IRONIC_VM_COUNT=${IRONIC_VM_COUNT:-1}
+IRONIC_VM_SPECS_CPU=${IRONIC_VM_SPECS_CPU:-1}
+IRONIC_VM_SPECS_RAM=${IRONIC_VM_SPECS_RAM:-256}
+IRONIC_VM_SPECS_DISK=${IRONIC_VM_SPECS_DISK:-10}
+IRONIC_VM_EMULATOR=${IRONIC_VM_EMULATOR:-/usr/bin/qemu-system-x86_64}
+IRONIC_VM_NETWORK_BRIDGE=${IRONIC_VM_NETWORK_BRIDGE:-brbm}
+IRONIC_VM_NETWORK_RANGE=${IRONIC_VM_NETWORK_RANGE:-192.0.2.0/24}
+IRONIC_VM_MACS_CSV_FILE=${IRONIC_VM_MACS_CSV_FILE:-$IRONIC_DATA_DIR/ironic_macs.csv}
+IRONIC_AUTHORIZED_KEYS_FILE=${IRONIC_AUTHORIZED_KEYS_FILE:-$HOME/.ssh/authorized_keys}
+
 # Support entry points installation of console scripts
 IRONIC_BIN_DIR=$(get_python_exec_prefix)
 
@@ -86,8 +111,8 @@
     iniset $IRONIC_CONF_FILE DEFAULT debug True
     inicomment $IRONIC_CONF_FILE DEFAULT log_file
     iniset $IRONIC_CONF_FILE DEFAULT sql_connection `database_connection_url ironic`
+    iniset $IRONIC_CONF_FILE DEFAULT state_path $IRONIC_STATE_PATH
     iniset $IRONIC_CONF_FILE DEFAULT use_syslog $SYSLOG
-
     # Configure Ironic conductor, if it was enabled.
     if is_service_enabled ir-cond; then
         configure_ironic_conductor
@@ -97,6 +122,10 @@
     if is_service_enabled ir-api; then
         configure_ironic_api
     fi
+
+    if [[ "$IRONIC_BAREMETAL_BASIC_OPS" == "True" ]]; then
+        configure_ironic_auxiliary
+    fi
 }
 
 # configure_ironic_api() - Is used by configure_ironic(). Performs
@@ -125,6 +154,10 @@
     cp -r $IRONIC_DIR/etc/ironic/rootwrap.d $IRONIC_CONF_DIR
 
     iniset $IRONIC_CONF_FILE DEFAULT rootwrap_config $IRONIC_ROOTWRAP_CONF
+    iniset $IRONIC_CONF_FILE conductor api_url http://$SERVICE_HOST:6385
+    iniset $IRONIC_CONF_FILE pxe tftp_server $SERVICE_HOST
+    iniset $IRONIC_CONF_FILE pxe tftp_root $IRONIC_TFTPBOOT_DIR
+    iniset $IRONIC_CONF_FILE pxe tftp_master_path $IRONIC_TFTPBOOT_DIR/master_images
 }
 
 # create_ironic_cache_dir() - Part of the init_ironic() process
@@ -225,9 +258,233 @@
     screen -S $SCREEN_NAME -p ir-cond -X kill
 }
 
+function is_ironic {
+    if ( is_service_enabled ir-cond && is_service_enabled ir-api ); then
+        return 0
+    fi
+    return 1
+}
 
-# Restore xtrace
+function configure_ironic_dirs {
+    sudo mkdir -p $IRONIC_DATA_DIR
+    sudo mkdir -p $IRONIC_STATE_PATH
+    sudo mkdir -p $IRONIC_TFTPBOOT_DIR
+    sudo chown -R $STACK_USER $IRONIC_DATA_DIR $IRONIC_STATE_PATH
+    sudo chown -R $STACK_USER:$LIBVIRT_GROUP $IRONIC_TFTPBOOT_DIR
+    if is_ubuntu; then
+        PXEBIN=/usr/lib/syslinux/pxelinux.0
+    elif is_fedora; then
+        PXEBIN=/usr/share/syslinux/pxelinux.0
+    fi
+    if [ ! -f $PXEBIN ]; then
+        die $LINENO "pxelinux.0 (from SYSLINUX) not found."
+    fi
+
+    cp $PXEBIN $IRONIC_TFTPBOOT_DIR
+    mkdir -p $IRONIC_TFTPBOOT_DIR/pxelinux.cfg
+}
+
+function ironic_ensure_libvirt_group {
+    groups $STACK_USER | grep -q $LIBVIRT_GROUP || adduser $STACK_USER $LIBVIRT_GROUP
+}
+
+function create_bridge_and_vms {
+    ironic_ensure_libvirt_group
+
+    # Call libvirt setup scripts in a new shell to ensure any new group membership
+    sudo su $STACK_USER -c "$IRONIC_SCRIPTS_DIR/setup-network"
+
+    sudo su $STACK_USER -c "$IRONIC_SCRIPTS_DIR/create-nodes \
+        $IRONIC_VM_SPECS_CPU $IRONIC_VM_SPECS_RAM $IRONIC_VM_SPECS_DISK \
+        amd64 $IRONIC_VM_COUNT $IRONIC_VM_NETWORK_BRIDGE $IRONIC_VM_EMULATOR" >> $IRONIC_VM_MACS_CSV_FILE
+
+}
+
+function enroll_vms {
+
+    CHASSIS_ID=$(ironic chassis-create -d "ironic test chassis" | grep " uuid " | get_field 2)
+    IRONIC_NET_ID=$(neutron net-list | grep private | get_field 1)
+    local idx=0
+
+    # work around; need to know what netns neutron uses for private network
+    neutron port-create private
+
+    while read MAC; do
+
+        NODE_ID=$(ironic node-create --chassis_uuid $CHASSIS_ID --driver pxe_ssh \
+            -i ssh_virt_type=$IRONIC_SSH_VIRT_TYPE \
+            -i ssh_address=$IRONIC_VM_SSH_ADDRESS \
+            -i ssh_port=$IRONIC_VM_SSH_PORT \
+            -i ssh_username=$IRONIC_SSH_USERNAME \
+            -i ssh_key_filename=$IRONIC_SSH_KEY_DIR/$IRONIC_SSH_KEY_FILENAME \
+            -p cpus=$IRONIC_VM_SPECS_CPU \
+            -p memory_mb=$IRONIC_VM_SPECS_RAM \
+            -p local_gb=$IRONIC_VM_SPECS_DISK \
+            -p cpu_arch=x86_64 \
+            | grep " uuid " | get_field 2)
+
+        ironic port-create --address $MAC --node_uuid $NODE_ID
+
+        idx=$((idx+1))
+
+    done < $IRONIC_VM_MACS_CSV_FILE
+
+    # create the nova flavor
+    nova flavor-create baremetal auto $IRONIC_VM_SPECS_RAM $IRONIC_VM_SPECS_DISK $IRONIC_VM_SPECS_CPU
+    nova flavor-key baremetal set "cpu_arch"="x86_64" "baremetal:deploy_kernel_id"="$BM_DEPLOY_KERNEL_ID" "baremetal:deploy_ramdisk_id"="$BM_DEPLOY_RAMDISK_ID"
+
+    # intentional sleep to make sure the tag has been set to port
+    sleep 10
+    TAPDEV=$(sudo ip netns exec qdhcp-${IRONIC_NET_ID} ip link list | grep tap | cut -d':' -f2 | cut -b2-)
+    TAG_ID=$(sudo ovs-vsctl show |grep ${TAPDEV} -A1 -m1 | grep tag | cut -d':' -f2 | cut -b2-)
+
+    # make sure veth pair is not existing, otherwise delete its links
+    sudo ip link show ovs-tap1 && sudo ip link delete ovs-tap1
+    sudo ip link show brbm-tap1 && sudo ip link delete brbm-tap1
+    # create veth pair for future interconnection between br-int and brbm
+    sudo ip link add brbm-tap1 type veth peer name ovs-tap1
+    sudo ip link set dev brbm-tap1 up
+    sudo ip link set dev ovs-tap1 up
+
+    sudo ovs-vsctl -- --if-exists del-port ovs-tap1 -- add-port br-int ovs-tap1 tag=$TAG_ID
+    sudo ovs-vsctl -- --if-exists del-port brbm-tap1 -- add-port $IRONIC_VM_NETWORK_BRIDGE brbm-tap1
+}
+
+function configure_tftpd {
+    # enable tftp natting for allowing connections to SERVICE_HOST's tftp server
+    sudo modprobe nf_conntrack_tftp
+    sudo modprobe nf_nat_tftp
+
+    if is_ubuntu; then
+        PXEBIN=/usr/lib/syslinux/pxelinux.0
+    elif is_fedora; then
+        PXEBIN=/usr/share/syslinux/pxelinux.0
+    fi
+    if [ ! -f $PXEBIN ]; then
+        die $LINENO "pxelinux.0 (from SYSLINUX) not found."
+    fi
+
+    # stop tftpd and setup serving via xinetd
+    stop_service tftpd-hpa || true
+    [ -f /etc/init/tftpd-hpa.conf ] && echo "manual" | sudo tee /etc/init/tftpd-hpa.override
+    sudo cp $IRONIC_TEMPLATES_DIR/tftpd-xinetd.template /etc/xinetd.d/tftp
+    sudo sed -e "s|%TFTPBOOT_DIR%|$IRONIC_TFTPBOOT_DIR|g" -i /etc/xinetd.d/tftp
+
+    # setup tftp file mapping to satisfy requests at the root (booting) and
+    # /tftpboot/ sub-dir (as per deploy-ironic elements)
+    echo "r ^([^/]) $IRONIC_TFTPBOOT_DIR/\1" >$IRONIC_TFTPBOOT_DIR/map-file
+    echo "r ^(/tftpboot/) $IRONIC_TFTPBOOT_DIR/\2" >>$IRONIC_TFTPBOOT_DIR/map-file
+
+    chmod -R 0755 $IRONIC_TFTPBOOT_DIR
+    restart_service xinetd
+}
+
+function configure_ironic_ssh_keypair {
+    # Generating ssh key pair for stack user
+    if [[ ! -d $IRONIC_SSH_KEY_DIR ]]; then
+        mkdir -p $IRONIC_SSH_KEY_DIR
+    fi
+    if [[ ! -d $HOME/.ssh ]]; then
+        mkdir -p $HOME/.ssh
+        chmod 700 $HOME/.ssh
+    fi
+    echo -e 'n\n' | ssh-keygen -q -t rsa -P '' -f $IRONIC_KEY_FILE
+    cat $IRONIC_KEY_FILE.pub | tee -a $IRONIC_AUTHORIZED_KEYS_FILE
+}
+
+function ironic_ssh_check {
+    local KEY_FILE=$1
+    local FLOATING_IP=$2
+    local PORT=$3
+    local DEFAULT_INSTANCE_USER=$4
+    local ACTIVE_TIMEOUT=$5
+    if ! timeout $ACTIVE_TIMEOUT sh -c "while ! ssh -p $PORT -o StrictHostKeyChecking=no -i $KEY_FILE ${DEFAULT_INSTANCE_USER}@$FLOATING_IP echo success; do sleep 1; done"; then
+        die $LINENO "server didn't become ssh-able!"
+    fi
+}
+
+function configure_ironic_sshd {
+    # Ensure sshd server accepts connections from localhost only
+
+    SSH_CONFIG=/etc/ssh/sshd_config
+    HOST_PORT=$IRONIC_VM_SSH_ADDRESS:$IRONIC_VM_SSH_PORT
+    if ! sudo grep ListenAddress $SSH_CONFIG | grep $HOST_PORT; then
+        echo "ListenAddress $HOST_PORT" | sudo tee -a $SSH_CONFIG
+    fi
+
+    SSH_SERVICE_NAME=sshd
+    if is_ubuntu; then
+        SSH_SERVICE_NAME=ssh
+    fi
+
+    restart_service $SSH_SERVICE_NAME
+    # to ensure ssh service is up and running
+    sleep 3
+    ironic_ssh_check $IRONIC_SSH_KEY_DIR/$IRONIC_SSH_KEY_FILENAME $IRONIC_VM_SSH_ADDRESS $IRONIC_VM_SSH_PORT $IRONIC_SSH_USERNAME 10
+
+}
+
+function configure_ironic_auxiliary {
+    configure_ironic_dirs
+    configure_ironic_ssh_keypair
+    configure_ironic_sshd
+}
+
+function prepare_baremetal_basic_ops {
+
+    # install diskimage-builder
+    git_clone $BM_IMAGE_BUILD_REPO $BM_IMAGE_BUILD_DIR $BM_IMAGE_BUILD_BRANCH
+
+    # make sure all needed service were enabled
+    for srv in nova glance key neutron; do
+        if ! is_service_enabled "$srv"; then
+            die $LINENO "$srv should be enabled for ironic tests"
+        fi
+    done
+
+    SCREEN_NAME=${SCREEN_NAME:-stack}
+    SERVICE_DIR=${SERVICE_DIR:-${DEST}/status}
+
+    # stop all nova services
+    stop_nova || true
+
+    # remove any nova services failure status
+    find $SERVICE_DIR/$SCREEN_NAME -name 'n-*.failure' -exec rm -f '{}' \;
+
+    # start them again
+    start_nova_api
+    start_nova
+
+    TOKEN=$(keystone token-get | grep ' id ' | get_field 2)
+    die_if_not_set $LINENO TOKEN "Keystone fail to get token"
+
+    echo_summary "Creating and uploading baremetal images for ironic"
+
+    # build and upload separate deploy kernel & ramdisk
+    upload_baremetal_deploy $TOKEN
+
+    create_bridge_and_vms
+    enroll_vms
+    configure_tftpd
+}
+
+function cleanup_baremetal_basic_ops {
+    rm -f $IRONIC_VM_MACS_CSV_FILE
+    if [ -f $IRONIC_KEY_FILE ]; then
+        KEY=`cat $IRONIC_KEY_FILE.pub`
+        # remove public key from authorized_keys
+        grep -v "$KEY" $IRONIC_AUTHORIZED_KEYS_FILE > temp && mv temp $IRONIC_AUTHORIZED_KEYS_FILE
+        chmod 0600 $IRONIC_AUTHORIZED_KEYS_FILE
+    fi
+    sudo rm -rf $IRONIC_DATA_DIR $IRONIC_STATE_PATH
+    sudo su $STACK_USER -c "$IRONIC_SCRIPTS_DIR/cleanup-nodes $IRONIC_VM_COUNT $IRONIC_VM_NETWORK_BRIDGE"
+    sudo rm -rf /etc/xinetd.d/tftp /etc/init/tftpd-hpa.override
+    restart_service xinetd
+}
+
+# Restore xtrace + pipefail
 $XTRACE
+$PIPEFAIL
 
 # Tell emacs to use shell-script-mode
 ## Local variables:
diff --git a/lib/marconi b/lib/marconi
index 3c4547f..fd1c351 100644
--- a/lib/marconi
+++ b/lib/marconi
@@ -154,7 +154,7 @@
 
 # start_marconi() - Start running processes, including screen
 function start_marconi {
-    screen_it marconi-server "marconi-server --config-file $MARCONI_CONF 2>&1"
+    screen_it marconi-server "marconi-server --config-file $MARCONI_CONF"
     echo "Waiting for Marconi to start..."
     if ! timeout $SERVICE_TIMEOUT sh -c "while ! wget --no-proxy -q -O- $MARCONI_SERVICE_PROTOCOL://$MARCONI_SERVICE_HOST:$MARCONI_SERVICE_PORT/v1/health; do sleep 1; done"; then
         die $LINENO "Marconi did not start"
diff --git a/lib/neutron_plugins/nuage b/lib/neutron_plugins/nuage
new file mode 100644
index 0000000..3649f39
--- /dev/null
+++ b/lib/neutron_plugins/nuage
@@ -0,0 +1,69 @@
+# Nuage Neutron Plugin
+# ----------------------
+
+# Save trace setting
+MY_XTRACE=$(set +o | grep xtrace)
+set +o xtrace
+
+function neutron_plugin_create_nova_conf {
+    NOVA_OVS_BRIDGE=${NOVA_OVS_BRIDGE:-"br-int"}
+    iniset $NOVA_CONF DEFAULT neutron_ovs_bridge $NOVA_OVS_BRIDGE
+    NOVA_VIF_DRIVER=${NOVA_VIF_DRIVER:-"nova.virt.libvirt.vif.LibvirtGenericVIFDriver"}
+    LIBVIRT_FIREWALL_DRIVER=nova.virt.firewall.NoopFirewallDriver
+    iniset $NOVA_CONF DEFAULT firewall_driver $LIBVIRT_FIREWALL_DRIVER
+}
+
+function neutron_plugin_install_agent_packages {
+    :
+}
+
+function neutron_plugin_configure_common {
+    Q_PLUGIN_CONF_PATH=etc/neutron/plugins/nuage
+    Q_PLUGIN_CONF_FILENAME=nuage_plugin.ini
+    Q_DB_NAME="nuage_neutron"
+    Q_PLUGIN_CLASS="neutron.plugins.nuage.plugin.NuagePlugin"
+    Q_PLUGIN_EXTENSIONS_PATH=neutron/plugins/nuage/extensions
+    #Nuage specific Neutron defaults. Actual value must be set and sourced
+    NUAGE_CNA_SERVERS=${NUAGE_CNA_SERVERS:-'localhost:8443'}
+    NUAGE_CNA_SERVER_AUTH=${NUAGE_CNA_SERVER_AUTH:-'username:password'}
+    NUAGE_CNA_ORGANIZATION=${NUAGE_CNA_ORGANIZATION:-'org'}
+    NUAGE_CNA_SERVER_SSL=${NUAGE_CNA_SERVER_SSL:-'True'}
+    NUAGE_CNA_BASE_URI=${NUAGE_CNA_BASE_URI:-'/'}
+    NUAGE_CNA_AUTH_RESOURCE=${NUAGE_CNA_AUTH_RESOURCE:-'/'}
+    NUAGE_CNA_DEF_NETPART_NAME=${NUAGE_CNA_DEF_NETPART_NAME:-''}
+}
+
+function neutron_plugin_configure_debug_command {
+    :
+}
+
+function neutron_plugin_configure_dhcp_agent {
+    :
+}
+
+function neutron_plugin_configure_l3_agent {
+    :
+}
+
+function neutron_plugin_configure_plugin_agent {
+    :
+}
+
+function neutron_plugin_configure_service {
+    iniset $NEUTRON_CONF DEFAULT api_extensions_path neutron/plugins/nuage/extensions/
+    iniset /$Q_PLUGIN_CONF_FILE restproxy base_uri $NUAGE_CNA_BASE_URI
+    iniset /$Q_PLUGIN_CONF_FILE restproxy serverssl $NUAGE_CNA_SERVER_SSL
+    iniset /$Q_PLUGIN_CONF_FILE restproxy serverauth $NUAGE_CNA_SERVER_AUTH
+    iniset /$Q_PLUGIN_CONF_FILE restproxy organization $NUAGE_CNA_ORGANIZATION
+    iniset /$Q_PLUGIN_CONF_FILE restproxy server $NUAGE_CNA_SERVERS
+    iniset /$Q_PLUGIN_CONF_FILE restproxy auth_resource $NUAGE_CNA_AUTH_RESOURCE
+    iniset /$Q_PLUGIN_CONF_FILE restproxy default_net_partition_name $NUAGE_CNA_DEF_NETPART_NAME
+}
+
+function has_neutron_plugin_security_group {
+    # 1 means False here
+    return 1
+}
+
+# Restore xtrace
+$MY_XTRACE
diff --git a/lib/nova b/lib/nova
index 360427d..b01d107 100644
--- a/lib/nova
+++ b/lib/nova
@@ -716,6 +716,7 @@
 }
 
 function stop_nova_compute {
+    screen_stop n-cpu
     if is_service_enabled n-cpu && [[ -r $NOVA_PLUGINS/hypervisor-$VIRT_DRIVER ]]; then
         stop_nova_hypervisor
     fi
@@ -725,7 +726,7 @@
     # Kill the nova screen windows
     # Some services are listed here twice since more than one instance
     # of a service may be running in certain configs.
-    for serv in n-api n-cpu n-crt n-net n-sch n-novnc n-xvnc n-cauth n-spice n-cond n-cell n-cell n-api-meta n-obj; do
+    for serv in n-api n-crt n-net n-sch n-novnc n-xvnc n-cauth n-spice n-cond n-cell n-cell n-api-meta n-obj; do
         screen_stop $serv
     done
 }
diff --git a/lib/nova_plugins/hypervisor-docker b/lib/nova_plugins/hypervisor-docker
deleted file mode 100644
index fd3c4fe..0000000
--- a/lib/nova_plugins/hypervisor-docker
+++ /dev/null
@@ -1,132 +0,0 @@
-# lib/nova_plugins/docker
-# Configure the Docker hypervisor
-
-# Enable with:
-#
-#   VIRT_DRIVER=docker
-
-# Dependencies:
-#
-# - ``functions`` file
-# - ``nova`` and ``glance`` configurations
-
-# install_nova_hypervisor - install any external requirements
-# configure_nova_hypervisor - make configuration changes, including those to other services
-# start_nova_hypervisor - start any external services
-# stop_nova_hypervisor - stop any external services
-# cleanup_nova_hypervisor - remove transient data and cache
-
-# Save trace setting
-MY_XTRACE=$(set +o | grep xtrace)
-set +o xtrace
-
-
-# Defaults
-# --------
-
-# Set up default directories
-DOCKER_DIR=$DEST/docker
-
-DOCKER_UNIX_SOCKET=/var/run/docker.sock
-DOCKER_PID_FILE=/var/run/docker.pid
-DOCKER_REGISTRY_PORT=${DOCKER_REGISTRY_PORT:-5042}
-
-DOCKER_IMAGE=${DOCKER_IMAGE:-cirros:latest}
-DOCKER_IMAGE_NAME=$DEFAULT_IMAGE_NAME
-DOCKER_REGISTRY_IMAGE=${DOCKER_REGISTRY_IMAGE:-registry:latest}
-DOCKER_REGISTRY_IMAGE_NAME=registry
-DOCKER_REPOSITORY_NAME=${SERVICE_HOST}:${DOCKER_REGISTRY_PORT}/${DOCKER_IMAGE_NAME}
-
-DOCKER_APT_REPO=${DOCKER_APT_REPO:-https://get.docker.io/ubuntu}
-
-
-# Entry Points
-# ------------
-
-# clean_nova_hypervisor - Clean up an installation
-function cleanup_nova_hypervisor {
-    stop_service docker
-
-    # Clean out work area
-    sudo rm -rf /var/lib/docker
-}
-
-# configure_nova_hypervisor - Set config files, create data dirs, etc
-function configure_nova_hypervisor {
-    iniset $NOVA_CONF DEFAULT compute_driver docker.DockerDriver
-    iniset $GLANCE_API_CONF DEFAULT container_formats ami,ari,aki,bare,ovf,docker
-}
-
-# is_docker_running - Return 0 (true) if Docker is running, otherwise 1
-function is_docker_running {
-    local docker_pid
-    if [ -f "$DOCKER_PID_FILE" ]; then
-        docker_pid=$(cat "$DOCKER_PID_FILE")
-    fi
-    if [[ -z "$docker_pid" ]] || ! ps -p "$docker_pid" | grep [d]ocker; then
-        return 1
-    fi
-    return 0
-}
-
-# install_nova_hypervisor() - Install external components
-function install_nova_hypervisor {
-    # So far this is Ubuntu only
-    if ! is_ubuntu; then
-        die $LINENO "Docker is only supported on Ubuntu at this time"
-    fi
-
-    # Make sure Docker is installed
-    if ! is_package_installed lxc-docker; then
-        die $LINENO "Docker is not installed.  Please run tools/docker/install_docker.sh"
-    fi
-
-    if ! (is_docker_running); then
-        die $LINENO "Docker not running"
-    fi
-}
-
-# start_nova_hypervisor - Start any required external services
-function start_nova_hypervisor {
-    if ! (is_docker_running); then
-        die $LINENO "Docker not running"
-    fi
-
-    # Start the Docker registry container
-    docker run -d -p ${DOCKER_REGISTRY_PORT}:5000 \
-        -e SETTINGS_FLAVOR=openstack -e OS_USERNAME=${OS_USERNAME} \
-        -e OS_PASSWORD=${OS_PASSWORD} -e OS_TENANT_NAME=${OS_TENANT_NAME} \
-        -e OS_GLANCE_URL="${SERVICE_PROTOCOL}://${GLANCE_HOSTPORT}" \
-        -e OS_AUTH_URL=${OS_AUTH_URL} \
-        $DOCKER_REGISTRY_IMAGE_NAME ./docker-registry/run.sh
-
-    echo "Waiting for docker registry to start..."
-    DOCKER_REGISTRY=${SERVICE_HOST}:${DOCKER_REGISTRY_PORT}
-    if ! timeout $SERVICE_TIMEOUT sh -c "while ! curl -s $DOCKER_REGISTRY; do sleep 1; done"; then
-        die $LINENO "docker-registry did not start"
-    fi
-
-    # Tag image if not already tagged
-    if ! docker images | grep $DOCKER_REPOSITORY_NAME; then
-        docker tag $DOCKER_IMAGE_NAME $DOCKER_REPOSITORY_NAME
-    fi
-
-    # Make sure we copied the image in Glance
-    if ! (glance image-show "$DOCKER_IMAGE"); then
-        docker push $DOCKER_REPOSITORY_NAME
-    fi
-}
-
-# stop_nova_hypervisor - Stop any external services
-function stop_nova_hypervisor {
-    # Stop the docker registry container
-    docker kill $(docker ps | grep docker-registry | cut -d' ' -f1)
-}
-
-
-# Restore xtrace
-$MY_XTRACE
-
-# Local variables:
-# mode: shell-script
-# End:
diff --git a/lib/nova_plugins/hypervisor-ironic b/lib/nova_plugins/hypervisor-ironic
new file mode 100644
index 0000000..5af7c0b
--- /dev/null
+++ b/lib/nova_plugins/hypervisor-ironic
@@ -0,0 +1,75 @@
+# lib/nova_plugins/hypervisor-ironic
+# Configure the ironic hypervisor
+
+# Enable with:
+# VIRT_DRIVER=ironic
+
+# Dependencies:
+# ``functions`` file
+# ``nova`` configuration
+
+# install_nova_hypervisor - install any external requirements
+# configure_nova_hypervisor - make configuration changes, including those to other services
+# start_nova_hypervisor - start any external services
+# stop_nova_hypervisor - stop any external services
+# cleanup_nova_hypervisor - remove transient data and cache
+
+# Save trace setting
+MY_XTRACE=$(set +o | grep xtrace)
+set +o xtrace
+
+
+# Defaults
+# --------
+
+# Entry Points
+# ------------
+
+# clean_nova_hypervisor - Clean up an installation
+function cleanup_nova_hypervisor {
+    # This function intentionally left blank
+    :
+}
+
+# configure_nova_hypervisor - Set config files, create data dirs, etc
+function configure_nova_hypervisor {
+    iniset $NOVA_CONF ironic sql_connection `database_connection_url nova_bm`
+    LIBVIRT_FIREWALL_DRIVER=${LIBVIRT_FIREWALL_DRIVER:-"nova.virt.firewall.NoopFirewallDriver"}
+    iniset $NOVA_CONF DEFAULT compute_driver ironic.nova.virt.ironic.IronicDriver
+    iniset $NOVA_CONF DEFAULT firewall_driver $LIBVIRT_FIREWALL_DRIVER
+    iniset $NOVA_CONF DEFAULT scheduler_host_manager ironic.nova.scheduler.ironic_host_manager.IronicHostManager
+    iniset $NOVA_CONF DEFAULT ram_allocation_ratio 1.0
+    iniset $NOVA_CONF DEFAULT reserved_host_memory_mb 0
+    # ironic section
+    iniset $NOVA_CONF ironic admin_username admin
+    iniset $NOVA_CONF ironic admin_password $ADMIN_PASSWORD
+    iniset $NOVA_CONF ironic admin_url $KEYSTONE_AUTH_PROTOCOL://$KEYSTONE_AUTH_HOST:$KEYSTONE_AUTH_PORT/v2.0
+    iniset $NOVA_CONF ironic admin_tenant_name demo
+    iniset $NOVA_CONF ironic api_endpoint http://$SERVICE_HOST:6358/v1
+}
+
+# install_nova_hypervisor() - Install external components
+function install_nova_hypervisor {
+    # This function intentionally left blank
+    :
+}
+
+# start_nova_hypervisor - Start any required external services
+function start_nova_hypervisor {
+    # This function intentionally left blank
+    :
+}
+
+# stop_nova_hypervisor - Stop any external services
+function stop_nova_hypervisor {
+    # This function intentionally left blank
+    :
+}
+
+
+# Restore xtrace
+$MY_XTRACE
+
+# Local variables:
+# mode: shell-script
+# End:
diff --git a/run_tests.sh b/run_tests.sh
index a0bfbee..685b203 100755
--- a/run_tests.sh
+++ b/run_tests.sh
@@ -27,3 +27,16 @@
 echo "Running bash8..."
 
 ./tools/bash8.py -v $FILES
+
+
+# Test that no one is trying to land crazy refs as branches
+
+echo "Ensuring we don't have crazy refs"
+
+REFS=`grep BRANCH stackrc | grep -v -- '-master'`
+rc=$?
+if [[ $rc -eq 0 ]]; then
+    echo "Branch defaults must be master. Found:"
+    echo $REFS
+    exit 1
+fi
diff --git a/stack.sh b/stack.sh
index e76a55c..7fa4d37 100755
--- a/stack.sh
+++ b/stack.sh
@@ -538,8 +538,9 @@
                     cmd | getline now
                     close("date +\"%Y-%m-%d %H:%M:%S.%3N | \"")
                     sub(/^/, now)
-                    print
                     print > logfile
+                    fflush(logfile)
+                    print
                     fflush("")
                 }' ) 2>&1
         # Set up a second fd for output
@@ -1355,12 +1356,14 @@
     echo_summary "WARNING: $DEPRECATED_TEXT"
 fi
 
+# TODO(dtroyer): Remove EXTRA_OPTS after stable/icehouse branch is cut
 # Specific warning for deprecated configs
 if [[ -n "$EXTRA_OPTS" ]]; then
     echo ""
     echo_summary "WARNING: EXTRA_OPTS is used"
     echo "You are using EXTRA_OPTS to pass configuration into nova.conf."
     echo "Please convert that configuration in localrc to a nova.conf section in local.conf:"
+    echo "EXTRA_OPTS will be removed early in the Juno development cycle"
     echo "
 [[post-config|\$NOVA_CONF]]
 [DEFAULT]
@@ -1371,11 +1374,13 @@
     done
 fi
 
+# TODO(dtroyer): Remove EXTRA_BAREMETAL_OPTS after stable/icehouse branch is cut
 if [[ -n "$EXTRA_BAREMETAL_OPTS" ]]; then
     echo ""
-    echo_summary "WARNING: EXTRA_OPTS is used"
-    echo "You are using EXTRA_OPTS to pass configuration into nova.conf."
+    echo_summary "WARNING: EXTRA_BAREMETAL_OPTS is used"
+    echo "You are using EXTRA_BAREMETAL_OPTS to pass configuration into nova.conf."
     echo "Please convert that configuration in localrc to a nova.conf section in local.conf:"
+    echo "EXTRA_BAREMETAL_OPTS will be removed early in the Juno development cycle"
     echo "
 [[post-config|\$NOVA_CONF]]
 [baremetal]
@@ -1386,13 +1391,49 @@
     done
 fi
 
+# TODO(dtroyer): Remove Q_AGENT_EXTRA_AGENT_OPTS after stable/juno branch is cut
+if [[ -n "$Q_AGENT_EXTRA_AGENT_OPTS" ]]; then
+    echo ""
+    echo_summary "WARNING: Q_AGENT_EXTRA_AGENT_OPTS is used"
+    echo "You are using Q_AGENT_EXTRA_AGENT_OPTS to pass configuration into $NEUTRON_CONF."
+    echo "Please convert that configuration in localrc to a $NEUTRON_CONF section in local.conf:"
+    echo "Q_AGENT_EXTRA_AGENT_OPTS will be removed early in the 'K' development cycle"
+    echo "
+[[post-config|/\$Q_PLUGIN_CONF_FILE]]
+[DEFAULT]
+"
+    for I in "${Q_AGENT_EXTRA_AGENT_OPTS[@]}"; do
+        # Replace the first '=' with ' ' for iniset syntax
+        echo ${I}
+    done
+fi
+
+# TODO(dtroyer): Remove Q_AGENT_EXTRA_SRV_OPTS after stable/juno branch is cut
+if [[ -n "$Q_AGENT_EXTRA_SRV_OPTS" ]]; then
+    echo ""
+    echo_summary "WARNING: Q_AGENT_EXTRA_SRV_OPTS is used"
+    echo "You are using Q_AGENT_EXTRA_SRV_OPTS to pass configuration into $NEUTRON_CONF."
+    echo "Please convert that configuration in localrc to a $NEUTRON_CONF section in local.conf:"
+    echo "Q_AGENT_EXTRA_AGENT_OPTS will be removed early in the 'K' development cycle"
+    echo "
+[[post-config|/\$Q_PLUGIN_CONF_FILE]]
+[DEFAULT]
+"
+    for I in "${Q_AGENT_EXTRA_SRV_OPTS[@]}"; do
+        # Replace the first '=' with ' ' for iniset syntax
+        echo ${I}
+    done
+fi
+
+# TODO(dtroyer): Remove Q_DHCP_EXTRA_DEFAULT_OPTS after stable/icehouse branch is cut
 if [[ -n "$Q_DHCP_EXTRA_DEFAULT_OPTS" ]]; then
     echo ""
     echo_summary "WARNING: Q_DHCP_EXTRA_DEFAULT_OPTS is used"
     echo "You are using Q_DHCP_EXTRA_DEFAULT_OPTS to pass configuration into $Q_DHCP_CONF_FILE."
     echo "Please convert that configuration in localrc to a $Q_DHCP_CONF_FILE section in local.conf:"
+    echo "Q_DHCP_EXTRA_DEFAULT_OPTS will be removed early in the Juno development cycle"
     echo "
-[[post-config|\$Q_DHCP_CONF_FILE]]
+[[post-config|/\$Q_DHCP_CONF_FILE]]
 [DEFAULT]
 "
     for I in "${Q_DHCP_EXTRA_DEFAULT_OPTS[@]}"; do
@@ -1401,11 +1442,13 @@
     done
 fi
 
+# TODO(dtroyer): Remove Q_SRV_EXTRA_DEFAULT_OPTS after stable/icehouse branch is cut
 if [[ -n "$Q_SRV_EXTRA_DEFAULT_OPTS" ]]; then
     echo ""
     echo_summary "WARNING: Q_SRV_EXTRA_DEFAULT_OPTS is used"
     echo "You are using Q_SRV_EXTRA_DEFAULT_OPTS to pass configuration into $NEUTRON_CONF."
     echo "Please convert that configuration in localrc to a $NEUTRON_CONF section in local.conf:"
+    echo "Q_SRV_EXTRA_DEFAULT_OPTS will be removed early in the Juno development cycle"
     echo "
 [[post-config|\$NEUTRON_CONF]]
 [DEFAULT]
diff --git a/stackrc b/stackrc
index 6bb6f37..4a997bf 100644
--- a/stackrc
+++ b/stackrc
@@ -267,7 +267,7 @@
 is_package_installed xenserver-core && DEFAULT_VIRT_DRIVER=xenserver
 VIRT_DRIVER=${VIRT_DRIVER:-$DEFAULT_VIRT_DRIVER}
 case "$VIRT_DRIVER" in
-    libvirt)
+    ironic|libvirt)
         LIBVIRT_TYPE=${LIBVIRT_TYPE:-kvm}
         if [[ "$os_VENDOR" =~ (Debian) ]]; then
             LIBVIRT_GROUP=libvirt
@@ -320,9 +320,6 @@
     openvz)
         DEFAULT_IMAGE_NAME=${DEFAULT_IMAGE_NAME:-ubuntu-12.04-x86_64}
         IMAGE_URLS=${IMAGE_URLS:-"http://download.openvz.org/template/precreated/ubuntu-12.04-x86_64.tar.gz"};;
-    docker)
-        DEFAULT_IMAGE_NAME=${DEFAULT_IMAGE_NAME:-cirros}
-        IMAGE_URLS=${IMAGE_URLS:-};;
     libvirt)
         case "$LIBVIRT_TYPE" in
             lxc) # the cirros root disk in the uec tarball is empty, so it will not work for lxc
@@ -335,7 +332,7 @@
         ;;
     vsphere)
         DEFAULT_IMAGE_NAME=${DEFAULT_IMAGE_NAME:-debian-2.6.32-i686}
-        IMAGE_URLS=${IMAGE_URLS:-"http://partnerweb.vmware.com/programs/vmdkimage/debian-2.6.32-i686.vmdk"};;
+        IMAGE_URLS=${IMAGE_URLS:-"http://partnerweb.vmware.com/programs/vmdkimage/cirros-0.3.0-i386-disk.vmdk"};;
     xenserver)
         DEFAULT_IMAGE_NAME=${DEFAULT_IMAGE_NAME:-cirros-0.3.0-x86_64-disk}
         IMAGE_URLS=${IMAGE_URLS:-"https://github.com/downloads/citrix-openstack/warehouse/cirros-0.3.0-x86_64-disk.vhd.tgz"};;
diff --git a/tools/docker/README.md b/tools/docker/README.md
deleted file mode 100644
index 976111f..0000000
--- a/tools/docker/README.md
+++ /dev/null
@@ -1,13 +0,0 @@
-# DevStack on Docker
-
-Using Docker as Nova's hypervisor requries two steps:
-
-* Configure DevStack by adding the following to `localrc`::
-
-    VIRT_DRIVER=docker
-
-* Download and install the Docker service and images::
-
-    tools/docker/install_docker.sh
-
-After this, `stack.sh` should run as normal.
diff --git a/tools/docker/install_docker.sh b/tools/docker/install_docker.sh
deleted file mode 100755
index 27c8c82..0000000
--- a/tools/docker/install_docker.sh
+++ /dev/null
@@ -1,68 +0,0 @@
-#!/usr/bin/env bash
-
-# **install_docker.sh** - Do the initial Docker installation and configuration
-
-# install_docker.sh
-#
-# Install docker package and images
-# * downloads a base busybox image and a glance registry image if necessary
-# * install the images in Docker's image cache
-
-
-# Keep track of the current directory
-SCRIPT_DIR=$(cd $(dirname "$0") && pwd)
-TOP_DIR=$(cd $SCRIPT_DIR/../..; pwd)
-
-# Import common functions
-source $TOP_DIR/functions
-
-# Load local configuration
-source $TOP_DIR/stackrc
-
-FILES=$TOP_DIR/files
-
-# Get our defaults
-source $TOP_DIR/lib/nova_plugins/hypervisor-docker
-
-SERVICE_TIMEOUT=${SERVICE_TIMEOUT:-60}
-
-
-# Install Docker Service
-# ======================
-
-if is_fedora; then
-    install_package docker-io socat
-else
-    # Stop the auto-repo updates and do it when required here
-    NO_UPDATE_REPOS=True
-
-    # Set up home repo
-    curl https://get.docker.io/gpg | sudo apt-key add -
-    install_package python-software-properties && \
-        sudo sh -c "echo deb $DOCKER_APT_REPO docker main > /etc/apt/sources.list.d/docker.list"
-    apt_get update
-    install_package --force-yes lxc-docker socat
-fi
-
-# Start the daemon - restart just in case the package ever auto-starts...
-restart_service docker
-
-echo "Waiting for docker daemon to start..."
-DOCKER_GROUP=$(groups | cut -d' ' -f1)
-CONFIGURE_CMD="while ! /bin/echo -e 'GET /v1.3/version HTTP/1.0\n\n' | socat - unix-connect:$DOCKER_UNIX_SOCKET 2>/dev/null | grep -q '200 OK'; do
-    # Set the right group on docker unix socket before retrying
-    sudo chgrp $DOCKER_GROUP $DOCKER_UNIX_SOCKET
-    sudo chmod g+rw $DOCKER_UNIX_SOCKET
-    sleep 1
-done"
-if ! timeout $SERVICE_TIMEOUT sh -c "$CONFIGURE_CMD"; then
-    die $LINENO "docker did not start"
-fi
-
-# Get guest container image
-docker pull $DOCKER_IMAGE
-docker tag $DOCKER_IMAGE $DOCKER_IMAGE_NAME
-
-# Get docker-registry image
-docker pull $DOCKER_REGISTRY_IMAGE
-docker tag $DOCKER_REGISTRY_IMAGE $DOCKER_REGISTRY_IMAGE_NAME
diff --git a/tools/install_prereqs.sh b/tools/install_prereqs.sh
index 0c65fd9..9651083 100755
--- a/tools/install_prereqs.sh
+++ b/tools/install_prereqs.sh
@@ -55,7 +55,13 @@
 # ================
 
 # Install package requirements
-install_package $(get_packages general $ENABLED_SERVICES)
+PACKAGES=$(get_packages general $ENABLED_SERVICES)
+if is_ubuntu && echo $PACKAGES | grep -q dkms ; then
+    # ensure headers for the running kernel are installed for any DKMS builds
+    PACKAGES="$PACKAGES linux-headers-$(uname -r)"
+fi
+
+install_package $PACKAGES
 
 if [[ -n "$SYSLOG" && "$SYSLOG" != "False" ]]; then
     if is_ubuntu || is_fedora; then
diff --git a/tools/ironic/scripts/cleanup-nodes b/tools/ironic/scripts/cleanup-nodes
new file mode 100755
index 0000000..dc5a19d
--- /dev/null
+++ b/tools/ironic/scripts/cleanup-nodes
@@ -0,0 +1,25 @@
+#!/usr/bin/env bash
+
+# **cleanup-nodes**
+
+# Cleans up baremetal poseur nodes and volumes created during ironic setup
+# Assumes calling user has proper libvirt group membership and access.
+
+set -exu
+
+LIBVIRT_STORAGE_POOL=${LIBVIRT_STORAGE_POOL:-"default"}
+
+VM_COUNT=$1
+NETWORK_BRIDGE=$2
+
+for (( idx=0; idx<$VM_COUNT; idx++ )); do
+    NAME="baremetal${NETWORK_BRIDGE}_${idx}"
+    VOL_NAME="baremetal${NETWORK_BRIDGE}-${idx}.qcow2"
+    virsh list | grep -q $NAME && virsh destroy $NAME
+    virsh list --inactive | grep -q $NAME && virsh undefine $NAME
+
+    if virsh pool-list | grep -q $LIBVIRT_STORAGE_POOL ; then
+      virsh vol-list $LIBVIRT_STORAGE_POOL | grep -q $VOL_NAME &&
+          virsh vol-delete $VOL_NAME --pool $LIBVIRT_STORAGE_POOL
+    fi
+done
diff --git a/tools/ironic/scripts/configure-vm b/tools/ironic/scripts/configure-vm
new file mode 100755
index 0000000..9936b76
--- /dev/null
+++ b/tools/ironic/scripts/configure-vm
@@ -0,0 +1,78 @@
+#!/usr/bin/env python
+
+import argparse
+import os.path
+
+import libvirt
+
+templatedir = os.path.join(os.path.dirname(os.path.dirname(__file__)),
+                           'templates')
+
+
+def main():
+    parser = argparse.ArgumentParser(
+        description="Configure a kvm virtual machine for the seed image.")
+    parser.add_argument('--name', default='seed',
+                        help='the name to give the machine in libvirt.')
+    parser.add_argument('--image',
+                        help='Use a custom image file (must be qcow2).')
+    parser.add_argument('--engine', default='qemu',
+                        help='The virtualization engine to use')
+    parser.add_argument('--arch', default='i686',
+                        help='The architecture to use')
+    parser.add_argument('--memory', default='2097152',
+                        help="Maximum memory for the VM in KB.")
+    parser.add_argument('--cpus', default='1',
+                        help="CPU count for the VM.")
+    parser.add_argument('--bootdev', default='hd',
+                        help="What boot device to use (hd/network).")
+    parser.add_argument('--network', default="brbm",
+                        help='The libvirt network name to use')
+    parser.add_argument('--libvirt-nic-driver', default='e1000',
+                        help='The libvirt network driver to use')
+    parser.add_argument('--emulator', default=None,
+                        help='Path to emulator bin for vm template')
+    args = parser.parse_args()
+    with file(templatedir + '/vm.xml', 'rb') as f:
+        source_template = f.read()
+    params = {
+        'name': args.name,
+        'imagefile': args.image,
+        'engine': args.engine,
+        'arch': args.arch,
+        'memory': args.memory,
+        'cpus': args.cpus,
+        'bootdev': args.bootdev,
+        'network': args.network,
+        'emulator': args.emulator,
+    }
+
+    if args.emulator:
+        params['emulator'] = args.emulator
+    else:
+        if os.path.exists("/usr/bin/kvm"):  # Debian
+            params['emulator'] = "/usr/bin/kvm"
+        elif os.path.exists("/usr/bin/qemu-kvm"):  # Redhat
+            params['emulator'] = "/usr/bin/qemu-kvm"
+
+    nicparams = {
+        'nicdriver': args.libvirt_nic_driver,
+        'network': args.network,
+    }
+
+    params['bm_network'] = """
+<!-- neutron friendly 'bare metal' network -->
+<interface type='network'>
+  <source network='%(network)s'/>
+  <virtualport type='openvswitch'/>
+  <model type='%(nicdriver)s'/>
+  <address type='pci' domain='0x0000' bus='0x00' slot='0x04' function='0x0'/>
+</interface>""" % nicparams
+
+    libvirt_template = source_template % params
+    conn = libvirt.open("qemu:///system")
+    a = conn.defineXML(libvirt_template)
+    print ("Created machine %s with UUID %s" % (args.name, a.UUIDString()))
+
+if __name__ == '__main__':
+    main()
diff --git a/tools/ironic/scripts/create-nodes b/tools/ironic/scripts/create-nodes
new file mode 100755
index 0000000..3232b50
--- /dev/null
+++ b/tools/ironic/scripts/create-nodes
@@ -0,0 +1,68 @@
+#!/usr/bin/env bash
+
+# **create-nodes**
+
+# Creates baremetal poseur nodes for ironic testing purposes
+
+set -exu
+
+# Keep track of the devstack directory
+TOP_DIR=$(cd $(dirname "$0")/.. && pwd)
+
+CPU=$1
+MEM=$(( 1024 * $2 ))
+# extra G to allow fuzz for partition table : flavor size and registered size
+# need to be different to actual size.
+DISK=$(( $3 + 1))
+
+case $4 in
+    i386) ARCH='i686' ;;
+    amd64) ARCH='x86_64' ;;
+    *) echo "Unsupported arch $4!" ; exit 1 ;;
+esac
+
+TOTAL=$(($5 - 1))
+BRIDGE=$6
+EMULATOR=$7
+
+LIBVIRT_NIC_DRIVER=${LIBVIRT_NIC_DRIVER:-"e1000"}
+LIBVIRT_STORAGE_POOL=${LIBVIRT_STORAGE_POOL:-"default"}
+
+if ! virsh pool-list --all | grep -q $LIBVIRT_STORAGE_POOL; then
+    virsh pool-define-as --name $LIBVIRT_STORAGE_POOL dir --target /var/lib/libvirt/images >&2
+    virsh pool-autostart $LIBVIRT_STORAGE_POOL >&2
+    virsh pool-start $LIBVIRT_STORAGE_POOL >&2
+fi
+
+pool_state=$(virsh pool-info $LIBVIRT_STORAGE_POOL | grep State | awk '{ print $2 }')
+if [ "$pool_state" != "running" ] ; then
+  [ ! -d /var/lib/libvirt/images ] && sudo mkdir /var/lib/libvirt/images
+  virsh pool-start $LIBVIRT_STORAGE_POOL >&2
+fi
+
+PREALLOC=
+if [ -f /etc/debian_version ]; then
+    PREALLOC="--prealloc-metadata"
+fi
+
+DOMS=""
+for idx in $(seq 0 $TOTAL) ; do
+    NAME="baremetal${BRIDGE}_${idx}"
+    DOMS="$DOMS $NAME"
+    VOL_NAME="baremetal${BRIDGE}-${idx}.qcow2"
+    (virsh list --all | grep -q $NAME) && continue
+
+    virsh vol-list --pool $LIBVIRT_STORAGE_POOL | grep -q $VOL_NAME &&
+        virsh vol-delete $VOL_NAME --pool $LIBVIRT_STORAGE_POOL >&2
+    virsh vol-create-as $LIBVIRT_STORAGE_POOL ${VOL_NAME} ${DISK}G --format qcow2 $PREALLOC >&2
+    volume_path=$(virsh vol-path --pool $LIBVIRT_STORAGE_POOL $VOL_NAME)
+    # Pre-touch the VM to set +C, as it can only be set on empty files.
+    sudo touch "$volume_path"
+    sudo chattr +C "$volume_path" || true
+    $TOP_DIR/scripts/configure-vm --bootdev network --name $NAME --image "$volume_path" --arch $ARCH --cpus $CPU --memory $MEM --libvirt-nic-driver $LIBVIRT_NIC_DRIVER --emulator $EMULATOR --network $BRIDGE >&2
+done
+
+for dom in $DOMS ; do
+    # echo mac
+    virsh dumpxml $dom | grep "mac address" | head -1 | cut -d\' -f2
+done
diff --git a/tools/ironic/scripts/setup-network b/tools/ironic/scripts/setup-network
new file mode 100755
index 0000000..8c3ea90
--- /dev/null
+++ b/tools/ironic/scripts/setup-network
@@ -0,0 +1,24 @@
+#!/usr/bin/env bash
+
+# **setup-network**
+
+# Setups openvswitch libvirt network suitable for
+# running baremetal poseur nodes for ironic testing purposes
+
+set -exu
+
+# Keep track of the devstack directory
+TOP_DIR=$(cd $(dirname "$0")/.. && pwd)
+BRIDGE_SUFFIX=${1:-''}
+BRIDGE_NAME=brbm$BRIDGE_SUFFIX
+
+# Only add bridge if missing
+(sudo ovs-vsctl list-br | grep ${BRIDGE_NAME}$) || sudo ovs-vsctl add-br ${BRIDGE_NAME}
+
+# remove bridge before replacing it.
+(virsh net-list | grep "${BRIDGE_NAME} ") && virsh net-destroy ${BRIDGE_NAME}
+(virsh net-list --inactive  | grep "${BRIDGE_NAME} ") && virsh net-undefine ${BRIDGE_NAME}
+
+virsh net-define <(sed s/brbm/$BRIDGE_NAME/ $TOP_DIR/templates/brbm.xml)
+virsh net-autostart ${BRIDGE_NAME}
+virsh net-start ${BRIDGE_NAME}
diff --git a/tools/ironic/templates/brbm.xml b/tools/ironic/templates/brbm.xml
new file mode 100644
index 0000000..0769d3f
--- /dev/null
+++ b/tools/ironic/templates/brbm.xml
@@ -0,0 +1,6 @@
+<network>
+  <name>brbm</name>
+  <forward mode='bridge'/>
+  <bridge name='brbm'/>
+  <virtualport type='openvswitch'/>
+</network>
diff --git a/tools/ironic/templates/tftpd-xinetd.template b/tools/ironic/templates/tftpd-xinetd.template
new file mode 100644
index 0000000..7b9b0f8
--- /dev/null
+++ b/tools/ironic/templates/tftpd-xinetd.template
@@ -0,0 +1,11 @@
+service tftp
+{
+  protocol        = udp
+  port            = 69
+  socket_type     = dgram
+  wait            = yes
+  user            = root
+  server          = /usr/sbin/in.tftpd
+  server_args     = -v -v -v -v -v --map-file %TFTPBOOT_DIR%/map-file %TFTPBOOT_DIR%
+  disable         = no
+}
diff --git a/tools/ironic/templates/vm.xml b/tools/ironic/templates/vm.xml
new file mode 100644
index 0000000..b18dec0
--- /dev/null
+++ b/tools/ironic/templates/vm.xml
@@ -0,0 +1,43 @@
+<domain type='%(engine)s'>
+  <name>%(name)s</name>
+  <memory unit='KiB'>%(memory)s</memory>
+  <vcpu>%(cpus)s</vcpu>
+  <os>
+    <type arch='%(arch)s' machine='pc-1.0'>hvm</type>
+    <boot dev='%(bootdev)s'/>
+    <bootmenu enable='no'/>
+  </os>
+  <features>
+    <acpi/>
+    <apic/>
+    <pae/>
+  </features>
+  <clock offset='utc'/>
+  <on_poweroff>destroy</on_poweroff>
+  <on_reboot>restart</on_reboot>
+  <on_crash>restart</on_crash>
+  <devices>
+    <emulator>%(emulator)s</emulator>
+    <disk type='file' device='disk'>
+      <driver name='qemu' type='qcow2' cache='writeback'/>
+      <source file='%(imagefile)s'/>
+      <target dev='vda' bus='virtio'/>
+      <address type='pci' domain='0x0000' bus='0x00' slot='0x06' function='0x0'/>
+    </disk>
+    <controller type='ide' index='0'>
+      <address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x1'/>
+    </controller>
+    %(network)s
+    %(bm_network)s
+    <input type='mouse' bus='ps2'/>
+    <graphics type='vnc' port='-1' autoport='yes'/>
+    <video>
+      <model type='cirrus' vram='9216' heads='1'/>
+      <address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x0'/>
+    </video>
+    <memballoon model='virtio'>
+      <address type='pci' domain='0x0000' bus='0x00' slot='0x07' function='0x0'/>
+    </memballoon>
+  </devices>
+</domain>
+