Merge "Fixed git show for upper-constraints.txt in lib/tempest"
diff --git a/.gitignore b/.gitignore
index d2c127d..e5e1f6a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,8 +1,11 @@
 *~
 .*.sw?
 *.log
-*.log.[1-9]
+*-log
+*.log.*
+*-log.*
 *.pem
+*.pyc
 .localrc.auto
 .localrc.password
 .prereqs
diff --git a/.zuul.yaml b/.zuul.yaml
index 07d8f48..feafa0c 100644
--- a/.zuul.yaml
+++ b/.zuul.yaml
@@ -39,10 +39,10 @@
           - controller
 
 - nodeset:
-    name: devstack-single-node-fedora-27
+    name: devstack-single-node-fedora-latest
     nodes:
       - name: controller
-        label: fedora-27
+        label: fedora-28
     groups:
       - name: tempest
         nodes:
@@ -93,10 +93,10 @@
       job.group-vars.peers, which is what is used by multi node jobs for subnode
       nodes (everything but the controller).
     required-projects:
-      - openstack-dev/devstack
+      - git.openstack.org/openstack-dev/devstack
     roles:
-      - zuul: openstack-infra/devstack-gate
-      - zuul: openstack-infra/openstack-zuul-jobs
+      - zuul: git.openstack.org/openstack-infra/devstack-gate
+      - zuul: git.openstack.org/openstack-infra/openstack-zuul-jobs
     vars:
       devstack_localrc:
         DATABASE_PASSWORD: secretdatabase
@@ -116,10 +116,6 @@
         ERROR_ON_CLONE: true
         # Gate jobs can't deal with nested virt. Disable it.
         LIBVIRT_TYPE: qemu
-        # NOTE(dims): etcd 3.x is not available in debian/ubuntu
-        # etc. As a stop gap measure, devstack uses wget to download
-        # from the location below for all the CI jobs.
-        ETCD_DOWNLOAD_URL: http://tarballs.openstack.org/etcd/
       devstack_services:
         # Ignore any default set by devstack. Emit a "disable_all_services".
         base: false
@@ -183,7 +179,6 @@
           NOVNC_FROM_PACKAGE: true
           ERROR_ON_CLONE: true
           LIBVIRT_TYPE: qemu
-          ETCD_DOWNLOAD_URL: http://tarballs.openstack.org/etcd/
         devstack_services:
           base: false
     pre-run: playbooks/pre.yaml
@@ -199,9 +194,48 @@
       - ^.*/locale/.*po$
 
 - job:
-    name: devstack
+    name: devstack-minimal
     parent: devstack-base
     description: |
+      Minimal devstack base job, intended for use by jobs that need
+      less than the normal minimum set of required-projects.
+    nodeset: openstack-single-node
+    required-projects:
+      - git.openstack.org/openstack/requirements
+    vars:
+      devstack_localrc:
+        # Multinode specific settings
+        SERVICE_HOST: "{{ hostvars['controller']['nodepool']['private_ipv4'] }}"
+        HOST_IP: "{{ hostvars['controller']['nodepool']['private_ipv4'] }}"
+        PUBLIC_BRIDGE_MTU: "{{ external_bridge_mtu }}"
+      devstack_services:
+        # Shared services
+        dstat: true
+        etcd3: true
+        mysql: true
+        peakmem_tracker: true
+        rabbit: true
+    group-vars:
+      subnode:
+        devstack_services:
+          # Shared services
+          dstat: true
+          peakmem_tracker: true
+        devstack_localrc:
+          # Multinode specific settings
+          HOST_IP: "{{ hostvars[inventory_hostname]['nodepool']['private_ipv4'] }}"
+          SERVICE_HOST: "{{ hostvars['controller']['nodepool']['private_ipv4'] }}"
+          PUBLIC_BRIDGE_MTU: "{{ external_bridge_mtu }}"
+          # Subnode specific settings
+          DATABASE_TYPE: mysql
+          RABBIT_HOST: "{{ hostvars['controller']['nodepool']['private_ipv4'] }}"
+          DATABASE_HOST: "{{ hostvars['controller']['nodepool']['private_ipv4'] }}"
+
+
+- job:
+    name: devstack
+    parent: devstack-minimal
+    description: |
       Base devstack job for integration gate.
 
       This base job can be used for single node and multinode devstack jobs.
@@ -226,13 +260,12 @@
       and extended.
     nodeset: openstack-single-node
     required-projects:
-      - openstack/cinder
-      - openstack/glance
-      - openstack/keystone
-      - openstack/neutron
-      - openstack/nova
-      - openstack/requirements
-      - openstack/swift
+      - git.openstack.org/openstack/cinder
+      - git.openstack.org/openstack/glance
+      - git.openstack.org/openstack/keystone
+      - git.openstack.org/openstack/neutron
+      - git.openstack.org/openstack/nova
+      - git.openstack.org/openstack/swift
     timeout: 7200
     vars:
       devstack_localrc:
@@ -244,11 +277,7 @@
         DEBUG_LIBVIRT_COREDUMPS: True
         NOVA_VNC_ENABLED: true
         VNCSERVER_LISTEN: 0.0.0.0
-        VNCSERVER_PROXYCLIENT_ADDRESS: "{{ hostvars[inventory_hostname]['nodepool']['private_ipv4'] }}"
-        # Multinode specific settings
-        SERVICE_HOST: "{{ hostvars['controller']['nodepool']['private_ipv4'] }}"
-        HOST_IP: "{{ hostvars['controller']['nodepool']['private_ipv4'] }}"
-        PUBLIC_BRIDGE_MTU: "{{ external_bridge_mtu }}"
+        VNCSERVER_PROXYCLIENT_ADDRESS: $HOST_IP
       devstack_local_conf:
         post-config:
           $NEUTRON_CONF:
@@ -339,16 +368,12 @@
           # integrated gate, so specifying the services has not effect.
           # ceilometer-*: false
         devstack_localrc:
-          # Multinode specific settings
-          HOST_IP: "{{ hostvars[inventory_hostname]['nodepool']['private_ipv4'] }}"
-          SERVICE_HOST: "{{ hostvars['controller']['nodepool']['private_ipv4'] }}"
-          PUBLIC_BRIDGE_MTU: "{{ external_bridge_mtu }}"
           # Subnode specific settings
-          DATABASE_TYPE: mysql
           GLANCE_HOSTPORT: "{{ hostvars['controller']['nodepool']['private_ipv4'] }}:9292"
           Q_HOST: "{{ hostvars['controller']['nodepool']['private_ipv4'] }}"
-          RABBIT_HOST: "{{ hostvars['controller']['nodepool']['private_ipv4'] }}"
-          DATABASE_HOST: "{{ hostvars['controller']['nodepool']['private_ipv4'] }}"
+          NOVA_VNC_ENABLED: true
+          VNCSERVER_LISTEN: 0.0.0.0
+          VNCSERVER_PROXYCLIENT_ADDRESS: $HOST_IP
 
 - job:
     name: devstack-multinode
@@ -384,10 +409,10 @@
     voting: false
 
 - job:
-    name: devstack-platform-fedora-27
+    name: devstack-platform-fedora-latest
     parent: tempest-full
-    description: Fedora 27 platform test
-    nodeset: devstack-single-node-fedora-27
+    description: Fedora latest platform test
+    nodeset: devstack-single-node-fedora-latest
     voting: false
 
 - job:
@@ -460,7 +485,7 @@
         - devstack-platform-centos-7
         - devstack-platform-opensuse-423
         - devstack-platform-opensuse-tumbleweed
-        - devstack-platform-fedora-27
+        - devstack-platform-fedora-latest
         - devstack-multinode
         - devstack-unit-tests
     gate:
@@ -471,8 +496,28 @@
     # being experimental any more, so we can keep this list somewhat
     # pruned.
     #
+    # * nova-cells-v1: maintained by nova for cells v1 (nova-cells service);
+    #    nova gates on this job, it's in experimental for testing cells v1
+    #    changes to devstack w/o gating on it for all devstack changes.
     # * nova-next: maintained by nova for unreleased/undefaulted
     #    things like cellsv2 and placement-api
+    # * neutron-fullstack-with-uwsgi: maintained by neutron for fullstack test
+    #    when neutron-api is served by uwsgi, it's in exprimental for testing.
+    #    the next cycle we can remove this  job if things turn out to be
+    #    stable enough.
+    # * neutron-functional-with-uwsgi: maintained by neutron for functional
+    #    test. Next cycle we can remove this one if things turn out to be
+    #    stable engouh with uwsgi.
+    # * neutron-tempest-with-uwsgi: maintained by neutron for tempest test.
+    #    Next cycle we can remove this if everything run out stable enough.
+
     experimental:
       jobs:
+        - nova-cells-v1:
+            irrelevant-files:
+              - ^.*\.rst$
+              - ^doc/.*$
         - nova-next
+        - neutron-fullstack-with-uwsgi
+        - neutron-functional-with-uwsgi
+        - neutron-tempest-with-uwsgi
\ No newline at end of file
diff --git a/HACKING.rst b/HACKING.rst
index d5d6fbc..3853eed 100644
--- a/HACKING.rst
+++ b/HACKING.rst
@@ -47,12 +47,7 @@
 level.
 
 ``doc`` - Contains the Sphinx source for the documentation.
-``tools/build_docs.sh`` is used to generate the HTML versions of the
-DevStack scripts.  A complete doc build can be run with ``tox -edocs``.
-
-``exercises`` - Contains the test scripts used to sanity-check and
-demonstrate some OpenStack functions. These scripts know how to exit
-early or skip services that are not enabled.
+A complete doc build can be run with ``tox -edocs``.
 
 ``extras.d`` - Contains the dispatch scripts called by the hooks in
 ``stack.sh``, ``unstack.sh`` and ``clean.sh``. See :doc:`the plugins
@@ -183,88 +178,6 @@
 OpenStack project standard.
 
 
-Exercises
----------
-
-The scripts in the exercises directory are meant to 1) perform basic operational
-checks on certain aspects of OpenStack; and b) document the use of the
-OpenStack command-line clients.
-
-In addition to the guidelines above, exercise scripts MUST follow the structure
-outlined here.  ``swift.sh`` is perhaps the clearest example of these guidelines.
-These scripts are executed serially by ``exercise.sh`` in testing situations.
-
-* Begin and end with a banner that stands out in a sea of script logs to aid
-  in debugging failures, particularly in automated testing situations.  If the
-  end banner is not displayed, the script ended prematurely and can be assumed
-  to have failed.
-
-  ::
-
-    echo "**************************************************"
-    echo "Begin DevStack Exercise: $0"
-    echo "**************************************************"
-    ...
-    set +o xtrace
-    echo "**************************************************"
-    echo "End DevStack Exercise: $0"
-    echo "**************************************************"
-
-* The scripts will generally have the shell ``xtrace`` attribute set to display
-  the actual commands being executed, and the ``errexit`` attribute set to exit
-  the script on non-zero exit codes::
-
-    # This script exits on an error so that errors don't compound and you see
-    # only the first error that occurred.
-    set -o errexit
-
-    # Print the commands being run so that we can see the command that triggers
-    # an error.  It is also useful for following as the install occurs.
-    set -o xtrace
-
-* Settings and configuration are stored in ``exerciserc``, which must be
-  sourced after ``openrc`` or ``stackrc``::
-
-    # Import exercise configuration
-    source $TOP_DIR/exerciserc
-
-* There are a couple of helper functions in the common ``functions`` sub-script
-  that will check for non-zero exit codes and unset environment variables and
-  print a message and exit the script.  These should be called after most client
-  commands that are not otherwise checked to short-circuit long timeouts
-  (instance boot failure, for example)::
-
-    swift post $CONTAINER
-    die_if_error "Failure creating container $CONTAINER"
-
-    FLOATING_IP=`euca-allocate-address | cut -f2`
-    die_if_not_set FLOATING_IP "Failure allocating floating IP"
-
-* If you want an exercise to be skipped when for example a service wasn't
-  enabled for the exercise to be run, you can exit your exercise with the
-  special exitcode 55 and it will be detected as skipped.
-
-* The exercise scripts should only use the various OpenStack client binaries to
-  interact with OpenStack.  This specifically excludes any ``*-manage`` tools
-  as those assume direct access to configuration and databases, as well as direct
-  database access from the exercise itself.
-
-* If specific configuration needs to be present for the exercise to complete,
-  it should be staged in ``stack.sh``, or called from ``stack.sh``.
-
-* The ``OS_*`` environment variables should be the only ones used for all
-  authentication to OpenStack clients as documented in the CLIAuth_ wiki page.
-
-.. _CLIAuth: https://wiki.openstack.org/CLIAuth
-
-* The exercise MUST clean up after itself if successful.  If it is not successful,
-  it is assumed that state will be left behind; this allows a chance for developers
-  to look around and attempt to debug the problem.  The exercise SHOULD clean up
-  or graciously handle possible artifacts left over from previous runs if executed
-  again.  It is acceptable to require a reboot or even a re-install of DevStack
-  to restore a clean test environment.
-
-
 Bash Style Guidelines
 ~~~~~~~~~~~~~~~~~~~~~
 DevStack defines a bash set of best practices for maintaining large
diff --git a/doc/source/configuration.rst b/doc/source/configuration.rst
index 7efe4d6..46e50df 100644
--- a/doc/source/configuration.rst
+++ b/doc/source/configuration.rst
@@ -665,8 +665,7 @@
     enable_service n-cell
 
 Be aware that there are some features currently missing in cells, one
-notable one being security groups.  The exercises have been patched to
-disable functionality not supported by cells.
+notable one being security groups.
 
 Cinder
 ~~~~~~
@@ -729,44 +728,6 @@
 
     ENABLE_IDENTITY_V2=False
 
-Exercises
-~~~~~~~~~
-
-``exerciserc`` is used to configure settings for the exercise scripts.
-The values shown below are the default values. These can all be
-overridden by setting them in the ``localrc`` section.
-
-* Max time to wait while vm goes from build to active state
-
-    ::
-
-        ACTIVE_TIMEOUT==30
-
-* Max time to wait for proper IP association and dis-association.
-
-    ::
-
-        ASSOCIATE_TIMEOUT=15
-
-* Max time till the vm is bootable
-
-    ::
-
-        BOOT_TIMEOUT=30
-
-* Max time from run instance command until it is running
-
-    ::
-
-        RUNNING_TIMEOUT=$(($BOOT_TIMEOUT + $ACTIVE_TIMEOUT))
-
-* Max time to wait for a vm to terminate
-
-    ::
-
-        TERMINATE_TIMEOUT=30
-
-
 .. _arch-configuration:
 
 Architectures
diff --git a/doc/source/faq.rst b/doc/source/faq.rst
index ed9b4da..efb315c 100644
--- a/doc/source/faq.rst
+++ b/doc/source/faq.rst
@@ -18,6 +18,57 @@
 Your best choice is probably to choose a `distribution of OpenStack
 <https://www.openstack.org/marketplace/distros/>`__.
 
+Can I use DevStack as a development environment?
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Sure, you can. That said, there are a couple of things you should note before
+doing so:
+
+- DevStack makes a lot of configuration changes to your system and should not
+  be run in your main development environment.
+
+- All the repositories that DevStack clones when deploying are considered
+  volatile by default and thus are subject to hard resets. This is necessary to
+  keep you in sync with the latest upstream, which is what you want in a CI
+  situation, but it can result in branches being overwritten and files being
+  removed.
+
+  The corollary of this is that if you are working on a specific project, using
+  the DevStack project repository (defaulted to ``/opt/stack/<project>``) as
+  the single master repository for storing all your work is not recommended.
+  This behavior can be overridden by setting the ``RECLONE`` config option to
+  ``no``.  Alternatively, you can avoid running ``stack.sh`` to redeploy by
+  restarting services manually. In any case, you should generally ensure work
+  in progress is pushed to Gerrit or otherwise backed up before running
+  ``stack.sh``.
+
+- If you use DevStack within a VM, you may wish to mount a local OpenStack
+  directory, such as ``~/src/openstack``, inside the VM and configure DevStack
+  to use this as the clone location using the ``{PROJECT}_REPO`` config
+  variables. For example, assuming you're using Vagrant and sharing your home
+  directory, you should place the following in ``local.conf``:
+
+  .. code-block:: shell
+
+     NEUTRON_REPO=/home/vagrant/src/neutron
+     NOVA_REPO=/home/vagrant/src/nova
+     KEYSTONE_REPO=/home/vagrant/src/keystone
+     GLANCE_REPO=/home/vagrant/src/glance
+     SWIFT_REPO=/home/vagrant/src/swift
+     HORIZON_REPO=/home/vagrant/src/horizon
+     CINDER_REPO=/home/vagrant/src/cinder
+     HEAT_REPO=/home/vagrant/src/heat
+     TEMPEST_REPO=/home/vagrant/src/tempest
+     HEATCLIENT_REPO=/home/vagrant/src/python-heatclient
+     GLANCECLIENT_REPO=/home/vagrant/src/python-glanceclient
+     NOVACLIENT_REPO=/home/vagrant/src/python-novaclient
+     NEUTRONCLIENT_REPO=/home/vagrant/src/python-neutronclient
+     OPENSTACKCLIENT_REPO=/home/vagrant/src/python-openstackclient
+     HEAT_CFNTOOLS_REPO=/home/vagrant/src/heat-cfntools
+     HEAT_TEMPLATES_REPO=/home/vagrant/src/heat-templates
+     NEUTRON_FWAAS_REPO=/home/vagrant/src/neutron-fwaas
+     # ...
+
 Why a shell script, why not chef/puppet/...
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
diff --git a/doc/source/guides.rst b/doc/source/guides.rst
index c2c7b91..82e0dd6 100644
--- a/doc/source/guides.rst
+++ b/doc/source/guides.rst
@@ -20,6 +20,7 @@
    guides/devstack-with-nested-kvm
    guides/nova
    guides/devstack-with-lbaas-v2
+   guides/devstack-with-ldap
 
 All-In-One Single VM
 --------------------
@@ -66,3 +67,8 @@
 --------------------------------
 
 Guide to working with nova features :doc:`Nova and devstack <guides/nova>`.
+
+Deploying DevStack with LDAP
+----------------------------
+
+Guide to setting up :doc:`DevStack with LDAP <guides/devstack-with-ldap>`.
diff --git a/doc/source/guides/devstack-with-lbaas-v2.rst b/doc/source/guides/devstack-with-lbaas-v2.rst
index 7dee520..df3c7ce 100644
--- a/doc/source/guides/devstack-with-lbaas-v2.rst
+++ b/doc/source/guides/devstack-with-lbaas-v2.rst
@@ -15,7 +15,7 @@
 
 Install devstack
 
-  ::
+::
 
     git clone https://git.openstack.org/openstack-dev/devstack
     cd devstack
@@ -23,7 +23,7 @@
 
 Edit your ``local.conf`` to look like
 
-  ::
+::
 
     [[local|localrc]]
     # Load the external LBaaS plugin.
@@ -60,7 +60,7 @@
 
 Run stack.sh and do some sanity checks
 
-  ::
+::
 
     ./stack.sh
     . ./openrc
@@ -69,7 +69,7 @@
 
 Create two nova instances that we can use as test http servers:
 
-  ::
+::
 
     #create nova instances on private network
     nova boot --image $(nova image-list | awk '/ cirros-.*-x86_64-uec / {print $2}') --flavor 1 --nic net-id=$(openstack network list | awk '/ private / {print $2}') node1
@@ -83,7 +83,7 @@
 
 Set up a simple web server on each of these instances. ssh into each instance (username 'cirros', password 'cubswin:)') and run
 
- ::
+::
 
     MYIP=$(ifconfig eth0|grep 'inet addr'|awk -F: '{print $2}'| awk '{print $1}')
     while true; do echo -e "HTTP/1.0 200 OK\r\n\r\nWelcome to $MYIP" | sudo nc -l -p 80 ; done&
@@ -91,7 +91,7 @@
 Phase 2: Create your load balancers
 ------------------------------------
 
- ::
+::
 
     neutron lbaas-loadbalancer-create --name lb1 private-subnet
     neutron lbaas-loadbalancer-show lb1  # Wait for the provisioning_status to be ACTIVE.
diff --git a/doc/source/guides/devstack-with-ldap.rst b/doc/source/guides/devstack-with-ldap.rst
new file mode 100644
index 0000000..ec41141
--- /dev/null
+++ b/doc/source/guides/devstack-with-ldap.rst
@@ -0,0 +1,174 @@
+============================
+Deploying DevStack with LDAP
+============================
+
+The OpenStack Identity service has the ability to integrate with LDAP. The goal
+of this guide is to walk you through setting up an LDAP-backed OpenStack
+development environment.
+
+Introduction
+============
+
+LDAP support in keystone is read-only. You can use it to back an entire
+OpenStack deployment to a single LDAP server, or you can use it to back
+separate LDAP servers to specific keystone domains. Users within those domains
+will can authenticate against keystone, assume role assignments, and interact
+with other OpenStack services.
+
+Configuration
+=============
+
+To deploy an OpenLDAP server, make sure ``ldap`` is added to the list of
+``ENABLED_SERVICES``::
+
+    enable_service ldap
+
+Devstack will require a password to set up an LDAP administrator. This
+administrative user is also the bind user specified in keystone's configuration
+files, similar to a ``keystone`` user for MySQL databases.
+
+Devstack will prompt you for a password when running ``stack.sh`` if
+``LDAP_PASSWORD`` is not set. You can add the following to your
+``local.conf``::
+
+    LDAP_PASSWORD=super_secret_password
+
+At this point, devstack should have everything it needs to deploy OpenLDAP,
+bootstrap it with a minimal set of users, and configure it to back to a domain
+in keystone::
+
+    ./stack.sh
+
+Once ``stack.sh`` completes, you should have a running keystone deployment with
+a basic set of users. It is important to note that not all users will live
+within LDAP. Instead, keystone will back different domains to different
+identity sources. For example, the ``default`` domain will be backed by MySQL.
+This is usually where you'll find your administrative and services users. If
+you query keystone for a list of domains, you should see a domain called
+``Users``. This domain is set up by devstack and points to OpenLDAP.
+
+User Management
+===============
+
+Initially, there will only be two users in the LDAP server. The ``Manager``
+user is used by keystone to talk to OpenLDAP. The ``demo`` user is a generic
+user that you should be able to see if you query keystone for users within the
+``Users`` domain. Both of these users were added to LDAP using basic LDAP
+utilities installed by devstack (e.g. ``ldap-utils``) and LDIFs. The LDIFs used
+to create these users can be found in ``devstack/files/ldap/``.
+
+Listing Users
+-------------
+
+To list all users in LDAP directly, you can use ``ldapsearch`` with the LDAP
+user bootstrapped by devstack::
+
+    ldapsearch -x -w LDAP_PASSWORD -D cn=Manager,dc=openstack,dc=org \
+        -H ldap://localhost -b dc=openstack,dc=org
+
+As you can see, devstack creates an OpenStack domain called ``openstack.org``
+as a container for the ``Manager`` and ``demo`` users.
+
+Creating Users
+--------------
+
+Since keystone's LDAP integration is read-only, users must be added directly to
+LDAP. Users added directly to OpenLDAP will automatically be placed into the
+``Users`` domain.
+
+LDIFs can be used to add users via the command line. The following is an
+example LDIF that can be used to create a new LDAP user, let's call it
+``peter.ldif.in``::
+
+    dn: cn=peter,ou=Users,dc=openstack,dc=org
+    cn: peter
+    displayName: Peter Quill
+    givenName: Peter Quill
+    mail: starlord@openstack.org
+    objectClass: inetOrgPerson
+    objectClass: top
+    sn: peter
+    uid: peter
+    userPassword: im-a-better-pilot-than-rocket
+
+Now, we use the ``Manager`` user to create a user for Peter in LDAP::
+
+    ldapadd -x -w LDAP_PASSWORD -D cn=Manager,dc=openstack,dc=org \
+        -H ldap://localhost -c -f peter.ldif.in
+
+We should be able to assign Peter roles on projects. After Peter has some level
+of authorization, he should be able to login to Horizon by specifying the
+``Users`` domain and using his ``peter`` username and password. Authorization
+can be given to Peter by creating a project within the ``Users`` domain and
+giving him a role assignment on that project::
+
+    $ openstack project create --domain Users awesome-mix-vol-1
+    +-------------+----------------------------------+
+    | Field       | Value                            |
+    +-------------+----------------------------------+
+    | description |                                  |
+    | domain_id   | 61a2de23107c46bea2d758167af707b9 |
+    | enabled     | True                             |
+    | id          | 7d422396d54945cdac8fe1e8e32baec4 |
+    | is_domain   | False                            |
+    | name        | awesome-mix-vol-1                |
+    | parent_id   | 61a2de23107c46bea2d758167af707b9 |
+    | tags        | []                               |
+    +-------------+----------------------------------+
+    $ openstack role add --user peter --user-domain Users \
+          --project awesome-mix-vol-1 --project-domain Users admin
+
+
+Deleting Users
+--------------
+
+We can use the same basic steps to remove users from LDAP, but instead of using
+LDIFs, we can just pass the ``dn`` of the user we want to delete::
+
+    ldapdelete -x -w LDAP_PASSWORD -D cn=Manager,dc=openstack,dc=org \
+        -H ldap://localhost cn=peter,ou=Users,dc=openstack,dc=org
+
+Group Management
+================
+
+Like users, groups are considered specific identities. This means that groups
+also fall under the same read-only constraints as users and they can be managed
+directly with LDAP in the same way users are with LDIFs.
+
+Adding Groups
+-------------
+
+Let's define a specific group with the following LDIF::
+
+    dn: cn=guardians,ou=UserGroups,dc=openstack,dc=org
+    objectClass: groupOfNames
+    cn: guardians
+    description: Guardians of the Galaxy
+    member: cn=peter,dc=openstack,dc=org
+    member: cn=gamora,dc=openstack,dc=org
+    member: cn=drax,dc=openstack,dc=org
+    member: cn=rocket,dc=openstack,dc=org
+    member: cn=groot,dc=openstack,dc=org
+
+We can create the group using the same ``ldapadd`` command as we did with
+users::
+
+    ldapadd -x -w LDAP_PASSWORD -D cn=Manager,dc=openstack,dc=org \
+        -H ldap://localhost -c -f guardian-group.ldif.in
+
+If we check the group membership in Horizon, we'll see that only Peter is a
+member of the ``guardians`` group, despite the whole crew being specified in
+the LDIF. Once those accounts are created in LDAP, they will automatically be
+added to the ``guardians`` group. They will also assume any role assignments
+given to the ``guardians`` group.
+
+Deleting Groups
+---------------
+
+Just like users, groups can be deleted using the ``dn``::
+
+    ldapdelete -x -w LDAP_PASSWORD -D cn=Manager,dc=openstack,dc=org \
+        -H ldap://localhost cn=guardians,ou=UserGroups,dc=openstack,dc=org
+
+Note that this operation will not remove users within that group. It will only
+remove the group itself and the memberships any users had with that group.
diff --git a/doc/source/guides/neutron.rst b/doc/source/guides/neutron.rst
index 1b8dccd..12c6d69 100644
--- a/doc/source/guides/neutron.rst
+++ b/doc/source/guides/neutron.rst
@@ -244,7 +244,7 @@
 
     ## Neutron options
     PUBLIC_INTERFACE=eth0
-    ENABLED_SERVICES=n-cpu,rabbit,q-agt
+    ENABLED_SERVICES=n-cpu,rabbit,q-agt,placement-client
 
 Network traffic from `eth0` on the compute nodes is then NAT'd by the
 controller node that runs Neutron's `neutron-l3-agent` and provides L3
@@ -376,8 +376,8 @@
 
         ## Neutron options
         Q_USE_SECGROUP=True
-        ENABLE_PROJECT_VLANS=True
-        PROJECT_VLAN_RANGE=3001:4000
+        ENABLE_TENANT_VLANS=True
+        TENANT_VLAN_RANGE=3001:4000
         PHYSICAL_NETWORK=default
         OVS_PHYSICAL_BRIDGE=br-ex
 
diff --git a/doc/source/guides/single-machine.rst b/doc/source/guides/single-machine.rst
index 48a4fa8..515ea9a 100644
--- a/doc/source/guides/single-machine.rst
+++ b/doc/source/guides/single-machine.rst
@@ -127,7 +127,3 @@
 http://192.168.1.201/ for the dashboard (aka Horizon). Launch VMs and if
 you give them floating IPs and security group access those VMs will be
 accessible from other machines on your network.
-
-Some examples of using the OpenStack command-line clients ``nova`` and
-``glance`` are in the shakedown scripts in ``devstack/exercises``.
-``exercise.sh`` will run all of those scripts and report on the results.
diff --git a/doc/source/overview.rst b/doc/source/overview.rst
index 814a2b1..2479cd0 100644
--- a/doc/source/overview.rst
+++ b/doc/source/overview.rst
@@ -75,11 +75,3 @@
 
 -  single node
 -  multi-node configurations as are tested by the gate
-
-Exercises
----------
-
-The DevStack exercise scripts are no longer used as integration and gate
-testing as that job has transitioned to Tempest. They are still
-maintained as a demonstrations of using OpenStack from the command line
-and for quick operational testing.
diff --git a/doc/source/plugin-registry.rst b/doc/source/plugin-registry.rst
index 04b7698..f0c6238 100644
--- a/doc/source/plugin-registry.rst
+++ b/doc/source/plugin-registry.rst
@@ -26,6 +26,7 @@
 ====================================== ===
 almanach                               `git://git.openstack.org/openstack/almanach <https://git.openstack.org/cgit/openstack/almanach>`__
 aodh                                   `git://git.openstack.org/openstack/aodh <https://git.openstack.org/cgit/openstack/aodh>`__
+apmec                                  `git://git.openstack.org/openstack/apmec <https://git.openstack.org/cgit/openstack/apmec>`__
 astara                                 `git://git.openstack.org/openstack/astara <https://git.openstack.org/cgit/openstack/astara>`__
 barbican                               `git://git.openstack.org/openstack/barbican <https://git.openstack.org/cgit/openstack/barbican>`__
 bilean                                 `git://git.openstack.org/openstack/bilean <https://git.openstack.org/cgit/openstack/bilean>`__
@@ -35,7 +36,7 @@
 ceilometer                             `git://git.openstack.org/openstack/ceilometer <https://git.openstack.org/cgit/openstack/ceilometer>`__
 ceilometer-powervm                     `git://git.openstack.org/openstack/ceilometer-powervm <https://git.openstack.org/cgit/openstack/ceilometer-powervm>`__
 cloudkitty                             `git://git.openstack.org/openstack/cloudkitty <https://git.openstack.org/cgit/openstack/cloudkitty>`__
-collectd-ceilometer-plugin             `git://git.openstack.org/openstack/collectd-ceilometer-plugin <https://git.openstack.org/cgit/openstack/collectd-ceilometer-plugin>`__
+collectd-openstack-plugins             `git://git.openstack.org/openstack/collectd-openstack-plugins <https://git.openstack.org/cgit/openstack/collectd-openstack-plugins>`__
 congress                               `git://git.openstack.org/openstack/congress <https://git.openstack.org/cgit/openstack/congress>`__
 cyborg                                 `git://git.openstack.org/openstack/cyborg <https://git.openstack.org/cgit/openstack/cyborg>`__
 designate                              `git://git.openstack.org/openstack/designate <https://git.openstack.org/cgit/openstack/designate>`__
@@ -59,6 +60,7 @@
 ec2-api                                `git://git.openstack.org/openstack/ec2-api <https://git.openstack.org/cgit/openstack/ec2-api>`__
 freezer                                `git://git.openstack.org/openstack/freezer <https://git.openstack.org/cgit/openstack/freezer>`__
 freezer-api                            `git://git.openstack.org/openstack/freezer-api <https://git.openstack.org/cgit/openstack/freezer-api>`__
+freezer-tempest-plugin                 `git://git.openstack.org/openstack/freezer-tempest-plugin <https://git.openstack.org/cgit/openstack/freezer-tempest-plugin>`__
 freezer-web-ui                         `git://git.openstack.org/openstack/freezer-web-ui <https://git.openstack.org/cgit/openstack/freezer-web-ui>`__
 fuxi                                   `git://git.openstack.org/openstack/fuxi <https://git.openstack.org/cgit/openstack/fuxi>`__
 gce-api                                `git://git.openstack.org/openstack/gce-api <https://git.openstack.org/cgit/openstack/gce-api>`__
@@ -98,6 +100,7 @@
 monasca-transform                      `git://git.openstack.org/openstack/monasca-transform <https://git.openstack.org/cgit/openstack/monasca-transform>`__
 murano                                 `git://git.openstack.org/openstack/murano <https://git.openstack.org/cgit/openstack/murano>`__
 networking-6wind                       `git://git.openstack.org/openstack/networking-6wind <https://git.openstack.org/cgit/openstack/networking-6wind>`__
+networking-ansible                     `git://git.openstack.org/openstack/networking-ansible <https://git.openstack.org/cgit/openstack/networking-ansible>`__
 networking-arista                      `git://git.openstack.org/openstack/networking-arista <https://git.openstack.org/cgit/openstack/networking-arista>`__
 networking-bagpipe                     `git://git.openstack.org/openstack/networking-bagpipe <https://git.openstack.org/cgit/openstack/networking-bagpipe>`__
 networking-baremetal                   `git://git.openstack.org/openstack/networking-baremetal <https://git.openstack.org/cgit/openstack/networking-baremetal>`__
@@ -147,14 +150,15 @@
 octavia                                `git://git.openstack.org/openstack/octavia <https://git.openstack.org/cgit/openstack/octavia>`__
 octavia-dashboard                      `git://git.openstack.org/openstack/octavia-dashboard <https://git.openstack.org/cgit/openstack/octavia-dashboard>`__
 omni                                   `git://git.openstack.org/openstack/omni <https://git.openstack.org/cgit/openstack/omni>`__
+openstacksdk                           `git://git.openstack.org/openstack/openstacksdk <https://git.openstack.org/cgit/openstack/openstacksdk>`__
 os-xenapi                              `git://git.openstack.org/openstack/os-xenapi <https://git.openstack.org/cgit/openstack/os-xenapi>`__
 osprofiler                             `git://git.openstack.org/openstack/osprofiler <https://git.openstack.org/cgit/openstack/osprofiler>`__
 oswin-tempest-plugin                   `git://git.openstack.org/openstack/oswin-tempest-plugin <https://git.openstack.org/cgit/openstack/oswin-tempest-plugin>`__
 panko                                  `git://git.openstack.org/openstack/panko <https://git.openstack.org/cgit/openstack/panko>`__
 patrole                                `git://git.openstack.org/openstack/patrole <https://git.openstack.org/cgit/openstack/patrole>`__
 picasso                                `git://git.openstack.org/openstack/picasso <https://git.openstack.org/cgit/openstack/picasso>`__
-python-openstacksdk                    `git://git.openstack.org/openstack/python-openstacksdk <https://git.openstack.org/cgit/openstack/python-openstacksdk>`__
 qinling                                `git://git.openstack.org/openstack/qinling <https://git.openstack.org/cgit/openstack/qinling>`__
+qinling-dashboard                      `git://git.openstack.org/openstack/qinling-dashboard <https://git.openstack.org/cgit/openstack/qinling-dashboard>`__
 rally                                  `git://git.openstack.org/openstack/rally <https://git.openstack.org/cgit/openstack/rally>`__
 rally-openstack                        `git://git.openstack.org/openstack/rally-openstack <https://git.openstack.org/cgit/openstack/rally-openstack>`__
 sahara                                 `git://git.openstack.org/openstack/sahara <https://git.openstack.org/cgit/openstack/sahara>`__
@@ -163,9 +167,11 @@
 searchlight                            `git://git.openstack.org/openstack/searchlight <https://git.openstack.org/cgit/openstack/searchlight>`__
 searchlight-ui                         `git://git.openstack.org/openstack/searchlight-ui <https://git.openstack.org/cgit/openstack/searchlight-ui>`__
 senlin                                 `git://git.openstack.org/openstack/senlin <https://git.openstack.org/cgit/openstack/senlin>`__
+slogging                               `git://git.openstack.org/openstack/slogging <https://git.openstack.org/cgit/openstack/slogging>`__
 solum                                  `git://git.openstack.org/openstack/solum <https://git.openstack.org/cgit/openstack/solum>`__
 stackube                               `git://git.openstack.org/openstack/stackube <https://git.openstack.org/cgit/openstack/stackube>`__
 storlets                               `git://git.openstack.org/openstack/storlets <https://git.openstack.org/cgit/openstack/storlets>`__
+stx-config                             `git://git.openstack.org/openstack/stx-config <https://git.openstack.org/cgit/openstack/stx-config>`__
 tacker                                 `git://git.openstack.org/openstack/tacker <https://git.openstack.org/cgit/openstack/tacker>`__
 tap-as-a-service                       `git://git.openstack.org/openstack/tap-as-a-service <https://git.openstack.org/cgit/openstack/tap-as-a-service>`__
 tap-as-a-service-dashboard             `git://git.openstack.org/openstack/tap-as-a-service-dashboard <https://git.openstack.org/cgit/openstack/tap-as-a-service-dashboard>`__
diff --git a/doc/source/systemd.rst b/doc/source/systemd.rst
index 9cc4017..1bc9911 100644
--- a/doc/source/systemd.rst
+++ b/doc/source/systemd.rst
@@ -152,6 +152,19 @@
 
   /usr/local/bin/nova-scheduler --config-file /etc/nova/nova.conf
 
+Some executables, such as :program:`nova-compute`, will need to be executed
+with a particular group. This will be shown in the systemd unit file::
+
+  sudo systemctl cat devstack@n-cpu.service | grep Group
+
+::
+
+  Group = libvirt
+
+Use the :program:`sg` tool to execute the command as this group::
+
+  sg libvirt -c '/usr/local/bin/nova-compute --config-file /etc/nova/nova-cpu.conf'
+
 Using remote-pdb
 ----------------
 
diff --git a/exercise.sh b/exercise.sh
deleted file mode 100755
index 9067033..0000000
--- a/exercise.sh
+++ /dev/null
@@ -1,74 +0,0 @@
-#!/usr/bin/env bash
-
-# **exercise.sh**
-
-# Keep track of the current DevStack directory.
-TOP_DIR=$(cd $(dirname "$0") && pwd)
-
-# Import common functions
-source $TOP_DIR/functions
-
-# Load local configuration
-source $TOP_DIR/stackrc
-
-# Run everything in the exercises/ directory that isn't explicitly disabled
-
-# comma separated list of script basenames to skip
-# to refrain from exercising foo.sh use ``SKIP_EXERCISES=foo``
-SKIP_EXERCISES=${SKIP_EXERCISES:-""}
-
-# comma separated list of script basenames to run
-# to run only foo.sh use ``RUN_EXERCISES=foo``
-basenames=${RUN_EXERCISES:-""}
-
-EXERCISE_DIR=$TOP_DIR/exercises
-
-if [[ -z "${basenames}" ]]; then
-    # Locate the scripts we should run
-    basenames=$(for b in `ls $EXERCISE_DIR/*.sh`; do basename $b .sh; done)
-else
-    # If ``RUN_EXERCISES`` was specified, ignore ``SKIP_EXERCISES``.
-    SKIP_EXERCISES=
-fi
-
-# Track the state of each script
-passes=""
-failures=""
-skips=""
-
-# Loop over each possible script (by basename)
-for script in $basenames; do
-    if [[ ,$SKIP_EXERCISES, =~ ,$script, ]]; then
-        skips="$skips $script"
-    else
-        echo "====================================================================="
-        echo Running $script
-        echo "====================================================================="
-        $EXERCISE_DIR/$script.sh
-        exitcode=$?
-        if [[ $exitcode == 55 ]]; then
-            skips="$skips $script"
-        elif [[ $exitcode -ne 0 ]]; then
-            failures="$failures $script"
-        else
-            passes="$passes $script"
-        fi
-    fi
-done
-
-# Output status of exercise run
-echo "====================================================================="
-for script in $skips; do
-    echo SKIP $script
-done
-for script in $passes; do
-    echo PASS $script
-done
-for script in $failures; do
-    echo FAILED $script
-done
-echo "====================================================================="
-
-if [[ -n "$failures" ]]; then
-    exit 1
-fi
diff --git a/exerciserc b/exerciserc
deleted file mode 100644
index 978e0b3..0000000
--- a/exerciserc
+++ /dev/null
@@ -1,26 +0,0 @@
-#!/usr/bin/env bash
-#
-# source exerciserc
-#
-# Configure the DevStack exercise scripts
-# For best results, source this _after_ stackrc/localrc as it will set
-# values only if they are not already set.
-
-# Max time to wait while vm goes from build to active state
-export ACTIVE_TIMEOUT=${ACTIVE_TIMEOUT:-30}
-
-# Max time to wait for proper IP association and dis-association.
-export ASSOCIATE_TIMEOUT=${ASSOCIATE_TIMEOUT:-15}
-
-# Max time till the vm is bootable
-export BOOT_TIMEOUT=${BOOT_TIMEOUT:-30}
-
-# Max time from run instance command until it is running
-export RUNNING_TIMEOUT=${RUNNING_TIMEOUT:-$(($BOOT_TIMEOUT + $ACTIVE_TIMEOUT))}
-
-# Max time to wait for a vm to terminate
-export TERMINATE_TIMEOUT=${TERMINATE_TIMEOUT:-30}
-
-# The size of the volume we want to boot from; some storage back-ends
-# do not allow a disk resize, so it's important that this can be tuned
-export DEFAULT_VOLUME_SIZE=${DEFAULT_VOLUME_SIZE:-1}
diff --git a/exercises/aggregates.sh b/exercises/aggregates.sh
deleted file mode 100755
index 8cbca54..0000000
--- a/exercises/aggregates.sh
+++ /dev/null
@@ -1,150 +0,0 @@
-#!/usr/bin/env bash
-
-# **aggregates.sh**
-
-# This script demonstrates how to use host aggregates:
-#
-# *  Create an Aggregate
-# *  Updating Aggregate details
-# *  Testing Aggregate metadata
-# *  Testing Aggregate delete
-# *  Testing General Aggregates (https://blueprints.launchpad.net/nova/+spec/general-host-aggregates)
-# *  Testing add/remove hosts (with one host)
-
-echo "**************************************************"
-echo "Begin DevStack Exercise: $0"
-echo "**************************************************"
-
-# This script exits on an error so that errors don't compound and you see
-# only the first error that occurred.
-set -o errexit
-
-# Print the commands being run so that we can see the command that triggers
-# an error.  It is also useful for following as the install occurs.
-set -o xtrace
-
-
-# Settings
-# ========
-
-# Keep track of the current directory
-EXERCISE_DIR=$(cd $(dirname "$0") && pwd)
-TOP_DIR=$(cd $EXERCISE_DIR/..; pwd)
-
-# Test as the admin user
-# note this imports stackrc/functions, etc
-. $TOP_DIR/openrc admin admin
-
-# Import exercise configuration
-source $TOP_DIR/exerciserc
-
-# If nova api is not enabled we exit with exitcode 55 so that
-# the exercise is skipped
-is_service_enabled n-api || exit 55
-
-# Cells does not support aggregates.
-is_service_enabled n-cell && exit 55
-
-# Create an aggregate
-# ===================
-
-AGGREGATE_NAME=test_aggregate_$RANDOM
-AGGREGATE2_NAME=test_aggregate_$RANDOM
-AGGREGATE_A_ZONE=nova
-
-function exit_if_aggregate_present {
-    aggregate_name=$1
-
-    if [ $(nova aggregate-list | grep -c " $aggregate_name ") == 0 ]; then
-        echo "SUCCESS $aggregate_name not present"
-    else
-        die $LINENO "found aggregate: $aggregate_name"
-        exit -1
-    fi
-}
-
-exit_if_aggregate_present $AGGREGATE_NAME
-
-AGGREGATE_ID=$(nova aggregate-create $AGGREGATE_NAME $AGGREGATE_A_ZONE | grep " $AGGREGATE_NAME " | get_field 1)
-die_if_not_set $LINENO AGGREGATE_ID "Failure creating AGGREGATE_ID for $AGGREGATE_NAME $AGGREGATE_A_ZONE"
-
-AGGREGATE2_ID=$(nova aggregate-create $AGGREGATE2_NAME $AGGREGATE_A_ZONE | grep " $AGGREGATE2_NAME " | get_field 1)
-die_if_not_set $LINENO AGGREGATE2_ID "Fail creating AGGREGATE2_ID for $AGGREGATE2_NAME $AGGREGATE_A_ZONE"
-
-# check aggregate created
-nova aggregate-list | grep -q " $AGGREGATE_NAME " || die $LINENO "Aggregate $AGGREGATE_NAME not created"
-
-
-# Ensure creating a duplicate fails
-# =================================
-
-if nova aggregate-create $AGGREGATE_NAME $AGGREGATE_A_ZONE; then
-    die $LINENO "could create duplicate aggregate"
-fi
-
-
-# Test aggregate-update (and aggregate-details)
-# =============================================
-AGGREGATE_NEW_NAME=test_aggregate_$RANDOM
-
-nova aggregate-update $AGGREGATE_ID $AGGREGATE_NEW_NAME
-nova aggregate-details $AGGREGATE_ID | grep $AGGREGATE_NEW_NAME
-nova aggregate-details $AGGREGATE_ID | grep $AGGREGATE_A_ZONE
-
-nova aggregate-update $AGGREGATE_ID $AGGREGATE_NAME $AGGREGATE_A_ZONE
-nova aggregate-details $AGGREGATE_ID | grep $AGGREGATE_NAME
-nova aggregate-details $AGGREGATE_ID | grep $AGGREGATE_A_ZONE
-
-
-# Test aggregate-set-metadata
-# ===========================
-META_DATA_1_KEY=asdf
-META_DATA_2_KEY=foo
-META_DATA_3_KEY=bar
-
-#ensure no additional metadata is set
-nova aggregate-details $AGGREGATE_ID | egrep "\|[{u ]*'availability_zone.+$AGGREGATE_A_ZONE'[ }]*\|"
-
-nova aggregate-set-metadata $AGGREGATE_ID ${META_DATA_1_KEY}=123
-nova aggregate-details $AGGREGATE_ID | grep $META_DATA_1_KEY
-nova aggregate-details $AGGREGATE_ID | grep 123
-
-nova aggregate-set-metadata $AGGREGATE_ID ${META_DATA_2_KEY}=456
-nova aggregate-details $AGGREGATE_ID | grep $META_DATA_1_KEY
-nova aggregate-details $AGGREGATE_ID | grep $META_DATA_2_KEY
-
-nova aggregate-set-metadata $AGGREGATE_ID $META_DATA_2_KEY ${META_DATA_3_KEY}=789
-nova aggregate-details $AGGREGATE_ID | grep $META_DATA_1_KEY
-nova aggregate-details $AGGREGATE_ID | grep $META_DATA_3_KEY
-
-nova aggregate-details $AGGREGATE_ID | grep $META_DATA_2_KEY && die $LINENO "ERROR metadata was not cleared"
-
-nova aggregate-set-metadata $AGGREGATE_ID $META_DATA_3_KEY $META_DATA_1_KEY
-nova aggregate-details $AGGREGATE_ID | egrep "\|[{u ]*'availability_zone.+$AGGREGATE_A_ZONE'[ }]*\|"
-
-
-# Test aggregate-add/remove-host
-# ==============================
-if [ "$VIRT_DRIVER" == "xenserver" ]; then
-    echo "TODO(johngarbutt) add tests for add/remove host from pool aggregate"
-fi
-FIRST_HOST=$(nova host-list | grep compute | get_field 1 | head -1)
-# Make sure can add two aggregates to same host
-nova aggregate-add-host $AGGREGATE_ID $FIRST_HOST
-nova aggregate-add-host $AGGREGATE2_ID $FIRST_HOST
-if nova aggregate-add-host $AGGREGATE2_ID $FIRST_HOST; then
-    die $LINENO "could add duplicate host to single aggregate"
-fi
-nova aggregate-remove-host $AGGREGATE2_ID $FIRST_HOST
-nova aggregate-remove-host $AGGREGATE_ID $FIRST_HOST
-
-# Test aggregate-delete
-# =====================
-nova aggregate-delete $AGGREGATE_ID
-nova aggregate-delete $AGGREGATE2_ID
-exit_if_aggregate_present $AGGREGATE_NAME
-
-set +o xtrace
-echo "**************************************************"
-echo "End DevStack Exercise: $0"
-echo "**************************************************"
diff --git a/exercises/boot_from_volume.sh b/exercises/boot_from_volume.sh
deleted file mode 100755
index 7478bdf..0000000
--- a/exercises/boot_from_volume.sh
+++ /dev/null
@@ -1,224 +0,0 @@
-#!/usr/bin/env bash
-
-# **boot_from_volume.sh**
-
-# This script demonstrates how to boot from a volume.  It does the following:
-#
-# *  Create a bootable volume
-# *  Boot a volume-backed instance
-
-echo "*********************************************************************"
-echo "Begin DevStack Exercise: $0"
-echo "*********************************************************************"
-
-# This script exits on an error so that errors don't compound and you see
-# only the first error that occurred.
-set -o errexit
-
-# Print the commands being run so that we can see the command that triggers
-# an error.  It is also useful for following as the install occurs.
-set -o xtrace
-
-
-# Settings
-# ========
-
-# Keep track of the current directory
-EXERCISE_DIR=$(cd $(dirname "$0") && pwd)
-TOP_DIR=$(cd $EXERCISE_DIR/..; pwd)
-
-# Import common functions
-source $TOP_DIR/functions
-
-# Import project functions
-source $TOP_DIR/lib/cinder
-source $TOP_DIR/lib/neutron
-source $TOP_DIR/lib/neutron-legacy
-
-# Import configuration
-source $TOP_DIR/openrc
-
-# Import exercise configuration
-source $TOP_DIR/exerciserc
-
-# If cinder is not enabled we exit with exitcode 55 so that
-# the exercise is skipped
-is_service_enabled cinder || exit 55
-
-# Ironic does not support boot from volume.
-[ "$VIRT_DRIVER" == "ironic" ] && exit 55
-
-# Instance type to create
-DEFAULT_INSTANCE_TYPE=${DEFAULT_INSTANCE_TYPE:-m1.tiny}
-
-# Boot this image, use first AMI image if unset
-DEFAULT_IMAGE_NAME=${DEFAULT_IMAGE_NAME:-ami}
-
-# Security group name
-SECGROUP=${SECGROUP:-boot_secgroup}
-
-# Instance and volume names
-VM_NAME=${VM_NAME:-ex-bfv-inst}
-VOL_NAME=${VOL_NAME:-ex-vol-bfv}
-
-
-# Launching a server
-# ==================
-
-# List servers for project:
-nova list
-
-# Images
-# ------
-
-# List the images available
-openstack image list
-
-# Grab the id of the image to launch
-IMAGE=$(openstack image list | egrep " $DEFAULT_IMAGE_NAME " | get_field 1)
-die_if_not_set $LINENO IMAGE "Failure getting image $DEFAULT_IMAGE_NAME"
-
-# Security Groups
-# ---------------
-
-# List security groups
-nova secgroup-list
-
-if is_service_enabled n-cell; then
-    # Cells does not support security groups, so force the use of "default"
-    SECGROUP="default"
-    echo "Using the default security group because of Cells."
-else
-    # Create a secgroup
-    if ! nova secgroup-list | grep -q $SECGROUP; then
-        nova secgroup-create $SECGROUP "$SECGROUP description"
-        if ! timeout $ASSOCIATE_TIMEOUT sh -c "while ! nova secgroup-list | grep -q $SECGROUP; do sleep 1; done"; then
-            echo "Security group not created"
-            exit 1
-        fi
-    fi
-fi
-
-# Configure Security Group Rules
-if ! nova secgroup-list-rules $SECGROUP | grep -q icmp; then
-    nova secgroup-add-rule $SECGROUP icmp -1 -1 0.0.0.0/0
-fi
-if ! nova secgroup-list-rules $SECGROUP | grep -q " tcp .* 22 "; then
-    nova secgroup-add-rule $SECGROUP tcp 22 22 0.0.0.0/0
-fi
-
-# List secgroup rules
-nova secgroup-list-rules $SECGROUP
-
-# Set up instance
-# ---------------
-
-# List flavors
-nova flavor-list
-
-# Select a flavor
-INSTANCE_TYPE=$(nova flavor-list | grep $DEFAULT_INSTANCE_TYPE | get_field 1)
-if [[ -z "$INSTANCE_TYPE" ]]; then
-    # grab the first flavor in the list to launch if default doesn't exist
-    INSTANCE_TYPE=$(nova flavor-list | head -n 4 | tail -n 1 | get_field 1)
-fi
-
-# Clean-up from previous runs
-nova delete $VM_NAME || true
-if ! timeout $ACTIVE_TIMEOUT sh -c "while nova show $VM_NAME; do sleep 1; done"; then
-    echo "server didn't terminate!"
-    exit 1
-fi
-
-# Setup Keypair
-KEY_NAME=test_key
-KEY_FILE=key.pem
-nova keypair-delete $KEY_NAME || true
-nova keypair-add $KEY_NAME > $KEY_FILE
-chmod 600 $KEY_FILE
-
-# Set up volume
-# -------------
-
-# Delete any old volume
-cinder delete $VOL_NAME || true
-if ! timeout $ACTIVE_TIMEOUT sh -c "while cinder list | grep $VOL_NAME; do sleep 1; done"; then
-    echo "Volume $VOL_NAME not deleted"
-    exit 1
-fi
-
-# Create the bootable volume
-start_time=$(date +%s)
-cinder create --image-id $IMAGE --display-name=$VOL_NAME --display-description "test bootable volume: $VOL_NAME" $DEFAULT_VOLUME_SIZE || \
-    die $LINENO "Failure creating volume $VOL_NAME"
-if ! timeout $ACTIVE_TIMEOUT sh -c "while ! cinder list | grep $VOL_NAME | grep available; do sleep 1; done"; then
-    echo "Volume $VOL_NAME not created"
-    exit 1
-fi
-end_time=$(date +%s)
-echo "Completed cinder create in $((end_time - start_time)) seconds"
-
-# Get volume ID
-VOL_ID=$(cinder list | grep $VOL_NAME  | get_field 1)
-die_if_not_set $LINENO VOL_ID "Failure retrieving volume ID for $VOL_NAME"
-
-# Boot instance
-# -------------
-
-# Boot using the --block-device-mapping param. The format of mapping is:
-# <dev_name>=<id>:<type>:<size(GB)>:<delete_on_terminate>
-# Leaving the middle two fields blank appears to do-the-right-thing
-VM_UUID=$(nova boot --flavor $INSTANCE_TYPE --image $IMAGE --block-device-mapping vda=$VOL_ID --security-groups=$SECGROUP --key-name $KEY_NAME $VM_NAME | grep ' id ' | get_field 2)
-die_if_not_set $LINENO VM_UUID "Failure launching $VM_NAME"
-
-# Check that the status is active within ACTIVE_TIMEOUT seconds
-if ! timeout $ACTIVE_TIMEOUT sh -c "while ! nova show $VM_UUID | grep status | grep -q ACTIVE; do sleep 1; done"; then
-    echo "server didn't become active!"
-    exit 1
-fi
-
-# Get the instance IP
-IP=$(get_instance_ip $VM_UUID $PRIVATE_NETWORK_NAME)
-
-die_if_not_set $LINENO IP "Failure retrieving IP address"
-
-# Private IPs can be pinged in single node deployments
-ping_check $IP $BOOT_TIMEOUT "$PRIVATE_NETWORK_NAME"
-
-# Clean up
-# --------
-
-# Delete volume backed instance
-nova delete $VM_UUID || die $LINENO "Failure deleting instance $VM_NAME"
-if ! timeout $TERMINATE_TIMEOUT sh -c "while nova list | grep -q $VM_UUID; do sleep 1; done"; then
-    echo "Server $VM_NAME not deleted"
-    exit 1
-fi
-
-# Wait for volume to be released
-if ! timeout $ACTIVE_TIMEOUT sh -c "while ! cinder list | grep $VOL_NAME | grep available; do sleep 1; done"; then
-    echo "Volume $VOL_NAME not released"
-    exit 1
-fi
-
-# Delete volume
-start_time=$(date +%s)
-cinder delete $VOL_ID || die $LINENO "Failure deleting volume $VOLUME_NAME"
-if ! timeout $ACTIVE_TIMEOUT sh -c "while cinder list | grep $VOL_NAME; do sleep 1; done"; then
-    echo "Volume $VOL_NAME not deleted"
-    exit 1
-fi
-end_time=$(date +%s)
-echo "Completed cinder delete in $((end_time - start_time)) seconds"
-
-if [[ $SECGROUP = "default" ]] ; then
-    echo "Skipping deleting default security group"
-else
-    # Delete secgroup
-    nova secgroup-delete $SECGROUP || die $LINENO "Failure deleting security group $SECGROUP"
-fi
-
-set +o xtrace
-echo "*********************************************************************"
-echo "SUCCESS: End DevStack Exercise: $0"
-echo "*********************************************************************"
diff --git a/exercises/client-args.sh b/exercises/client-args.sh
deleted file mode 100755
index b380968..0000000
--- a/exercises/client-args.sh
+++ /dev/null
@@ -1,174 +0,0 @@
-#!/usr/bin/env bash
-
-# **client-args.sh**
-
-# Test OpenStack client authentication arguments handling
-
-echo "*********************************************************************"
-echo "Begin DevStack Exercise: $0"
-echo "*********************************************************************"
-
-# This script exits on an error so that errors don't compound and you see
-# only the first error that occurred.
-set -o errexit
-
-# Print the commands being run so that we can see the command that triggers
-# an error.  It is also useful for following as the install occurs.
-set -o xtrace
-
-
-# Settings
-# ========
-
-# Keep track of the current directory
-EXERCISE_DIR=$(cd $(dirname "$0") && pwd)
-TOP_DIR=$(cd $EXERCISE_DIR/..; pwd)
-
-# Import common functions
-source $TOP_DIR/functions
-
-# Import configuration
-source $TOP_DIR/openrc
-
-# Import exercise configuration
-source $TOP_DIR/exerciserc
-
-# Unset all of the known NOVA_* vars
-unset NOVA_API_KEY
-unset NOVA_ENDPOINT_NAME
-unset NOVA_PASSWORD
-unset NOVA_PROJECT_ID
-unset NOVA_REGION_NAME
-unset NOVA_URL
-unset NOVA_USERNAME
-
-# Save the known variables for later
-export x_PROJECT_NAME=$OS_PROJECT_NAME
-export x_USERNAME=$OS_USERNAME
-export x_PASSWORD=$OS_PASSWORD
-export x_AUTH_URL=$OS_AUTH_URL
-
-# Unset the usual variables to force argument processing
-unset OS_PROJECT_NAME
-unset OS_USERNAME
-unset OS_PASSWORD
-unset OS_AUTH_URL
-
-# Common authentication args
-PROJECT_ARG="--os-project-name=$x_PROJECT_NAME"
-ARGS="--os-username=$x_USERNAME --os-password=$x_PASSWORD --os-auth-url=$x_AUTH_URL"
-
-# Set global return
-RETURN=0
-
-# Keystone client
-# ---------------
-if [[ "$ENABLED_SERVICES" =~ "key" ]]; then
-    if [[ "$SKIP_EXERCISES" =~ "key" ]]; then
-        STATUS_KEYSTONE="Skipped"
-    else
-        echo -e "\nTest Keystone"
-        if openstack $PROJECT_ARG $ARGS catalog show identity; then
-            STATUS_KEYSTONE="Succeeded"
-        else
-            STATUS_KEYSTONE="Failed"
-            RETURN=1
-        fi
-    fi
-fi
-
-# Nova client
-# -----------
-
-if [[ "$ENABLED_SERVICES" =~ "n-api" ]]; then
-    if [[ "$SKIP_EXERCISES" =~ "n-api" ]]; then
-        STATUS_NOVA="Skipped"
-    else
-        # Test OSAPI
-        echo -e "\nTest Nova"
-        if nova $PROJECT_ARG $ARGS flavor-list; then
-            STATUS_NOVA="Succeeded"
-        else
-            STATUS_NOVA="Failed"
-            RETURN=1
-        fi
-    fi
-fi
-
-# Cinder client
-# -------------
-
-if [[ "$ENABLED_SERVICES" =~ "c-api" ]]; then
-    if [[ "$SKIP_EXERCISES" =~ "c-api" ]]; then
-        STATUS_CINDER="Skipped"
-    else
-        echo -e "\nTest Cinder"
-        if cinder $PROJECT_ARG $ARGS list; then
-            STATUS_CINDER="Succeeded"
-        else
-            STATUS_CINDER="Failed"
-            RETURN=1
-        fi
-    fi
-fi
-
-# Glance client
-# -------------
-
-if [[ "$ENABLED_SERVICES" =~ "g-api" ]]; then
-    if [[ "$SKIP_EXERCISES" =~ "g-api" ]]; then
-        STATUS_GLANCE="Skipped"
-    else
-        echo -e "\nTest Glance"
-        if openstack $PROJECT_ARG $ARGS image list; then
-            STATUS_GLANCE="Succeeded"
-        else
-            STATUS_GLANCE="Failed"
-            RETURN=1
-        fi
-    fi
-fi
-
-# Swift client
-# ------------
-
-if [[ "$ENABLED_SERVICES" =~ "swift" || "$ENABLED_SERVICES" =~ "s-proxy" ]]; then
-    if [[ "$SKIP_EXERCISES" =~ "swift" ]]; then
-        STATUS_SWIFT="Skipped"
-    else
-        echo -e "\nTest Swift"
-        if swift $PROJECT_ARG $ARGS stat; then
-            STATUS_SWIFT="Succeeded"
-        else
-            STATUS_SWIFT="Failed"
-            RETURN=1
-        fi
-    fi
-fi
-
-set +o xtrace
-
-
-# Results
-# =======
-
-function report {
-    if [[ -n "$2" ]]; then
-        echo "$1: $2"
-    fi
-}
-
-echo -e "\n"
-report "Keystone" $STATUS_KEYSTONE
-report "Nova" $STATUS_NOVA
-report "Cinder" $STATUS_CINDER
-report "Glance" $STATUS_GLANCE
-report "Swift" $STATUS_SWIFT
-
-if (( $RETURN == 0 )); then
-    echo "*********************************************************************"
-    echo "SUCCESS: End DevStack Exercise: $0"
-    echo "*********************************************************************"
-fi
-
-exit $RETURN
diff --git a/exercises/client-env.sh b/exercises/client-env.sh
deleted file mode 100755
index fff04df..0000000
--- a/exercises/client-env.sh
+++ /dev/null
@@ -1,171 +0,0 @@
-#!/usr/bin/env bash
-
-# **client-env.sh**
-
-# Test OpenStack client environment variable handling
-
-echo "*********************************************************************"
-echo "Begin DevStack Exercise: $0"
-echo "*********************************************************************"
-
-# This script exits on an error so that errors don't compound and you see
-# only the first error that occurred.
-set -o errexit
-
-# Print the commands being run so that we can see the command that triggers
-# an error.  It is also useful for following as the install occurs.
-set -o xtrace
-
-
-# Settings
-# ========
-
-# Keep track of the current directory
-EXERCISE_DIR=$(cd $(dirname "$0") && pwd)
-TOP_DIR=$(cd $EXERCISE_DIR/..; pwd)
-
-# Import common functions
-source $TOP_DIR/functions
-
-# Import configuration
-source $TOP_DIR/openrc admin
-
-# Import exercise configuration
-source $TOP_DIR/exerciserc
-
-# Unset all of the known NOVA_* vars
-unset NOVA_API_KEY
-unset NOVA_ENDPOINT_NAME
-unset NOVA_PASSWORD
-unset NOVA_PROJECT_ID
-unset NOVA_REGION_NAME
-unset NOVA_URL
-unset NOVA_USERNAME
-
-for i in OS_TENANT_NAME OS_USERNAME OS_PASSWORD OS_AUTH_URL; do
-    is_set $i
-    if [[ $? -ne 0 ]]; then
-        echo "$i expected to be set"
-        ABORT=1
-    fi
-done
-if [[ -n "$ABORT" ]]; then
-    exit 1
-fi
-
-# Set global return
-RETURN=0
-
-# Keystone client
-# ---------------
-if [[ "$ENABLED_SERVICES" =~ "key" ]]; then
-    if [[ "$SKIP_EXERCISES" =~ "key" ]]; then
-        STATUS_KEYSTONE="Skipped"
-    else
-        echo -e "\nTest Keystone"
-        if openstack endpoint show identity; then
-            STATUS_KEYSTONE="Succeeded"
-        else
-            STATUS_KEYSTONE="Failed"
-            RETURN=1
-        fi
-    fi
-fi
-
-# Nova client
-# -----------
-
-if [[ "$ENABLED_SERVICES" =~ "n-api" ]]; then
-    if [[ "$SKIP_EXERCISES" =~ "n-api" ]]; then
-        STATUS_NOVA="Skipped"
-    else
-        # Test OSAPI
-        echo -e "\nTest Nova"
-        if nova flavor-list; then
-            STATUS_NOVA="Succeeded"
-        else
-            STATUS_NOVA="Failed"
-            RETURN=1
-        fi
-
-    fi
-fi
-
-# Cinder client
-# -------------
-
-if [[ "$ENABLED_SERVICES" =~ "c-api" ]]; then
-    if [[ "$SKIP_EXERCISES" =~ "c-api" ]]; then
-        STATUS_CINDER="Skipped"
-    else
-        echo -e "\nTest Cinder"
-        if cinder list; then
-            STATUS_CINDER="Succeeded"
-        else
-            STATUS_CINDER="Failed"
-            RETURN=1
-        fi
-    fi
-fi
-
-# Glance client
-# -------------
-
-if [[ "$ENABLED_SERVICES" =~ "g-api" ]]; then
-    if [[ "$SKIP_EXERCISES" =~ "g-api" ]]; then
-        STATUS_GLANCE="Skipped"
-    else
-        echo -e "\nTest Glance"
-        if openstack image list; then
-            STATUS_GLANCE="Succeeded"
-        else
-            STATUS_GLANCE="Failed"
-            RETURN=1
-        fi
-    fi
-fi
-
-# Swift client
-# ------------
-
-
-if [[ "$ENABLED_SERVICES" =~ "swift" || "$ENABLED_SERVICES" =~ "s-proxy" ]]; then
-    if [[ "$SKIP_EXERCISES" =~ "swift" ]]; then
-        STATUS_SWIFT="Skipped"
-    else
-        echo -e "\nTest Swift"
-        if swift stat; then
-            STATUS_SWIFT="Succeeded"
-        else
-            STATUS_SWIFT="Failed"
-            RETURN=1
-        fi
-    fi
-fi
-
-set +o xtrace
-
-
-# Results
-# =======
-
-function report {
-    if [[ -n "$2" ]]; then
-        echo "$1: $2"
-    fi
-}
-
-echo -e "\n"
-report "Keystone" $STATUS_KEYSTONE
-report "Nova" $STATUS_NOVA
-report "Cinder" $STATUS_CINDER
-report "Glance" $STATUS_GLANCE
-report "Swift" $STATUS_SWIFT
-
-if (( $RETURN == 0 )); then
-    echo "*********************************************************************"
-    echo "SUCCESS: End DevStack Exercise: $0"
-    echo "*********************************************************************"
-fi
-
-exit $RETURN
diff --git a/exercises/floating_ips.sh b/exercises/floating_ips.sh
deleted file mode 100755
index 5abc713..0000000
--- a/exercises/floating_ips.sh
+++ /dev/null
@@ -1,216 +0,0 @@
-#!/usr/bin/env bash
-
-# **floating_ips.sh** - using the cloud can be fun
-
-# Test instance connectivity with the ``nova`` command from ``python-novaclient``
-
-echo "*********************************************************************"
-echo "Begin DevStack Exercise: $0"
-echo "*********************************************************************"
-
-# This script exits on an error so that errors don't compound and you see
-# only the first error that occurred.
-set -o errexit
-
-# Print the commands being run so that we can see the command that triggers
-# an error.  It is also useful for following as the install occurs.
-set -o xtrace
-
-
-# Settings
-# ========
-
-# Keep track of the current directory
-EXERCISE_DIR=$(cd $(dirname "$0") && pwd)
-TOP_DIR=$(cd $EXERCISE_DIR/..; pwd)
-
-# Import common functions
-source $TOP_DIR/functions
-
-# Import configuration
-source $TOP_DIR/openrc
-
-# Import project functions
-source $TOP_DIR/lib/neutron
-source $TOP_DIR/lib/neutron-legacy
-
-# Import exercise configuration
-source $TOP_DIR/exerciserc
-
-# If nova api is not enabled we exit with exitcode 55 so that
-# the exercise is skipped
-is_service_enabled n-api || exit 55
-
-# Instance type to create
-DEFAULT_INSTANCE_TYPE=${DEFAULT_INSTANCE_TYPE:-m1.tiny}
-
-# Boot this image, use first AMI image if unset
-DEFAULT_IMAGE_NAME=${DEFAULT_IMAGE_NAME:-ami}
-
-# Security group name
-SECGROUP=${SECGROUP:-test_secgroup}
-
-# Default floating IP pool name
-DEFAULT_FLOATING_POOL=${DEFAULT_FLOATING_POOL:-public}
-
-# Additional floating IP pool and range
-TEST_FLOATING_POOL=${TEST_FLOATING_POOL:-test}
-
-# Instance name
-VM_NAME="ex-float"
-
-# Cells does not support floating ips API calls
-is_service_enabled n-cell && exit 55
-
-# Launching a server
-# ==================
-
-# List servers for tenant:
-nova list
-
-# Images
-# ------
-
-# List the images available
-openstack image list
-
-# Grab the id of the image to launch
-IMAGE=$(openstack image list | egrep " $DEFAULT_IMAGE_NAME " | get_field 1)
-die_if_not_set $LINENO IMAGE "Failure getting image $DEFAULT_IMAGE_NAME"
-
-# Security Groups
-# ---------------
-
-# List security groups
-nova secgroup-list
-
-# Create a secgroup
-if ! nova secgroup-list | grep -q $SECGROUP; then
-    nova secgroup-create $SECGROUP "$SECGROUP description"
-    if ! timeout $ASSOCIATE_TIMEOUT sh -c "while ! nova secgroup-list | grep -q $SECGROUP; do sleep 1; done"; then
-        die $LINENO "Security group not created"
-    fi
-fi
-
-# Configure Security Group Rules
-if ! nova secgroup-list-rules $SECGROUP | grep -q icmp; then
-    nova secgroup-add-rule $SECGROUP icmp -1 -1 0.0.0.0/0
-fi
-if ! nova secgroup-list-rules $SECGROUP | grep -q " tcp .* 22 "; then
-    nova secgroup-add-rule $SECGROUP tcp 22 22 0.0.0.0/0
-fi
-
-# List secgroup rules
-nova secgroup-list-rules $SECGROUP
-
-# Set up instance
-# ---------------
-
-# List flavors
-nova flavor-list
-
-# Select a flavor
-INSTANCE_TYPE=$(nova flavor-list | grep $DEFAULT_INSTANCE_TYPE | get_field 1)
-if [[ -z "$INSTANCE_TYPE" ]]; then
-    # grab the first flavor in the list to launch if default doesn't exist
-    INSTANCE_TYPE=$(nova flavor-list | head -n 4 | tail -n 1 | get_field 1)
-    die_if_not_set $LINENO INSTANCE_TYPE "Failure retrieving INSTANCE_TYPE"
-fi
-
-# Clean-up from previous runs
-nova delete $VM_NAME || true
-if ! timeout $ACTIVE_TIMEOUT sh -c "while nova show $VM_NAME; do sleep 1; done"; then
-    die $LINENO "server didn't terminate!"
-    exit 1
-fi
-
-# Boot instance
-# -------------
-
-VM_UUID=$(nova boot --flavor $INSTANCE_TYPE --image $IMAGE --security-groups=$SECGROUP $VM_NAME | grep ' id ' | get_field 2)
-die_if_not_set $LINENO VM_UUID "Failure launching $VM_NAME"
-
-# Check that the status is active within ACTIVE_TIMEOUT seconds
-if ! timeout $ACTIVE_TIMEOUT sh -c "while ! nova show $VM_UUID | grep status | grep -q ACTIVE; do sleep 1; done"; then
-    die $LINENO "server didn't become active!"
-fi
-
-# Get the instance IP
-IP=$(get_instance_ip $VM_UUID $PRIVATE_NETWORK_NAME)
-die_if_not_set $LINENO IP "Failure retrieving IP address"
-
-# Private IPs can be pinged in single node deployments
-ping_check $IP $BOOT_TIMEOUT "$PRIVATE_NETWORK_NAME"
-
-# Floating IPs
-# ------------
-
-# Allocate a floating IP from the default pool
-FLOATING_IP=$(nova floating-ip-create | grep $DEFAULT_FLOATING_POOL | get_field 1)
-die_if_not_set $LINENO FLOATING_IP "Failure creating floating IP from pool $DEFAULT_FLOATING_POOL"
-
-# List floating addresses
-if ! timeout $ASSOCIATE_TIMEOUT sh -c "while ! nova floating-ip-list | grep -q $FLOATING_IP; do sleep 1; done"; then
-    die $LINENO "Floating IP not allocated"
-fi
-
-# Add floating IP to our server
-nova add-floating-ip $VM_UUID $FLOATING_IP || \
-    die $LINENO "Failure adding floating IP $FLOATING_IP to $VM_NAME"
-
-# Test we can ping our floating IP within ASSOCIATE_TIMEOUT seconds
-ping_check $FLOATING_IP $ASSOCIATE_TIMEOUT "$PUBLIC_NETWORK_NAME"
-
-if ! is_service_enabled neutron; then
-    # Allocate an IP from second floating pool
-    TEST_FLOATING_IP=$(nova floating-ip-create $TEST_FLOATING_POOL | grep $TEST_FLOATING_POOL | get_field 1)
-    die_if_not_set $LINENO TEST_FLOATING_IP "Failure creating floating IP in $TEST_FLOATING_POOL"
-
-    # list floating addresses
-    if ! timeout $ASSOCIATE_TIMEOUT sh -c "while ! nova floating-ip-list | grep $TEST_FLOATING_POOL | grep -q $TEST_FLOATING_IP; do sleep 1; done"; then
-        die $LINENO "Floating IP not allocated"
-    fi
-fi
-
-# Dis-allow icmp traffic (ping)
-nova secgroup-delete-rule $SECGROUP icmp -1 -1 0.0.0.0/0 || \
-    die $LINENO "Failure deleting security group rule from $SECGROUP"
-
-if ! timeout $ASSOCIATE_TIMEOUT sh -c "while nova secgroup-list-rules $SECGROUP | grep -q icmp; do sleep 1; done"; then
-    die $LINENO "Security group rule not deleted from $SECGROUP"
-fi
-
-# FIXME (anthony): make xs support security groups
-if [ "$VIRT_DRIVER" != "ironic" -a "$VIRT_DRIVER" != "xenserver" -a "$VIRT_DRIVER" != "openvz" ]; then
-    # Test we can aren't able to ping our floating ip within ASSOCIATE_TIMEOUT seconds
-    ping_check $FLOATING_IP $ASSOCIATE_TIMEOUT "$PUBLIC_NETWORK_NAME" Fail
-fi
-
-# Clean up
-# --------
-
-if ! is_service_enabled neutron; then
-    # Delete second floating IP
-    nova floating-ip-delete $TEST_FLOATING_IP || \
-        die $LINENO "Failure deleting floating IP $TEST_FLOATING_IP"
-fi
-
-# Delete the floating ip
-nova floating-ip-delete $FLOATING_IP || \
-    die $LINENO "Failure deleting floating IP $FLOATING_IP"
-
-# Delete instance
-nova delete $VM_UUID || die $LINENO "Failure deleting instance $VM_NAME"
-# Wait for termination
-if ! timeout $TERMINATE_TIMEOUT sh -c "while nova list | grep -q $VM_UUID; do sleep 1; done"; then
-    die $LINENO "Server $VM_NAME not deleted"
-fi
-
-# Delete secgroup
-nova secgroup-delete $SECGROUP || \
-    die $LINENO "Failure deleting security group $SECGROUP"
-
-set +o xtrace
-echo "*********************************************************************"
-echo "SUCCESS: End DevStack Exercise: $0"
-echo "*********************************************************************"
diff --git a/exercises/neutron-adv-test.sh b/exercises/neutron-adv-test.sh
deleted file mode 100755
index e8c8f62..0000000
--- a/exercises/neutron-adv-test.sh
+++ /dev/null
@@ -1,466 +0,0 @@
-#!/usr/bin/env bash
-#
-# Copyright 2012, Cisco Systems
-# Copyright 2012, VMware, Inc.
-# Copyright 2012, NTT MCL, Inc.
-#
-# Please direct any questions to dedutta@cisco.com, dwendlandt@vmware.com, nachi@nttmcl.com
-#
-# **neutron-adv-test.sh**
-
-# Perform integration testing of Nova and other components with Neutron.
-
-echo "*********************************************************************"
-echo "Begin DevStack Exercise: $0"
-echo "*********************************************************************"
-
-# This script exits on an error so that errors don't compound and you see
-# only the first error that occurred.
-
-set -o errtrace
-
-# Print the commands being run so that we can see the command that triggers
-# an error.  It is also useful for following as the install occurs.
-set -o xtrace
-
-# Environment
-# -----------
-
-# Keep track of the current directory
-EXERCISE_DIR=$(cd $(dirname "$0") && pwd)
-TOP_DIR=$(cd $EXERCISE_DIR/..; pwd)
-
-# Import common functions
-source $TOP_DIR/functions
-
-# Import configuration
-source $TOP_DIR/openrc
-
-# Import neutron functions
-source $TOP_DIR/lib/neutron
-source $TOP_DIR/lib/neutron-legacy
-
-# If neutron is not enabled we exit with exitcode 55, which means exercise is skipped.
-neutron_plugin_check_adv_test_requirements || exit 55
-
-# Import exercise configuration
-source $TOP_DIR/exerciserc
-
-# Neutron Settings
-# ----------------
-
-PROJECTS="DEMO1"
-# TODO (nati)_Test public network
-#PROJECTS="DEMO1,DEMO2"
-
-PUBLIC_NAME="admin"
-DEMO1_NAME="demo1"
-DEMO2_NAME="demo2"
-
-PUBLIC_NUM_NET=1
-DEMO1_NUM_NET=1
-DEMO2_NUM_NET=2
-
-PUBLIC_NET1_CIDR="200.0.0.0/24"
-DEMO1_NET1_CIDR="10.10.0.0/24"
-DEMO2_NET1_CIDR="10.20.0.0/24"
-DEMO2_NET2_CIDR="10.20.1.0/24"
-
-PUBLIC_NET1_GATEWAY="200.0.0.1"
-DEMO1_NET1_GATEWAY="10.10.0.1"
-DEMO2_NET1_GATEWAY="10.20.0.1"
-DEMO2_NET2_GATEWAY="10.20.1.1"
-
-PUBLIC_NUM_VM=1
-DEMO1_NUM_VM=1
-DEMO2_NUM_VM=2
-
-PUBLIC_VM1_NET='admin-net1'
-DEMO1_VM1_NET='demo1-net1'
-# Multinic settings. But this is fail without nic setting in OS image
-DEMO2_VM1_NET='demo2-net1'
-DEMO2_VM2_NET='demo2-net2'
-
-PUBLIC_NUM_ROUTER=1
-DEMO1_NUM_ROUTER=1
-DEMO2_NUM_ROUTER=1
-
-PUBLIC_ROUTER1_NET="admin-net1"
-DEMO1_ROUTER1_NET="demo1-net1"
-DEMO2_ROUTER1_NET="demo2-net1"
-
-# Various functions
-# -----------------
-
-function foreach_project {
-    COMMAND=$1
-    for PROJECT in ${PROJECTS//,/ };do
-        eval ${COMMAND//%PROJECT%/$PROJECT}
-    done
-}
-
-function foreach_project_resource {
-    COMMAND=$1
-    RESOURCE=$2
-    for PROJECT in ${PROJECTS//,/ };do
-        eval 'NUM=$'"${PROJECT}_NUM_$RESOURCE"
-        for i in `seq $NUM`;do
-            local COMMAND_LOCAL=${COMMAND//%PROJECT%/$PROJECT}
-            COMMAND_LOCAL=${COMMAND_LOCAL//%NUM%/$i}
-            eval $COMMAND_LOCAL
-        done
-    done
-}
-
-function foreach_project_vm {
-    COMMAND=$1
-    foreach_project_resource "$COMMAND" 'VM'
-}
-
-function foreach_project_net {
-    COMMAND=$1
-    foreach_project_resource "$COMMAND" 'NET'
-}
-
-function get_image_id {
-    local IMAGE_ID
-    IMAGE_ID=$(openstack image list | egrep " $DEFAULT_IMAGE_NAME " | get_field 1)
-    die_if_not_set $LINENO IMAGE_ID "Failure retrieving IMAGE_ID"
-    echo "$IMAGE_ID"
-}
-
-function get_project_id {
-    local PROJECT_NAME=$1
-    local PROJECT_ID
-    PROJECT_ID=`openstack project list | grep " $PROJECT_NAME " | head -n 1 | get_field 1`
-    die_if_not_set $LINENO PROJECT_ID "Failure retrieving PROJECT_ID for $PROJECT_NAME"
-    echo "$PROJECT_ID"
-}
-
-function get_user_id {
-    local USER_NAME=$1
-    local USER_ID
-    USER_ID=`openstack user list | grep $USER_NAME | awk '{print $2}'`
-    die_if_not_set $LINENO USER_ID "Failure retrieving USER_ID for $USER_NAME"
-    echo "$USER_ID"
-}
-
-function get_role_id {
-    local ROLE_NAME=$1
-    local ROLE_ID
-    ROLE_ID=`openstack role assignment list | grep $ROLE_NAME | awk '{print $2}'`
-    die_if_not_set $LINENO ROLE_ID "Failure retrieving ROLE_ID for $ROLE_NAME"
-    echo "$ROLE_ID"
-}
-
-function get_network_id {
-    local NETWORK_NAME="$1"
-    local NETWORK_ID
-    NETWORK_ID=`openstack network show -f value -c id $NETWORK_NAME`
-    echo $NETWORK_ID
-}
-
-function get_flavor_id {
-    local INSTANCE_TYPE=$1
-    local FLAVOR_ID
-    FLAVOR_ID=`nova flavor-list | grep $INSTANCE_TYPE | awk '{print $2}'`
-    die_if_not_set $LINENO FLAVOR_ID "Failure retrieving FLAVOR_ID for $INSTANCE_TYPE"
-    echo "$FLAVOR_ID"
-}
-
-function confirm_server_active {
-    local VM_UUID=$1
-    if ! timeout $ACTIVE_TIMEOUT sh -c "while ! nova show $VM_UUID | grep status | grep -q ACTIVE; do sleep 1; done"; then
-        echo "server '$VM_UUID' did not become active!"
-        false
-    fi
-}
-
-function neutron_debug_admin {
-    local os_username=$OS_USERNAME
-    local os_project_id=$OS_PROJECT_ID
-    source $TOP_DIR/openrc admin admin
-    neutron-debug $@
-    source $TOP_DIR/openrc $os_username $os_project_id
-}
-
-function add_project {
-    openstack project create $1
-    openstack user create $2 --password ${ADMIN_PASSWORD} --project $1
-    openstack role add Member --project $1 --user $2
-}
-
-function remove_project {
-    local PROJECT=$1
-    local PROJECT_ID
-    PROJECT_ID=$(get_project_id $PROJECT)
-    openstack project delete $PROJECT_ID
-}
-
-function remove_user {
-    local USER=$1
-    local USER_ID
-    USER_ID=$(get_user_id $USER)
-    openstack user delete $USER_ID
-}
-
-function create_projects {
-    source $TOP_DIR/openrc admin admin
-    add_project demo1 demo1 demo1
-    add_project demo2 demo2 demo2
-    source $TOP_DIR/openrc demo demo
-}
-
-function delete_projects_and_users {
-    source $TOP_DIR/openrc admin admin
-    remove_user demo1
-    remove_project demo1
-    remove_user demo2
-    remove_project demo2
-    echo "removed all projects"
-    source $TOP_DIR/openrc demo demo
-}
-
-function create_network {
-    local PROJECT=$1
-    local GATEWAY=$2
-    local CIDR=$3
-    local NUM=$4
-    local EXTRA=$5
-    local NET_NAME="${PROJECT}-net$NUM"
-    local ROUTER_NAME="${PROJECT}-router${NUM}"
-    source $TOP_DIR/openrc admin admin
-    local PROJECT_ID
-    PROJECT_ID=$(get_project_id $PROJECT)
-    source $TOP_DIR/openrc $PROJECT $PROJECT
-    local NET_ID
-    NET_ID=$(openstack network create --project $PROJECT_ID $NET_NAME $EXTRA| grep ' id ' | awk '{print $4}' )
-    die_if_not_set $LINENO NET_ID "Failure creating NET_ID for $PROJECT_ID $NET_NAME $EXTRA"
-    openstack subnet create --ip-version 4 --project $PROJECT_ID --gateway $GATEWAY --subnet-pool None --network $NET_ID --subnet-range $CIDR "${NET_NAME}_subnet"
-    neutron_debug_admin probe-create --device-owner compute $NET_ID
-    source $TOP_DIR/openrc demo demo
-}
-
-function create_networks {
-    foreach_project_net 'create_network ${%PROJECT%_NAME} ${%PROJECT%_NET%NUM%_GATEWAY} ${%PROJECT%_NET%NUM%_CIDR} %NUM% ${%PROJECT%_NET%NUM%_EXTRA}'
-    #TODO(nati) test security group function
-    # allow ICMP for both project's security groups
-    #source $TOP_DIR/openrc demo1 demo1
-    #$NOVA secgroup-add-rule default icmp -1 -1 0.0.0.0/0
-    #source $TOP_DIR/openrc demo2 demo2
-    #$NOVA secgroup-add-rule default icmp -1 -1 0.0.0.0/0
-}
-
-function create_vm {
-    local PROJECT=$1
-    local NUM=$2
-    local NET_NAMES=$3
-    source $TOP_DIR/openrc $PROJECT $PROJECT
-    local NIC=""
-    for NET_NAME in ${NET_NAMES//,/ };do
-        NIC="$NIC --nic net-id="`get_network_id $NET_NAME`
-    done
-    #TODO (nati) Add multi-nic test
-    #TODO (nati) Add public-net test
-    local VM_UUID
-    VM_UUID=`nova boot --flavor $(get_flavor_id m1.tiny) \
-        --image $(get_image_id) \
-        $NIC \
-        $PROJECT-server$NUM | grep ' id ' | cut -d"|" -f3 | sed 's/ //g'`
-    die_if_not_set $LINENO VM_UUID "Failure launching $PROJECT-server$NUM"
-    confirm_server_active $VM_UUID
-}
-
-function create_vms {
-    foreach_project_vm 'create_vm ${%PROJECT%_NAME} %NUM% ${%PROJECT%_VM%NUM%_NET}'
-}
-
-function ping_ip {
-    # Test agent connection.  Assumes namespaces are disabled, and
-    # that DHCP is in use, but not L3
-    local VM_NAME=$1
-    local NET_NAME=$2
-    IP=$(get_instance_ip $VM_NAME $NET_NAME)
-    ping_check $IP $BOOT_TIMEOUT $NET_NAME
-}
-
-function check_vm {
-    local PROJECT=$1
-    local NUM=$2
-    local VM_NAME="$PROJECT-server$NUM"
-    local NET_NAME=$3
-    source $TOP_DIR/openrc $PROJECT $PROJECT
-    ping_ip $VM_NAME $NET_NAME
-    # TODO (nati) test ssh connection
-    # TODO (nati) test inter connection between vm
-    # TODO (nati) test dhcp host routes
-    # TODO (nati) test multi-nic
-}
-
-function check_vms {
-    foreach_project_vm 'check_vm ${%PROJECT%_NAME} %NUM% ${%PROJECT%_VM%NUM%_NET}'
-}
-
-function shutdown_vm {
-    local PROJECT=$1
-    local NUM=$2
-    source $TOP_DIR/openrc $PROJECT $PROJECT
-    VM_NAME=${PROJECT}-server$NUM
-    nova delete $VM_NAME
-}
-
-function shutdown_vms {
-    foreach_project_vm 'shutdown_vm ${%PROJECT%_NAME} %NUM%'
-    if ! timeout $TERMINATE_TIMEOUT sh -c "while nova list | grep -q ACTIVE; do sleep 1; done"; then
-        die $LINENO "Some VMs failed to shutdown"
-    fi
-}
-
-function delete_network {
-    local PROJECT=$1
-    local NUM=$2
-    local NET_NAME="${PROJECT}-net$NUM"
-    source $TOP_DIR/openrc admin admin
-    local PROJECT_ID
-    PROJECT_ID=$(get_project_id $PROJECT)
-    #TODO(nati) comment out until l3-agent merged
-    #for res in port subnet net router;do
-    for net_id in `openstack network list -c ID -c Name | grep $NET_NAME | awk '{print $2}'`;do
-        delete_probe $net_id
-        openstack subnet list | grep $net_id | awk '{print $2}' | xargs -I% openstack subnet delete %
-        openstack network delete $net_id
-    done
-    source $TOP_DIR/openrc demo demo
-}
-
-function delete_networks {
-    foreach_project_net 'delete_network ${%PROJECT%_NAME} %NUM%'
-    # TODO(nati) add secuirty group check after it is implemented
-    # source $TOP_DIR/openrc demo1 demo1
-    # nova secgroup-delete-rule default icmp -1 -1 0.0.0.0/0
-    # source $TOP_DIR/openrc demo2 demo2
-    # nova secgroup-delete-rule default icmp -1 -1 0.0.0.0/0
-}
-
-function create_all {
-    create_projects
-    create_networks
-    create_vms
-}
-
-function delete_all {
-    shutdown_vms
-    delete_networks
-    delete_projects_and_users
-}
-
-function all {
-    create_all
-    check_vms
-    delete_all
-}
-
-# Test functions
-# --------------
-
-function test_functions {
-    IMAGE=$(get_image_id)
-    echo $IMAGE
-
-    PROJECT_ID=$(get_project_id demo)
-    echo $PROJECT_ID
-
-    FLAVOR_ID=$(get_flavor_id m1.tiny)
-    echo $FLAVOR_ID
-
-    NETWORK_ID=$(get_network_id admin)
-    echo $NETWORK_ID
-}
-
-# Usage and main
-# --------------
-
-function usage {
-    echo "$0: [-h]"
-    echo "  -h, --help              Display help message"
-    echo "  -t, --project            Create projects"
-    echo "  -n, --net               Create networks"
-    echo "  -v, --vm                Create vms"
-    echo "  -c, --check             Check connection"
-    echo "  -x, --delete-projects    Delete projects"
-    echo "  -y, --delete-nets       Delete networks"
-    echo "  -z, --delete-vms        Delete vms"
-    echo "  -T, --test              Test functions"
-}
-
-function main {
-
-    echo Description
-
-    if [ $# -eq 0 ] ; then
-        # if no args are provided, run all tests
-        all
-    else
-
-        while [ "$1" != "" ]; do
-            case $1 in
-                -h | --help )   usage
-                                exit
-                                ;;
-                -n | --net )    create_networks
-                                exit
-                                ;;
-                -v | --vm )     create_vms
-                                exit
-                                ;;
-                -t | --project ) create_projects
-                                exit
-                                ;;
-                -c | --check )   check_vms
-                                exit
-                                ;;
-                -T | --test )   test_functions
-                                exit
-                                ;;
-                -x | --delete-projects ) delete_projects_and_users
-                                exit
-                                ;;
-                -y | --delete-nets ) delete_networks
-                                exit
-                                ;;
-                -z | --delete-vms ) shutdown_vms
-                                exit
-                                ;;
-                -a | --all )    all
-                                exit
-                                ;;
-                * )             usage
-                                exit 1
-            esac
-            shift
-        done
-    fi
-}
-
-trap failed ERR
-function failed {
-    local r=$?
-    set +o errtrace
-    set +o xtrace
-    echo "Failed to execute"
-    echo "Starting cleanup..."
-    delete_all
-    echo "Finished cleanup"
-    exit $r
-}
-
-# Kick off script
-# ---------------
-
-echo $*
-main $*
-
-set +o xtrace
-echo "*********************************************************************"
-echo "SUCCESS: End DevStack Exercise: $0"
-echo "*********************************************************************"
diff --git a/exercises/sec_groups.sh b/exercises/sec_groups.sh
deleted file mode 100755
index 2f78e39..0000000
--- a/exercises/sec_groups.sh
+++ /dev/null
@@ -1,81 +0,0 @@
-#!/usr/bin/env bash
-
-# **sec_groups.sh**
-
-# Test security groups via the command line
-
-echo "*********************************************************************"
-echo "Begin DevStack Exercise: $0"
-echo "*********************************************************************"
-
-# This script exits on an error so that errors don't compound and you see
-# only the first error that occurred.
-set -o errexit
-
-# Print the commands being run so that we can see the command that triggers
-# an error.  It is also useful for following as the install occurs.
-set -o xtrace
-
-
-# Settings
-# ========
-
-# Keep track of the current directory
-EXERCISE_DIR=$(cd $(dirname "$0") && pwd)
-TOP_DIR=$(cd $EXERCISE_DIR/..; pwd)
-
-# Import common functions
-source $TOP_DIR/functions
-
-# Import configuration
-source $TOP_DIR/openrc
-
-# Import exercise configuration
-source $TOP_DIR/exerciserc
-
-# If nova api is not enabled we exit with exitcode 55 so that
-# the exercise is skipped
-is_service_enabled n-api || exit 55
-
-
-# Testing Security Groups
-# =======================
-
-# List security groups
-nova secgroup-list
-
-# Create random name for new sec group and create secgroup of said name
-SEC_GROUP_NAME="ex-secgroup-$(openssl rand -hex 4)"
-nova secgroup-create $SEC_GROUP_NAME 'a test security group'
-
-# Add some rules to the secgroup
-RULES_TO_ADD=( 22 3389 5900 )
-
-for RULE in "${RULES_TO_ADD[@]}"; do
-    nova secgroup-add-rule $SEC_GROUP_NAME tcp $RULE $RULE 0.0.0.0/0
-done
-
-# Check to make sure rules were added
-SEC_GROUP_RULES=( $(nova secgroup-list-rules $SEC_GROUP_NAME | grep -v \- | grep -v 'Source Group' | cut -d '|' -f3 | tr -d ' ') )
-die_if_not_set $LINENO SEC_GROUP_RULES "Failure retrieving SEC_GROUP_RULES for $SEC_GROUP_NAME"
-for i in "${RULES_TO_ADD[@]}"; do
-    skip=
-    for j in "${SEC_GROUP_RULES[@]}"; do
-        [[ $i == $j ]] && { skip=1; break; }
-    done
-    [[ -n $skip ]] || exit 1
-done
-
-# Delete rules and secgroup
-for RULE in "${RULES_TO_ADD[@]}"; do
-    nova secgroup-delete-rule $SEC_GROUP_NAME tcp $RULE $RULE 0.0.0.0/0
-done
-
-# Delete secgroup
-nova secgroup-delete $SEC_GROUP_NAME || \
-    die $LINENO "Failure deleting security group $SEC_GROUP_NAME"
-
-set +o xtrace
-echo "*********************************************************************"
-echo "SUCCESS: End DevStack Exercise: $0"
-echo "*********************************************************************"
diff --git a/exercises/swift.sh b/exercises/swift.sh
deleted file mode 100755
index 8aa376b..0000000
--- a/exercises/swift.sh
+++ /dev/null
@@ -1,69 +0,0 @@
-#!/usr/bin/env bash
-
-# **swift.sh**
-
-# Test swift via the ``python-openstackclient`` command line
-
-echo "*********************************************************************"
-echo "Begin DevStack Exercise: $0"
-echo "*********************************************************************"
-
-# This script exits on an error so that errors don't compound and you see
-# only the first error that occurred.
-set -o errexit
-
-# Print the commands being run so that we can see the command that triggers
-# an error.  It is also useful for following as the install occurs.
-set -o xtrace
-
-
-# Settings
-# ========
-
-# Keep track of the current directory
-EXERCISE_DIR=$(cd $(dirname "$0") && pwd)
-TOP_DIR=$(cd $EXERCISE_DIR/..; pwd)
-
-# Import common functions
-source $TOP_DIR/functions
-
-# Import configuration
-source $TOP_DIR/openrc
-
-# Import exercise configuration
-source $TOP_DIR/exerciserc
-
-# If swift is not enabled we exit with exitcode 55 which mean
-# exercise is skipped.
-is_service_enabled s-proxy || exit 55
-
-# Container name
-CONTAINER=ex-swift
-OBJECT=/etc/issue
-
-
-# Testing Swift
-# =============
-
-# Check if we have to swift via keystone
-openstack object store account show || die $LINENO "Failure getting account status"
-
-# We start by creating a test container
-openstack container create $CONTAINER || die $LINENO "Failure creating container $CONTAINER"
-
-# add a file into it.
-openstack object create $CONTAINER $OBJECT || die $LINENO "Failure uploading file to container $CONTAINER"
-
-# list the objects
-openstack object list $CONTAINER || die $LINENO "Failure listing contents of container $CONTAINER"
-
-# delete the object first
-openstack object delete $CONTAINER $OBJECT || die $LINENO "Failure deleting object $OBJECT in container $CONTAINER"
-
-# delete the container
-openstack container delete $CONTAINER || die $LINENO "Failure deleting container $CONTAINER"
-
-set +o xtrace
-echo "*********************************************************************"
-echo "SUCCESS: End DevStack Exercise: $0"
-echo "*********************************************************************"
diff --git a/exercises/volumes.sh b/exercises/volumes.sh
deleted file mode 100755
index e7c3560..0000000
--- a/exercises/volumes.sh
+++ /dev/null
@@ -1,225 +0,0 @@
-#!/usr/bin/env bash
-
-# **volumes.sh**
-
-# Test cinder volumes with the ``cinder`` command from ``python-cinderclient``
-
-echo "*********************************************************************"
-echo "Begin DevStack Exercise: $0"
-echo "*********************************************************************"
-
-# This script exits on an error so that errors don't compound and you see
-# only the first error that occurred.
-set -o errexit
-
-# Print the commands being run so that we can see the command that triggers
-# an error.  It is also useful for following as the install occurs.
-set -o xtrace
-
-
-# Settings
-# ========
-
-# Keep track of the current directory
-EXERCISE_DIR=$(cd $(dirname "$0") && pwd)
-TOP_DIR=$(cd $EXERCISE_DIR/..; pwd)
-
-# Import common functions
-source $TOP_DIR/functions
-
-# Import configuration
-source $TOP_DIR/openrc
-
-# Import project functions
-source $TOP_DIR/lib/cinder
-source $TOP_DIR/lib/neutron
-source $TOP_DIR/lib/neutron-legacy
-
-# Import exercise configuration
-source $TOP_DIR/exerciserc
-
-# If cinder is not enabled we exit with exitcode 55 which mean
-# exercise is skipped.
-is_service_enabled cinder || exit 55
-
-# Ironic does not currently support volume attachment.
-[ "$VIRT_DRIVER" == "ironic" ] && exit 55
-
-# Instance type to create
-DEFAULT_INSTANCE_TYPE=${DEFAULT_INSTANCE_TYPE:-m1.tiny}
-
-# Boot this image, use first AMI image if unset
-DEFAULT_IMAGE_NAME=${DEFAULT_IMAGE_NAME:-ami}
-
-# Security group name
-SECGROUP=${SECGROUP:-vol_secgroup}
-
-# Instance and volume names
-VM_NAME=${VM_NAME:-ex-vol-inst}
-VOL_NAME="ex-vol-$(openssl rand -hex 4)"
-
-
-# Launching a server
-# ==================
-
-# List servers for tenant:
-nova list
-
-# Images
-# ------
-
-# List the images available
-openstack image list
-
-# Grab the id of the image to launch
-IMAGE=$(openstack image list | egrep " $DEFAULT_IMAGE_NAME " | get_field 1)
-die_if_not_set $LINENO IMAGE "Failure getting image $DEFAULT_IMAGE_NAME"
-
-# Security Groups
-# ---------------
-
-# List security groups
-nova secgroup-list
-
-if is_service_enabled n-cell; then
-    # Cells does not support security groups, so force the use of "default"
-    SECGROUP="default"
-    echo "Using the default security group because of Cells."
-else
-    # Create a secgroup
-    if ! nova secgroup-list | grep -q $SECGROUP; then
-        nova secgroup-create $SECGROUP "$SECGROUP description"
-        if ! timeout $ASSOCIATE_TIMEOUT sh -c "while ! nova secgroup-list | grep -q $SECGROUP; do sleep 1; done"; then
-            echo "Security group not created"
-            exit 1
-        fi
-    fi
-fi
-
-# Configure Security Group Rules
-if ! nova secgroup-list-rules $SECGROUP | grep -q icmp; then
-    nova secgroup-add-rule $SECGROUP icmp -1 -1 0.0.0.0/0
-fi
-if ! nova secgroup-list-rules $SECGROUP | grep -q " tcp .* 22 "; then
-    nova secgroup-add-rule $SECGROUP tcp 22 22 0.0.0.0/0
-fi
-
-# List secgroup rules
-nova secgroup-list-rules $SECGROUP
-
-# Set up instance
-# ---------------
-
-# List flavors
-nova flavor-list
-
-# Select a flavor
-INSTANCE_TYPE=$(nova flavor-list | grep $DEFAULT_INSTANCE_TYPE | get_field 1)
-if [[ -z "$INSTANCE_TYPE" ]]; then
-    # grab the first flavor in the list to launch if default doesn't exist
-    INSTANCE_TYPE=$(nova flavor-list | head -n 4 | tail -n 1 | get_field 1)
-    die_if_not_set $LINENO INSTANCE_TYPE "Failure retrieving INSTANCE_TYPE"
-fi
-
-# Clean-up from previous runs
-nova delete $VM_NAME || true
-if ! timeout $ACTIVE_TIMEOUT sh -c "while nova show $VM_NAME; do sleep 1; done"; then
-    die $LINENO "server didn't terminate!"
-fi
-
-# Boot instance
-# -------------
-
-VM_UUID=$(nova boot --flavor $INSTANCE_TYPE --image $IMAGE --security-groups=$SECGROUP $VM_NAME | grep ' id ' | get_field 2)
-die_if_not_set $LINENO VM_UUID "Failure launching $VM_NAME"
-
-# Check that the status is active within ACTIVE_TIMEOUT seconds
-if ! timeout $ACTIVE_TIMEOUT sh -c "while ! nova show $VM_UUID | grep status | grep -q ACTIVE; do sleep 1; done"; then
-    die $LINENO "server didn't become active!"
-fi
-
-# Get the instance IP
-IP=$(get_instance_ip $VM_UUID $PRIVATE_NETWORK_NAME)
-
-die_if_not_set $LINENO IP "Failure retrieving IP address"
-
-# Private IPs can be pinged in single node deployments
-ping_check $IP $BOOT_TIMEOUT "$PRIVATE_NETWORK_NAME"
-
-# Volumes
-# -------
-
-# Verify it doesn't exist
-if [[ -n $(cinder list | grep $VOL_NAME | head -1 | get_field 2) ]]; then
-    die $LINENO "Volume $VOL_NAME already exists"
-fi
-
-# Create a new volume
-start_time=$(date +%s)
-cinder create --display-name $VOL_NAME --display-description "test volume: $VOL_NAME" $DEFAULT_VOLUME_SIZE || \
-    die $LINENO "Failure creating volume $VOL_NAME"
-if ! timeout $ACTIVE_TIMEOUT sh -c "while ! cinder list | grep $VOL_NAME | grep available; do sleep 1; done"; then
-    die $LINENO "Volume $VOL_NAME not created"
-fi
-end_time=$(date +%s)
-echo "Completed cinder create in $((end_time - start_time)) seconds"
-
-# Get volume ID
-VOL_ID=$(cinder list | grep $VOL_NAME | head -1 | get_field 1)
-die_if_not_set $LINENO VOL_ID "Failure retrieving volume ID for $VOL_NAME"
-
-# Attach to server
-DEVICE=/dev/vdb
-start_time=$(date +%s)
-nova volume-attach $VM_UUID $VOL_ID $DEVICE || \
-    die $LINENO "Failure attaching volume $VOL_NAME to $VM_NAME"
-if ! timeout $ACTIVE_TIMEOUT sh -c "while ! cinder list | grep $VOL_NAME | grep in-use; do sleep 1; done"; then
-    die $LINENO "Volume $VOL_NAME not attached to $VM_NAME"
-fi
-end_time=$(date +%s)
-echo "Completed volume-attach in $((end_time - start_time)) seconds"
-
-VOL_ATTACH=$(cinder list | grep $VOL_NAME | head -1 | get_field -1)
-die_if_not_set $LINENO VOL_ATTACH "Failure retrieving $VOL_NAME status"
-if [[ "$VOL_ATTACH" != $VM_UUID ]]; then
-    die $LINENO "Volume not attached to correct instance"
-fi
-
-# Clean up
-# --------
-
-# Detach volume
-start_time=$(date +%s)
-nova volume-detach $VM_UUID $VOL_ID || die $LINENO "Failure detaching volume $VOL_NAME from $VM_NAME"
-if ! timeout $ACTIVE_TIMEOUT sh -c "while ! cinder list | grep $VOL_NAME | grep available; do sleep 1; done"; then
-    die $LINENO "Volume $VOL_NAME not detached from $VM_NAME"
-fi
-end_time=$(date +%s)
-echo "Completed volume-detach in $((end_time - start_time)) seconds"
-
-# Delete volume
-start_time=$(date +%s)
-cinder delete $VOL_ID || die $LINENO "Failure deleting volume $VOL_NAME"
-if ! timeout $ACTIVE_TIMEOUT sh -c "while cinder list | grep $VOL_NAME; do sleep 1; done"; then
-    die $LINENO "Volume $VOL_NAME not deleted"
-fi
-end_time=$(date +%s)
-echo "Completed cinder delete in $((end_time - start_time)) seconds"
-
-# Delete instance
-nova delete $VM_UUID || die $LINENO "Failure deleting instance $VM_NAME"
-if ! timeout $TERMINATE_TIMEOUT sh -c "while nova list | grep -q $VM_UUID; do sleep 1; done"; then
-    die $LINENO "Server $VM_NAME not deleted"
-fi
-
-if [[ $SECGROUP = "default" ]] ; then
-    echo "Skipping deleting default security group"
-else
-    # Delete secgroup
-    nova secgroup-delete $SECGROUP || die $LINENO "Failure deleting security group $SECGROUP"
-fi
-
-set +o xtrace
-echo "*********************************************************************"
-echo "SUCCESS: End DevStack Exercise: $0"
-echo "*********************************************************************"
diff --git a/files/apache-neutron.template b/files/apache-neutron.template
new file mode 100644
index 0000000..c7796b9
--- /dev/null
+++ b/files/apache-neutron.template
@@ -0,0 +1,36 @@
+Listen %PUBLICPORT%
+LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\" %D(us)" neutron_combined
+
+<Directory %NEUTRON_BIN%>
+    Require all granted
+</Directory>
+
+<VirtualHost *:%PUBLICPORT%>
+    WSGIDaemonProcess neutron-server processes=%APIWORKERS% threads=1 user=%USER% display-name=%{GROUP} %VIRTUALENV%
+    WSGIProcessGroup neutron-server
+    WSGIScriptAlias / %NEUTRON_BIN%/neutron-api
+    WSGIApplicationGroup %{GLOBAL}
+    WSGIPassAuthorization On
+    ErrorLogFormat "%M"
+    ErrorLog /var/log/%APACHE_NAME%/neutron.log
+    CustomLog /var/log/%APACHE_NAME%/neutron_access.log neutron_combined
+    %SSLENGINE%
+    %SSLCERTFILE%
+    %SSLKEYFILE%
+</VirtualHost>
+
+
+%SSLLISTEN%<VirtualHost *:443>
+%SSLLISTEN%    %SSLENGINE%
+%SSLLISTEN%    %SSLCERTFILE%
+%SSLLISTEN%    %SSLKEYFILE%
+%SSLLISTEN%</VirtualHost>
+
+Alias /networking %NEUTRON_BIN%/neutron-api
+<Location /networking>
+    SetHandler wsgi-script
+    Options +ExecCGI
+    WSGIProcessGroup neutron-server
+    WSGIApplicationGroup %{GLOBAL}
+    WSGIPassAuthorization On
+</Location>
diff --git a/files/debs/n-cpu b/files/debs/n-cpu
index d8bbf59..636644f 100644
--- a/files/debs/n-cpu
+++ b/files/debs/n-cpu
@@ -1,4 +1,5 @@
 cryptsetup
+dosfstools
 genisoimage
 gir1.2-libosinfo-1.0
 lvm2 # NOPRIME
diff --git a/files/rpms-suse/general b/files/rpms-suse/general
index 0b69cb1..b870d72 100644
--- a/files/rpms-suse/general
+++ b/files/rpms-suse/general
@@ -11,7 +11,6 @@
 iputils
 libffi-devel  # pyOpenSSL
 libjpeg8-devel # Pillow 3.0.0
-libmysqlclient-devel # MySQL-python
 libopenssl-devel # to rebuild pyOpenSSL if needed
 libxslt-devel  # lxml
 lsof # useful when debugging
diff --git a/files/rpms-suse/n-cpu b/files/rpms-suse/n-cpu
index 9ece115..c11e9f0 100644
--- a/files/rpms-suse/n-cpu
+++ b/files/rpms-suse/n-cpu
@@ -1,7 +1,8 @@
 cryptsetup
-genisoimage
+dosfstools
 libosinfo
 lvm2
+mkisofs
 open-iscsi
 sg3_utils
 # Stuff for diablo volumes
diff --git a/files/rpms-suse/nova b/files/rpms-suse/nova
index ae115d2..4103a40 100644
--- a/files/rpms-suse/nova
+++ b/files/rpms-suse/nova
@@ -4,7 +4,6 @@
 dnsmasq-utils # dist:opensuse-12.3,opensuse-13.1
 ebtables
 gawk
-genisoimage # required for config_drive
 iptables
 iputils
 kpartx
@@ -12,6 +11,7 @@
 libvirt # NOPRIME
 libvirt-python # NOPRIME
 mariadb # NOPRIME
+mkisofs # required for config_drive
 parted
 polkit
 # qemu as fallback if kvm cannot be used
diff --git a/files/rpms/cinder b/files/rpms/cinder
index e6addc6..058c235 100644
--- a/files/rpms/cinder
+++ b/files/rpms/cinder
@@ -1,5 +1,5 @@
 iscsi-initiator-utils
 lvm2
 qemu-img
-scsi-target-utils # not:rhel7,f25,f26,f27 NOPRIME
-targetcli # dist:rhel7,f25,f26,f27 NOPRIME
+scsi-target-utils # not:rhel7,f25,f26,f27,f28 NOPRIME
+targetcli # dist:rhel7,f25,f26,f27,f28 NOPRIME
diff --git a/files/rpms/general b/files/rpms/general
index 5d9a4ad..c7863e4 100644
--- a/files/rpms/general
+++ b/files/rpms/general
@@ -9,9 +9,9 @@
 graphviz # needed only for docs
 httpd
 httpd-devel
-iptables-services  # NOPRIME f25,f26,f27
+iptables-services  # NOPRIME f25,f26,f27,f28
 java-1.7.0-openjdk-headless  # NOPRIME rhel7
-java-1.8.0-openjdk-headless  # NOPRIME f25,f26,f27
+java-1.8.0-openjdk-headless  # NOPRIME f25,f26,f27,f28
 libffi-devel
 libjpeg-turbo-devel # Pillow 3.0.0
 libxml2-devel # lxml
diff --git a/files/rpms/horizon b/files/rpms/horizon
index fa5601a..a88552b 100644
--- a/files/rpms/horizon
+++ b/files/rpms/horizon
@@ -1,4 +1,2 @@
-Django
 httpd # NOPRIME
 mod_wsgi  # NOPRIME
-pyxattr
diff --git a/files/rpms/n-cpu b/files/rpms/n-cpu
index 26c5ced..68e5472 100644
--- a/files/rpms/n-cpu
+++ b/files/rpms/n-cpu
@@ -1,4 +1,5 @@
 cryptsetup
+dosfstools
 genisoimage
 iscsi-initiator-utils
 libosinfo
diff --git a/files/rpms/nova b/files/rpms/nova
index 9fb7282..4140cd7 100644
--- a/files/rpms/nova
+++ b/files/rpms/nova
@@ -7,7 +7,7 @@
 genisoimage # required for config_drive
 iptables
 iputils
-kernel-modules # dist:f25,f26,f27
+kernel-modules # dist:f25,f26,f27,f28
 kpartx
 libxml2-python
 m2crypto
diff --git a/files/rpms/swift b/files/rpms/swift
index be0db14..f2f5de6 100644
--- a/files/rpms/swift
+++ b/files/rpms/swift
@@ -2,7 +2,7 @@
 liberasurecode-devel
 memcached
 pyxattr
-rsync-daemon # dist:f25,f26,f27
+rsync-daemon # dist:f25,f26,f27,f28
 sqlite
 xfsprogs
 xinetd
diff --git a/functions b/functions
index 24994c0..f63595d 100644
--- a/functions
+++ b/functions
@@ -441,7 +441,12 @@
 function wait_for_compute {
     local timeout=$1
     local rval=0
+    local compute_hostname
     time_start "wait_for_service"
+    compute_hostname=$(iniget $NOVA_CONF DEFAULT host)
+    if [[ -z $compute_hostname ]]; then
+        compute_hostname=$(hostname)
+    fi
     timeout $timeout bash -x <<EOF || rval=$?
         ID=""
         while [[ "\$ID" == "" ]]; do
@@ -450,7 +455,7 @@
                 # When using the fake driver the compute hostnames have a suffix of 1 to NUMBER_FAKE_NOVA_COMPUTE
                 ID=\$(openstack --os-cloud devstack-admin --os-region "$REGION_NAME" compute service list --host `hostname`1 --service nova-compute -c ID -f value)
             else
-                ID=\$(openstack --os-cloud devstack-admin --os-region "$REGION_NAME" compute service list --host `hostname` --service nova-compute -c ID -f value)
+                ID=\$(openstack --os-cloud devstack-admin --os-region "$REGION_NAME" compute service list --host "$compute_hostname" --service nova-compute -c ID -f value)
             fi
         done
 EOF
diff --git a/functions-common b/functions-common
index b1b0995..fae936a 100644
--- a/functions-common
+++ b/functions-common
@@ -2052,6 +2052,11 @@
     fi
 }
 
+# Remove "[]" around urlquoted IPv6 addresses
+function ipv6_unquote {
+    echo $1 | tr -d []
+}
+
 # Gracefully cp only if source file/dir exists
 # cp_it source destination
 function cp_it {
@@ -2128,7 +2133,7 @@
 function python3_version {
     local python3_version
     python3_version=$(_get_python_version python3)
-    echo "python${python_version}"
+    echo "python${python3_version}"
 }
 
 
diff --git a/inc/ini-config b/inc/ini-config
index 68d48d1..6fe7788 100644
--- a/inc/ini-config
+++ b/inc/ini-config
@@ -200,7 +200,7 @@
         local sep
         sep=$(echo -ne "\x01")
         # Replace it
-        $sudo sed -i -e '/^\['${section}'\]/,/^\[.*\]/ s'${sep}'^\('${option}'[ \t]*=[ \t]*\).*$'${sep}'\1'"${value}"${sep} "$file"
+        $sudo sed -i -e '/^\['${section}'\]/,/^\[.*\]/ s'${sep}'^\('"${option}"'[ \t]*=[ \t]*\).*$'${sep}'\1'"${value}"${sep} "$file"
     fi
     $xtrace
 }
diff --git a/inc/python b/inc/python
index e074ea4..96be107 100644
--- a/inc/python
+++ b/inc/python
@@ -411,12 +411,6 @@
 function lib_installed_from_git {
     local name=$1
     local safe_name
-    # TODO(mordred) This is a special case for python-openstacksdk, where the
-    # repo name and the pip name do not match. We should either add systemic
-    # support for providing aliases, or we should rename the git repo.
-    if [[ $name == 'python-openstacksdk' ]] ; then
-        name=openstacksdk
-    fi
     safe_name=$(python -c "from pkg_resources import safe_name; \
         print(safe_name('${name}'))")
     # Note "pip freeze" doesn't always work here, because it tries to
@@ -435,22 +429,6 @@
     [[ -n $(pip list --format=columns 2>/dev/null | awk "/^$safe_name/ {print \$3}") ]]
 }
 
-# check that everything that's in LIBS_FROM_GIT was actually installed
-# correctly, this helps double check issues with library fat fingering.
-function check_libs_from_git {
-    local lib=""
-    local not_installed=""
-    for lib in $(echo ${LIBS_FROM_GIT} | tr "," " "); do
-        if ! lib_installed_from_git "$lib"; then
-            not_installed+=" $lib"
-        fi
-    done
-    # if anything is not installed, say what it is.
-    if [[ -n "$not_installed" ]]; then
-        die $LINENO "The following LIBS_FROM_GIT were not installed correct: $not_installed"
-    fi
-}
-
 # setup a library by name. If we are trying to use the library from
 # git, we'll do a git based install, otherwise we'll punt and the
 # library should be installed by a requirements pull from another
@@ -561,6 +539,13 @@
 
     setup_package $project_dir "$flags" $extras
 
+    # If this project is in LIBS_FROM_GIT, verify it was actually installed
+    # correctly.  This helps catch errors caused by constraints mismatches.
+    if use_library_from_git "$project_dir"; then
+        if ! lib_installed_from_git "$project_dir"; then
+            die $LINENO "The following LIBS_FROM_GIT was not installed correctly: $project_dir"
+        fi
+    fi
 }
 
 # ``pip install -e`` the package, which processes the dependencies
diff --git a/lib/cinder b/lib/cinder
index 3a8097f..664f423 100644
--- a/lib/cinder
+++ b/lib/cinder
@@ -67,7 +67,7 @@
 CINDER_SERVICE_PORT=${CINDER_SERVICE_PORT:-8776}
 CINDER_SERVICE_PORT_INT=${CINDER_SERVICE_PORT_INT:-18776}
 CINDER_SERVICE_PROTOCOL=${CINDER_SERVICE_PROTOCOL:-$SERVICE_PROTOCOL}
-CINDER_SERVICE_LISTEN_ADDRESS=${CINDER_SERVICE_LISTEN_ADDRESS:-$SERVICE_LISTEN_ADDRESS}
+CINDER_SERVICE_LISTEN_ADDRESS=${CINDER_SERVICE_LISTEN_ADDRESS:-$(ipv6_unquote $SERVICE_LISTEN_ADDRESS)}
 
 # What type of LVM device should Cinder use for LVM backend
 # Defaults to auto, which will do thin provisioning if it's a fresh
@@ -268,7 +268,12 @@
         configure_cinder_image_volume_cache
     fi
 
-    if is_service_enabled swift; then
+    if is_service_enabled c-bak; then
+        # NOTE(mriedem): The default backup driver uses swift and if we're
+        # on a subnode we might not know if swift is enabled, but chances are
+        # good that it is on the controller so configure the backup service
+        # to use it. If we want to configure the backup service to use
+        # a non-swift driver, we'll likely need environment variables.
         iniset $CINDER_CONF DEFAULT backup_swift_url "$SWIFT_SERVICE_PROTOCOL://$SERVICE_HOST:$SWIFT_DEFAULT_BIND_PORT/v1/AUTH_"
     fi
 
@@ -349,7 +354,7 @@
             get_or_create_endpoint \
                 "block-storage" \
                 "$REGION_NAME" \
-                "$CINDER_SERVICE_PROTOCOL://$CINDER_SERVICE_HOST:$CINDER_SERVICE_PORT/"
+                "$CINDER_SERVICE_PROTOCOL://$CINDER_SERVICE_HOST:$CINDER_SERVICE_PORT/v3/\$(project_id)s"
 
             get_or_create_endpoint \
                 "volume" \
@@ -371,7 +376,7 @@
             get_or_create_endpoint \
                 "block-storage" \
                 "$REGION_NAME" \
-                "$CINDER_SERVICE_PROTOCOL://$CINDER_SERVICE_HOST/volume/"
+                "$CINDER_SERVICE_PROTOCOL://$CINDER_SERVICE_HOST/volume/v3/\$(project_id)s"
 
             get_or_create_endpoint \
                 "volume" \
diff --git a/lib/databases/mysql b/lib/databases/mysql
index 0089663..cf61056 100644
--- a/lib/databases/mysql
+++ b/lib/databases/mysql
@@ -93,7 +93,7 @@
 
     # Change bind-address from localhost (127.0.0.1) to any (::) and
     # set default db type to InnoDB
-    iniset -sudo $my_conf mysqld bind-address "$SERVICE_LISTEN_ADDRESS"
+    iniset -sudo $my_conf mysqld bind-address "$(ipv6_unquote $SERVICE_LISTEN_ADDRESS)"
     iniset -sudo $my_conf mysqld sql_mode TRADITIONAL
     iniset -sudo $my_conf mysqld default-storage-engine InnoDB
     iniset -sudo $my_conf mysqld max_connections 1024
diff --git a/lib/glance b/lib/glance
index 95d2450..94f6a22 100644
--- a/lib/glance
+++ b/lib/glance
@@ -65,7 +65,7 @@
 
 # Glance connection info.  Note the port must be specified.
 GLANCE_SERVICE_HOST=${GLANCE_SERVICE_HOST:-$SERVICE_HOST}
-GLANCE_SERVICE_LISTEN_ADDRESS=${GLANCE_SERVICE_LISTEN_ADDRESS:-$SERVICE_LISTEN_ADDRESS}
+GLANCE_SERVICE_LISTEN_ADDRESS=${GLANCE_SERVICE_LISTEN_ADDRESS:-$(ipv6_unquote $SERVICE_LISTEN_ADDRESS)}
 GLANCE_SERVICE_PORT=${GLANCE_SERVICE_PORT:-9292}
 GLANCE_SERVICE_PORT_INT=${GLANCE_SERVICE_PORT_INT:-19292}
 GLANCE_HOSTPORT=${GLANCE_HOSTPORT:-$GLANCE_SERVICE_HOST:$GLANCE_SERVICE_PORT}
@@ -111,11 +111,10 @@
     # Server is configured through this function and not init_glance.
     create_glance_cache_dir
 
-    # Copy over our glance configurations and update them
-    cp $GLANCE_DIR/etc/glance-registry.conf $GLANCE_REGISTRY_CONF
+    # Set non-default configuration options for registry
     iniset $GLANCE_REGISTRY_CONF DEFAULT debug $ENABLE_DEBUG_LOG_LEVEL
     iniset $GLANCE_REGISTRY_CONF DEFAULT bind_host $GLANCE_SERVICE_LISTEN_ADDRESS
-    inicomment $GLANCE_REGISTRY_CONF DEFAULT log_file
+    iniset $GLANCE_REGISTRY_CONF DEFAULT workers $API_WORKERS
     local dburl
     dburl=`database_connection_url glance`
     iniset $GLANCE_REGISTRY_CONF database connection $dburl
@@ -126,8 +125,8 @@
     iniset_rpc_backend glance $GLANCE_REGISTRY_CONF
     iniset $GLANCE_REGISTRY_CONF DEFAULT graceful_shutdown_timeout "$SERVICE_GRACEFUL_SHUTDOWN_TIMEOUT"
 
+    # Set non-default configuration options for the API server
     iniset $GLANCE_API_CONF DEFAULT debug $ENABLE_DEBUG_LOG_LEVEL
-    inicomment $GLANCE_API_CONF DEFAULT log_file
     iniset $GLANCE_API_CONF database connection $dburl
     iniset $GLANCE_API_CONF DEFAULT use_syslog $SYSLOG
     iniset $GLANCE_API_CONF DEFAULT image_cache_dir $GLANCE_CACHE_DIR/
@@ -152,7 +151,7 @@
 
     # Store specific configs
     iniset $GLANCE_API_CONF glance_store filesystem_store_datadir $GLANCE_IMAGE_DIR/
-    iniset $GLANCE_API_CONF DEFAULT registry_host $GLANCE_SERVICE_HOST
+    iniset $GLANCE_API_CONF DEFAULT registry_host $(ipv6_unquote $GLANCE_SERVICE_HOST)
 
     # CORS feature support - to allow calls from Horizon by default
     if [ -n "$GLANCE_CORS_ALLOWED_ORIGIN" ]; then
@@ -185,11 +184,6 @@
             iniset $GLANCE_SWIFT_STORE_CONF ref1 auth_address $KEYSTONE_SERVICE_URI/v3
         fi
         iniset $GLANCE_SWIFT_STORE_CONF ref1 auth_version 3
-
-        # commenting is not strictly necessary but it's confusing to have bad values in conf
-        inicomment $GLANCE_API_CONF glance_store swift_store_user
-        inicomment $GLANCE_API_CONF glance_store swift_store_key
-        inicomment $GLANCE_API_CONF glance_store swift_store_auth_address
     fi
 
     # We need to tell glance what it's public endpoint is so that the version
@@ -215,20 +209,15 @@
     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
 
-    cp $GLANCE_DIR/etc/glance-cache.conf $GLANCE_CACHE_CONF
+    # Set non-default configuration options for the glance-cache
     iniset $GLANCE_CACHE_CONF DEFAULT debug $ENABLE_DEBUG_LOG_LEVEL
-    inicomment $GLANCE_CACHE_CONF DEFAULT log_file
     iniset $GLANCE_CACHE_CONF DEFAULT use_syslog $SYSLOG
     iniset $GLANCE_CACHE_CONF DEFAULT image_cache_dir $GLANCE_CACHE_DIR/
-    iniuncomment $GLANCE_CACHE_CONF DEFAULT auth_url
     iniset $GLANCE_CACHE_CONF DEFAULT auth_url $KEYSTONE_AUTH_URI
-    iniuncomment $GLANCE_CACHE_CONF DEFAULT auth_tenant_name
     iniset $GLANCE_CACHE_CONF DEFAULT admin_tenant_name $SERVICE_PROJECT_NAME
-    iniuncomment $GLANCE_CACHE_CONF DEFAULT auth_user
     iniset $GLANCE_CACHE_CONF DEFAULT admin_user glance
-    iniuncomment $GLANCE_CACHE_CONF DEFAULT auth_password
     iniset $GLANCE_CACHE_CONF DEFAULT admin_password $SERVICE_PASSWORD
-    iniset $GLANCE_CACHE_CONF DEFAULT registry_host $GLANCE_SERVICE_HOST
+    iniset $GLANCE_CACHE_CONF DEFAULT registry_host $(ipv6_unquote $GLANCE_SERVICE_HOST)
 
     # Store specific confs
     iniset $GLANCE_CACHE_CONF glance_store filesystem_store_datadir $GLANCE_IMAGE_DIR/
diff --git a/lib/horizon b/lib/horizon
index fab41bb..293a627 100644
--- a/lib/horizon
+++ b/lib/horizon
@@ -87,7 +87,7 @@
     _horizon_config_set $local_settings "" WEBROOT \"$HORIZON_APACHE_ROOT/\"
 
     _horizon_config_set $local_settings "" COMPRESS_OFFLINE True
-    _horizon_config_set $local_settings "" OPENSTACK_KEYSTONE_DEFAULT_ROLE \"Member\"
+    _horizon_config_set $local_settings "" OPENSTACK_KEYSTONE_DEFAULT_ROLE \"member\"
 
     _horizon_config_set $local_settings "" OPENSTACK_HOST \"${KEYSTONE_SERVICE_HOST}\"
 
diff --git a/lib/keystone b/lib/keystone
index 714f089..02e2822 100644
--- a/lib/keystone
+++ b/lib/keystone
@@ -49,7 +49,6 @@
 
 KEYSTONE_CONF_DIR=${KEYSTONE_CONF_DIR:-/etc/keystone}
 KEYSTONE_CONF=$KEYSTONE_CONF_DIR/keystone.conf
-KEYSTONE_PASTE_INI=${KEYSTONE_PASTE_INI:-$KEYSTONE_CONF_DIR/keystone-paste.ini}
 KEYSTONE_PUBLIC_UWSGI_CONF=$KEYSTONE_CONF_DIR/keystone-uwsgi-public.ini
 KEYSTONE_ADMIN_UWSGI_CONF=$KEYSTONE_CONF_DIR/keystone-uwsgi-admin.ini
 KEYSTONE_PUBLIC_UWSGI=$KEYSTONE_BIN_DIR/keystone-wsgi-public
@@ -64,9 +63,6 @@
     KEYSTONE_DEPLOY=mod_wsgi
 fi
 
-# Select the token persistence backend driver
-KEYSTONE_TOKEN_BACKEND=${KEYSTONE_TOKEN_BACKEND:-sql}
-
 # Select the Identity backend driver
 KEYSTONE_IDENTITY_BACKEND=${KEYSTONE_IDENTITY_BACKEND:-sql}
 
@@ -80,7 +76,8 @@
 KEYSTONE_RESOURCE_BACKEND=${KEYSTONE_RESOURCE_BACKEND:-sql}
 
 # Select Keystone's token provider (and format)
-# Choose from 'uuid', 'pki', 'pkiz', or 'fernet'
+# Refer keystone doc for supported token provider:
+# https://docs.openstack.org/keystone/latest/admin/token-provider.html
 KEYSTONE_TOKEN_FORMAT=${KEYSTONE_TOKEN_FORMAT:-fernet}
 KEYSTONE_TOKEN_FORMAT=$(echo ${KEYSTONE_TOKEN_FORMAT} | tr '[:upper:]' '[:lower:]')
 
@@ -202,25 +199,9 @@
     sudo install -d -o $STACK_USER $KEYSTONE_CONF_DIR
 
     if [[ "$KEYSTONE_CONF_DIR" != "$KEYSTONE_DIR/etc" ]]; then
-        install -m 600 $KEYSTONE_DIR/etc/keystone.conf.sample $KEYSTONE_CONF
-        if [[ -f "$KEYSTONE_DIR/etc/keystone-paste.ini" ]]; then
-            cp -p "$KEYSTONE_DIR/etc/keystone-paste.ini" "$KEYSTONE_PASTE_INI"
-        fi
+        install -m 600 /dev/null $KEYSTONE_CONF
     fi
-    if [[ -f "$KEYSTONE_PASTE_INI" ]]; then
-        iniset "$KEYSTONE_CONF" paste_deploy config_file "$KEYSTONE_PASTE_INI"
-    else
-        # compatibility with mixed cfg and paste.deploy configuration
-        KEYSTONE_PASTE_INI="$KEYSTONE_CONF"
-    fi
-
-    if [ "$ENABLE_IDENTITY_V2" == "False" ]; then
-        # Only Identity v3 API should be available; then disable v2 pipelines
-        inidelete $KEYSTONE_PASTE_INI composite:main \\/v2.0
-        inidelete $KEYSTONE_PASTE_INI composite:admin \\/v2.0
-    fi
-
-    # Rewrite stock ``keystone.conf``
+    # Populate ``keystone.conf``
     if is_service_enabled ldap; then
         iniset $KEYSTONE_CONF identity domain_config_dir "$KEYSTONE_CONF_DIR/domains"
         iniset $KEYSTONE_CONF identity domain_specific_drivers_enabled "True"
@@ -236,7 +217,7 @@
     iniset $KEYSTONE_CONF cache backend "dogpile.cache.memcached"
     iniset $KEYSTONE_CONF cache memcache_servers localhost:11211
 
-    iniset_rpc_backend keystone $KEYSTONE_CONF
+    iniset_rpc_backend keystone $KEYSTONE_CONF oslo_messaging_notifications
 
     local service_port=$KEYSTONE_SERVICE_PORT
     local auth_port=$KEYSTONE_AUTH_PORT
@@ -262,8 +243,6 @@
 
     iniset $KEYSTONE_CONF database connection `database_connection_url keystone`
 
-    iniset $KEYSTONE_CONF token driver "$KEYSTONE_TOKEN_BACKEND"
-
     # Set up logging
     if [ "$SYSLOG" != "False" ]; then
         iniset $KEYSTONE_CONF DEFAULT use_syslog "True"
@@ -309,30 +288,32 @@
 # service              --         --
 # --                   --         service
 # --                   --         ResellerAdmin
-# --                   --         Member
+# --                   --         member
 # demo                 admin      admin
-# demo                 demo       Member, anotherrole
+# demo                 demo       member, anotherrole
 # alt_demo             admin      admin
-# alt_demo             alt_demo   Member, anotherrole
-# invisible_to_admin   demo       Member
+# alt_demo             alt_demo   member, anotherrole
+# invisible_to_admin   demo       member
 
 # Group                Users            Roles                 Project
 # ------------------------------------------------------------------
 # admins               admin            admin                 admin
-# nonadmins            demo, alt_demo   Member, anotherrole   demo, alt_demo
+# nonadmins            demo, alt_demo   member, anotherrole   demo, alt_demo
 
 
 # Migrated from keystone_data.sh
 function create_keystone_accounts {
 
-    # The keystone bootstrapping process (performed via keystone-manage bootstrap)
-    # creates an admin user, admin role and admin project. As a sanity check
-    # we exercise the CLI to retrieve the IDs for these values.
+    # The keystone bootstrapping process (performed via keystone-manage
+    # bootstrap) creates an admin user, admin role, member role, and admin
+    # project. As a sanity check we exercise the CLI to retrieve the IDs for
+    # these values.
     local admin_project
     admin_project=$(openstack project show "admin" -f value -c id)
     local admin_user
     admin_user=$(openstack user show "admin" -f value -c id)
     local admin_role="admin"
+    local member_role="member"
 
     get_or_add_user_domain_role $admin_role $admin_user default
 
@@ -349,17 +330,6 @@
     # role is also configurable in swift-proxy.conf
     get_or_create_role ResellerAdmin
 
-    # The Member role is used by Horizon and Swift so we need to keep it:
-    local member_role="member"
-
-    # Capital Member role is legacy hard coded in Horizon / Swift
-    # configs. Keep it around.
-    get_or_create_role "Member"
-
-    # The reality is that the rest of the roles listed below honestly
-    # should work by symbolic names.
-    get_or_create_role $member_role
-
     # 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="anotherrole"
@@ -469,11 +439,6 @@
     $KEYSTONE_BIN_DIR/keystone-manage --config-file $KEYSTONE_CONF db_sync
     time_stop "dbsync"
 
-    if [[ "$KEYSTONE_TOKEN_FORMAT" == "pki" || "$KEYSTONE_TOKEN_FORMAT" == "pkiz" ]]; then
-        # Set up certificates
-        rm -rf $KEYSTONE_CONF_DIR/ssl
-        $KEYSTONE_BIN_DIR/keystone-manage --config-file $KEYSTONE_CONF pki_setup
-    fi
     if [[ "$KEYSTONE_TOKEN_FORMAT" == "fernet" ]]; then
         rm -rf "$KEYSTONE_CONF_DIR/fernet-keys/"
         $KEYSTONE_BIN_DIR/keystone-manage --config-file $KEYSTONE_CONF fernet_setup
diff --git a/lib/libraries b/lib/libraries
index 6d52f64..b4f3c31 100644
--- a/lib/libraries
+++ b/lib/libraries
@@ -28,6 +28,7 @@
 GITDIR["cursive"]=$DEST/cursive
 GITDIR["debtcollector"]=$DEST/debtcollector
 GITDIR["futurist"]=$DEST/futurist
+GITDIR["openstacksdk"]=$DEST/openstacksdk
 GITDIR["os-client-config"]=$DEST/os-client-config
 GITDIR["osc-lib"]=$DEST/osc-lib
 GITDIR["osc-placement"]=$DEST/osc-placement
@@ -51,7 +52,6 @@
 GITDIR["oslo.vmware"]=$DEST/oslo.vmware
 GITDIR["osprofiler"]=$DEST/osprofiler
 GITDIR["pycadf"]=$DEST/pycadf
-GITDIR["python-openstacksdk"]=$DEST/python-openstacksdk
 GITDIR["stevedore"]=$DEST/stevedore
 GITDIR["taskflow"]=$DEST/taskflow
 GITDIR["tooz"]=$DEST/tooz
@@ -91,6 +91,7 @@
     _install_lib_from_source "cursive"
     _install_lib_from_source "debtcollector"
     _install_lib_from_source "futurist"
+    _install_lib_from_source "openstacksdk"
     _install_lib_from_source "osc-lib"
     _install_lib_from_source "osc-placement"
     _install_lib_from_source "os-client-config"
@@ -114,7 +115,6 @@
     _install_lib_from_source "oslo.vmware"
     _install_lib_from_source "osprofiler"
     _install_lib_from_source "pycadf"
-    _install_lib_from_source "python-openstacksdk"
     _install_lib_from_source "stevedore"
     _install_lib_from_source "taskflow"
     _install_lib_from_source "tooz"
diff --git a/lib/neutron b/lib/neutron
index cef8d1f..4847e87 100644
--- a/lib/neutron
+++ b/lib/neutron
@@ -28,6 +28,12 @@
 # Set up default directories
 GITDIR["python-neutronclient"]=$DEST/python-neutronclient
 
+# NEUTRON_DEPLOY_MOD_WSGI defines how neutron is deployed, allowed values:
+# - False (default) : Run neutron under Eventlet
+# - True : Run neutron under uwsgi
+# TODO(annp): Switching to uwsgi in next cycle if things turn out to be stable
+# enough
+NEUTRON_DEPLOY_MOD_WSGI=${NEUTRON_DEPLOY_MOD_WSGI:-False}
 NEUTRON_AGENT=${NEUTRON_AGENT:-openvswitch}
 NEUTRON_DIR=$DEST/neutron
 NEUTRON_AUTH_CACHE_DIR=${NEUTRON_AUTH_CACHE_DIR:-/var/cache/neutron}
@@ -58,6 +64,8 @@
 NEUTRON_STATE_PATH=${NEUTRON_STATE_PATH:=$DATA_DIR/neutron}
 NEUTRON_AUTH_CACHE_DIR=${NEUTRON_AUTH_CACHE_DIR:-/var/cache/neutron}
 
+NEUTRON_UWSGI_CONF=$NEUTRON_CONF_DIR/neutron-api-uwsgi.ini
+
 # By default, use the ML2 plugin
 NEUTRON_CORE_PLUGIN=${NEUTRON_CORE_PLUGIN:-ml2}
 NEUTRON_CORE_PLUGIN_CONF_FILENAME=${NEUTRON_CORE_PLUGIN_CONF_FILENAME:-ml2_conf.ini}
@@ -220,8 +228,8 @@
         if [[ $NEUTRON_AGENT == "linuxbridge" ]]; then
             iniset $NEUTRON_CORE_PLUGIN_CONF securitygroup firewall_driver iptables
             iniset $NEUTRON_CORE_PLUGIN_CONF vxlan local_ip $HOST_IP
-        else
-            iniset $NEUTRON_CORE_PLUGIN_CONF securitygroup firewall_driver iptables_hybrid
+        elif [[ $NEUTRON_AGENT == "openvswitch" ]]; then
+            iniset $NEUTRON_CORE_PLUGIN_CONF securitygroup firewall_driver openvswitch
             iniset $NEUTRON_CORE_PLUGIN_CONF ovs local_ip $HOST_IP
 
             if [[ "$NEUTRON_DISTRIBUTED_ROUTING" = "True" ]]; then
@@ -286,7 +294,7 @@
     # Format logging
     setup_logging $NEUTRON_CONF
 
-    if is_service_enabled tls-proxy; then
+    if is_service_enabled tls-proxy && [ "$NEUTRON_DEPLOY_MOD_WSGI" == "False" ]; then
         # Set the service port for a proxy to take the original
         iniset $NEUTRON_CONF DEFAULT bind_port "$NEUTRON_SERVICE_PORT_INT"
         iniset $NEUTRON_CONF oslo_middleware enable_proxy_headers_parsing True
@@ -325,25 +333,28 @@
 }
 
 # Make Neutron-required changes to nova.conf
+# Takes a single optional argument which is the config file to update,
+# if not passed $NOVA_CONF is used.
 function configure_neutron_nova_new {
-    iniset $NOVA_CONF DEFAULT use_neutron True
-    iniset $NOVA_CONF neutron auth_type "password"
-    iniset $NOVA_CONF neutron auth_url "$KEYSTONE_SERVICE_URI"
-    iniset $NOVA_CONF neutron username neutron
-    iniset $NOVA_CONF neutron password "$SERVICE_PASSWORD"
-    iniset $NOVA_CONF neutron user_domain_name "Default"
-    iniset $NOVA_CONF neutron project_name "$SERVICE_TENANT_NAME"
-    iniset $NOVA_CONF neutron project_domain_name "Default"
-    iniset $NOVA_CONF neutron auth_strategy $NEUTRON_AUTH_STRATEGY
-    iniset $NOVA_CONF neutron region_name "$REGION_NAME"
+    local conf=${1:-$NOVA_CONF}
+    iniset $conf DEFAULT use_neutron True
+    iniset $conf neutron auth_type "password"
+    iniset $conf neutron auth_url "$KEYSTONE_SERVICE_URI"
+    iniset $conf neutron username neutron
+    iniset $conf neutron password "$SERVICE_PASSWORD"
+    iniset $conf neutron user_domain_name "Default"
+    iniset $conf neutron project_name "$SERVICE_TENANT_NAME"
+    iniset $conf neutron project_domain_name "Default"
+    iniset $conf neutron auth_strategy $NEUTRON_AUTH_STRATEGY
+    iniset $conf neutron region_name "$REGION_NAME"
 
-    iniset $NOVA_CONF DEFAULT firewall_driver nova.virt.firewall.NoopFirewallDriver
+    iniset $conf DEFAULT firewall_driver nova.virt.firewall.NoopFirewallDriver
 
     # optionally set options in nova_conf
-    neutron_plugin_create_nova_conf
+    neutron_plugin_create_nova_conf $conf
 
     if is_service_enabled neutron-metadata-agent; then
-        iniset $NOVA_CONF neutron service_metadata_proxy "True"
+        iniset $conf neutron service_metadata_proxy "True"
     fi
 
 }
@@ -354,6 +365,15 @@
 
 # create_neutron_accounts() - Create required service accounts
 function create_neutron_accounts_new {
+    local neutron_url
+
+    if [ "$NEUTRON_DEPLOY_MOD_WSGI" == "True" ]; then
+        neutron_url=$NEUTRON_SERVICE_PROTOCOL://$NEUTRON_SERVICE_HOST/networking/
+    else
+        neutron_url=$NEUTRON_SERVICE_PROTOCOL://$NEUTRON_SERVICE_HOST:$NEUTRON_SERVICE_PORT/
+    fi
+
+
     if [[ "$ENABLED_SERVICES" =~ "neutron-api" ]]; then
 
         create_service_user "neutron"
@@ -361,8 +381,7 @@
         neutron_service=$(get_or_create_service "neutron" \
             "network" "Neutron Service")
         get_or_create_endpoint $neutron_service \
-            "$REGION_NAME" \
-            "$NEUTRON_SERVICE_PROTOCOL://$NEUTRON_SERVICE_HOST:$NEUTRON_SERVICE_PORT/"
+            "$REGION_NAME" "$neutron_url"
     fi
 }
 
@@ -424,6 +443,7 @@
 function start_neutron_api {
     local service_port=$NEUTRON_SERVICE_PORT
     local service_protocol=$NEUTRON_SERVICE_PROTOCOL
+    local neutron_url
     if is_service_enabled tls-proxy; then
         service_port=$NEUTRON_SERVICE_PORT_INT
         service_protocol="http"
@@ -437,17 +457,24 @@
         opts+=" --config-file $cfg_file"
     done
 
-    # Start the Neutron service
-    # TODO(sc68cal) Stop hard coding this
-    run_process neutron-api "$NEUTRON_BIN_DIR/neutron-server $opts"
-
-    if ! wait_for_service $SERVICE_TIMEOUT $service_protocol://$NEUTRON_SERVICE_HOST:$service_port; then
-        die $LINENO "neutron-api did not start"
+    if [ "$NEUTRON_DEPLOY_MOD_WSGI" == "True" ]; then
+        run_process neutron-api "$NEUTRON_BIN_DIR/uwsgi --procname-prefix neutron-api --ini $NEUTRON_UWSGI_CONF"
+        neutron_url=$service_protocol://$NEUTRON_SERVICE_HOST/networking/
+        enable_service neutron-rpc-server
+        run_process neutron-rpc-server "$NEUTRON_BIN_DIR/neutron-rpc-server $opts"
+    else
+        # Start the Neutron service
+        # TODO(sc68cal) Stop hard coding this
+        run_process neutron-api "$NEUTRON_BIN_DIR/neutron-server $opts"
+        neutron_url=$service_protocol://$NEUTRON_SERVICE_HOST:$service_port
+        # Start proxy if enabled
+        if is_service_enabled tls-proxy; then
+            start_tls_proxy neutron '*' $NEUTRON_SERVICE_PORT $NEUTRON_SERVICE_HOST $NEUTRON_SERVICE_PORT_INT
+        fi
     fi
 
-    # Start proxy if enabled
-    if is_service_enabled tls-proxy; then
-        start_tls_proxy neutron '*' $NEUTRON_SERVICE_PORT $NEUTRON_SERVICE_HOST $NEUTRON_SERVICE_PORT_INT
+    if ! wait_for_service $SERVICE_TIMEOUT $neutron_url; then
+        die $LINENO "neutron-api did not start"
     fi
 }
 
@@ -494,6 +521,10 @@
         stop_process $serv
     done
 
+    if is_service_enabled neutron-rpc-server; then
+        stop_process neutron-rpc-server
+    fi
+
     if is_service_enabled neutron-dhcp; then
         stop_process neutron-dhcp
         pid=$(ps aux | awk '/[d]nsmasq.+interface=(tap|ns-)/ { print $2 }')
@@ -548,6 +579,13 @@
 # neutron-legacy is removed.
 # TODO(sc68cal) Remove when neutron-legacy is no more.
 function cleanup_neutron {
+    if [ "$NEUTRON_DEPLOY_MOD_WSGI" == "True" ]; then
+        stop_process neutron-api
+        stop_process neutron-rpc-server
+        remove_uwsgi_config "$NEUTRON_UWSGI_CONF" "$NEUTRON_BIN_DIR/neutron-api"
+        sudo rm -f $(apache_site_config_for neutron-api)
+    fi
+
     if is_neutron_legacy_enabled; then
         # Call back to old function
         cleanup_mutnauq "$@"
@@ -563,14 +601,32 @@
     else
         configure_neutron_new "$@"
     fi
+
+    if [ "$NEUTRON_DEPLOY_MOD_WSGI" == "True" ]; then
+        write_uwsgi_config "$NEUTRON_UWSGI_CONF" "$NEUTRON_BIN_DIR/neutron-api" "/networking"
+    fi
 }
 
 function configure_neutron_nova {
     if is_neutron_legacy_enabled; then
         # Call back to old function
-        create_nova_conf_neutron "$@"
+        create_nova_conf_neutron $NOVA_CONF
+        if [[ "${CELLSV2_SETUP}" == "superconductor" ]]; then
+            for i in $(seq 1 $NOVA_NUM_CELLS); do
+                local conf
+                conf=$(conductor_conf $i)
+                create_nova_conf_neutron $conf
+            done
+        fi
     else
-        configure_neutron_nova_new "$@"
+        configure_neutron_nova_new $NOVA_CONF
+        if [[ "${CELLSV2_SETUP}" == "superconductor" ]]; then
+            for i in $(seq 1 $NOVA_NUM_CELLS); do
+                local conf
+                conf=$(conductor_conf $i)
+                configure_neutron_nova_new $conf
+            done
+        fi
     fi
 }
 
diff --git a/lib/neutron-legacy b/lib/neutron-legacy
index 0cd7e31..be5b73f 100644
--- a/lib/neutron-legacy
+++ b/lib/neutron-legacy
@@ -86,6 +86,15 @@
 NEUTRON_CONF=$NEUTRON_CONF_DIR/neutron.conf
 export NEUTRON_TEST_CONFIG_FILE=${NEUTRON_TEST_CONFIG_FILE:-"$NEUTRON_CONF_DIR/debug.ini"}
 
+# NEUTRON_DEPLOY_MOD_WSGI defines how neutron is deployed, allowed values:
+# - False (default) : Run neutron under Eventlet
+# - True : Run neutron under uwsgi
+# TODO(annp): Switching to uwsgi in next cycle if things turn out to be stable
+# enough
+NEUTRON_DEPLOY_MOD_WSGI=${NEUTRON_DEPLOY_MOD_WSGI:-False}
+
+NEUTRON_UWSGI_CONF=$NEUTRON_CONF_DIR/neutron-api-uwsgi.ini
+
 # Agent binaries.  Note, binary paths for other agents are set in per-service
 # scripts in lib/neutron_plugins/services/
 AGENT_DHCP_BINARY="$NEUTRON_BIN_DIR/neutron-dhcp-agent"
@@ -111,7 +120,7 @@
 # Default protocol
 Q_PROTOCOL=${Q_PROTOCOL:-$SERVICE_PROTOCOL}
 # Default listen address
-Q_LISTEN_ADDRESS=${Q_LISTEN_ADDRESS:-$SERVICE_LISTEN_ADDRESS}
+Q_LISTEN_ADDRESS=${Q_LISTEN_ADDRESS:-$(ipv6_unquote $SERVICE_LISTEN_ADDRESS)}
 # Default admin username
 Q_ADMIN_USERNAME=${Q_ADMIN_USERNAME:-neutron}
 # Default auth strategy
@@ -121,7 +130,7 @@
 Q_USE_ROOTWRAP=${Q_USE_ROOTWRAP:-True}
 Q_USE_ROOTWRAP_DAEMON=$(trueorfalse True Q_USE_ROOTWRAP_DAEMON)
 # Meta data IP
-Q_META_DATA_IP=${Q_META_DATA_IP:-$SERVICE_HOST}
+Q_META_DATA_IP=${Q_META_DATA_IP:-$(ipv6_unquote $SERVICE_HOST)}
 # Allow Overlapping IP among subnets
 Q_ALLOW_OVERLAPPING_IP=${Q_ALLOW_OVERLAPPING_IP:-True}
 Q_NOTIFY_NOVA_PORT_STATUS_CHANGES=${Q_NOTIFY_NOVA_PORT_STATUS_CHANGES:-True}
@@ -366,31 +375,32 @@
 }
 
 function create_nova_conf_neutron {
-    iniset $NOVA_CONF DEFAULT use_neutron True
-    iniset $NOVA_CONF neutron auth_type "password"
-    iniset $NOVA_CONF neutron auth_url "$KEYSTONE_AUTH_URI"
-    iniset $NOVA_CONF neutron username "$Q_ADMIN_USERNAME"
-    iniset $NOVA_CONF neutron password "$SERVICE_PASSWORD"
-    iniset $NOVA_CONF neutron user_domain_name "$SERVICE_DOMAIN_NAME"
-    iniset $NOVA_CONF neutron project_name "$SERVICE_PROJECT_NAME"
-    iniset $NOVA_CONF neutron project_domain_name "$SERVICE_DOMAIN_NAME"
-    iniset $NOVA_CONF neutron auth_strategy "$Q_AUTH_STRATEGY"
-    iniset $NOVA_CONF neutron region_name "$REGION_NAME"
+    local conf=${1:-$NOVA_CONF}
+    iniset $conf DEFAULT use_neutron True
+    iniset $conf neutron auth_type "password"
+    iniset $conf neutron auth_url "$KEYSTONE_AUTH_URI"
+    iniset $conf neutron username "$Q_ADMIN_USERNAME"
+    iniset $conf neutron password "$SERVICE_PASSWORD"
+    iniset $conf neutron user_domain_name "$SERVICE_DOMAIN_NAME"
+    iniset $conf neutron project_name "$SERVICE_PROJECT_NAME"
+    iniset $conf neutron project_domain_name "$SERVICE_DOMAIN_NAME"
+    iniset $conf neutron auth_strategy "$Q_AUTH_STRATEGY"
+    iniset $conf neutron region_name "$REGION_NAME"
 
     if [[ "$Q_USE_SECGROUP" == "True" ]]; then
         LIBVIRT_FIREWALL_DRIVER=nova.virt.firewall.NoopFirewallDriver
-        iniset $NOVA_CONF DEFAULT firewall_driver $LIBVIRT_FIREWALL_DRIVER
+        iniset $conf DEFAULT firewall_driver $LIBVIRT_FIREWALL_DRIVER
     fi
 
     # optionally set options in nova_conf
-    neutron_plugin_create_nova_conf
+    neutron_plugin_create_nova_conf $conf
 
     if is_service_enabled q-meta; then
-        iniset $NOVA_CONF neutron service_metadata_proxy "True"
+        iniset $conf neutron service_metadata_proxy "True"
     fi
 
-    iniset $NOVA_CONF DEFAULT vif_plugging_is_fatal "$VIF_PLUGGING_IS_FATAL"
-    iniset $NOVA_CONF DEFAULT vif_plugging_timeout "$VIF_PLUGGING_TIMEOUT"
+    iniset $conf DEFAULT vif_plugging_is_fatal "$VIF_PLUGGING_IS_FATAL"
+    iniset $conf DEFAULT vif_plugging_timeout "$VIF_PLUGGING_TIMEOUT"
 }
 
 # create_mutnauq_accounts() - Set up common required neutron accounts
@@ -401,6 +411,13 @@
 
 # Migrated from keystone_data.sh
 function create_mutnauq_accounts {
+    local neutron_url
+    if [ "$NEUTRON_DEPLOY_MOD_WSGI" == "True" ]; then
+        neutron_url=$Q_PROTOCOL://$SERVICE_HOST/networking/
+    else
+        neutron_url=$Q_PROTOCOL://$SERVICE_HOST:$Q_PORT/
+    fi
+
     if [[ "$ENABLED_SERVICES" =~ "q-svc" ]]; then
 
         create_service_user "neutron"
@@ -408,8 +425,7 @@
         get_or_create_service "neutron" "network" "Neutron Service"
         get_or_create_endpoint \
             "network" \
-            "$REGION_NAME" \
-            "$Q_PROTOCOL://$SERVICE_HOST:$Q_PORT/"
+            "$REGION_NAME" "$neutron_url"
     fi
 }
 
@@ -459,6 +475,7 @@
     local service_port=$Q_PORT
     local service_protocol=$Q_PROTOCOL
     local cfg_file_options
+    local neutron_url
 
     cfg_file_options="$(determine_config_files neutron-server)"
 
@@ -467,16 +484,24 @@
         service_protocol="http"
     fi
     # Start the Neutron service
-    run_process q-svc "$NEUTRON_BIN_DIR/neutron-server $cfg_file_options"
+    if [ "$NEUTRON_DEPLOY_MOD_WSGI" == "True" ]; then
+        enable_service neutron-api
+        run_process neutron-api "$NEUTRON_BIN_DIR/uwsgi --procname-prefix neutron-api --ini $NEUTRON_UWSGI_CONF"
+        neutron_url=$Q_PROTOCOL://$Q_HOST/networking/
+        enable_service neutron-rpc-server
+        run_process neutron-rpc-server "$NEUTRON_BIN_DIR/neutron-rpc-server $cfg_file_options"
+    else
+        run_process q-svc "$NEUTRON_BIN_DIR/neutron-server $cfg_file_options"
+        neutron_url=$service_protocol://$Q_HOST:$service_port
+        # Start proxy if enabled
+        if is_service_enabled tls-proxy; then
+            start_tls_proxy neutron '*' $Q_PORT $Q_HOST $Q_PORT_INT
+        fi
+    fi
     echo "Waiting for Neutron to start..."
 
-    local testcmd="wget ${ssl_ca} --no-proxy -q -O- $service_protocol://$Q_HOST:$service_port"
+    local testcmd="wget ${ssl_ca} --no-proxy -q -O- $neutron_url"
     test_with_retry "$testcmd" "Neutron did not start" $SERVICE_TIMEOUT
-
-    # Start proxy if enabled
-    if is_service_enabled tls-proxy; then
-        start_tls_proxy neutron '*' $Q_PORT $Q_HOST $Q_PORT_INT
-    fi
 }
 
 # Control of the l2 agent is separated out to make it easier to test partial
@@ -531,7 +556,12 @@
         [ ! -z "$pid" ] && sudo kill -9 $pid
     fi
 
-    stop_process q-svc
+    if [ "$NEUTRON_DEPLOY_MOD_WSGI" == "True" ]; then
+        stop_process neutron-rpc-server
+        stop_process neutron-api
+    else
+        stop_process q-svc
+    fi
 
     if is_service_enabled q-l3; then
         sudo pkill -f "radvd -C $DATA_DIR/neutron/ra"
@@ -603,7 +633,7 @@
             IP_UP="sudo ip link set $to_intf up"
             if [[ "$af" == "inet" ]]; then
                 IP=$(echo $IP_BRD | awk '{ print $1; exit }' | grep -o -E '(.*)/' | cut -d "/" -f1)
-                ARP_CMD="arping -A -c 3 -w 4.5 -I $to_intf $IP "
+                ARP_CMD="sudo arping -A -c 3 -w 4.5 -I $to_intf $IP "
             fi
         fi
 
@@ -714,7 +744,7 @@
     # Format logging
     setup_logging $NEUTRON_CONF
 
-    if is_service_enabled tls-proxy; then
+    if is_service_enabled tls-proxy && [ "$NEUTRON_DEPLOY_MOD_WSGI" == "False" ]; then
         # Set the service port for a proxy to take the original
         iniset $NEUTRON_CONF DEFAULT bind_port "$Q_PORT_INT"
         iniset $NEUTRON_CONF oslo_middleware enable_proxy_headers_parsing True
diff --git a/lib/neutron_plugins/nuage b/lib/neutron_plugins/nuage
index 1c04aaa..f39c7c4 100644
--- a/lib/neutron_plugins/nuage
+++ b/lib/neutron_plugins/nuage
@@ -8,10 +8,11 @@
 set +o xtrace
 
 function neutron_plugin_create_nova_conf {
+    local conf="$1"
     NOVA_OVS_BRIDGE=${NOVA_OVS_BRIDGE:-"br-int"}
-    iniset $NOVA_CONF neutron ovs_bridge $NOVA_OVS_BRIDGE
+    iniset $conf neutron ovs_bridge $NOVA_OVS_BRIDGE
     LIBVIRT_FIREWALL_DRIVER=nova.virt.firewall.NoopFirewallDriver
-    iniset $NOVA_CONF DEFAULT firewall_driver $LIBVIRT_FIREWALL_DRIVER
+    iniset $conf DEFAULT firewall_driver $LIBVIRT_FIREWALL_DRIVER
 }
 
 function neutron_plugin_install_agent_packages {
diff --git a/lib/neutron_plugins/ovs_base b/lib/neutron_plugins/ovs_base
index 50b9ae5..523024e 100644
--- a/lib/neutron_plugins/ovs_base
+++ b/lib/neutron_plugins/ovs_base
@@ -72,14 +72,21 @@
         if [[ $DISTRO == "sle12" ]] && [[ $os_RELEASE -lt 12.2 ]]; then
             restart_service openvswitch-switch
         else
-            restart_service openvswitch
+            # workaround for https://bugzilla.suse.com/show_bug.cgi?id=1085971
+            if [[ $DISTRO =~ "tumbleweed" ]]; then
+                sudo sed -i -e "s,^OVS_USER_ID=.*,OVS_USER_ID='root:root'," /etc/sysconfig/openvswitch
+            fi
+            restart_service openvswitch || {
+                journalctl -xe || :
+                systemctl status openvswitch
+            }
         fi
     fi
 }
 
 function _neutron_ovs_base_configure_firewall_driver {
     if [[ "$Q_USE_SECGROUP" == "True" ]]; then
-        iniset /$Q_PLUGIN_CONF_FILE securitygroup firewall_driver iptables_hybrid
+        iniset /$Q_PLUGIN_CONF_FILE securitygroup firewall_driver openvswitch
         if ! running_in_container; then
             enable_kernel_bridge_firewall
         fi
diff --git a/lib/nova b/lib/nova
index 56e3093..5e157c5 100644
--- a/lib/nova
+++ b/lib/nova
@@ -92,7 +92,7 @@
 NOVA_SERVICE_PORT_INT=${NOVA_SERVICE_PORT_INT:-18774}
 NOVA_SERVICE_PROTOCOL=${NOVA_SERVICE_PROTOCOL:-$SERVICE_PROTOCOL}
 NOVA_SERVICE_LOCAL_HOST=${NOVA_SERVICE_LOCAL_HOST:-$SERVICE_LOCAL_HOST}
-NOVA_SERVICE_LISTEN_ADDRESS=${NOVA_SERVICE_LISTEN_ADDRESS:-$SERVICE_LISTEN_ADDRESS}
+NOVA_SERVICE_LISTEN_ADDRESS=${NOVA_SERVICE_LISTEN_ADDRESS:-$(ipv6_unquote $SERVICE_LISTEN_ADDRESS)}
 METADATA_SERVICE_PORT=${METADATA_SERVICE_PORT:-8775}
 
 # Option to enable/disable config drive
@@ -506,11 +506,19 @@
     if [ "$FORCE_CONFIG_DRIVE" != "False" ]; then
         iniset $NOVA_CONF DEFAULT force_config_drive "$FORCE_CONFIG_DRIVE"
     fi
+
+    # nova defaults to genisoimage but only mkisofs is available for 15.0+
+    if is_suse; then
+        iniset $NOVA_CONF DEFAULT mkisofs_cmd /usr/bin/mkisofs
+    fi
+
     # Format logging
     setup_logging $NOVA_CONF
 
+    iniset $NOVA_CONF upgrade_levels compute "auto"
+
     write_uwsgi_config "$NOVA_UWSGI_CONF" "$NOVA_UWSGI" "/compute"
-    write_uwsgi_config "$NOVA_METADATA_UWSGI_CONF" "$NOVA_METADATA_UWSGI" "" ":${METADATA_SERVICE_PORT}"
+    write_uwsgi_config "$NOVA_METADATA_UWSGI_CONF" "$NOVA_METADATA_UWSGI" "" "$SERVICE_LISTEN_ADDRESS:${METADATA_SERVICE_PORT}"
 
     if is_service_enabled ceilometer; then
         iniset $NOVA_CONF DEFAULT instance_usage_audit "True"
@@ -518,52 +526,6 @@
         iniset $NOVA_CONF DEFAULT notify_on_state_change "vm_and_task_state"
     fi
 
-    # All nova-compute workers need to know the vnc configuration options
-    # These settings don't hurt anything if n-xvnc and n-novnc are disabled
-    if is_service_enabled n-cpu; then
-        NOVNCPROXY_URL=${NOVNCPROXY_URL:-"http://$SERVICE_HOST:6080/vnc_auto.html"}
-        iniset $NOVA_CONF vnc novncproxy_base_url "$NOVNCPROXY_URL"
-        XVPVNCPROXY_URL=${XVPVNCPROXY_URL:-"http://$SERVICE_HOST:6081/console"}
-        iniset $NOVA_CONF vnc xvpvncproxy_base_url "$XVPVNCPROXY_URL"
-        SPICEHTML5PROXY_URL=${SPICEHTML5PROXY_URL:-"http://$SERVICE_HOST:6082/spice_auto.html"}
-        iniset $NOVA_CONF spice html5proxy_base_url "$SPICEHTML5PROXY_URL"
-    fi
-
-    if is_service_enabled n-novnc || is_service_enabled n-xvnc || [ "$NOVA_VNC_ENABLED" != False ]; then
-        # Address on which instance vncservers will listen on compute hosts.
-        # For multi-host, this should be the management ip of the compute host.
-        VNCSERVER_LISTEN=${VNCSERVER_LISTEN=$NOVA_SERVICE_LOCAL_HOST}
-        VNCSERVER_PROXYCLIENT_ADDRESS=${VNCSERVER_PROXYCLIENT_ADDRESS=$NOVA_SERVICE_LOCAL_HOST}
-        iniset $NOVA_CONF vnc server_listen "$VNCSERVER_LISTEN"
-        iniset $NOVA_CONF vnc server_proxyclient_address "$VNCSERVER_PROXYCLIENT_ADDRESS"
-        iniset $NOVA_CONF vnc novncproxy_host "$NOVA_SERVICE_LISTEN_ADDRESS"
-        iniset $NOVA_CONF vnc xvpvncproxy_host "$NOVA_SERVICE_LISTEN_ADDRESS"
-
-        if is_nova_console_proxy_compute_tls_enabled ; then
-            iniset $NOVA_CONF vnc auth_schemes "vencrypt"
-            iniset $NOVA_CONF vnc vencrypt_client_key "/etc/pki/nova-novnc/client-key.pem"
-            iniset $NOVA_CONF vnc vencrypt_client_cert "/etc/pki/nova-novnc/client-cert.pem"
-            iniset $NOVA_CONF vnc vencrypt_ca_certs "/etc/pki/nova-novnc/ca-cert.pem"
-
-            sudo mkdir -p /etc/pki/nova-novnc
-            deploy_int_CA /etc/pki/nova-novnc/ca-cert.pem
-            deploy_int_cert /etc/pki/nova-novnc/client-cert.pem /etc/pki/nova-novnc/client-key.pem
-        fi
-    else
-        iniset $NOVA_CONF vnc enabled false
-    fi
-
-    if is_service_enabled n-spice; then
-        # Address on which instance spiceservers will listen on compute hosts.
-        # For multi-host, this should be the management ip of the compute host.
-        SPICESERVER_PROXYCLIENT_ADDRESS=${SPICESERVER_PROXYCLIENT_ADDRESS=$NOVA_SERVICE_LOCAL_HOST}
-        SPICESERVER_LISTEN=${SPICESERVER_LISTEN=$NOVA_SERVICE_LOCAL_HOST}
-        iniset $NOVA_CONF spice enabled true
-        iniset $NOVA_CONF spice server_listen "$SPICESERVER_LISTEN"
-        iniset $NOVA_CONF spice server_proxyclient_address "$SPICESERVER_PROXYCLIENT_ADDRESS"
-        iniset $NOVA_CONF spice html5proxy_host "$NOVA_SERVICE_LISTEN_ADDRESS"
-    fi
-
     # Set the oslo messaging driver to the typical default. This does not
     # enable notifications, but it will allow them to function when enabled.
     iniset $NOVA_CONF oslo_messaging_notifications driver "messagingv2"
@@ -582,10 +544,6 @@
         iniset $NOVA_CONF oslo_middleware enable_proxy_headers_parsing True
     fi
 
-    if is_service_enabled n-sproxy; then
-        iniset $NOVA_CONF serial_console serialproxy_host "$NOVA_SERVICE_LISTEN_ADDRESS"
-        iniset $NOVA_CONF serial_console enabled True
-    fi
     iniset $NOVA_CONF DEFAULT graceful_shutdown_timeout "$SERVICE_GRACEFUL_SHUTDOWN_TIMEOUT"
 
     # Setup logging for nova-dhcpbridge command line
@@ -635,6 +593,86 @@
             setup_logging $conf
         done
     fi
+
+    # Console proxy configuration has to go after conductor configuration
+    # because the per cell config file nova_cellN.conf is cleared out as part
+    # of conductor configuration.
+    if [[ "${CELLSV2_SETUP}" == "singleconductor" ]]; then
+        configure_console_proxies
+    else
+        for i in $(seq 1 $NOVA_NUM_CELLS); do
+            local conf
+            conf=$(conductor_conf $i)
+            configure_console_proxies $conf
+        done
+    fi
+}
+
+function configure_console_compute {
+    # All nova-compute workers need to know the vnc configuration options
+    # These settings don't hurt anything if n-xvnc and n-novnc are disabled
+    if is_service_enabled n-cpu; then
+        NOVNCPROXY_URL=${NOVNCPROXY_URL:-"http://$SERVICE_HOST:6080/vnc_auto.html"}
+        iniset $NOVA_CPU_CONF vnc novncproxy_base_url "$NOVNCPROXY_URL"
+        XVPVNCPROXY_URL=${XVPVNCPROXY_URL:-"http://$SERVICE_HOST:6081/console"}
+        iniset $NOVA_CPU_CONF vnc xvpvncproxy_base_url "$XVPVNCPROXY_URL"
+        SPICEHTML5PROXY_URL=${SPICEHTML5PROXY_URL:-"http://$SERVICE_HOST:6082/spice_auto.html"}
+        iniset $NOVA_CPU_CONF spice html5proxy_base_url "$SPICEHTML5PROXY_URL"
+    fi
+
+    if is_service_enabled n-novnc || is_service_enabled n-xvnc || [ "$NOVA_VNC_ENABLED" != False ]; then
+        # Address on which instance vncservers will listen on compute hosts.
+        # For multi-host, this should be the management ip of the compute host.
+        VNCSERVER_LISTEN=${VNCSERVER_LISTEN=$NOVA_SERVICE_LOCAL_HOST}
+        VNCSERVER_PROXYCLIENT_ADDRESS=${VNCSERVER_PROXYCLIENT_ADDRESS=$NOVA_SERVICE_LOCAL_HOST}
+        iniset $NOVA_CPU_CONF vnc server_listen "$VNCSERVER_LISTEN"
+        iniset $NOVA_CPU_CONF vnc server_proxyclient_address "$VNCSERVER_PROXYCLIENT_ADDRESS"
+    else
+        iniset $NOVA_CPU_CONF vnc enabled false
+    fi
+
+    if is_service_enabled n-spice; then
+        # Address on which instance spiceservers will listen on compute hosts.
+        # For multi-host, this should be the management ip of the compute host.
+        SPICESERVER_PROXYCLIENT_ADDRESS=${SPICESERVER_PROXYCLIENT_ADDRESS=$NOVA_SERVICE_LOCAL_HOST}
+        SPICESERVER_LISTEN=${SPICESERVER_LISTEN=$NOVA_SERVICE_LOCAL_HOST}
+        iniset $NOVA_CPU_CONF spice enabled true
+        iniset $NOVA_CPU_CONF spice server_listen "$SPICESERVER_LISTEN"
+        iniset $NOVA_CPU_CONF spice server_proxyclient_address "$SPICESERVER_PROXYCLIENT_ADDRESS"
+    fi
+
+    if is_service_enabled n-sproxy; then
+        iniset $NOVA_CPU_CONF serial_console enabled True
+    fi
+}
+
+function configure_console_proxies {
+    # Use the provided config file path or default to $NOVA_CONF.
+    local conf=${1:-$NOVA_CONF}
+
+    if is_service_enabled n-novnc || is_service_enabled n-xvnc || [ "$NOVA_VNC_ENABLED" != False ]; then
+        iniset $conf vnc novncproxy_host "$NOVA_SERVICE_LISTEN_ADDRESS"
+        iniset $conf vnc xvpvncproxy_host "$NOVA_SERVICE_LISTEN_ADDRESS"
+
+        if is_nova_console_proxy_compute_tls_enabled ; then
+            iniset $conf vnc auth_schemes "vencrypt"
+            iniset $conf vnc vencrypt_client_key "/etc/pki/nova-novnc/client-key.pem"
+            iniset $conf vnc vencrypt_client_cert "/etc/pki/nova-novnc/client-cert.pem"
+            iniset $conf vnc vencrypt_ca_certs "/etc/pki/nova-novnc/ca-cert.pem"
+
+            sudo mkdir -p /etc/pki/nova-novnc
+            deploy_int_CA /etc/pki/nova-novnc/ca-cert.pem
+            deploy_int_cert /etc/pki/nova-novnc/client-cert.pem /etc/pki/nova-novnc/client-key.pem
+        fi
+    fi
+
+    if is_service_enabled n-spice; then
+        iniset $conf spice html5proxy_host "$NOVA_SERVICE_LISTEN_ADDRESS"
+    fi
+
+    if is_service_enabled n-sproxy; then
+        iniset $conf serial_console serialproxy_host "$NOVA_SERVICE_LISTEN_ADDRESS"
+    fi
 }
 
 function init_nova_service_user_conf {
@@ -685,7 +723,7 @@
         $NOVA_BIN_DIR/nova-manage cell create --name=child --cell_type=child --username=$RABBIT_USERID --hostname=$RABBIT_HOST --port=5672 --password=$RABBIT_PASSWORD --virtual_host=child_cell --woffset=0 --wscale=1
 
         # Creates the single cells v2 cell for the child cell (v1) nova db.
-        nova-manage --config-file $NOVA_CELLS_CONF cell_v2 create_cell \
+        $NOVA_BIN_DIR/nova-manage --config-file $NOVA_CELLS_CONF cell_v2 create_cell \
             --transport-url $(get_transport_url child_cell) --name 'cell1'
     fi
 }
@@ -729,12 +767,12 @@
         # this needs to come after the api_db sync happens. We also want to run
         # this before the db sync below since that will migrate both the nova
         # and nova_cell0 databases.
-        nova-manage cell_v2 map_cell0 --database_connection `database_connection_url nova_cell0`
+        $NOVA_BIN_DIR/nova-manage cell_v2 map_cell0 --database_connection `database_connection_url nova_cell0`
 
         # (Re)create nova databases
         for i in $(seq 1 $NOVA_NUM_CELLS); do
             recreate_database nova_cell${i}
-            $NOVA_BIN_DIR/nova-manage --config-file $(conductor_conf $i) db sync
+            $NOVA_BIN_DIR/nova-manage --config-file $(conductor_conf $i) db sync --local_cell
         done
 
         # Migrate nova and nova_cell0 databases.
@@ -750,7 +788,7 @@
 
         # create the cell1 cell for the main nova db where the hosts live
         for i in $(seq 1 $NOVA_NUM_CELLS); do
-            nova-manage --config-file $NOVA_CONF --config-file $(conductor_conf $i) cell_v2 create_cell --name "cell$i"
+            $NOVA_BIN_DIR/nova-manage --config-file $NOVA_CONF --config-file $(conductor_conf $i) cell_v2 create_cell --name "cell$i"
         done
     fi
 
@@ -886,6 +924,11 @@
         iniset_rpc_backend nova $NOVA_CPU_CONF DEFAULT "nova_cell${NOVA_CPU_CELL}"
     fi
 
+    # Console proxies were configured earlier in create_nova_conf. Now that the
+    # nova-cpu.conf has been created, configure the console settings required
+    # by the compute process.
+    configure_console_compute
+
     if [[ "$VIRT_DRIVER" = 'libvirt' ]]; then
         # The group **$LIBVIRT_GROUP** is added to the current user in this script.
         # ``sg`` is used in run_process to execute nova-compute as a member of the
@@ -946,11 +989,46 @@
         run_process n-api-meta "$NOVA_BIN_DIR/uwsgi --procname-prefix nova-api-meta --ini $NOVA_METADATA_UWSGI_CONF"
     fi
 
-    run_process n-novnc "$NOVA_BIN_DIR/nova-novncproxy --config-file $api_cell_conf --web $NOVNC_WEB_DIR"
-    run_process n-xvnc "$NOVA_BIN_DIR/nova-xvpvncproxy --config-file $api_cell_conf"
-    run_process n-spice "$NOVA_BIN_DIR/nova-spicehtml5proxy --config-file $api_cell_conf --web $SPICE_WEB_DIR"
+    # nova-consoleauth always runs globally
     run_process n-cauth "$NOVA_BIN_DIR/nova-consoleauth --config-file $api_cell_conf"
-    run_process n-sproxy "$NOVA_BIN_DIR/nova-serialproxy --config-file $api_cell_conf"
+
+    export PATH=$old_path
+}
+
+function enable_nova_console_proxies {
+    for i in $(seq 1 $NOVA_NUM_CELLS); do
+        for srv in n-novnc n-xvnc n-spice n-sproxy; do
+            if is_service_enabled $srv; then
+                enable_service ${srv}-cell${i}
+            fi
+        done
+    done
+}
+
+function start_nova_console_proxies {
+    # Hack to set the path for rootwrap
+    local old_path=$PATH
+    # This is needed to find the nova conf
+    export PATH=$NOVA_BIN_DIR:$PATH
+
+    local api_cell_conf=$NOVA_CONF
+    # console proxies run globally for singleconductor, else they run per cell
+    if [[ "${CELLSV2_SETUP}" == "singleconductor" ]]; then
+        run_process n-novnc "$NOVA_BIN_DIR/nova-novncproxy --config-file $api_cell_conf --web $NOVNC_WEB_DIR"
+        run_process n-xvnc "$NOVA_BIN_DIR/nova-xvpvncproxy --config-file $api_cell_conf"
+        run_process n-spice "$NOVA_BIN_DIR/nova-spicehtml5proxy --config-file $api_cell_conf --web $SPICE_WEB_DIR"
+        run_process n-sproxy "$NOVA_BIN_DIR/nova-serialproxy --config-file $api_cell_conf"
+    else
+        enable_nova_console_proxies
+        for i in $(seq 1 $NOVA_NUM_CELLS); do
+            local conf
+            conf=$(conductor_conf $i)
+            run_process n-novnc-cell${i} "$NOVA_BIN_DIR/nova-novncproxy --config-file $conf --web $NOVNC_WEB_DIR"
+            run_process n-xvnc-cell${i} "$NOVA_BIN_DIR/nova-xvpvncproxy --config-file $conf"
+            run_process n-spice-cell${i} "$NOVA_BIN_DIR/nova-spicehtml5proxy --config-file $conf --web $SPICE_WEB_DIR"
+            run_process n-sproxy-cell${i} "$NOVA_BIN_DIR/nova-serialproxy --config-file $conf"
+        done
+    fi
 
     export PATH=$old_path
 }
@@ -1010,12 +1088,13 @@
     # this catches the cells v1 case early
     _set_singleconductor
     start_nova_rest
+    start_nova_console_proxies
     start_nova_conductor
     start_nova_compute
     if is_service_enabled n-api; then
         # dump the cell mapping to ensure life is good
         echo "Dumping cells_v2 mapping"
-        nova-manage cell_v2 list_cells --verbose
+        $NOVA_BIN_DIR/nova-manage cell_v2 list_cells --verbose
     fi
 }
 
@@ -1035,11 +1114,26 @@
 
 function stop_nova_rest {
     # Kill the non-compute nova processes
-    for serv in n-api n-api-meta n-net n-sch n-novnc n-xvnc n-cauth n-spice n-cell n-cell n-sproxy; do
+    for serv in n-api n-api-meta n-net n-sch n-cauth n-cell n-cell; do
         stop_process $serv
     done
 }
 
+function stop_nova_console_proxies {
+    if [[ "${CELLSV2_SETUP}" == "singleconductor" ]]; then
+        for srv in n-novnc n-xvnc n-spice n-sproxy; do
+            stop_process $srv
+        done
+    else
+        enable_nova_console_proxies
+        for i in $(seq 1 $NOVA_NUM_CELLS); do
+            for srv in n-novnc n-xvnc n-spice n-sproxy; do
+                stop_process ${srv}-cell${i}
+            done
+        done
+    fi
+}
+
 function stop_nova_conductor {
     if [[ "${CELLSV2_SETUP}" == "singleconductor" ]]; then
         stop_process n-cond
@@ -1057,6 +1151,7 @@
 # stop_nova() - Stop running processes
 function stop_nova {
     stop_nova_rest
+    stop_nova_console_proxies
     stop_nova_conductor
     stop_nova_compute
 }
diff --git a/lib/nova_plugins/hypervisor-fake b/lib/nova_plugins/hypervisor-fake
index 49c8dee..87ee49f 100644
--- a/lib/nova_plugins/hypervisor-fake
+++ b/lib/nova_plugins/hypervisor-fake
@@ -38,18 +38,7 @@
 function configure_nova_hypervisor {
     iniset $NOVA_CONF DEFAULT compute_driver "fake.FakeDriver"
     # Disable arbitrary limits
-    iniset $NOVA_CONF DEFAULT quota_instances -1
-    iniset $NOVA_CONF DEFAULT quota_cores -1
-    iniset $NOVA_CONF DEFAULT quota_ram -1
-    iniset $NOVA_CONF DEFAULT quota_floating_ips -1
-    iniset $NOVA_CONF DEFAULT quota_fixed_ips -1
-    iniset $NOVA_CONF DEFAULT quota_metadata_items -1
-    iniset $NOVA_CONF DEFAULT quota_injected_files -1
-    iniset $NOVA_CONF DEFAULT quota_injected_file_path_length -1
-    iniset $NOVA_CONF DEFAULT quota_security_groups -1
-    iniset $NOVA_CONF DEFAULT quota_security_group_rules -1
-    iniset $NOVA_CONF DEFAULT quota_key_pairs -1
-    iniset $NOVA_CONF filter_scheduler enabled_filters "RetryFilter,AvailabilityZoneFilter,ComputeFilter,ComputeCapabilitiesFilter,ImagePropertiesFilter,CoreFilter,RamFilter,DiskFilter"
+    iniset $NOVA_CONF quota driver nova.quota.NoopQuotaDriver
 }
 
 # install_nova_hypervisor() - Install external components
diff --git a/lib/nova_plugins/hypervisor-ironic b/lib/nova_plugins/hypervisor-ironic
index c91f70b..49110a8 100644
--- a/lib/nova_plugins/hypervisor-ironic
+++ b/lib/nova_plugins/hypervisor-ironic
@@ -42,14 +42,6 @@
     iniset $NOVA_CONF DEFAULT compute_driver ironic.IronicDriver
     iniset $NOVA_CONF DEFAULT firewall_driver $LIBVIRT_FIREWALL_DRIVER
 
-    if [[ "$IRONIC_USE_RESOURCE_CLASSES" == "False" ]]; then
-        iniset $NOVA_CONF DEFAULT scheduler_host_manager ironic_host_manager
-        iniset $NOVA_CONF filter_scheduler use_baremetal_filters True
-        iniset $NOVA_CONF filter_scheduler host_subset_size 999
-        iniset $NOVA_CONF DEFAULT ram_allocation_ratio 1.0
-        iniset $NOVA_CONF DEFAULT reserved_host_memory_mb 0
-    fi
-
     # ironic section
     iniset $NOVA_CONF ironic auth_type password
     iniset $NOVA_CONF ironic username admin
diff --git a/lib/placement b/lib/placement
index 1d68f8a..a1602ba 100644
--- a/lib/placement
+++ b/lib/placement
@@ -44,8 +44,6 @@
 
 # The placement service can optionally use a separate database
 # connection. Set PLACEMENT_DB_ENABLED to True to use it.
-# NOTE(cdent): This functionality depends on some code that is not
-# yet merged in nova but is coming soon.
 PLACEMENT_DB_ENABLED=$(trueorfalse False PLACEMENT_DB_ENABLED)
 
 if is_service_enabled tls-proxy; then
@@ -112,7 +110,6 @@
     iniset $conf placement user_domain_name "$SERVICE_DOMAIN_NAME"
     iniset $conf placement project_name "$SERVICE_TENANT_NAME"
     iniset $conf placement project_domain_name "$SERVICE_DOMAIN_NAME"
-    iniset $conf placement os_region_name "$REGION_NAME"
     # TODO(cdent): auth_strategy, which is common to see in these
     # blocks is not currently used here. For the time being the
     # placement api uses the auth_strategy configuration setting
@@ -152,9 +149,9 @@
 function init_placement {
     if [ "$PLACEMENT_DB_ENABLED" != False ]; then
         recreate_database placement
-        time_start "dbsync"
-        $NOVA_BIN_DIR/nova-manage --config-file $NOVA_CONF api_db sync
-        time_stop "dbsync"
+        # Database migration will be handled when nova does an api_db sync
+        # TODO(cdent): When placement is extracted we'll do our own sync
+        # here.
     fi
     create_placement_accounts
 }
diff --git a/lib/swift b/lib/swift
index 6cda9c8..3b3e608 100644
--- a/lib/swift
+++ b/lib/swift
@@ -37,6 +37,7 @@
 
 # Set up default directories
 GITDIR["python-swiftclient"]=$DEST/python-swiftclient
+SWIFT_DIR=$DEST/swift
 
 # Swift virtual environment
 if [[ ${USE_VENV} = True ]]; then
@@ -46,8 +47,6 @@
     SWIFT_BIN_DIR=$(get_python_exec_prefix)
 fi
 
-
-SWIFT_DIR=$DEST/swift
 SWIFT_AUTH_CACHE_DIR=${SWIFT_AUTH_CACHE_DIR:-/var/cache/swift}
 SWIFT_APACHE_WSGI_DIR=${SWIFT_APACHE_WSGI_DIR:-/var/www/swift}
 SWIFT3_DIR=$DEST/swift3
@@ -56,7 +55,7 @@
 SWIFT_DEFAULT_BIND_PORT=${SWIFT_DEFAULT_BIND_PORT:-8080}
 SWIFT_DEFAULT_BIND_PORT_INT=${SWIFT_DEFAULT_BIND_PORT_INT:-8081}
 SWIFT_SERVICE_LOCAL_HOST=${SWIFT_SERVICE_LOCAL_HOST:-$SERVICE_LOCAL_HOST}
-SWIFT_SERVICE_LISTEN_ADDRESS=${SWIFT_SERVICE_LISTEN_ADDRESS:-$SERVICE_LISTEN_ADDRESS}
+SWIFT_SERVICE_LISTEN_ADDRESS=${SWIFT_SERVICE_LISTEN_ADDRESS:-$(ipv6_unquote $SERVICE_LISTEN_ADDRESS)}
 
 # TODO: add logging to different location.
 
@@ -341,7 +340,7 @@
     local user_group
 
     # Make sure to kill all swift processes first
-    swift-init --run-dir=${SWIFT_DATA_DIR}/run all stop || true
+    $SWIFT_BIN_DIR/swift-init --run-dir=${SWIFT_DATA_DIR}/run all stop || true
 
     sudo install -d -o ${STACK_USER} ${SWIFT_CONF_DIR}
     sudo install -d -o ${STACK_USER} ${SWIFT_CONF_DIR}/{object,container,account}-server
@@ -369,6 +368,7 @@
 
     SWIFT_CONFIG_PROXY_SERVER=${SWIFT_CONF_DIR}/proxy-server.conf
     cp ${SWIFT_DIR}/etc/proxy-server.conf-sample ${SWIFT_CONFIG_PROXY_SERVER}
+    cp ${SWIFT_DIR}/etc/internal-client.conf-sample ${SWIFT_CONF_DIR}/internal-client.conf
 
     # To run container sync feature introduced in Swift ver 1.12.0,
     # container sync "realm" is added in container-sync-realms.conf
@@ -704,7 +704,7 @@
 function init_swift {
     local node_number
     # Make sure to kill all swift processes first
-    swift-init --run-dir=${SWIFT_DATA_DIR}/run all stop || true
+    $SWIFT_BIN_DIR/swift-init --run-dir=${SWIFT_DATA_DIR}/run all stop || true
 
     # Forcibly re-create the backing filesystem
     create_swift_disk
@@ -715,9 +715,9 @@
 
         rm -f *.builder *.ring.gz backups/*.builder backups/*.ring.gz
 
-        swift-ring-builder object.builder create ${SWIFT_PARTITION_POWER_SIZE} ${SWIFT_REPLICAS} 1
-        swift-ring-builder container.builder create ${SWIFT_PARTITION_POWER_SIZE} ${SWIFT_REPLICAS} 1
-        swift-ring-builder account.builder create ${SWIFT_PARTITION_POWER_SIZE} ${SWIFT_REPLICAS} 1
+        $SWIFT_BIN_DIR/swift-ring-builder object.builder create ${SWIFT_PARTITION_POWER_SIZE} ${SWIFT_REPLICAS} 1
+        $SWIFT_BIN_DIR/swift-ring-builder container.builder create ${SWIFT_PARTITION_POWER_SIZE} ${SWIFT_REPLICAS} 1
+        $SWIFT_BIN_DIR/swift-ring-builder account.builder create ${SWIFT_PARTITION_POWER_SIZE} ${SWIFT_REPLICAS} 1
 
         # The ring will be created on each node, and because the order of
         # nodes is identical we can use a seed for rebalancing, making it
@@ -728,26 +728,26 @@
             node_number=1
 
             for node in ${SWIFT_STORAGE_IPS}; do
-                swift-ring-builder object.builder add z${node_number}-${node}:${OBJECT_PORT_BASE}/sdb1 1
-                swift-ring-builder container.builder add z${node_number}-${node}:${CONTAINER_PORT_BASE}/sdb1 1
-                swift-ring-builder account.builder add z${node_number}-${node}:${ACCOUNT_PORT_BASE}/sdb1 1
+                $SWIFT_BIN_DIR/swift-ring-builder object.builder add z${node_number}-${node}:${OBJECT_PORT_BASE}/sdb1 1
+                $SWIFT_BIN_DIR/swift-ring-builder container.builder add z${node_number}-${node}:${CONTAINER_PORT_BASE}/sdb1 1
+                $SWIFT_BIN_DIR/swift-ring-builder account.builder add z${node_number}-${node}:${ACCOUNT_PORT_BASE}/sdb1 1
                 let "node_number=node_number+1"
             done
 
         else
 
             for node_number in ${SWIFT_REPLICAS_SEQ}; do
-                swift-ring-builder object.builder add z${node_number}-${SWIFT_SERVICE_LOCAL_HOST}:$(( OBJECT_PORT_BASE + 10 * (node_number - 1) ))/sdb1 1
-                swift-ring-builder container.builder add z${node_number}-${SWIFT_SERVICE_LOCAL_HOST}:$(( CONTAINER_PORT_BASE + 10 * (node_number - 1) ))/sdb1 1
-                swift-ring-builder account.builder add z${node_number}-${SWIFT_SERVICE_LOCAL_HOST}:$(( ACCOUNT_PORT_BASE + 10 * (node_number - 1) ))/sdb1 1
+                $SWIFT_BIN_DIR/swift-ring-builder object.builder add z${node_number}-${SWIFT_SERVICE_LOCAL_HOST}:$(( OBJECT_PORT_BASE + 10 * (node_number - 1) ))/sdb1 1
+                $SWIFT_BIN_DIR/swift-ring-builder container.builder add z${node_number}-${SWIFT_SERVICE_LOCAL_HOST}:$(( CONTAINER_PORT_BASE + 10 * (node_number - 1) ))/sdb1 1
+                $SWIFT_BIN_DIR/swift-ring-builder account.builder add z${node_number}-${SWIFT_SERVICE_LOCAL_HOST}:$(( ACCOUNT_PORT_BASE + 10 * (node_number - 1) ))/sdb1 1
             done
         fi
 
         # We use a seed for rebalancing. Doing this allows us to create
         # identical rings on multiple nodes if SWIFT_STORAGE_IPS is the same
-        swift-ring-builder object.builder rebalance 42
-        swift-ring-builder container.builder rebalance 42
-        swift-ring-builder account.builder rebalance 42
+        $SWIFT_BIN_DIR/swift-ring-builder object.builder rebalance 42
+        $SWIFT_BIN_DIR/swift-ring-builder container.builder rebalance 42
+        $SWIFT_BIN_DIR/swift-ring-builder account.builder rebalance 42
     } && popd >/dev/null
 
     # Create cache dir
@@ -803,7 +803,7 @@
         # Apache should serve the "PACO" a.k.a "main" services
         restart_apache_server
         # The rest of the services should be started in backgroud
-        swift-init --run-dir=${SWIFT_DATA_DIR}/run rest start
+        $SWIFT_BIN_DIR/swift-init --run-dir=${SWIFT_DATA_DIR}/run rest start
         return 0
     fi
 
@@ -827,7 +827,7 @@
         done
 
         if [[ "$SWIFT_START_ALL_SERVICES" == "True" ]]; then
-            swift-init --run-dir=${SWIFT_DATA_DIR}/run rest start
+            $SWIFT_BIN_DIR/swift-init --run-dir=${SWIFT_DATA_DIR}/run rest start
         else
             # The container-sync daemon is strictly needed to pass the container
             # sync Tempest tests.
@@ -835,8 +835,8 @@
             run_process s-container-sync "$SWIFT_BIN_DIR/swift-container-sync ${SWIFT_CONF_DIR}/container-server/1.conf"
         fi
     else
-        swift-init --run-dir=${SWIFT_DATA_DIR}/run all restart || true
-        swift-init --run-dir=${SWIFT_DATA_DIR}/run proxy stop || true
+        $SWIFT_BIN_DIR/swift-init --run-dir=${SWIFT_DATA_DIR}/run all restart || true
+        $SWIFT_BIN_DIR/swift-init --run-dir=${SWIFT_DATA_DIR}/run proxy stop || true
     fi
 
     if is_service_enabled tls-proxy; then
@@ -863,12 +863,12 @@
     local type
 
     if [ "$SWIFT_USE_MOD_WSGI" == "True" ]; then
-        swift-init --run-dir=${SWIFT_DATA_DIR}/run rest stop && return 0
+        $SWIFT_BIN_DIR/swift-init --run-dir=${SWIFT_DATA_DIR}/run rest stop && return 0
     fi
 
     # screen normally killed by ``unstack.sh``
-    if type -p swift-init >/dev/null; then
-        swift-init --run-dir=${SWIFT_DATA_DIR}/run all stop || true
+    if type -p $SWIFT_BIN_DIR/swift-init >/dev/null; then
+        $SWIFT_BIN_DIR/swift-init --run-dir=${SWIFT_DATA_DIR}/run all stop || true
     fi
     # Dump all of the servers
     # Maintain the iteration as stop_process() has some desirable side-effects
diff --git a/lib/tempest b/lib/tempest
index 6da2330..3fefa5b 100644
--- a/lib/tempest
+++ b/lib/tempest
@@ -279,8 +279,8 @@
         iniset $TEMPEST_CONFIG identity-feature-enabled api_v2 False
     fi
     iniset $TEMPEST_CONFIG identity auth_version ${TEMPEST_AUTH_VERSION:-v3}
-    if [[ "$TEMPEST_AUTH_VERSION" != "v2.0" ]]; then
-        # we're going to disable v2 admin unless we're using v2.0 by default.
+    if [[ "$TEMPEST_AUTH_VERSION" != "v2" ]]; then
+        # we're going to disable v2 admin unless we're using v2 by default.
         iniset $TEMPEST_CONFIG identity-feature-enabled api_v2_admin False
     fi
 
@@ -303,6 +303,10 @@
     # as this is supported in Queens and beyond.
     iniset $TEMPEST_CONFIG identity-feature-enabled project_tags True
 
+    # In Queens and later, application credentials are enabled by default
+    # so remove this once Tempest no longer supports Pike.
+    iniset $TEMPEST_CONFIG identity-feature-enabled application_credentials True
+
     # Image
     # We want to be able to override this variable in the gate to avoid
     # doing an external HTTP fetch for this test.
@@ -429,6 +433,13 @@
     iniset $TEMPEST_CONFIG validation network_for_ssh $TEMPEST_SSH_NETWORK_NAME
 
     # Volume
+    # Set the service catalog entry for Tempest to run on. Typically
+    # used to try different Volume API version targets. The tempest
+    # default it to 'volumev3'(v3 APIs endpoint) , so only set this
+    # if you want to change it.
+    if [[ -n "$TEMPEST_VOLUME_TYPE" ]]; then
+        iniset $TEMPEST_CONFIG volume catalog_type $TEMPEST_VOLUME_TYPE
+    fi
     # Only turn on TEMPEST_VOLUME_MANAGE_SNAPSHOT by default for "lvm" backends
     if [[ "$CINDER_ENABLED_BACKENDS" == *"lvm"* ]]; then
         TEMPEST_VOLUME_MANAGE_SNAPSHOT=${TEMPEST_VOLUME_MANAGE_SNAPSHOT:-True}
@@ -450,6 +461,12 @@
     iniset $TEMPEST_CONFIG volume-feature-enabled api_v1 $(trueorfalse False TEMPEST_VOLUME_API_V1)
     local tempest_volume_min_microversion=${TEMPEST_VOLUME_MIN_MICROVERSION:-None}
     local tempest_volume_max_microversion=${TEMPEST_VOLUME_MAX_MICROVERSION:-"latest"}
+    # Reset microversions to None where v2 is running which does not support microversion.
+    # Both "None" means no microversion testing.
+    if [[ "$TEMPEST_VOLUME_TYPE" == "volumev2" ]]; then
+        tempest_volume_min_microversion=None
+        tempest_volume_max_microversion=None
+    fi
     if [ "$tempest_volume_min_microversion" == "None" ]; then
         inicomment $TEMPEST_CONFIG volume min_microversion
     else
@@ -564,7 +581,7 @@
     tox -evenv-tempest -- pip install -c u-c-m.txt -r requirements.txt
 
     # Auth:
-    iniset $TEMPEST_CONFIG auth tempest_roles "Member"
+    iniset $TEMPEST_CONFIG auth tempest_roles "member"
     if [[ $TEMPEST_USE_TEST_ACCOUNTS == "True" ]]; then
         if [[ $TEMPEST_HAS_ADMIN == "True" ]]; then
             tox -evenv-tempest -- tempest-account-generator -c $TEMPEST_CONFIG --os-username $admin_username --os-password "$password" --os-tenant-name $admin_project_name -r $TEMPEST_CONCURRENCY --with-admin etc/accounts.yaml
@@ -646,7 +663,7 @@
 function install_tempest_plugins {
     pushd $TEMPEST_DIR
     if [[ $TEMPEST_PLUGINS != 0 ]] ; then
-        tox -evenv-tempest -- pip install $TEMPEST_PLUGINS
+        tox -evenv-tempest -- pip install -c $REQUIREMENTS_DIR/upper-constraints.txt $TEMPEST_PLUGINS
         echo "Checking installed Tempest plugins:"
         tox -evenv-tempest -- tempest list-plugins
     fi
diff --git a/lib/tls b/lib/tls
index a72b708..e3ed3cc 100644
--- a/lib/tls
+++ b/lib/tls
@@ -37,7 +37,7 @@
 
 if is_service_enabled tls-proxy; then
     # TODO(dtroyer): revisit this below after the search for HOST_IP has been done
-    TLS_IP=${TLS_IP:-$SERVICE_IP}
+    TLS_IP=${TLS_IP:-$(ipv6_unquote $SERVICE_HOST)}
 fi
 
 DEVSTACK_HOSTNAME=$(hostname -f)
@@ -67,9 +67,9 @@
     # build common config file
 
     # Verify ``TLS_IP`` is good
-    if [[ -n "$HOST_IP" && "$HOST_IP" != "$TLS_IP" ]]; then
+    if [[ -n "$SERVICE_HOST" && "$(ipv6_unquote $SERVICE_HOST)" != "$TLS_IP" ]]; then
         # auto-discover has changed the IP
-        TLS_IP=$HOST_IP
+        TLS_IP=$(ipv6_unquote $SERVICE_HOST)
     fi
 }
 
@@ -228,6 +228,7 @@
     if [[ ! -r $DEVSTACK_CERT ]]; then
         if [[ -n "$TLS_IP" ]]; then
             # Lie to let incomplete match routines work
+            # see https://bugs.python.org/issue23239
             TLS_IP="DNS:$TLS_IP,IP:$TLS_IP"
         fi
         make_cert $INT_CA_DIR $DEVSTACK_CERT_NAME $DEVSTACK_HOSTNAME "$TLS_IP"
@@ -246,11 +247,6 @@
     local alt_names=$4
 
     if [ "$common_name" != "$SERVICE_HOST" ]; then
-        if [[ -z "$alt_names" ]]; then
-            alt_names="DNS:$SERVICE_HOST"
-        else
-            alt_names="$alt_names,DNS:$SERVICE_HOST"
-        fi
         if is_ipv4_address "$SERVICE_HOST" ; then
             alt_names="$alt_names,IP:$SERVICE_HOST"
         fi
diff --git a/openrc b/openrc
index 37724c5..99d3351 100644
--- a/openrc
+++ b/openrc
@@ -29,6 +29,7 @@
 # Load the last env variables if available
 if [[ -r $RC_DIR/.stackenv ]]; then
     source $RC_DIR/.stackenv
+    export OS_CACERT
 fi
 
 # Get some necessary configuration
@@ -108,5 +109,5 @@
 
 # Currently cinderclient needs you to specify the *volume api* version. This
 # needs to match the config of your catalog returned by Keystone.
-export CINDER_VERSION=${CINDER_VERSION:-2}
+export CINDER_VERSION=${CINDER_VERSION:-3}
 export OS_VOLUME_API_VERSION=${OS_VOLUME_API_VERSION:-$CINDER_VERSION}
diff --git a/pkg/elasticsearch.sh b/pkg/elasticsearch.sh
index fefd454..bd44153 100755
--- a/pkg/elasticsearch.sh
+++ b/pkg/elasticsearch.sh
@@ -37,7 +37,7 @@
 function download_elasticsearch {
     if is_ubuntu; then
         wget_elasticsearch elasticsearch-${ELASTICSEARCH_VERSION}.deb
-    elif is_fedora; then
+    elif is_fedora || is_suse; then
         wget_elasticsearch elasticsearch-${ELASTICSEARCH_VERSION}.noarch.rpm
     fi
 }
@@ -49,7 +49,7 @@
 
 function _check_elasticsearch_ready {
     # poll elasticsearch to see if it's started
-    if ! wait_for_service 30 http://localhost:9200; then
+    if ! wait_for_service 120 http://localhost:9200; then
         die $LINENO "Maximum timeout reached. Could not connect to ElasticSearch"
     fi
 }
@@ -61,6 +61,9 @@
     elif is_fedora; then
         sudo /bin/systemctl start elasticsearch.service
         _check_elasticsearch_ready
+    elif is_suse; then
+        sudo /usr/bin/systemctl start elasticsearch.service
+        _check_elasticsearch_ready
     else
         echo "Unsupported architecture...can not start elasticsearch."
     fi
@@ -71,6 +74,8 @@
         sudo /etc/init.d/elasticsearch stop
     elif is_fedora; then
         sudo /bin/systemctl stop elasticsearch.service
+    elif is_suse ; then
+        sudo /usr/bin/systemctl stop elasticsearch.service
     else
         echo "Unsupported architecture...can not stop elasticsearch."
     fi
@@ -92,6 +97,11 @@
         yum_install ${FILES}/elasticsearch-${ELASTICSEARCH_VERSION}.noarch.rpm
         sudo /bin/systemctl daemon-reload
         sudo /bin/systemctl enable elasticsearch.service
+    elif is_suse; then
+        is_package_installed java-1_8_0-openjdk-headless || install_package java-1_8_0-openjdk-headless
+        zypper_install --no-gpg-checks ${FILES}/elasticsearch-${ELASTICSEARCH_VERSION}.noarch.rpm
+        sudo /usr/bin/systemctl daemon-reload
+        sudo /usr/bin/systemctl enable elasticsearch.service
     else
         echo "Unsupported install of elasticsearch on this architecture."
     fi
@@ -103,6 +113,8 @@
             sudo apt-get purge elasticsearch
         elif is_fedora; then
             sudo yum remove elasticsearch
+        elif is_suse; then
+            sudo zypper rm elasticsearch
         else
             echo "Unsupported install of elasticsearch on this architecture."
         fi
diff --git a/roles/orchestrate-devstack/tasks/main.yaml b/roles/orchestrate-devstack/tasks/main.yaml
index 12db58c..f747943 100644
--- a/roles/orchestrate-devstack/tasks/main.yaml
+++ b/roles/orchestrate-devstack/tasks/main.yaml
@@ -6,6 +6,12 @@
 - name: Setup devstack on sub-nodes
   block:
 
+  - name: Distribute the build sshkey for the user "stack"
+    include_role:
+      name: copy-build-sshkey
+    vars:
+      copy_sshkey_target_user: 'stack'
+
   - name: Sync CA data to subnodes (when any)
     # Only do this if the tls-proxy service is defined and enabled
     include_role:
diff --git a/roles/write-devstack-local-conf/README.rst b/roles/write-devstack-local-conf/README.rst
index 73f9f0d..e9739cd 100644
--- a/roles/write-devstack-local-conf/README.rst
+++ b/roles/write-devstack-local-conf/README.rst
@@ -20,6 +20,15 @@
    bash shell variables, and will be ordered so that variables used by
    later entries appear first.
 
+   As a special case, the variable ``LIBS_FROM_GIT`` will be
+   constructed automatically from the projects which appear in the
+   ``required-projects`` list defined by the job plus the project of
+   the change under test.  To instruct devstack to install a library
+   from source rather than pypi, simply add that library to the job's
+   ``required-projects`` list.  To override the
+   automatically-generated value, set ``LIBS_FROM_GIT`` in
+   ``devstack_localrc`` to the desired value.
+
 .. zuul:rolevar:: devstack_local_conf
    :type: dict
 
@@ -75,3 +84,7 @@
    A dictionary mapping a plugin name to a git repo location.  If the
    location is a non-empty string, then an ``enable_plugin`` line will
    be emmitted for the plugin name.
+
+   If a plugin declares a dependency on another plugin (via
+   ``plugin_requires`` in the plugin's settings file), this role will
+   automatically emit ``enable_plugin`` lines in the correct order.
diff --git a/roles/write-devstack-local-conf/library/devstack_local_conf.py b/roles/write-devstack-local-conf/library/devstack_local_conf.py
index 55ba4af..bba7e31 100644
--- a/roles/write-devstack-local-conf/library/devstack_local_conf.py
+++ b/roles/write-devstack-local-conf/library/devstack_local_conf.py
@@ -14,16 +14,69 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+import os
 import re
 
 
-class VarGraph(object):
+class DependencyGraph(object):
     # This is based on the JobGraph from Zuul.
 
+    def __init__(self):
+        self._names = set()
+        self._dependencies = {}  # dependent_name -> set(parent_names)
+
+    def add(self, name, dependencies):
+        # Append the dependency information
+        self._dependencies.setdefault(name, set())
+        try:
+            for dependency in dependencies:
+                # Make sure a circular dependency is never created
+                ancestors = self._getParentNamesRecursively(
+                    dependency, soft=True)
+                ancestors.add(dependency)
+                if name in ancestors:
+                    raise Exception("Dependency cycle detected in {}".
+                                    format(name))
+                self._dependencies[name].add(dependency)
+        except Exception:
+            del self._dependencies[name]
+            raise
+
+    def getDependenciesRecursively(self, parent):
+        dependencies = []
+
+        current_dependencies = self._dependencies[parent]
+        for current in current_dependencies:
+            if current not in dependencies:
+                dependencies.append(current)
+            for dep in self.getDependenciesRecursively(current):
+                if dep not in dependencies:
+                    dependencies.append(dep)
+        return dependencies
+
+    def _getParentNamesRecursively(self, dependent, soft=False):
+        all_parent_items = set()
+        items_to_iterate = set([dependent])
+        while len(items_to_iterate) > 0:
+            current_item = items_to_iterate.pop()
+            current_parent_items = self._dependencies.get(current_item)
+            if current_parent_items is None:
+                if soft:
+                    current_parent_items = set()
+                else:
+                    raise Exception("Dependent item {} not found: ".format(
+                                    dependent))
+            new_parent_items = current_parent_items - all_parent_items
+            items_to_iterate |= new_parent_items
+            all_parent_items |= new_parent_items
+        return all_parent_items
+
+
+class VarGraph(DependencyGraph):
     def __init__(self, vars):
+        super(VarGraph, self).__init__()
         self.vars = {}
         self._varnames = set()
-        self._dependencies = {}  # dependent_var_name -> set(parent_var_names)
         for k, v in vars.items():
             self._varnames.add(k)
         for k, v in vars.items():
@@ -38,28 +91,21 @@
             raise Exception("Variable {} already added".format(key))
         self.vars[key] = value
         # Append the dependency information
-        self._dependencies.setdefault(key, set())
+        dependencies = set()
+        for dependency in self.getDependencies(value):
+            if dependency == key:
+                # A variable is allowed to reference itself; no
+                # dependency link needed in that case.
+                continue
+            if dependency not in self._varnames:
+                # It's not necessary to create a link for an
+                # external variable.
+                continue
+            dependencies.add(dependency)
         try:
-            for dependency in self.getDependencies(value):
-                if dependency == key:
-                    # A variable is allowed to reference itself; no
-                    # dependency link needed in that case.
-                    continue
-                if dependency not in self._varnames:
-                    # It's not necessary to create a link for an
-                    # external variable.
-                    continue
-                # Make sure a circular dependency is never created
-                ancestor_vars = self._getParentVarNamesRecursively(
-                    dependency, soft=True)
-                ancestor_vars.add(dependency)
-                if any((key == anc_var) for anc_var in ancestor_vars):
-                    raise Exception("Dependency cycle detected in var {}".
-                                    format(key))
-                self._dependencies[key].add(dependency)
+            self.add(key, dependencies)
         except Exception:
             del self.vars[key]
-            del self._dependencies[key]
             raise
 
     def getVars(self):
@@ -67,59 +113,118 @@
         keys = sorted(self.vars.keys())
         seen = set()
         for key in keys:
-            dependencies = self.getDependentVarsRecursively(key)
+            dependencies = self.getDependenciesRecursively(key)
             for var in dependencies + [key]:
                 if var not in seen:
                     ret.append((var, self.vars[var]))
                     seen.add(var)
         return ret
 
-    def getDependentVarsRecursively(self, parent_var):
-        dependent_vars = []
 
-        current_dependent_vars = self._dependencies[parent_var]
-        for current_var in current_dependent_vars:
-            if current_var not in dependent_vars:
-                dependent_vars.append(current_var)
-            for dep in self.getDependentVarsRecursively(current_var):
-                if dep not in dependent_vars:
-                    dependent_vars.append(dep)
-        return dependent_vars
+class PluginGraph(DependencyGraph):
+    def __init__(self, base_dir, plugins):
+        super(PluginGraph, self).__init__()
+        # The dependency trees expressed by all the plugins we found
+        # (which may be more than those the job is using).
+        self._plugin_dependencies = {}
+        self.loadPluginNames(base_dir)
 
-    def _getParentVarNamesRecursively(self, dependent_var, soft=False):
-        all_parent_vars = set()
-        vars_to_iterate = set([dependent_var])
-        while len(vars_to_iterate) > 0:
-            current_var = vars_to_iterate.pop()
-            current_parent_vars = self._dependencies.get(current_var)
-            if current_parent_vars is None:
-                if soft:
-                    current_parent_vars = set()
-                else:
-                    raise Exception("Dependent var {} not found: ".format(
-                                    dependent_var))
-            new_parent_vars = current_parent_vars - all_parent_vars
-            vars_to_iterate |= new_parent_vars
-            all_parent_vars |= new_parent_vars
-        return all_parent_vars
+        self.plugins = {}
+        self._pluginnames = set()
+        for k, v in plugins.items():
+            self._pluginnames.add(k)
+        for k, v in plugins.items():
+            self._addPlugin(k, str(v))
+
+    def loadPluginNames(self, base_dir):
+        if base_dir is None:
+            return
+        git_roots = []
+        for root, dirs, files in os.walk(base_dir):
+            if '.git' not in dirs:
+                continue
+            # Don't go deeper than git roots
+            dirs[:] = []
+            git_roots.append(root)
+        for root in git_roots:
+            devstack = os.path.join(root, 'devstack')
+            if not (os.path.exists(devstack) and os.path.isdir(devstack)):
+                continue
+            settings = os.path.join(devstack, 'settings')
+            if not (os.path.exists(settings) and os.path.isfile(settings)):
+                continue
+            self.loadDevstackPluginInfo(settings)
+
+    define_re = re.compile(r'^define_plugin\s+(\w+).*')
+    require_re = re.compile(r'^plugin_requires\s+(\w+)\s+(\w+).*')
+    def loadDevstackPluginInfo(self, fn):
+        name = None
+        reqs = set()
+        with open(fn) as f:
+            for line in f:
+                m = self.define_re.match(line)
+                if m:
+                    name = m.group(1)
+                m = self.require_re.match(line)
+                if m:
+                    if name == m.group(1):
+                        reqs.add(m.group(2))
+        if name and reqs:
+            self._plugin_dependencies[name] = reqs
+
+    def getDependencies(self, value):
+        return self._plugin_dependencies.get(value, [])
+
+    def _addPlugin(self, key, value):
+        if key in self.plugins:
+            raise Exception("Plugin {} already added".format(key))
+        self.plugins[key] = value
+        # Append the dependency information
+        dependencies = set()
+        for dependency in self.getDependencies(key):
+            if dependency == key:
+                continue
+            dependencies.add(dependency)
+        try:
+            self.add(key, dependencies)
+        except Exception:
+            del self.plugins[key]
+            raise
+
+    def getPlugins(self):
+        ret = []
+        keys = sorted(self.plugins.keys())
+        seen = set()
+        for key in keys:
+            dependencies = self.getDependenciesRecursively(key)
+            for plugin in dependencies + [key]:
+                if plugin not in seen:
+                    ret.append((plugin, self.plugins[plugin]))
+                    seen.add(plugin)
+        return ret
 
 
 class LocalConf(object):
 
-    def __init__(self, localrc, localconf, base_services, services, plugins):
+    def __init__(self, localrc, localconf, base_services, services, plugins,
+                 base_dir, projects, project):
         self.localrc = []
         self.meta_sections = {}
+        self.plugin_deps = {}
+        self.base_dir = base_dir
+        self.projects = projects
+        self.project = project
         if plugins:
             self.handle_plugins(plugins)
         if services or base_services:
             self.handle_services(base_services, services or {})
-        if localrc:
-            self.handle_localrc(localrc)
+        self.handle_localrc(localrc)
         if localconf:
             self.handle_localconf(localconf)
 
     def handle_plugins(self, plugins):
-        for k, v in plugins.items():
+        pg = PluginGraph(self.base_dir, plugins)
+        for k, v in pg.getPlugins():
             if v:
                 self.localrc.append('enable_plugin {} {}'.format(k, v))
 
@@ -137,9 +242,26 @@
                 self.localrc.append('enable_service {}'.format(k))
 
     def handle_localrc(self, localrc):
-        vg = VarGraph(localrc)
-        for k, v in vg.getVars():
-            self.localrc.append('{}={}'.format(k, v))
+        lfg = False
+        if localrc:
+            vg = VarGraph(localrc)
+            for k, v in vg.getVars():
+                self.localrc.append('{}={}'.format(k, v))
+                if k == 'LIBS_FROM_GIT':
+                    lfg = True
+
+        if not lfg and (self.projects or self.project):
+            required_projects = []
+            if self.projects:
+                for project_name, project_info in self.projects.items():
+                    if project_info.get('required'):
+                        required_projects.append(project_info['short_name'])
+            if self.project:
+                if self.project['short_name'] not in required_projects:
+                    required_projects.append(self.project['short_name'])
+            if required_projects:
+                self.localrc.append('LIBS_FROM_GIT={}'.format(
+                    ','.join(required_projects)))
 
     def handle_localconf(self, localconf):
         for phase, phase_data in localconf.items():
@@ -171,7 +293,10 @@
             services=dict(type='dict'),
             localrc=dict(type='dict'),
             local_conf=dict(type='dict'),
+            base_dir=dict(type='path'),
             path=dict(type='str'),
+            projects=dict(type='dict'),
+            project=dict(type='dict'),
         )
     )
 
@@ -180,14 +305,20 @@
                    p.get('local_conf'),
                    p.get('base_services'),
                    p.get('services'),
-                   p.get('plugins'))
+                   p.get('plugins'),
+                   p.get('base_dir'),
+                   p.get('projects'),
+                   p.get('project'))
     lc.write(p['path'])
 
     module.exit_json()
 
 
-from ansible.module_utils.basic import *  # noqa
-from ansible.module_utils.basic import AnsibleModule
+try:
+    from ansible.module_utils.basic import *  # noqa
+    from ansible.module_utils.basic import AnsibleModule
+except ImportError:
+    pass
 
 if __name__ == '__main__':
     main()
diff --git a/roles/write-devstack-local-conf/library/test.py b/roles/write-devstack-local-conf/library/test.py
new file mode 100644
index 0000000..791552d
--- /dev/null
+++ b/roles/write-devstack-local-conf/library/test.py
@@ -0,0 +1,235 @@
+# Copyright (C) 2017 Red Hat, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+#
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import os
+import shutil
+import tempfile
+import unittest
+
+from devstack_local_conf import LocalConf
+from collections import OrderedDict
+
+class TestDevstackLocalConf(unittest.TestCase):
+    def setUp(self):
+        self.tmpdir = tempfile.mkdtemp()
+
+    def tearDown(self):
+        shutil.rmtree(self.tmpdir)
+
+    def test_plugins(self):
+        "Test that plugins without dependencies work"
+        localrc = {'test_localrc': '1'}
+        local_conf = {'install':
+                      {'nova.conf':
+                       {'main':
+                        {'test_conf': '2'}}}}
+        services = {'cinder': True}
+        # We use ordereddict here to make sure the plugins are in the
+        # *wrong* order for testing.
+        plugins = OrderedDict([
+            ('bar', 'git://git.openstack.org/openstack/bar-plugin'),
+            ('foo', 'git://git.openstack.org/openstack/foo-plugin'),
+            ('baz', 'git://git.openstack.org/openstack/baz-plugin'),
+            ])
+        p = dict(localrc=localrc,
+                 local_conf=local_conf,
+                 base_services=[],
+                 services=services,
+                 plugins=plugins,
+                 base_dir='./test',
+                 path=os.path.join(self.tmpdir, 'test.local.conf'))
+        lc = LocalConf(p.get('localrc'),
+                       p.get('local_conf'),
+                       p.get('base_services'),
+                       p.get('services'),
+                       p.get('plugins'),
+                       p.get('base_dir'),
+                       p.get('projects'),
+                       p.get('project'))
+        lc.write(p['path'])
+
+        plugins = []
+        with open(p['path']) as f:
+            for line in f:
+                if line.startswith('enable_plugin'):
+                    plugins.append(line.split()[1])
+        self.assertEqual(['bar', 'baz', 'foo'], plugins)
+
+
+    def test_plugin_deps(self):
+        "Test that plugins with dependencies work"
+        os.makedirs(os.path.join(self.tmpdir, 'foo-plugin', 'devstack'))
+        os.makedirs(os.path.join(self.tmpdir, 'foo-plugin', '.git'))
+        os.makedirs(os.path.join(self.tmpdir, 'bar-plugin', 'devstack'))
+        os.makedirs(os.path.join(self.tmpdir, 'bar-plugin', '.git'))
+        with open(os.path.join(
+                self.tmpdir,
+                'foo-plugin', 'devstack', 'settings'), 'w') as f:
+            f.write('define_plugin foo\n')
+        with open(os.path.join(
+                self.tmpdir,
+                'bar-plugin', 'devstack', 'settings'), 'w') as f:
+            f.write('define_plugin bar\n')
+            f.write('plugin_requires bar foo\n')
+
+        localrc = {'test_localrc': '1'}
+        local_conf = {'install':
+                      {'nova.conf':
+                       {'main':
+                        {'test_conf': '2'}}}}
+        services = {'cinder': True}
+        # We use ordereddict here to make sure the plugins are in the
+        # *wrong* order for testing.
+        plugins = OrderedDict([
+            ('bar', 'git://git.openstack.org/openstack/bar-plugin'),
+            ('foo', 'git://git.openstack.org/openstack/foo-plugin'),
+            ])
+        p = dict(localrc=localrc,
+                 local_conf=local_conf,
+                 base_services=[],
+                 services=services,
+                 plugins=plugins,
+                 base_dir=self.tmpdir,
+                 path=os.path.join(self.tmpdir, 'test.local.conf'))
+
+    def test_libs_from_git(self):
+        "Test that LIBS_FROM_GIT is auto-generated"
+        projects = {
+            'git.openstack.org/openstack/nova': {
+                'required': True,
+                'short_name': 'nova',
+            },
+            'git.openstack.org/openstack/oslo.messaging': {
+                'required': True,
+                'short_name': 'oslo.messaging',
+            },
+            'git.openstack.org/openstack/devstack-plugin': {
+                'required': False,
+                'short_name': 'devstack-plugin',
+            },
+        }
+        project = {
+            'short_name': 'glance',
+        }
+        p = dict(base_services=[],
+                 base_dir='./test',
+                 path=os.path.join(self.tmpdir, 'test.local.conf'),
+                 projects=projects,
+                 project=project)
+        lc = LocalConf(p.get('localrc'),
+                       p.get('local_conf'),
+                       p.get('base_services'),
+                       p.get('services'),
+                       p.get('plugins'),
+                       p.get('base_dir'),
+                       p.get('projects'),
+                       p.get('project'))
+        lc.write(p['path'])
+
+        lfg = None
+        with open(p['path']) as f:
+            for line in f:
+                if line.startswith('LIBS_FROM_GIT'):
+                    lfg = line.strip().split('=')[1]
+        self.assertEqual('nova,oslo.messaging,glance', lfg)
+
+    def test_overridelibs_from_git(self):
+        "Test that LIBS_FROM_GIT can be overridden"
+        localrc = {'LIBS_FROM_GIT': 'oslo.db'}
+        projects = {
+            'git.openstack.org/openstack/nova': {
+                'required': True,
+                'short_name': 'nova',
+            },
+            'git.openstack.org/openstack/oslo.messaging': {
+                'required': True,
+                'short_name': 'oslo.messaging',
+            },
+            'git.openstack.org/openstack/devstack-plugin': {
+                'required': False,
+                'short_name': 'devstack-plugin',
+            },
+        }
+        p = dict(localrc=localrc,
+                 base_services=[],
+                 base_dir='./test',
+                 path=os.path.join(self.tmpdir, 'test.local.conf'),
+                 projects=projects)
+        lc = LocalConf(p.get('localrc'),
+                       p.get('local_conf'),
+                       p.get('base_services'),
+                       p.get('services'),
+                       p.get('plugins'),
+                       p.get('base_dir'),
+                       p.get('projects'),
+                       p.get('project'))
+        lc.write(p['path'])
+
+        lfg = None
+        with open(p['path']) as f:
+            for line in f:
+                if line.startswith('LIBS_FROM_GIT'):
+                    lfg = line.strip().split('=')[1]
+        self.assertEqual('oslo.db', lfg)
+
+    def test_plugin_circular_deps(self):
+        "Test that plugins with circular dependencies fail"
+        os.makedirs(os.path.join(self.tmpdir, 'foo-plugin', 'devstack'))
+        os.makedirs(os.path.join(self.tmpdir, 'foo-plugin', '.git'))
+        os.makedirs(os.path.join(self.tmpdir, 'bar-plugin', 'devstack'))
+        os.makedirs(os.path.join(self.tmpdir, 'bar-plugin', '.git'))
+        with open(os.path.join(
+                self.tmpdir,
+                'foo-plugin', 'devstack', 'settings'), 'w') as f:
+            f.write('define_plugin foo\n')
+            f.write('plugin_requires foo bar\n')
+        with open(os.path.join(
+                self.tmpdir,
+                'bar-plugin', 'devstack', 'settings'), 'w') as f:
+            f.write('define_plugin bar\n')
+            f.write('plugin_requires bar foo\n')
+
+        localrc = {'test_localrc': '1'}
+        local_conf = {'install':
+                      {'nova.conf':
+                       {'main':
+                        {'test_conf': '2'}}}}
+        services = {'cinder': True}
+        # We use ordereddict here to make sure the plugins are in the
+        # *wrong* order for testing.
+        plugins = OrderedDict([
+            ('bar', 'git://git.openstack.org/openstack/bar-plugin'),
+            ('foo', 'git://git.openstack.org/openstack/foo-plugin'),
+            ])
+        p = dict(localrc=localrc,
+                 local_conf=local_conf,
+                 base_services=[],
+                 services=services,
+                 plugins=plugins,
+                 base_dir=self.tmpdir,
+                 path=os.path.join(self.tmpdir, 'test.local.conf'))
+        with self.assertRaises(Exception):
+            lc = LocalConf(p.get('localrc'),
+                           p.get('local_conf'),
+                           p.get('base_services'),
+                           p.get('services'),
+                           p.get('plugins'),
+                           p.get('base_dir'))
+            lc.write(p['path'])
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/roles/write-devstack-local-conf/tasks/main.yaml b/roles/write-devstack-local-conf/tasks/main.yaml
index cc21426..9a6b083 100644
--- a/roles/write-devstack-local-conf/tasks/main.yaml
+++ b/roles/write-devstack-local-conf/tasks/main.yaml
@@ -8,3 +8,6 @@
     services: "{{ devstack_services|default(omit) }}"
     localrc: "{{ devstack_localrc|default(omit) }}"
     local_conf: "{{ devstack_local_conf|default(omit) }}"
+    base_dir: "{{ devstack_base_dir|default(omit) }}"
+    projects: "{{ zuul.projects }}"
+    project: "{{ zuul.project }}"
\ No newline at end of file
diff --git a/stack.sh b/stack.sh
index badc1a1..56e00bf 100755
--- a/stack.sh
+++ b/stack.sh
@@ -221,7 +221,7 @@
 
 # Warn users who aren't on an explicitly supported distro, but allow them to
 # override check and attempt installation with ``FORCE=yes ./stack``
-if [[ ! ${DISTRO} =~ (xenial|zesty|artful|stretch|jessie|f25|f26|f27|opensuse-42.3|opensuse-tumbleweed|rhel7) ]]; then
+if [[ ! ${DISTRO} =~ (xenial|artful|bionic|stretch|jessie|f27|f28|opensuse-42.3|opensuse-tumbleweed|rhel7) ]]; then
     echo "WARNING: this script has not been tested on $DISTRO"
     if [[ "$FORCE" != "yes" ]]; then
         die $LINENO "If you wish to run this script anyway run with FORCE=yes"
@@ -894,6 +894,8 @@
     stack_install_service neutron
 fi
 
+# Nova configuration is used by placement so we need to create nova.conf
+# first.
 if is_service_enabled nova; then
     # Compute service
     stack_install_service nova
@@ -1184,6 +1186,13 @@
     init_cinder
 fi
 
+# Placement Service
+# ---------------
+
+if is_service_enabled placement; then
+    echo_summary "Configuring placement"
+    init_placement
+fi
 
 # Compute Service
 # ---------------
@@ -1202,11 +1211,6 @@
     init_nova_cells
 fi
 
-if is_service_enabled placement; then
-    echo_summary "Configuring placement"
-    init_placement
-fi
-
 
 # Extras Configuration
 # ====================
@@ -1398,11 +1402,6 @@
 # Check the status of running services
 service_check
 
-# ensure that all the libraries we think we installed from git,
-# actually were.
-check_libs_from_git
-
-
 # Configure nova cellsv2
 # ----------------------
 
diff --git a/stackrc b/stackrc
index e8c35f4..34bd677 100644
--- a/stackrc
+++ b/stackrc
@@ -133,7 +133,7 @@
 # base name of the directory from which they are installed. See
 # enable_python3_package to edit this variable and use_python3_for to
 # test membership.
-export ENABLED_PYTHON3_PACKAGES="nova,glance,cinder,uwsgi,python-openstackclient,python-openstacksdk"
+export ENABLED_PYTHON3_PACKAGES="nova,glance,cinder,uwsgi,python-openstackclient,openstacksdk"
 
 # Explicitly list services not to run under Python 3. See
 # disable_python3_package to edit this variable.
@@ -258,7 +258,7 @@
 # Setting the variable to 'ALL' will activate the download for all
 # libraries.
 
-DEVSTACK_SERIES="rocky"
+DEVSTACK_SERIES="stein"
 
 ##############
 #
@@ -525,6 +525,10 @@
 GITBRANCH["ceilometermiddleware"]=${CEILOMETERMIDDLEWARE_BRANCH:-$TARGET_BRANCH}
 GITDIR["ceilometermiddleware"]=$DEST/ceilometermiddleware
 
+# openstacksdk OpenStack Python SDK
+GITREPO["openstacksdk"]=${OPENSTACKSDK_REPO:-${GIT_BASE}/openstack/openstacksdk.git}
+GITBRANCH["openstacksdk"]=${OPENSTACKSDK_BRANCH:-$TARGET_BRANCH}
+
 # os-brick library to manage local volume attaches
 GITREPO["os-brick"]=${OS_BRICK_REPO:-${GIT_BASE}/openstack/os-brick.git}
 GITBRANCH["os-brick"]=${OS_BRICK_BRANCH:-$TARGET_BRANCH}
@@ -542,10 +546,6 @@
 GITREPO["osc-lib"]=${OSC_LIB_REPO:-${GIT_BASE}/openstack/osc-lib.git}
 GITBRANCH["osc-lib"]=${OSC_LIB_BRANCH:-$TARGET_BRANCH}
 
-# python-openstacksdk OpenStack Python SDK
-GITREPO["python-openstacksdk"]=${OPENSTACKSDK_REPO:-${GIT_BASE}/openstack/python-openstacksdk.git}
-GITBRANCH["python-openstacksdk"]=${OPENSTACKSDK_BRANCH:-$TARGET_BRANCH}
-
 # ironic common lib
 GITREPO["ironic-lib"]=${IRONIC_LIB_REPO:-${GIT_BASE}/openstack/ironic-lib.git}
 GITBRANCH["ironic-lib"]=${IRONIC_LIB_BRANCH:-$TARGET_BRANCH}
@@ -625,12 +625,7 @@
 case "$VIRT_DRIVER" in
     ironic|libvirt)
         LIBVIRT_TYPE=${LIBVIRT_TYPE:-kvm}
-        # If ENABLE_VOLUME_MULTIATTACH is True, the Ubuntu Cloud Archive can't
-        # be used until it provides libvirt>=3.10, and with older versions of
-        # Ubuntu the group is "libvirtd".
-        # TODO(mriedem): Remove the ENABLE_VOLUME_MULTIATTACH check when
-        # UCA has libvirt>=3.10.
-        if [[ "$os_VENDOR" =~ (Debian|Ubuntu) && "${ENABLE_VOLUME_MULTIATTACH}" == "False" ]]; then
+        if [[ "$os_VENDOR" =~ (Debian|Ubuntu) ]]; then
             # The groups change with newer libvirt. Older Ubuntu used
             # 'libvirtd', but now uses libvirt like Debian. Do a quick check
             # to see if libvirtd group already exists to handle grenade's case.
@@ -737,11 +732,11 @@
 EXTRA_CACHE_URLS=""
 
 # etcd3 defaults
-ETCD_VERSION=${ETCD_VERSION:-v3.1.10}
-ETCD_SHA256_AMD64=${ETCD_SHA256_AMD64:-"2d335f298619c6fb02b1124773a56966e448ad9952b26fea52909da4fe80d2be"}
-# NOTE(sdague): etcd v3.1.10 doesn't have anything for these architectures, though 3.2.x does.
-ETCD_SHA256_ARM64=${ETCD_SHA256_ARM64:-""}
-ETCD_SHA256_PPC64=${ETCD_SHA256_PPC64:-""}
+ETCD_VERSION=${ETCD_VERSION:-v3.2.17}
+ETCD_SHA256_AMD64=${ETCD_SHA256_AMD64:-"0a75e794502e2e76417b19da2807a9915fa58dcbf0985e397741d570f4f305cd"}
+ETCD_SHA256_ARM64=${ETCD_SHA256_ARM64:-"0ab4621c44c79d17d94e43bd184d0f23b763a3669056ce4ae2d0b2942410a98f"}
+ETCD_SHA256_PPC64=${ETCD_SHA256_PPC64:-"69e1279c4a2a52256b78d2a8dd23346ac46b836e678b971a459f2afaef3c275e"}
+# etcd v3.2.x doesn't have anything for s390x
 ETCD_SHA256_S390X=${ETCD_SHA256_S390X:-""}
 # Make sure etcd3 downloads the correct architecture
 if is_arch "x86_64"; then
@@ -805,12 +800,7 @@
 # the memory used where there are a large number of CPUs present
 # (the default number of workers for many services is the number of CPUs)
 # Also sets the minimum number of workers to 2.
-if [[ "$VIRT_DRIVER" = 'fake' ]]; then
-    # we need more workers for the large ops job
-    API_WORKERS=${API_WORKERS:=$(( ($(nproc)/2)<2 ? 2 : ($(nproc)/2) ))}
-else
-    API_WORKERS=${API_WORKERS:=$(( ($(nproc)/4)<2 ? 2 : ($(nproc)/4) ))}
-fi
+API_WORKERS=${API_WORKERS:=$(( ($(nproc)/4)<2 ? 2 : ($(nproc)/4) ))}
 
 # Service startup timeout
 SERVICE_TIMEOUT=${SERVICE_TIMEOUT:-60}
@@ -824,11 +814,14 @@
 # Service graceful shutdown timeout
 WORKER_TIMEOUT=${WORKER_TIMEOUT:-90}
 
-# Support alternative yum -- in future Fedora 'dnf' will become the
-# only supported installer, but for now 'yum' and 'dnf' are both
-# available in parallel with compatible CLIs.  Allow manual switching
-# till we get to the point we need to handle this automatically
-YUM=${YUM:-yum}
+# Choose DNF on RedHat/Fedora platforms with it, or otherwise default
+# to YUM.  Can remove this when only dnf is supported (i.e. centos7
+# disappears)
+if [[ -e /usr/bin/dnf ]]; then
+    YUM=${YUM:-dnf}
+else
+    YUM=${YUM:-yum}
+fi
 
 # Common Configuration
 # --------------------
@@ -894,10 +887,10 @@
 
     DEF_SERVICE_HOST=[$HOST_IPV6]
     DEF_SERVICE_LOCAL_HOST=::1
-    DEF_SERVICE_LISTEN_ADDRESS=::
+    DEF_SERVICE_LISTEN_ADDRESS="[::]"
 fi
 
-# This is either 0.0.0.0 for IPv4 or :: for IPv6
+# This is either 0.0.0.0 for IPv4 or [::] for IPv6
 SERVICE_LISTEN_ADDRESS=${SERVICE_LISTEN_ADDRESS:-${DEF_SERVICE_LISTEN_ADDRESS}}
 
 # Allow the use of an alternate hostname (such as localhost/127.0.0.1) for
diff --git a/tests/test_ini_config.sh b/tests/test_ini_config.sh
index a5e1107..f7dc89a 100755
--- a/tests/test_ini_config.sh
+++ b/tests/test_ini_config.sh
@@ -44,6 +44,9 @@
 multi = foo1
 multi = foo2
 
+[key_with_spaces]
+rgw special key = something
+
 # inidelete(a)
 [del_separate_options]
 a=b
@@ -82,8 +85,9 @@
 
 # test iniget_sections
 VAL=$(iniget_sections "${TEST_INI}")
-assert_equal "$VAL" "default aaa bbb ccc ddd eee del_separate_options \
-del_same_option del_missing_option del_missing_option_multi del_no_options"
+assert_equal "$VAL" "default aaa bbb ccc ddd eee key_with_spaces \
+del_separate_options del_same_option del_missing_option \
+del_missing_option_multi del_no_options"
 
 # Test with missing arguments
 BEFORE=$(cat ${TEST_INI})
@@ -209,6 +213,20 @@
 VAL=$(iniget ${INI_TMP_ETC_DIR}/test.new.ini test foo)
 assert_equal "$VAL" "bar" "iniset created file"
 
+# test creation of keys with spaces
+iniset ${SUDO_ARG} ${TEST_INI} key_with_spaces "rgw another key" somethingelse
+VAL=$(iniget ${TEST_INI} key_with_spaces "rgw another key")
+assert_equal "$VAL" "somethingelse" "iniset created a key with spaces"
+
+# test update of keys with spaces
+iniset ${SUDO_ARG} ${TEST_INI} key_with_spaces "rgw special key" newvalue
+VAL=$(iniget ${TEST_INI} key_with_spaces "rgw special key")
+assert_equal "$VAL" "newvalue" "iniset updated a key with spaces"
+
+inidelete ${SUDO_ARG} ${TEST_INI} key_with_spaces "rgw another key"
+VAL=$(iniget ${TEST_INI} key_with_spaces "rgw another key")
+assert_empty VAL "inidelete removed a key with spaces"
+
 $SUDO rm -rf ${INI_TMP_DIR}
 
 report_results
diff --git a/tests/test_libs_from_pypi.sh b/tests/test_libs_from_pypi.sh
index a544b56..c3b4457 100755
--- a/tests/test_libs_from_pypi.sh
+++ b/tests/test_libs_from_pypi.sh
@@ -38,7 +38,7 @@
 ALL_LIBS+=" oslo.serialization"
 ALL_LIBS+=" python-openstackclient osc-lib osc-placement"
 ALL_LIBS+=" os-client-config oslo.rootwrap"
-ALL_LIBS+=" oslo.i18n oslo.utils python-openstacksdk python-swiftclient"
+ALL_LIBS+=" oslo.i18n oslo.utils openstacksdk python-swiftclient"
 ALL_LIBS+=" python-neutronclient tooz ceilometermiddleware oslo.policy"
 ALL_LIBS+=" debtcollector os-brick os-traits automaton futurist oslo.service"
 ALL_LIBS+=" oslo.cache oslo.reports osprofiler cursive"
diff --git a/tests/test_write_devstack_local_conf_role.sh b/tests/test_write_devstack_local_conf_role.sh
new file mode 100755
index 0000000..b2bc0a2
--- /dev/null
+++ b/tests/test_write_devstack_local_conf_role.sh
@@ -0,0 +1,9 @@
+#!/usr/bin/env bash
+
+TOP=$(cd $(dirname "$0")/.. && pwd)
+
+# Import common functions
+source $TOP/functions
+source $TOP/tests/unittest.sh
+
+python ./roles/write-devstack-local-conf/library/test.py
diff --git a/tools/cap-pip.txt b/tools/cap-pip.txt
index c280267..f5278d7 100644
--- a/tools/cap-pip.txt
+++ b/tools/cap-pip.txt
@@ -1 +1 @@
-pip!=8
+pip!=8,<10
diff --git a/tools/fixup_stuff.sh b/tools/fixup_stuff.sh
index 90b2c8b..9147932 100755
--- a/tools/fixup_stuff.sh
+++ b/tools/fixup_stuff.sh
@@ -77,28 +77,23 @@
 # Make it possible to switch this based on an environment variable as
 # libvirt 2.5.0 doesn't handle nested virtualization quite well and this
 # is required for the trove development environment.
-# The Pike UCA has qemu 2.10 but libvirt 3.6, therefore if
-# ENABLE_VOLUME_MULTIATTACH is True, we can't use the Pike UCA
-# because multiattach won't work with those package versions.
-# We can remove this check when the UCA has libvirt>=3.10.
 function fixup_uca {
-    if [[ "${ENABLE_UBUNTU_CLOUD_ARCHIVE}" == "False" || "$DISTRO" != "xenial" || \
-            "${ENABLE_VOLUME_MULTIATTACH}" == "True" ]]; then
+    if [[ "${ENABLE_UBUNTU_CLOUD_ARCHIVE}" == "False" || "$DISTRO" != "xenial" ]]; then
         return
     fi
 
     # This pulls in apt-add-repository
     install_package "software-properties-common"
-    # Use UCA for newer libvirt. Should give us libvirt 2.5.0.
+    # Use UCA for newer libvirt.
     if [[ -f /etc/ci/mirror_info.sh ]] ; then
         # If we are on a nodepool provided host and it has told us about where
         # we can find local mirrors then use that mirror.
         source /etc/ci/mirror_info.sh
 
-        sudo apt-add-repository -y "deb $NODEPOOL_UCA_MIRROR xenial-updates/pike main"
+        sudo apt-add-repository -y "deb $NODEPOOL_UCA_MIRROR xenial-updates/queens main"
     else
         # Otherwise use upstream UCA
-        sudo add-apt-repository -y cloud-archive:pike
+        sudo add-apt-repository -y cloud-archive:queens
     fi
 
     # Disable use of libvirt wheel since a cached wheel build might be