Merge "Source lib/dstat in unstack.sh"
diff --git a/files/apache-keystone.template b/files/apache-keystone.template
index e7b2157..1bdb84c 100644
--- a/files/apache-keystone.template
+++ b/files/apache-keystone.template
@@ -8,7 +8,7 @@
     WSGIApplicationGroup %{GLOBAL}
     %ERRORLOGFORMAT%
     ErrorLog /var/log/%APACHE_NAME%/keystone.log
-    CustomLog /var/log/%APACHE_NAME%/access.log combined
+    CustomLog /var/log/%APACHE_NAME%/keystone_access.log combined
 </VirtualHost>
 
 <VirtualHost *:%ADMINPORT%>
@@ -18,7 +18,7 @@
     WSGIApplicationGroup %{GLOBAL}
     %ERRORLOGFORMAT%
     ErrorLog /var/log/%APACHE_NAME%/keystone.log
-    CustomLog /var/log/%APACHE_NAME%/access.log combined
+    CustomLog /var/log/%APACHE_NAME%/keystone_access.log combined
 </VirtualHost>
 
 # Workaround for missing path on RHEL6, see
diff --git a/files/apts/ironic b/files/apts/ironic
index 8674d9f..283d1b2 100644
--- a/files/apts/ironic
+++ b/files/apts/ironic
@@ -1,3 +1,4 @@
+docker.io
 ipmitool
 iptables
 ipxe
diff --git a/files/rpms/ironic b/files/rpms/ironic
index 959ac3c..e646f3a 100644
--- a/files/rpms/ironic
+++ b/files/rpms/ironic
@@ -1,3 +1,4 @@
+docker-io
 ipmitool
 iptables
 ipxe-bootimgs
diff --git a/functions-common b/functions-common
index 87b9ece..bf9447c 100644
--- a/functions-common
+++ b/functions-common
@@ -1135,8 +1135,8 @@
 # fork.  It includes the dirty work of closing extra filehandles and preparing log
 # files to produce the same logs as screen_it().  The log filename is derived
 # from the service name and global-and-now-misnamed ``SCREEN_LOGDIR``
-# Uses globals ``CURRENT_LOG_TIME``, ``SCREEN_LOGDIR``
-# _run_process service "command-line"
+# Uses globals ``CURRENT_LOG_TIME``, ``SCREEN_LOGDIR``, ``SCREEN_NAME``, ``SERVICE_DIR``
+# _old_run_process service "command-line"
 function _run_process {
     local service=$1
     local command="$2"
@@ -1155,8 +1155,12 @@
         export PYTHONUNBUFFERED=1
     fi
 
-    exec /bin/bash -c "$command"
-    die "$service exec failure: $command"
+    # Run under ``setsid`` to force the process to become a session and group leader.
+    # The pid saved can be used with pkill -g to get the entire process group.
+    setsid $command & echo $! >$SERVICE_DIR/$SCREEN_NAME/$1.pid
+
+    # Just silently exit this process
+    exit 0
 }
 
 # Helper to remove the ``*.failure`` files under ``$SERVICE_DIR/$SCREEN_NAME``.
@@ -1184,61 +1188,63 @@
     return $exitcode
 }
 
-# run_process() launches a child process that closes all file descriptors and
-# then exec's the passed in command.  This is meant to duplicate the semantics
-# of screen_it() without screen.  PIDs are written to
-# ``$SERVICE_DIR/$SCREEN_NAME/$service.pid``
+# Run a single service under screen or directly
+# If the command includes shell metachatacters (;<>*) it must be run using a shell
 # run_process service "command-line"
 function run_process {
     local service=$1
     local command="$2"
 
-    # Spawn the child process
-    _run_process "$service" "$command" &
-    echo $!
+    if is_service_enabled $service; then
+        if [[ "$USE_SCREEN" = "True" ]]; then
+            screen_service "$service" "$command"
+        else
+            # Spawn directly without screen
+            _run_process "$service" "$command" &
+        fi
+    fi
 }
 
 # Helper to launch a service in a named screen
 # Uses globals ``CURRENT_LOG_TIME``, ``SCREEN_NAME``, ``SCREEN_LOGDIR``,
 # ``SERVICE_DIR``, ``USE_SCREEN``
-# screen_it service "command-line"
-function screen_it {
+# screen_service service "command-line"
+# Run a command in a shell in a screen window
+function screen_service {
+    local service=$1
+    local command="$2"
+
     SCREEN_NAME=${SCREEN_NAME:-stack}
     SERVICE_DIR=${SERVICE_DIR:-${DEST}/status}
     USE_SCREEN=$(trueorfalse True $USE_SCREEN)
 
-    if is_service_enabled $1; then
+    if is_service_enabled $service; then
         # Append the service to the screen rc file
-        screen_rc "$1" "$2"
+        screen_rc "$service" "$command"
 
-        if [[ "$USE_SCREEN" = "True" ]]; then
-            screen -S $SCREEN_NAME -X screen -t $1
+        screen -S $SCREEN_NAME -X screen -t $service
 
-            if [[ -n ${SCREEN_LOGDIR} ]]; then
-                screen -S $SCREEN_NAME -p $1 -X logfile ${SCREEN_LOGDIR}/screen-${1}.${CURRENT_LOG_TIME}.log
-                screen -S $SCREEN_NAME -p $1 -X log on
-                ln -sf ${SCREEN_LOGDIR}/screen-${1}.${CURRENT_LOG_TIME}.log ${SCREEN_LOGDIR}/screen-${1}.log
-            fi
-
-            # sleep to allow bash to be ready to be send the command - we are
-            # creating a new window in screen and then sends characters, so if
-            # bash isn't running by the time we send the command, nothing happens
-            sleep 3
-
-            NL=`echo -ne '\015'`
-            # This fun command does the following:
-            # - the passed server command is backgrounded
-            # - the pid of the background process is saved in the usual place
-            # - the server process is brought back to the foreground
-            # - if the server process exits prematurely the fg command errors
-            #   and a message is written to stdout and the service failure file
-            # The pid saved can be used in screen_stop() as a process group
-            # id to kill off all child processes
-            screen -S $SCREEN_NAME -p $1 -X stuff "$2 & echo \$! >$SERVICE_DIR/$SCREEN_NAME/$1.pid; fg || echo \"$1 failed to start\" | tee \"$SERVICE_DIR/$SCREEN_NAME/$1.failure\"$NL"
-        else
-            # Spawn directly without screen
-            run_process "$1" "$2" >$SERVICE_DIR/$SCREEN_NAME/$1.pid
+        if [[ -n ${SCREEN_LOGDIR} ]]; then
+            screen -S $SCREEN_NAME -p $service -X logfile ${SCREEN_LOGDIR}/screen-${service}.${CURRENT_LOG_TIME}.log
+            screen -S $SCREEN_NAME -p $service -X log on
+            ln -sf ${SCREEN_LOGDIR}/screen-${service}.${CURRENT_LOG_TIME}.log ${SCREEN_LOGDIR}/screen-${service}.log
         fi
+
+        # sleep to allow bash to be ready to be send the command - we are
+        # creating a new window in screen and then sends characters, so if
+        # bash isn't running by the time we send the command, nothing happens
+        sleep 3
+
+        NL=`echo -ne '\015'`
+        # This fun command does the following:
+        # - the passed server command is backgrounded
+        # - the pid of the background process is saved in the usual place
+        # - the server process is brought back to the foreground
+        # - if the server process exits prematurely the fg command errors
+        #   and a message is written to stdout and the service failure file
+        # The pid saved can be used in screen_stop() as a process group
+        # id to kill off all child processes
+        screen -S $SCREEN_NAME -p $service -X stuff "$command & echo \$! >$SERVICE_DIR/$SCREEN_NAME/${service}.pid; fg || echo \"$service failed to start\" | tee \"$SERVICE_DIR/$SCREEN_NAME/${service}.failure\"$NL"
     fi
 }
 
@@ -1276,20 +1282,40 @@
 # that did not leave a PID behind
 # Uses globals ``SCREEN_NAME``, ``SERVICE_DIR``, ``USE_SCREEN``
 # screen_stop service
-function screen_stop {
+function screen_stop_service {
+    local service=$1
+
     SCREEN_NAME=${SCREEN_NAME:-stack}
     SERVICE_DIR=${SERVICE_DIR:-${DEST}/status}
     USE_SCREEN=$(trueorfalse True $USE_SCREEN)
 
-    if is_service_enabled $1; then
+    if is_service_enabled $service; then
+        # Clean up the screen window
+        screen -S $SCREEN_NAME -p $service -X kill
+    fi
+}
+
+# Stop a service process
+# If a PID is available use it, kill the whole process group via TERM
+# If screen is being used kill the screen window; this will catch processes
+# that did not leave a PID behind
+# Uses globals ``SERVICE_DIR``, ``USE_SCREEN``
+# stop_process service
+function stop_process {
+    local service=$1
+
+    SERVICE_DIR=${SERVICE_DIR:-${DEST}/status}
+    USE_SCREEN=$(trueorfalse True $USE_SCREEN)
+
+    if is_service_enabled $service; then
         # Kill via pid if we have one available
-        if [[ -r $SERVICE_DIR/$SCREEN_NAME/$1.pid ]]; then
-            pkill -TERM -P -$(cat $SERVICE_DIR/$SCREEN_NAME/$1.pid)
-            rm $SERVICE_DIR/$SCREEN_NAME/$1.pid
+        if [[ -r $SERVICE_DIR/$SCREEN_NAME/$service.pid ]]; then
+            pkill -g $(cat $SERVICE_DIR/$SCREEN_NAME/$service.pid)
+            rm $SERVICE_DIR/$SCREEN_NAME/$service.pid
         fi
         if [[ "$USE_SCREEN" = "True" ]]; then
             # Clean up the screen window
-            screen -S $SCREEN_NAME -p $1 -X kill
+            screen_stop_service $service
         fi
     fi
 }
@@ -1325,6 +1351,80 @@
 }
 
 
+# Deprecated Functions
+# --------------------
+
+# _old_run_process() is designed to be backgrounded by old_run_process() to simulate a
+# fork.  It includes the dirty work of closing extra filehandles and preparing log
+# files to produce the same logs as screen_it().  The log filename is derived
+# from the service name and global-and-now-misnamed ``SCREEN_LOGDIR``
+# Uses globals ``CURRENT_LOG_TIME``, ``SCREEN_LOGDIR``, ``SCREEN_NAME``, ``SERVICE_DIR``
+# _old_run_process service "command-line"
+function _old_run_process {
+    local service=$1
+    local command="$2"
+
+    # Undo logging redirections and close the extra descriptors
+    exec 1>&3
+    exec 2>&3
+    exec 3>&-
+    exec 6>&-
+
+    if [[ -n ${SCREEN_LOGDIR} ]]; then
+        exec 1>&${SCREEN_LOGDIR}/screen-${1}.${CURRENT_LOG_TIME}.log 2>&1
+        ln -sf ${SCREEN_LOGDIR}/screen-${1}.${CURRENT_LOG_TIME}.log ${SCREEN_LOGDIR}/screen-${1}.log
+
+        # TODO(dtroyer): Hack to get stdout from the Python interpreter for the logs.
+        export PYTHONUNBUFFERED=1
+    fi
+
+    exec /bin/bash -c "$command"
+    die "$service exec failure: $command"
+}
+
+# old_run_process() launches a child process that closes all file descriptors and
+# then exec's the passed in command.  This is meant to duplicate the semantics
+# of screen_it() without screen.  PIDs are written to
+# ``$SERVICE_DIR/$SCREEN_NAME/$service.pid`` by the spawned child process.
+# old_run_process service "command-line"
+function old_run_process {
+    local service=$1
+    local command="$2"
+
+    # Spawn the child process
+    _old_run_process "$service" "$command" &
+    echo $!
+}
+
+# Compatibility for existing start_XXXX() functions
+# Uses global ``USE_SCREEN``
+# screen_it service "command-line"
+function screen_it {
+    if is_service_enabled $1; then
+        # Append the service to the screen rc file
+        screen_rc "$1" "$2"
+
+        if [[ "$USE_SCREEN" = "True" ]]; then
+            screen_service "$1" "$2"
+        else
+            # Spawn directly without screen
+            old_run_process "$1" "$2" >$SERVICE_DIR/$SCREEN_NAME/$1.pid
+        fi
+    fi
+}
+
+# Compatibility for existing stop_XXXX() functions
+# Stop a service in screen
+# If a PID is available use it, kill the whole process group via TERM
+# If screen is being used kill the screen window; this will catch processes
+# that did not leave a PID behind
+# screen_stop service
+function screen_stop {
+    # Clean up the screen window
+    stop_process $1
+}
+
+
 # Python Functions
 # ================
 
@@ -1616,6 +1716,7 @@
         [[ ${service} == "trove" && ${ENABLED_SERVICES} =~ "tr-" ]] && enabled=0
         [[ ${service} == "swift" && ${ENABLED_SERVICES} =~ "s-" ]] && enabled=0
         [[ ${service} == s-* && ${ENABLED_SERVICES} =~ "swift" ]] && enabled=0
+        [[ ${service} == key-* && ${ENABLED_SERVICES} =~ "key" ]] && enabled=0
     done
     $xtrace
     return $enabled
diff --git a/lib/cinder b/lib/cinder
index ce13b86..0426dbe 100644
--- a/lib/cinder
+++ b/lib/cinder
@@ -247,8 +247,8 @@
             if type configure_cinder_backend_${be_type} >/dev/null 2>&1; then
                 configure_cinder_backend_${be_type} ${be_name}
             fi
-            if [[ -z "$default_type" ]]; then
-                default_name=$be_type
+            if [[ -z "$default_name" ]]; then
+                default_name=$be_name
             fi
             enabled_backends+=$be_name,
         done
@@ -431,15 +431,15 @@
         sudo tgtadm --mode system --op update --name debug --value on
     fi
 
-    screen_it c-api "cd $CINDER_DIR && $CINDER_BIN_DIR/cinder-api --config-file $CINDER_CONF"
+    run_process c-api "$CINDER_BIN_DIR/cinder-api --config-file $CINDER_CONF"
     echo "Waiting for Cinder API to start..."
     if ! wait_for_service $SERVICE_TIMEOUT $CINDER_SERVICE_PROTOCOL://$CINDER_SERVICE_HOST:$CINDER_SERVICE_PORT; then
         die $LINENO "c-api did not start"
     fi
 
-    screen_it c-sch "cd $CINDER_DIR && $CINDER_BIN_DIR/cinder-scheduler --config-file $CINDER_CONF"
-    screen_it c-bak "cd $CINDER_DIR && $CINDER_BIN_DIR/cinder-backup --config-file $CINDER_CONF"
-    screen_it c-vol "cd $CINDER_DIR && $CINDER_BIN_DIR/cinder-volume --config-file $CINDER_CONF"
+    run_process c-sch "$CINDER_BIN_DIR/cinder-scheduler --config-file $CINDER_CONF"
+    run_process c-bak "$CINDER_BIN_DIR/cinder-backup --config-file $CINDER_CONF"
+    run_process c-vol "$CINDER_BIN_DIR/cinder-volume --config-file $CINDER_CONF"
 
     # NOTE(jdg): For cinder, startup order matters.  To ensure that repor_capabilities is received
     # by the scheduler start the cinder-volume service last (or restart it) after the scheduler
diff --git a/lib/glance b/lib/glance
index 7a28b68..d9c4a20 100644
--- a/lib/glance
+++ b/lib/glance
@@ -34,6 +34,7 @@
 GLANCE_AUTH_CACHE_DIR=${GLANCE_AUTH_CACHE_DIR:-/var/cache/glance}
 
 GLANCE_CONF_DIR=${GLANCE_CONF_DIR:-/etc/glance}
+GLANCE_METADEF_DIR=$GLANCE_CONF_DIR/metadefs
 GLANCE_REGISTRY_CONF=$GLANCE_CONF_DIR/glance-registry.conf
 GLANCE_API_CONF=$GLANCE_CONF_DIR/glance-api.conf
 GLANCE_REGISTRY_PASTE_INI=$GLANCE_CONF_DIR/glance-registry-paste.ini
@@ -81,6 +82,11 @@
     fi
     sudo chown $STACK_USER $GLANCE_CONF_DIR
 
+    if [[ ! -d $GLANCE_METADEF_DIR ]]; then
+        sudo mkdir -p $GLANCE_METADEF_DIR
+    fi
+    sudo chown $STACK_USER $GLANCE_METADEF_DIR
+
     # Copy over our glance configurations and update them
     cp $GLANCE_DIR/etc/glance-registry.conf $GLANCE_REGISTRY_CONF
     iniset $GLANCE_REGISTRY_CONF DEFAULT debug $ENABLE_DEBUG_LOG_LEVEL
@@ -177,6 +183,8 @@
 
     cp -p $GLANCE_DIR/etc/policy.json $GLANCE_POLICY_JSON
     cp -p $GLANCE_DIR/etc/schema-image.json $GLANCE_SCHEMA_JSON
+
+    cp -p $GLANCE_DIR/etc/metadefs/*.json $GLANCE_METADEF_DIR
 }
 
 # create_glance_accounts() - Set up common required glance accounts
@@ -241,6 +249,9 @@
     # Migrate glance database
     $GLANCE_BIN_DIR/glance-manage db_sync
 
+    # Load metadata definitions
+    $GLANCE_BIN_DIR/glance-manage db_load_metadefs
+
     create_glance_cache_dir
 }
 
diff --git a/lib/ironic b/lib/ironic
index 7986b93..2fad0b5 100644
--- a/lib/ironic
+++ b/lib/ironic
@@ -29,6 +29,7 @@
 
 # Set up default directories
 IRONIC_DIR=$DEST/ironic
+IRONIC_PYTHON_AGENT_DIR=$DEST/ironic-python-agent
 IRONIC_DATA_DIR=$DATA_DIR/ironic
 IRONIC_STATE_PATH=/var/lib/ironic
 IRONICCLIENT_DIR=$DEST/python-ironicclient
@@ -496,8 +497,12 @@
     done < $IRONIC_VM_MACS_CSV_FILE
 
     # create the nova flavor
+    # NOTE(adam_g): Attempting to use an autogenerated UUID for flavor id here uncovered
+    # bug (LP: #1333852) in Trove.  This can be changed to use an auto flavor id when the
+    # bug is fixed in Juno.
     local adjusted_disk=$(($IRONIC_VM_SPECS_DISK - $IRONIC_VM_EPHEMERAL_DISK))
-    nova flavor-create --ephemeral $IRONIC_VM_EPHEMERAL_DISK baremetal auto $IRONIC_VM_SPECS_RAM $adjusted_disk $IRONIC_VM_SPECS_CPU
+    nova flavor-create --ephemeral $IRONIC_VM_EPHEMERAL_DISK baremetal 551 $IRONIC_VM_SPECS_RAM $adjusted_disk $IRONIC_VM_SPECS_CPU
+
     # TODO(lucasagomes): Remove the 'baremetal:deploy_kernel_id'
     # and 'baremetal:deploy_ramdisk_id' parameters
     # from the flavor after the completion of
@@ -559,6 +564,19 @@
     ironic_ssh_check $IRONIC_SSH_KEY_DIR/$IRONIC_SSH_KEY_FILENAME $IRONIC_VM_SSH_ADDRESS $IRONIC_VM_SSH_PORT $IRONIC_SSH_USERNAME 10
 }
 
+function build_ipa_coreos_ramdisk {
+    echo "Building ironic-python-agent deploy ramdisk"
+    local kernel_path=$1
+    local ramdisk_path=$2
+    git_clone $IRONIC_PYTHON_AGENT_REPO $IRONIC_PYTHON_AGENT_DIR $IRONIC_PYTHON_AGENT_BRANCH
+    cd $IRONIC_PYTHON_AGENT_DIR
+    imagebuild/coreos/build_coreos_image.sh
+    cp imagebuild/coreos/UPLOAD/coreos_production_pxe_image-oem.cpio.gz $ramdisk_path
+    cp imagebuild/coreos/UPLOAD/coreos_production_pxe.vmlinuz $kernel_path
+    sudo rm -rf UPLOAD
+    cd -
+}
+
 # build deploy kernel+ramdisk, then upload them to glance
 # this function sets ``IRONIC_DEPLOY_KERNEL_ID``, ``IRONIC_DEPLOY_RAMDISK_ID``
 function upload_baremetal_ironic_deploy {
@@ -583,8 +601,8 @@
         if [ "$IRONIC_BUILD_DEPLOY_RAMDISK" = "True" ]; then
             # we can build them only if we're not offline
             if [ "$OFFLINE" != "True" ]; then
-                if [ "$IRONIC_DEPLOY_RAMDISK" == "agent_ssh" ]; then
-                    die $LINENO "Ironic-python-agent build is not yet supported"
+                if [ "$IRONIC_DEPLOY_DRIVER" == "agent_ssh" ]; then
+                    build_ipa_coreos_ramdisk $IRONIC_DEPLOY_KERNEL_PATH $IRONIC_DEPLOY_RAMDISK_PATH
                 else
                     ramdisk-image-create $IRONIC_DEPLOY_FLAVOR \
                         -o $TOP_DIR/files/ir-deploy
diff --git a/lib/keystone b/lib/keystone
index c1b0b87..da5cf89 100644
--- a/lib/keystone
+++ b/lib/keystone
@@ -357,9 +357,8 @@
     # The Member role is used by Horizon and Swift so we need to keep it:
     local member_role=$(get_or_create_role "Member")
 
-    # ANOTHER_ROLE demonstrates that an arbitrary role may be created and used
+    # another_role demonstrates that an arbitrary role may be created and used
     # TODO(sleepsonthefloor): show how this can be used for rbac in the future!
-
     local another_role=$(get_or_create_role "anotherrole")
 
     # invisible tenant - admin can't see this one
@@ -476,6 +475,7 @@
     if [ "$KEYSTONE_USE_MOD_WSGI" == "True" ]; then
         restart_apache_server
         screen_it key "cd $KEYSTONE_DIR && sudo tail -f /var/log/$APACHE_NAME/keystone.log"
+        screen_it key-access "sudo tail -f /var/log/$APACHE_NAME/keystone_access.log"
     else
         # Start Keystone in a screen window
         screen_it key "cd $KEYSTONE_DIR && $KEYSTONE_DIR/bin/keystone-all --config-file $KEYSTONE_CONF --debug"
diff --git a/lib/swift b/lib/swift
index 6b96348..b050b57 100644
--- a/lib/swift
+++ b/lib/swift
@@ -556,6 +556,7 @@
 
     local service_tenant=$(openstack project list | awk "/ $SERVICE_TENANT_NAME / { print \$2 }")
     local admin_role=$(openstack role list | awk "/ admin / { print \$2 }")
+    local another_role=$(openstack role list | awk "/ anotherrole / { print \$2 }")
 
     local swift_user=$(get_or_create_user "swift" \
         "$SERVICE_PASSWORD" $service_tenant)
@@ -582,7 +583,7 @@
     local swift_user_test3=$(get_or_create_user swiftusertest3 $swiftusertest3_password \
         "$swift_tenant_test1" "test3@example.com")
     die_if_not_set $LINENO swift_user_test3 "Failure creating swift_user_test3"
-    get_or_add_user_role $ANOTHER_ROLE $swift_user_test3 $swift_tenant_test1
+    get_or_add_user_role $another_role $swift_user_test3 $swift_tenant_test1
 
     local swift_tenant_test2=$(get_or_create_project swifttenanttest2)
     die_if_not_set $LINENO swift_tenant_test2 "Failure creating swift_tenant_test2"
diff --git a/stackrc b/stackrc
index ca28d32..e071132 100644
--- a/stackrc
+++ b/stackrc
@@ -172,9 +172,11 @@
 HORIZONAUTH_REPO=${HORIZONAUTH_REPO:-${GIT_BASE}/openstack/django_openstack_auth.git}
 HORIZONAUTH_BRANCH=${HORIZONAUTH_BRANCH:-master}
 
-# baremetal provisionint service
+# baremetal provisioning service
 IRONIC_REPO=${IRONIC_REPO:-${GIT_BASE}/openstack/ironic.git}
 IRONIC_BRANCH=${IRONIC_BRANCH:-master}
+IRONIC_PYTHON_AGENT_REPO=${IRONIC_PYTHON_AGENT_REPO:-${GIT_BASE}/openstack/ironic-python-agent.git}
+IRONIC_PYTHON_AGENT_BRANCH=${IRONIC_PYTHON_AGENT_BRANCH:-master}
 
 # ironic client
 IRONICCLIENT_REPO=${IRONICCLIENT_REPO:-${GIT_BASE}/openstack/python-ironicclient.git}
diff --git a/tests/fake-service.sh b/tests/fake-service.sh
new file mode 100755
index 0000000..d4b9b56
--- /dev/null
+++ b/tests/fake-service.sh
@@ -0,0 +1,19 @@
+#!/bin/bash
+# fake-service.sh - a fake service for start/stop testing
+# $1 - sleep time
+
+SLEEP_TIME=${1:-3}
+
+LOG=/tmp/fake-service.log
+TIMESTAMP_FORMAT=${TIMESTAMP_FORMAT:-"%F-%H%M%S"}
+
+# duplicate output
+exec 1> >(tee -a ${LOG})
+
+echo ""
+echo "Starting fake-service for ${SLEEP_TIME}"
+while true; do
+    echo "$(date +${TIMESTAMP_FORMAT}) [$$]"
+    sleep ${SLEEP_TIME}
+done
+
diff --git a/tests/run-process.sh b/tests/run-process.sh
new file mode 100755
index 0000000..cdffc3a
--- /dev/null
+++ b/tests/run-process.sh
@@ -0,0 +1,109 @@
+#!/bin/bash
+# tests/exec.sh - Test DevStack screen_it() and screen_stop()
+#
+# exec.sh start|stop|status
+#
+# Set USE_SCREEN to change the default
+#
+# This script emulates the basic exec envirnment in ``stack.sh`` to test
+# the process spawn and kill operations.
+
+if [[ -z $1 ]]; then
+    echo "$0 start|stop"
+    exit 1
+fi
+
+TOP_DIR=$(cd $(dirname "$0")/.. && pwd)
+source $TOP_DIR/functions
+
+USE_SCREEN=${USE_SCREEN:-False}
+
+ENABLED_SERVICES=fake-service
+
+SERVICE_DIR=/tmp
+SCREEN_NAME=test
+SCREEN_LOGDIR=${SERVICE_DIR}/${SCREEN_NAME}
+
+
+# Kill background processes on exit
+trap clean EXIT
+clean() {
+    local r=$?
+    jobs -p
+    kill >/dev/null 2>&1 $(jobs -p)
+    exit $r
+}
+
+
+# Exit on any errors so that errors don't compound
+trap failed ERR
+failed() {
+    local r=$?
+    jobs -p
+    kill >/dev/null 2>&1 $(jobs -p)
+    set +o xtrace
+    [ -n "$LOGFILE" ] && echo "${0##*/} failed: full log in $LOGFILE"
+    exit $r
+}
+
+function status {
+    if [[ -r $SERVICE_DIR/$SCREEN_NAME/fake-service.pid ]]; then
+        pstree -pg $(cat $SERVICE_DIR/$SCREEN_NAME/fake-service.pid)
+    fi
+    ps -ef | grep fake
+}
+
+function setup_screen {
+if [[ ! -d $SERVICE_DIR/$SCREEN_NAME ]]; then
+    rm -rf $SERVICE_DIR/$SCREEN_NAME
+    mkdir -p $SERVICE_DIR/$SCREEN_NAME
+fi
+
+if [[ "$USE_SCREEN" == "True" ]]; then
+    # Create a new named screen to run processes in
+    screen -d -m -S $SCREEN_NAME -t shell -s /bin/bash
+    sleep 1
+
+    # Set a reasonable status bar
+    if [ -z "$SCREEN_HARDSTATUS" ]; then
+        SCREEN_HARDSTATUS='%{= .} %-Lw%{= .}%> %n%f %t*%{= .}%+Lw%< %-=%{g}(%{d}%H/%l%{g})'
+    fi
+    screen -r $SCREEN_NAME -X hardstatus alwayslastline "$SCREEN_HARDSTATUS"
+fi
+
+# Clear screen rc file
+SCREENRC=$TOP_DIR/tests/$SCREEN_NAME-screenrc
+if [[ -e $SCREENRC ]]; then
+    echo -n > $SCREENRC
+fi
+}
+
+# Mimic logging
+    # Set up output redirection without log files
+    # Copy stdout to fd 3
+    exec 3>&1
+    if [[ "$VERBOSE" != "True" ]]; then
+        # Throw away stdout and stderr
+        #exec 1>/dev/null 2>&1
+        :
+    fi
+    # Always send summary fd to original stdout
+    exec 6>&3
+
+
+if [[ "$1" == "start" ]]; then
+    echo "Start service"
+    setup_screen
+    screen_it fake-service "$TOP_DIR/tests/fake-service.sh"
+    sleep 1
+    status
+elif [[ "$1" == "stop" ]]; then
+    echo "Stop service"
+    screen_stop fake-service
+    status
+elif [[ "$1" == "status" ]]; then
+    status
+else
+    echo "Unknown command"
+    exit 1
+fi