Merge "Export OS_CACERT after sourcing .stackenv file"
diff --git a/.zuul.yaml b/.zuul.yaml
index b772481..87eb8c5 100644
--- a/.zuul.yaml
+++ b/.zuul.yaml
@@ -49,6 +49,16 @@
           - controller
 
 - nodeset:
+    name: devstack-single-node-fedora-latest
+    nodes:
+      - name: controller
+        label: fedora-28
+    groups:
+      - name: tempest
+        nodes:
+          - controller
+
+- nodeset:
     name: openstack-two-node
     nodes:
       - name: controller
@@ -116,10 +126,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 +189,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 +204,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:
+      - 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.
@@ -231,7 +275,6 @@
       - openstack/keystone
       - openstack/neutron
       - openstack/nova
-      - openstack/requirements
       - openstack/swift
     timeout: 7200
     vars:
@@ -245,10 +288,6 @@
         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 }}"
       devstack_local_conf:
         post-config:
           $NEUTRON_CONF:
@@ -339,16 +378,9 @@
           # 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'] }}"
 
 - job:
     name: devstack-multinode
@@ -384,10 +416,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 +492,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:
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..7f360c6 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
diff --git a/doc/source/plugin-registry.rst b/doc/source/plugin-registry.rst
index c21e0ef..9b2cb7e 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>`__
@@ -163,6 +164,7 @@
 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>`__
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/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/inc/python b/inc/python
index ec4233b..96be107 100644
--- a/inc/python
+++ b/inc/python
@@ -429,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
@@ -555,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..f6cad9d 100644
--- a/lib/cinder
+++ b/lib/cinder
@@ -349,7 +349,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 +371,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/glance b/lib/glance
index 95d2450..528a05f 100644
--- a/lib/glance
+++ b/lib/glance
@@ -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/
@@ -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,18 +209,13 @@
     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
 
diff --git a/lib/keystone b/lib/keystone
index 714f089..696e351 100644
--- a/lib/keystone
+++ b/lib/keystone
@@ -202,7 +202,7 @@
     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
+        install -m 600 /dev/null $KEYSTONE_CONF
         if [[ -f "$KEYSTONE_DIR/etc/keystone-paste.ini" ]]; then
             cp -p "$KEYSTONE_DIR/etc/keystone-paste.ini" "$KEYSTONE_PASTE_INI"
         fi
@@ -220,7 +220,7 @@
         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"
diff --git a/lib/libraries b/lib/libraries
index 52ec784..b4f3c31 100644
--- a/lib/libraries
+++ b/lib/libraries
@@ -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..3cad80a 100644
--- a/lib/neutron
+++ b/lib/neutron
@@ -220,8 +220,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
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..20f2995 100644
--- a/lib/nova
+++ b/lib/nova
@@ -506,9 +506,17 @@
     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}"
 
@@ -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-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..2343ac9 100644
--- a/lib/placement
+++ b/lib/placement
@@ -112,7 +112,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
diff --git a/lib/swift b/lib/swift
index 6cda9c8..762f1dd 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
@@ -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 0605ffb..60f571c 100644
--- a/lib/tempest
+++ b/lib/tempest
@@ -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
@@ -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/openrc b/openrc
index 01ec6c6..99d3351 100644
--- a/openrc
+++ b/openrc
@@ -109,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/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..bfce9c9 100644
--- a/roles/write-devstack-local-conf/README.rst
+++ b/roles/write-devstack-local-conf/README.rst
@@ -20,6 +20,14 @@
    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.  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 +83,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 746f54f..9728fef 100644
--- a/roles/write-devstack-local-conf/library/devstack_local_conf.py
+++ b/roles/write-devstack-local-conf/library/devstack_local_conf.py
@@ -207,17 +207,17 @@
 class LocalConf(object):
 
     def __init__(self, localrc, localconf, base_services, services, plugins,
-                 base_dir):
+                 base_dir, projects):
         self.localrc = []
         self.meta_sections = {}
         self.plugin_deps = {}
         self.base_dir = base_dir
+        self.projects = projects
         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)
 
@@ -241,9 +241,22 @@
                 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:
+            required_projects = []
+            for project_name, project_info in self.projects.items():
+                if project_info.get('required'):
+                    required_projects.append(project_info['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():
@@ -277,6 +290,7 @@
             local_conf=dict(type='dict'),
             base_dir=dict(type='path'),
             path=dict(type='str'),
+            projects=dict(type='dict'),
         )
     )
 
@@ -286,7 +300,8 @@
                    p.get('base_services'),
                    p.get('services'),
                    p.get('plugins'),
-                   p.get('base_dir'))
+                   p.get('base_dir'),
+                   p.get('projects'))
     lc.write(p['path'])
 
     module.exit_json()
diff --git a/roles/write-devstack-local-conf/library/test.py b/roles/write-devstack-local-conf/library/test.py
index 843ca6e..7ccb68f 100644
--- a/roles/write-devstack-local-conf/library/test.py
+++ b/roles/write-devstack-local-conf/library/test.py
@@ -56,7 +56,8 @@
                        p.get('base_services'),
                        p.get('services'),
                        p.get('plugins'),
-                       p.get('base_dir'))
+                       p.get('base_dir'),
+                       p.get('projects'))
         lc.write(p['path'])
 
         plugins = []
@@ -66,6 +67,7 @@
                     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'))
@@ -101,20 +103,80 @@
                  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',
+            },
+        }
+        p = dict(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('base_dir'),
+                       p.get('projects'))
         lc.write(p['path'])
 
-        plugins = []
+        lfg = None
         with open(p['path']) as f:
             for line in f:
-                if line.startswith('enable_plugin'):
-                    plugins.append(line.split()[1])
-        self.assertEqual(['foo', 'bar'], plugins)
+                if line.startswith('LIBS_FROM_GIT'):
+                    lfg = line.strip().split('=')[1]
+        self.assertEqual('nova,oslo.messaging', 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'))
+        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"
diff --git a/roles/write-devstack-local-conf/tasks/main.yaml b/roles/write-devstack-local-conf/tasks/main.yaml
index 2a9f898..a294cae 100644
--- a/roles/write-devstack-local-conf/tasks/main.yaml
+++ b/roles/write-devstack-local-conf/tasks/main.yaml
@@ -9,3 +9,4 @@
     localrc: "{{ devstack_localrc|default(omit) }}"
     local_conf: "{{ devstack_local_conf|default(omit) }}"
     base_dir: "{{ devstack_base_dir|default(omit) }}"
+    projects: "{{ zuul.projects }}"
diff --git a/stack.sh b/stack.sh
index ce6e6fe..2528e2b 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|artful|bionic|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"
@@ -1398,11 +1398,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 f05bc6e..3c4e437 100644
--- a/stackrc
+++ b/stackrc
@@ -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
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