Merge "Split disk creation out of configure_swift()"
diff --git a/files/horizon_settings.py b/files/horizon_settings.py
deleted file mode 100644
index ce92e2c..0000000
--- a/files/horizon_settings.py
+++ /dev/null
@@ -1,169 +0,0 @@
-import os
-
-from django.utils.translation import ugettext_lazy as _
-
-DEBUG = True
-TEMPLATE_DEBUG = DEBUG
-PROD = False
-USE_SSL = False
-
-# Set SSL proxy settings:
-# For Django 1.4+ pass this header from the proxy after terminating the SSL,
-# and don't forget to strip it from the client's request.
-# For more information see:
-# https://docs.djangoproject.com/en/1.4/ref/settings/#secure-proxy-ssl-header
-# SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTOCOL', 'https')
-
-# Specify a regular expression to validate user passwords.
-# HORIZON_CONFIG = {
-#     "password_validator": {
-#         "regex": '.*',
-#         "help_text": _("Your password does not meet the requirements.")
-#     },
-#    'help_url': "http://docs.openstack.org"
-# }
-
-LOCAL_PATH = os.path.dirname(os.path.abspath(__file__))
-
-# FIXME: We need to change this to mysql, instead of sqlite.
-DATABASES = {
-    'default': {
-        'ENGINE': 'django.db.backends.sqlite3',
-        'NAME': os.path.join(LOCAL_PATH, 'dashboard_openstack.sqlite3'),
-        'TEST_NAME': os.path.join(LOCAL_PATH, 'test.sqlite3'),
-    },
-}
-
-# Set custom secret key:
-# You can either set it to a specific value or you can let horizion generate a
-# default secret key that is unique on this machine, e.i. regardless of the
-# amount of Python WSGI workers (if used behind Apache+mod_wsgi): However, there
-# may be situations where you would want to set this explicitly, e.g. when
-# multiple dashboard instances are distributed on different machines (usually
-# behind a load-balancer). Either you have to make sure that a session gets all
-# requests routed to the same dashboard instance or you set the same SECRET_KEY
-# for all of them.
-from horizon.utils import secret_key
-SECRET_KEY = secret_key.generate_or_read_from_file(os.path.join(LOCAL_PATH, '.secret_key_store'))
-
-# We recommend you use memcached for development; otherwise after every reload
-# of the django development server, you will have to login again. To use
-# memcached set CACHE_BACKED to something like 'memcached://127.0.0.1:11211/'
-CACHE_BACKEND = 'dummy://'
-SESSION_ENGINE = 'django.contrib.sessions.backends.cached_db'
-
-# Send email to the console by default
-EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
-# Or send them to /dev/null
-#EMAIL_BACKEND = 'django.core.mail.backends.dummy.EmailBackend'
-
-# django-mailer uses a different settings attribute
-MAILER_EMAIL_BACKEND = EMAIL_BACKEND
-
-# Configure these for your outgoing email host
-# EMAIL_HOST = 'smtp.my-company.com'
-# EMAIL_PORT = 25
-# EMAIL_HOST_USER = 'djangomail'
-# EMAIL_HOST_PASSWORD = 'top-secret!'
-
-# For multiple regions uncomment this configuration, and add (endpoint, title).
-# AVAILABLE_REGIONS = [
-#     ('http://cluster1.example.com:5000/v2.0', 'cluster1'),
-#     ('http://cluster2.example.com:5000/v2.0', 'cluster2'),
-# ]
-
-OPENSTACK_HOST = "127.0.0.1"
-OPENSTACK_KEYSTONE_URL = "http://%s:5000/v2.0" % OPENSTACK_HOST
-OPENSTACK_KEYSTONE_DEFAULT_ROLE = "Member"
-
-# Disable SSL certificate checks (useful for self-signed certificates):
-# OPENSTACK_SSL_NO_VERIFY = True
-
-HORIZON_CONFIG = {
-    'dashboards': ('project', 'admin', 'settings',),
-    'default_dashboard': 'project',
-}
-
-# The OPENSTACK_KEYSTONE_BACKEND settings can be used to identify the
-# capabilities of the auth backend for Keystone.
-# If Keystone has been configured to use LDAP as the auth backend then set
-# can_edit_user to False and name to 'ldap'.
-#
-# TODO(tres): Remove these once Keystone has an API to identify auth backend.
-OPENSTACK_KEYSTONE_BACKEND = {
-    'name': 'native',
-    'can_edit_user': True
-}
-
-OPENSTACK_HYPERVISOR_FEATURES = {
-    'can_set_mount_point': True
-}
-
-# OPENSTACK_ENDPOINT_TYPE specifies the endpoint type to use for the endpoints
-# in the Keystone service catalog. Use this setting when Horizon is running
-# external to the OpenStack environment. The default is 'internalURL'.
-#OPENSTACK_ENDPOINT_TYPE = "publicURL"
-
-# The number of objects (Swift containers/objects or images) to display
-# on a single page before providing a paging element (a "more" link)
-# to paginate results.
-API_RESULT_LIMIT = 1000
-API_RESULT_PAGE_SIZE = 20
-
-SWIFT_PAGINATE_LIMIT = 100
-
-# The timezone of the server. This should correspond with the timezone
-# of your entire OpenStack installation, and hopefully be in UTC.
-TIME_ZONE = "UTC"
-
-#LOGGING = {
-#        'version': 1,
-#        # When set to True this will disable all logging except
-#        # for loggers specified in this configuration dictionary. Note that
-#        # if nothing is specified here and disable_existing_loggers is True,
-#        # django.db.backends will still log unless it is disabled explicitly.
-#        'disable_existing_loggers': False,
-#        'handlers': {
-#            'null': {
-#                'level': 'DEBUG',
-#                'class': 'django.utils.log.NullHandler',
-#                },
-#            'console': {
-#                # Set the level to "DEBUG" for verbose output logging.
-#                'level': 'INFO',
-#                'class': 'logging.StreamHandler',
-#                },
-#            },
-#        'loggers': {
-#            # Logging from django.db.backends is VERY verbose, send to null
-#            # by default.
-#            'django.db.backends': {
-#                'handlers': ['null'],
-#                'propagate': False,
-#                },
-#            'horizon': {
-#                'handlers': ['console'],
-#                'propagate': False,
-#            },
-#            'openstack_dashboard': {
-#                'handlers': ['console'],
-#                'propagate': False,
-#            },
-#            'novaclient': {
-#                'handlers': ['console'],
-#                'propagate': False,
-#            },
-#            'keystoneclient': {
-#                'handlers': ['console'],
-#                'propagate': False,
-#            },
-#            'glanceclient': {
-#                'handlers': ['console'],
-#                'propagate': False,
-#            },
-#            'nose.plugins.manager': {
-#                'handlers': ['console'],
-#                'propagate': False,
-#            }
-#        }
-#}
diff --git a/functions b/functions
index fe50547..1590166 100644
--- a/functions
+++ b/functions
@@ -1148,6 +1148,12 @@
             DISK_FORMAT=qcow2
             CONTAINER_FORMAT=bare
             ;;
+        *.iso)
+            IMAGE="$FILES/${IMAGE_FNAME}"
+            IMAGE_NAME=$(basename "$IMAGE" ".iso")
+            DISK_FORMAT=iso
+            CONTAINER_FORMAT=bare
+            ;;
         *) echo "Do not know what to do with $IMAGE_FNAME"; false;;
     esac
 
diff --git a/lib/glance b/lib/glance
index edf6982..e9d0562 100644
--- a/lib/glance
+++ b/lib/glance
@@ -116,6 +116,15 @@
     iniset_rpc_backend glance $GLANCE_API_CONF DEFAULT
     iniset $GLANCE_API_CONF keystone_authtoken signing_dir $GLANCE_AUTH_CACHE_DIR/api
 
+    # Store the images in swift if enabled.
+    if is_service_enabled s-proxy; then
+        iniset $GLANCE_API_CONF DEFAULT default_store swift
+        iniset $GLANCE_API_CONF DEFAULT swift_store_auth_address $KEYSTONE_SERVICE_PROTOCOL://$KEYSTONE_SERVICE_HOST:$KEYSTONE_SERVICE_PORT/v2.0/
+        iniset $GLANCE_API_CONF DEFAULT swift_store_user $SERVICE_TENANT_NAME:glance
+        iniset $GLANCE_API_CONF DEFAULT swift_store_key $SERVICE_PASSWORD
+        iniset $GLANCE_API_CONF DEFAULT swift_store_create_container_on_put True
+    fi
+
     cp -p $GLANCE_DIR/etc/glance-registry-paste.ini $GLANCE_REGISTRY_PASTE_INI
 
     cp -p $GLANCE_DIR/etc/glance-api-paste.ini $GLANCE_API_PASTE_INI
diff --git a/lib/horizon b/lib/horizon
index 9c96b58..bc739ed 100644
--- a/lib/horizon
+++ b/lib/horizon
@@ -29,6 +29,10 @@
 # Set up default directories
 HORIZON_DIR=$DEST/horizon
 
+# local_settings.py is used to customize Dashboard settings.
+# The example file in Horizon repo is used by default.
+HORIZON_SETTINGS=${HORIZON_SETTINGS:-$HORIZON_DIR/openstack_dashboard/local/local_settings.py.example}
+
 # Allow overriding the default Apache user and group, default to
 # current user and his default group.
 APACHE_USER=${APACHE_USER:-$USER}
@@ -77,7 +81,7 @@
 
     # ``local_settings.py`` is used to override horizon default settings.
     local_settings=$HORIZON_DIR/openstack_dashboard/local/local_settings.py
-    cp $FILES/horizon_settings.py $local_settings
+    cp $HORIZON_SETTINGS $local_settings
 
     # enable loadbalancer dashboard in case service is enabled
     if is_service_enabled q-lbaas; then
diff --git a/lib/nova b/lib/nova
index 8f74897..61c05a1 100644
--- a/lib/nova
+++ b/lib/nova
@@ -156,8 +156,11 @@
         fi
 
         # Logout and delete iscsi sessions
-        sudo iscsiadm --mode node | grep $VOLUME_NAME_PREFIX | cut -d " " -f2 | xargs sudo iscsiadm --mode node --logout || true
-        sudo iscsiadm --mode node | grep $VOLUME_NAME_PREFIX | cut -d " " -f2 | sudo iscsiadm --mode node --op delete || true
+        tgts=$(sudo iscsiadm --mode node | grep $VOLUME_NAME_PREFIX | cut -d ' ' -f2)
+        for target in $tgts; do
+            sudo iscsiadm --mode node -T $target --logout || true
+        done
+        sudo iscsiadm --mode node --op delete || true
 
         # Clean out the instances directory.
         sudo rm -rf $NOVA_INSTANCES_PATH/*
diff --git a/stack.sh b/stack.sh
index 9034864..7d0dd9b 100755
--- a/stack.sh
+++ b/stack.sh
@@ -826,17 +826,7 @@
 
 if is_service_enabled g-reg; then
     echo_summary "Configuring Glance"
-
     init_glance
-
-    # Store the images in swift if enabled.
-    if is_service_enabled s-proxy; then
-        iniset $GLANCE_API_CONF DEFAULT default_store swift
-        iniset $GLANCE_API_CONF DEFAULT swift_store_auth_address $KEYSTONE_SERVICE_PROTOCOL://$KEYSTONE_SERVICE_HOST:$KEYSTONE_SERVICE_PORT/v2.0/
-        iniset $GLANCE_API_CONF DEFAULT swift_store_user $SERVICE_TENANT_NAME:glance
-        iniset $GLANCE_API_CONF DEFAULT swift_store_key $SERVICE_PASSWORD
-        iniset $GLANCE_API_CONF DEFAULT swift_store_create_container_on_put True
-    fi
 fi
 
 
@@ -1017,7 +1007,10 @@
     iniset $NOVA_CONF DEFAULT s3_affix_tenant "True"
 fi
 
-screen_it zeromq "cd $NOVA_DIR && $NOVA_BIN_DIR/nova-rpc-zmq-receiver"
+if is_service_enabled zeromq; then
+    echo_summary "Starting zermomq receiver"
+    screen_it zeromq "cd $NOVA_DIR && $NOVA_BIN_DIR/nova-rpc-zmq-receiver"
+fi
 
 # Launch the nova-api and wait for it to answer before continuing
 if is_service_enabled n-api; then
diff --git a/tools/xen/functions b/tools/xen/functions
new file mode 100644
index 0000000..5b4a661
--- /dev/null
+++ b/tools/xen/functions
@@ -0,0 +1,55 @@
+#!/bin/bash
+
+function xapi_plugin_location {
+    for PLUGIN_DIR in "/etc/xapi.d/plugins/" "/usr/lib/xcp/plugins/"
+    do
+        if [ -d $PLUGIN_DIR ]
+        then
+            echo $PLUGIN_DIR
+            return 0
+        fi
+    done
+    return 1
+}
+
+function zip_snapshot_location {
+    echo $1 | sed "s:\.git$::;s:$:/zipball/$2:g"
+}
+
+function create_directory_for_kernels {
+    mkdir -p "/boot/guest"
+}
+
+function extract_remote_zipball {
+    local ZIPBALL_URL=$1
+
+    local LOCAL_ZIPBALL=$(mktemp)
+    local EXTRACTED_FILES=$(mktemp -d)
+
+    (
+        wget -nv $ZIPBALL_URL -O $LOCAL_ZIPBALL --no-check-certificate
+        unzip -q -o $LOCAL_ZIPBALL -d $EXTRACTED_FILES
+        rm -f $LOCAL_ZIPBALL
+    ) >&2
+
+    echo "$EXTRACTED_FILES"
+}
+
+function find_xapi_plugins_dir {
+    find $1 -path '*/xapi.d/plugins' -type d -print
+}
+
+function install_xapi_plugins_from_zipball {
+    local XAPI_PLUGIN_DIR
+    local EXTRACTED_FILES
+    local EXTRACTED_PLUGINS_DIR
+
+    XAPI_PLUGIN_DIR=$(xapi_plugin_location)
+
+    EXTRACTED_FILES=$(extract_remote_zipball $1)
+    EXTRACTED_PLUGINS_DIR=$(find_xapi_plugins_dir $EXTRACTED_FILES)
+
+    cp -pr $EXTRACTED_PLUGINS_DIR/* $XAPI_PLUGIN_DIR
+    rm -rf $EXTRACTED_FILES
+    chmod a+x ${XAPI_PLUGIN_DIR}*
+}
diff --git a/tools/xen/install_os_domU.sh b/tools/xen/install_os_domU.sh
index 0c0e1e2..7c3b839 100755
--- a/tools/xen/install_os_domU.sh
+++ b/tools/xen/install_os_domU.sh
@@ -28,6 +28,9 @@
 # Include onexit commands
 . $THIS_DIR/scripts/on_exit.sh
 
+# xapi functions
+. $THIS_DIR/functions
+
 
 #
 # Get Settings
@@ -43,48 +46,26 @@
   xe "$cmd" --minimal "$@"
 }
 
-
 #
 # Prepare Dom0
 # including installing XenAPI plugins
 #
 
 cd $THIS_DIR
-if [ -f ./master ]
-then
-    rm -rf ./master
-    rm -rf ./nova
-fi
 
-# get nova
-NOVA_ZIPBALL_URL=${NOVA_ZIPBALL_URL:-$(echo $NOVA_REPO | sed "s:\.git$::;s:$:/zipball/$NOVA_BRANCH:g")}
-wget -nv $NOVA_ZIPBALL_URL -O nova-zipball --no-check-certificate
-unzip -q -o nova-zipball  -d ./nova
+# Install plugins
 
-# install xapi plugins
-XAPI_PLUGIN_DIR=/etc/xapi.d/plugins/
-if [ ! -d $XAPI_PLUGIN_DIR ]; then
-    # the following is needed when using xcp-xapi
-    XAPI_PLUGIN_DIR=/usr/lib/xcp/plugins/
-fi
-cp -pr ./nova/*/plugins/xenserver/xenapi/etc/xapi.d/plugins/* $XAPI_PLUGIN_DIR
+## Nova plugins
+NOVA_ZIPBALL_URL=${NOVA_ZIPBALL_URL:-$(zip_snapshot_location $NOVA_REPO $NOVA_BRANCH)}
+install_xapi_plugins_from_zipball $NOVA_ZIPBALL_URL
 
-# Install the netwrap xapi plugin to support agent control of dom0 networking
+## Install the netwrap xapi plugin to support agent control of dom0 networking
 if [[ "$ENABLED_SERVICES" =~ "q-agt" && "$Q_PLUGIN" = "openvswitch" ]]; then
-    if [ -f ./quantum ]; then
-        rm -rf ./quantum
-    fi
-    # get quantum
-    QUANTUM_ZIPBALL_URL=${QUANTUM_ZIPBALL_URL:-$(echo $QUANTUM_REPO | sed "s:\.git$::;s:$:/zipball/$QUANTUM_BRANCH:g")}
-    wget -nv $QUANTUM_ZIPBALL_URL -O quantum-zipball --no-check-certificate
-    unzip -q -o quantum-zipball  -d ./quantum
-    cp -pr ./quantum/*/quantum/plugins/openvswitch/agent/xenapi/etc/xapi.d/plugins/* $XAPI_PLUGIN_DIR
+    QUANTUM_ZIPBALL_URL=${QUANTUM_ZIPBALL_URL:-$(zip_snapshot_location $QUANTUM_REPO $QUANTUM_BRANCH)}
+    install_xapi_plugins_from_zipball $QUANTUM_ZIPBALL_URL
 fi
 
-chmod a+x ${XAPI_PLUGIN_DIR}*
-
-mkdir -p /boot/guest
-
+create_directory_for_kernels
 
 #
 # Configure Networking
diff --git a/tools/xen/mocks b/tools/xen/mocks
new file mode 100644
index 0000000..b006558
--- /dev/null
+++ b/tools/xen/mocks
@@ -0,0 +1,59 @@
+#!/bin/bash
+
+test ! -e "$LIST_OF_ACTIONS" && {
+    echo "Mocking is not set up properly."
+    echo "LIST_OF_ACTIONS should point to an existing file."
+    exit 1
+}
+
+test ! -e "$LIST_OF_DIRECTORIES" && {
+    echo "Mocking is not set up properly."
+    echo "LIST_OF_DIRECTORIES should point to an existing file."
+    exit 1
+}
+
+function mktemp {
+    if test "${1:-}" = "-d";
+    then
+        echo "tempdir"
+    else
+        echo "tempfile"
+    fi
+}
+
+function wget {
+    echo "wget $@" >> $LIST_OF_ACTIONS
+}
+
+function mkdir {
+    if test "${1:-}" = "-p";
+    then
+        echo "$2" >> $LIST_OF_DIRECTORIES
+    fi
+}
+
+function unzip {
+    echo "Random rubbish from unzip"
+    echo "unzip $@" >> $LIST_OF_ACTIONS
+}
+
+function rm {
+    echo "rm $@" >> $LIST_OF_ACTIONS
+}
+
+function [ {
+    if test "${1:-}" = "-d";
+    then
+        echo "[ $@" >> $LIST_OF_ACTIONS
+        for directory in $(cat $LIST_OF_DIRECTORIES)
+        do
+            if test "$directory" = "$2"
+            then
+                return 0
+            fi
+        done
+        return 1
+    fi
+    echo "Mock test does not implement the requested function"
+    exit 1
+}
diff --git a/tools/xen/test_functions.sh b/tools/xen/test_functions.sh
new file mode 100755
index 0000000..6817ec3
--- /dev/null
+++ b/tools/xen/test_functions.sh
@@ -0,0 +1,134 @@
+#!/bin/bash
+
+# Tests for functions.
+#
+# The tests are sourcing the mocks file to mock out various functions. The
+# mocking-out always happens in a sub-shell, thus it does not have impact on
+# the functions defined here.
+
+# To run the tests, please run:
+#
+# ./test_functions.sh run_tests
+#
+# To only print out the discovered test functions, run:
+#
+# ./test_functions.sh
+
+. functions
+
+# Setup
+function before_each_test {
+    LIST_OF_DIRECTORIES=$(mktemp)
+    truncate -s 0 $LIST_OF_DIRECTORIES
+
+    LIST_OF_ACTIONS=$(mktemp)
+    truncate -s 0 $LIST_OF_ACTIONS
+}
+
+# Teardown
+function after_each_test {
+    rm -f $LIST_OF_DIRECTORIES
+    rm -f $LIST_OF_ACTIONS
+}
+
+# Helpers
+function given_directory_exists {
+    echo "$1" >> $LIST_OF_DIRECTORIES
+}
+
+function assert_directory_exists {
+    grep "$1" $LIST_OF_DIRECTORIES
+}
+
+function assert_previous_command_failed {
+    [ "$?" != "0" ] || exit 1
+}
+
+# Tests
+function test_plugin_directory_on_xenserver {
+    given_directory_exists "/etc/xapi.d/plugins/"
+
+    PLUGDIR=$(. mocks && xapi_plugin_location)
+
+    [ "/etc/xapi.d/plugins/" = "$PLUGDIR" ]
+}
+
+function test_plugin_directory_on_xcp {
+    given_directory_exists "/usr/lib/xcp/plugins/"
+
+    PLUGDIR=$(. mocks && xapi_plugin_location)
+
+    [ "/usr/lib/xcp/plugins/" = "$PLUGDIR" ]
+}
+
+function test_no_plugin_directory_found {
+    set +e
+
+    local IGNORE
+    IGNORE=$(. mocks && xapi_plugin_location)
+
+    assert_previous_command_failed
+
+    grep "[ -d /etc/xapi.d/plugins/ ]" $LIST_OF_ACTIONS
+    grep "[ -d /usr/lib/xcp/plugins/ ]" $LIST_OF_ACTIONS
+}
+
+function test_zip_snapshot_location {
+    diff \
+    <(zip_snapshot_location "https://github.com/openstack/nova.git" "master") \
+    <(echo "https://github.com/openstack/nova/zipball/master")
+}
+
+function test_create_directory_for_kernels {
+    (. mocks && create_directory_for_kernels)
+
+    assert_directory_exists "/boot/guest"
+}
+
+function test_extract_remote_zipball {
+    local RESULT=$(. mocks && extract_remote_zipball "someurl")
+
+    diff <(cat $LIST_OF_ACTIONS) - << EOF
+wget -nv someurl -O tempfile --no-check-certificate
+unzip -q -o tempfile -d tempdir
+rm -f tempfile
+EOF
+
+    [ "$RESULT" = "tempdir" ]
+}
+
+function test_find_nova_plugins {
+    local tmpdir=$(mktemp -d)
+
+    mkdir -p "$tmpdir/blah/blah/u/xapi.d/plugins"
+
+    [ "$tmpdir/blah/blah/u/xapi.d/plugins" = $(find_xapi_plugins_dir $tmpdir) ]
+
+    rm -rf $tmpdir
+}
+
+# Test runner
+[ "$1" = "" ] && {
+    grep -e "^function *test_" $0 | cut -d" " -f2
+}
+
+[ "$1" = "run_tests" ] && {
+    for testname in $($0)
+    do
+        echo "$testname"
+        before_each_test
+        (
+            set -eux
+            $testname
+        )
+        if [ "$?" != "0" ]
+        then
+            echo "FAIL"
+            exit 1
+        else
+            echo "PASS"
+        fi
+
+        after_each_test
+    done
+}