Merge "Create block-storage endpoint for cinder"
diff --git a/.zuul.yaml b/.zuul.yaml
new file mode 100644
index 0000000..037e9a9
--- /dev/null
+++ b/.zuul.yaml
@@ -0,0 +1,164 @@
+- nodeset:
+    name: openstack-single-node
+    nodes:
+      - name: controller
+        label: ubuntu-xenial
+    groups:
+      - name: tempest
+        nodes:
+          - controller
+
+- nodeset:
+    name: openstack-two-node
+    nodes:
+      - name: controller
+        label: ubuntu-xenial
+      - name: compute1
+        label: ubuntu-xenial
+    groups:
+      - name: tempest
+        nodes:
+          - controller
+      - name: compute
+        nodes:
+          - controller
+          - compute1
+      - name: subnode
+        nodes:
+          - compute1
+
+- job:
+    name: devstack
+    parent: multinode
+    description: Base devstack job
+    nodeset: openstack-single-node
+    required-projects:
+      - openstack-dev/devstack
+      - openstack/cinder
+      - openstack/glance
+      - openstack/keystone
+      - openstack/neutron
+      - openstack/nova
+      - openstack/requirements
+      - openstack/swift
+    roles:
+      - zuul: openstack-infra/devstack-gate
+      - zuul: openstack-infra/openstack-zuul-jobs
+    timeout: 7200
+    vars:
+      test_matrix_configs: ['neutron', 'tlsproxy']
+      devstack_localrc:
+        DATABASE_PASSWORD: secretdatabase
+        RABBIT_PASSWORD: secretrabbit
+        ADMIN_PASSWORD: secretadmin
+        SERVICE_PASSWORD: secretservice
+        NETWORK_GATEWAY: 10.1.0.1
+        Q_USE_DEBUG_COMMAND: true
+        FIXED_RANGE: 10.1.0.0/20
+        IPV4_ADDRS_SAFE_TO_USE: 10.1.0.0/20
+        FLOATING_RANGE: 172.24.5.0/24
+        PUBLIC_NETWORK_GATEWAY: 172.24.5.1
+        FLOATING_HOST_PREFIX: 172.24.4
+        FLOATING_HOST_MASK: 23
+        SWIFT_REPLICAS: 1
+        SWIFT_START_ALL_SERVICES: false
+        SWIFT_HASH: 1234123412341234
+        LOGFILE: /opt/stack/logs/devstacklog.txt
+        LOG_COLOR: false
+        VERBOSE: true
+        NOVNC_FROM_PACKAGE: true
+        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:
+        horizon: false
+        tempest: false
+    pre-run: playbooks/pre.yaml
+    run: playbooks/devstack.yaml
+    post-run: playbooks/post.yaml
+    irrelevant-files:
+      # Documentation related
+      - ^.*\.rst$
+      - ^api-ref/.*$
+      - ^doc/.*$
+      - ^releasenotes/.*$
+      # Translations
+      - ^.*/locale/.*po$
+
+- job:
+    name: devstack-multinode
+    parent: devstack
+    description: Base devstack multinode job
+    nodeset: openstack-two-node
+    # NOTE(andreaf) The multinode job is useful to see the setup of different
+    # services on different nodes, however the subnode configuration is not
+    # ready yet. Until then this job should stay non-voting.
+    voting: false
+
+- job:
+    name: devstack-tox-base
+    parent: devstack
+    description: |
+      Base job for devstack-based functional tests that use tox.
+
+      This job is not intended to be run directly. It's just here
+      for organizational purposes for devstack-tox-functional and
+      devstack-tox-functional-consumer.
+    post-run: playbooks/tox/post.yaml
+    vars:
+      tox_envlist: functional
+      tox_install_siblings: false
+
+- job:
+    name: devstack-tox-functional
+    parent: devstack-tox-base
+    description: |
+      Base job for devstack-based functional tests that use tox.
+
+      Runs devstack, then runs the tox ``functional`` environment,
+      then collects tox/testr build output like normal tox jobs.
+
+      Turns off tox sibling installation. Projects may be involved
+      in the devstack deployment and so may be in the required-projects
+      list, but may not want to test against master of the other
+      projects in their tox env. Child jobs can set tox_install_siblings
+      to True to re-enable sibling processing.
+    run: playbooks/tox/run-both.yaml
+
+- job:
+    name: devstack-tox-functional-consumer
+    parent: devstack
+    description: |
+      Base job for devstack-based functional tests for projects that
+      consume the devstack cloud.
+
+      This base job should only be used by projects that are not involved
+      in the devstack deployment step, but are instead projects that are using
+      devstack to get a cloud against which they can test things.
+
+      Runs devstack in pre-run, then runs the tox ``functional`` environment,
+      then collects tox/testr build output like normal tox jobs.
+
+      Turns off tox sibling installation. Projects may be involved
+      in the devstack deployment and so may be in the required-projects
+      list, but may not want to test against master of the other
+      projects in their tox env. Child jobs can set tox_install_siblings
+      to True to re-enable sibling processing.
+    pre-run:
+      - playbooks/devstack.yaml
+      - playbooks/tox/pre.yaml
+    run: playbooks/tox/run.yaml
+
+- project:
+    name: openstack-dev/devstack
+    check:
+      jobs:
+        - devstack
+        - devstack-multinode
+    gate:
+      jobs:
+        - devstack
diff --git a/doc/requirements.txt b/doc/requirements.txt
new file mode 100644
index 0000000..e140bc0
--- /dev/null
+++ b/doc/requirements.txt
@@ -0,0 +1,10 @@
+pbr>=2.0.0,!=2.1.0
+
+Pygments
+docutils
+sphinx>=1.6.2
+openstackdocstheme>=1.11.0
+nwdiag
+blockdiag
+sphinxcontrib-blockdiag
+sphinxcontrib-nwdiag
diff --git a/doc/source/configuration.rst b/doc/source/configuration.rst
index 23f680a..49cad05 100644
--- a/doc/source/configuration.rst
+++ b/doc/source/configuration.rst
@@ -294,7 +294,7 @@
 
 To query the logs use the ``journalctl`` command, such as::
 
-  journalctl --unit devstack@*
+  sudo journalctl --unit devstack@*
 
 More examples can be found in :ref:`journalctl-examples`.
 
@@ -667,7 +667,7 @@
 
         VOLUME_GROUP_NAME="stack-volumes"
         VOLUME_NAME_PREFIX="volume-"
-        VOLUME_BACKING_FILE_SIZE=10250M
+        VOLUME_BACKING_FILE_SIZE=24G
 
 
 Keystone
diff --git a/doc/source/overview.rst b/doc/source/overview.rst
index c07a8e6..814a2b1 100644
--- a/doc/source/overview.rst
+++ b/doc/source/overview.rst
@@ -24,7 +24,7 @@
 
 -  Ubuntu: current LTS release plus current development release
 -  Fedora: current release plus previous release
--  RHEL/Centos: current major release
+-  RHEL/CentOS: current major release
 -  Other OS platforms may continue to be included but the maintenance of
    those platforms shall not be assumed simply due to their presence.
    Having a listed point-of-contact for each additional OS will greatly
diff --git a/doc/source/plugin-registry.rst b/doc/source/plugin-registry.rst
index 6aa2e93..1dab9ce 100644
--- a/doc/source/plugin-registry.rst
+++ b/doc/source/plugin-registry.rst
@@ -26,7 +26,6 @@
 ====================================== ===
 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>`__
-app-catalog-ui                         `git://git.openstack.org/openstack/app-catalog-ui <https://git.openstack.org/cgit/openstack/app-catalog-ui>`__
 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>`__
@@ -34,7 +33,6 @@
 broadview-collector                    `git://git.openstack.org/openstack/broadview-collector <https://git.openstack.org/cgit/openstack/broadview-collector>`__
 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>`__
-cerberus                               `git://git.openstack.org/openstack/cerberus <https://git.openstack.org/cgit/openstack/cerberus>`__
 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>`__
 congress                               `git://git.openstack.org/openstack/congress <https://git.openstack.org/cgit/openstack/congress>`__
@@ -67,6 +65,7 @@
 glare                                  `git://git.openstack.org/openstack/glare <https://git.openstack.org/cgit/openstack/glare>`__
 group-based-policy                     `git://git.openstack.org/openstack/group-based-policy <https://git.openstack.org/cgit/openstack/group-based-policy>`__
 heat                                   `git://git.openstack.org/openstack/heat <https://git.openstack.org/cgit/openstack/heat>`__
+heat-dashboard                         `git://git.openstack.org/openstack/heat-dashboard <https://git.openstack.org/cgit/openstack/heat-dashboard>`__
 horizon-mellanox                       `git://git.openstack.org/openstack/horizon-mellanox <https://git.openstack.org/cgit/openstack/horizon-mellanox>`__
 ironic                                 `git://git.openstack.org/openstack/ironic <https://git.openstack.org/cgit/openstack/ironic>`__
 ironic-inspector                       `git://git.openstack.org/openstack/ironic-inspector <https://git.openstack.org/cgit/openstack/ironic-inspector>`__
@@ -95,6 +94,7 @@
 monasca-ceilometer                     `git://git.openstack.org/openstack/monasca-ceilometer <https://git.openstack.org/cgit/openstack/monasca-ceilometer>`__
 monasca-events-api                     `git://git.openstack.org/openstack/monasca-events-api <https://git.openstack.org/cgit/openstack/monasca-events-api>`__
 monasca-log-api                        `git://git.openstack.org/openstack/monasca-log-api <https://git.openstack.org/cgit/openstack/monasca-log-api>`__
+monasca-tempest-plugin                 `git://git.openstack.org/openstack/monasca-tempest-plugin <https://git.openstack.org/cgit/openstack/monasca-tempest-plugin>`__
 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>`__
@@ -114,6 +114,7 @@
 networking-hyperv                      `git://git.openstack.org/openstack/networking-hyperv <https://git.openstack.org/cgit/openstack/networking-hyperv>`__
 networking-infoblox                    `git://git.openstack.org/openstack/networking-infoblox <https://git.openstack.org/cgit/openstack/networking-infoblox>`__
 networking-l2gw                        `git://git.openstack.org/openstack/networking-l2gw <https://git.openstack.org/cgit/openstack/networking-l2gw>`__
+networking-lagopus                     `git://git.openstack.org/openstack/networking-lagopus <https://git.openstack.org/cgit/openstack/networking-lagopus>`__
 networking-midonet                     `git://git.openstack.org/openstack/networking-midonet <https://git.openstack.org/cgit/openstack/networking-midonet>`__
 networking-mlnx                        `git://git.openstack.org/openstack/networking-mlnx <https://git.openstack.org/cgit/openstack/networking-mlnx>`__
 networking-nec                         `git://git.openstack.org/openstack/networking-nec <https://git.openstack.org/cgit/openstack/networking-nec>`__
@@ -125,6 +126,7 @@
 networking-plumgrid                    `git://git.openstack.org/openstack/networking-plumgrid <https://git.openstack.org/cgit/openstack/networking-plumgrid>`__
 networking-powervm                     `git://git.openstack.org/openstack/networking-powervm <https://git.openstack.org/cgit/openstack/networking-powervm>`__
 networking-sfc                         `git://git.openstack.org/openstack/networking-sfc <https://git.openstack.org/cgit/openstack/networking-sfc>`__
+networking-spp                         `git://git.openstack.org/openstack/networking-spp <https://git.openstack.org/cgit/openstack/networking-spp>`__
 networking-vpp                         `git://git.openstack.org/openstack/networking-vpp <https://git.openstack.org/cgit/openstack/networking-vpp>`__
 networking-vsphere                     `git://git.openstack.org/openstack/networking-vsphere <https://git.openstack.org/cgit/openstack/networking-vsphere>`__
 neutron                                `git://git.openstack.org/openstack/neutron <https://git.openstack.org/cgit/openstack/neutron>`__
@@ -133,6 +135,7 @@
 neutron-fwaas-dashboard                `git://git.openstack.org/openstack/neutron-fwaas-dashboard <https://git.openstack.org/cgit/openstack/neutron-fwaas-dashboard>`__
 neutron-lbaas                          `git://git.openstack.org/openstack/neutron-lbaas <https://git.openstack.org/cgit/openstack/neutron-lbaas>`__
 neutron-lbaas-dashboard                `git://git.openstack.org/openstack/neutron-lbaas-dashboard <https://git.openstack.org/cgit/openstack/neutron-lbaas-dashboard>`__
+neutron-tempest-plugin                 `git://git.openstack.org/openstack/neutron-tempest-plugin <https://git.openstack.org/cgit/openstack/neutron-tempest-plugin>`__
 neutron-vpnaas                         `git://git.openstack.org/openstack/neutron-vpnaas <https://git.openstack.org/cgit/openstack/neutron-vpnaas>`__
 neutron-vpnaas-dashboard               `git://git.openstack.org/openstack/neutron-vpnaas-dashboard <https://git.openstack.org/cgit/openstack/neutron-vpnaas-dashboard>`__
 nova-dpm                               `git://git.openstack.org/openstack/nova-dpm <https://git.openstack.org/cgit/openstack/nova-dpm>`__
@@ -149,6 +152,7 @@
 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>`__
 rally                                  `git://git.openstack.org/openstack/rally <https://git.openstack.org/cgit/openstack/rally>`__
 sahara                                 `git://git.openstack.org/openstack/sahara <https://git.openstack.org/cgit/openstack/sahara>`__
@@ -159,9 +163,11 @@
 senlin                                 `git://git.openstack.org/openstack/senlin <https://git.openstack.org/cgit/openstack/senlin>`__
 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>`__
 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>`__
+telemetry-tempest-plugin               `git://git.openstack.org/openstack/telemetry-tempest-plugin <https://git.openstack.org/cgit/openstack/telemetry-tempest-plugin>`__
 tricircle                              `git://git.openstack.org/openstack/tricircle <https://git.openstack.org/cgit/openstack/tricircle>`__
 trio2o                                 `git://git.openstack.org/openstack/trio2o <https://git.openstack.org/cgit/openstack/trio2o>`__
 trove                                  `git://git.openstack.org/openstack/trove <https://git.openstack.org/cgit/openstack/trove>`__
@@ -169,6 +175,7 @@
 valet                                  `git://git.openstack.org/openstack/valet <https://git.openstack.org/cgit/openstack/valet>`__
 vitrage                                `git://git.openstack.org/openstack/vitrage <https://git.openstack.org/cgit/openstack/vitrage>`__
 vitrage-dashboard                      `git://git.openstack.org/openstack/vitrage-dashboard <https://git.openstack.org/cgit/openstack/vitrage-dashboard>`__
+vitrage-tempest-plugin                 `git://git.openstack.org/openstack/vitrage-tempest-plugin <https://git.openstack.org/cgit/openstack/vitrage-tempest-plugin>`__
 vmware-nsx                             `git://git.openstack.org/openstack/vmware-nsx <https://git.openstack.org/cgit/openstack/vmware-nsx>`__
 vmware-vspc                            `git://git.openstack.org/openstack/vmware-vspc <https://git.openstack.org/cgit/openstack/vmware-vspc>`__
 watcher                                `git://git.openstack.org/openstack/watcher <https://git.openstack.org/cgit/openstack/watcher>`__
diff --git a/doc/source/plugins.rst b/doc/source/plugins.rst
index fae1a1d..89b9381 100644
--- a/doc/source/plugins.rst
+++ b/doc/source/plugins.rst
@@ -54,6 +54,31 @@
   default value only if the variable is unset or empty; e.g. in bash
   syntax ``FOO=${FOO:-default}``.
 
+  The file should include a ``define_plugin`` line to indicate the
+  plugin's name, which is the name that should be used by users on
+  "enable_plugin" lines.  It should generally be the last component of
+  the git repo path (e.g., if the plugin's repo is
+  openstack/devstack-foo, then the name here should be "foo") ::
+
+    define_plugin <YOUR PLUGIN>
+
+  If your plugin depends on another plugin, indicate it in this file
+  with one or more lines like the following::
+
+    plugin_requires <YOUR PLUGIN> <OTHER PLUGIN>
+
+  For a complete example, if the plugin "foo" depends on "bar", the
+  ``settings`` file should include::
+
+    define_plugin foo
+    plugin_requires foo bar
+
+  Devstack does not currently use this dependency information, so it's
+  important that users continue to add enable_plugin lines in the
+  correct order in ``local.conf``, however adding this information
+  allows other tools to consider dependency information when
+  automatically generating ``local.conf`` files.
+
 - ``plugin.sh`` - the actual plugin. It is executed by devstack at
   well defined points during a ``stack.sh`` run. The plugin.sh
   internal structure is discussed below.
diff --git a/files/debs/general b/files/debs/general
index 8e0018d..df872a0 100644
--- a/files/debs/general
+++ b/files/debs/general
@@ -15,6 +15,7 @@
 libffi-dev # for pyOpenSSL
 libjpeg-dev # Pillow 3.0.0
 libmysqlclient-dev  # MySQL-python
+libpcre3-dev # for python-pcre
 libpq-dev  # psycopg2
 libssl-dev # for pyOpenSSL
 libsystemd-dev # for systemd-python
diff --git a/files/rpms/cinder b/files/rpms/cinder
index 3bc4e7a..e6addc6 100644
--- a/files/rpms/cinder
+++ b/files/rpms/cinder
@@ -1,5 +1,5 @@
 iscsi-initiator-utils
 lvm2
 qemu-img
-scsi-target-utils # not:rhel7,f24,f25,f26 NOPRIME
-targetcli # dist:rhel7,f24,f25,f26 NOPRIME
+scsi-target-utils # not:rhel7,f25,f26,f27 NOPRIME
+targetcli # dist:rhel7,f25,f26,f27 NOPRIME
diff --git a/files/rpms/general b/files/rpms/general
index f3f8708..5d9a4ad 100644
--- a/files/rpms/general
+++ b/files/rpms/general
@@ -9,9 +9,9 @@
 graphviz # needed only for docs
 httpd
 httpd-devel
-iptables-services  # NOPRIME f23,f24,f25,f26
+iptables-services  # NOPRIME f25,f26,f27
 java-1.7.0-openjdk-headless  # NOPRIME rhel7
-java-1.8.0-openjdk-headless  # NOPRIME f23,f24,f25,f26
+java-1.8.0-openjdk-headless  # NOPRIME f25,f26,f27
 libffi-devel
 libjpeg-turbo-devel # Pillow 3.0.0
 libxml2-devel # lxml
@@ -22,6 +22,7 @@
 openssh-server
 openssl
 openssl-devel # to rebuild pyOpenSSL if needed
+pcre-devel # for python-pcre
 pkgconfig
 postgresql-devel  # psycopg2
 psmisc
diff --git a/files/rpms/nova b/files/rpms/nova
index 64ed480..9fb7282 100644
--- a/files/rpms/nova
+++ b/files/rpms/nova
@@ -7,7 +7,7 @@
 genisoimage # required for config_drive
 iptables
 iputils
-kernel-modules # dist:f23,f24,f25,f26
+kernel-modules # dist:f25,f26,f27
 kpartx
 libxml2-python
 m2crypto
diff --git a/files/rpms/swift b/files/rpms/swift
index 2e09cec..be0db14 100644
--- a/files/rpms/swift
+++ b/files/rpms/swift
@@ -2,7 +2,7 @@
 liberasurecode-devel
 memcached
 pyxattr
-rsync-daemon # dist:f23,f24,f25,f26
+rsync-daemon # dist:f25,f26,f27
 sqlite
 xfsprogs
 xinetd
diff --git a/functions b/functions
index 8b69c73..959133c 100644
--- a/functions
+++ b/functions
@@ -364,7 +364,7 @@
     esac
 
     if is_arch "ppc64le" || is_arch "ppc64" || is_arch "ppc"; then
-        img_property="--property hw_disk_bus=scsi --property hw_scsi_model=virtio-scsi --property hw_cdrom_bus=scsi --property os_command_line=console=hvc0"
+        img_property="--property hw_cdrom_bus=scsi --property os_command_line=console=hvc0"
     fi
 
     if is_arch "aarch64"; then
@@ -503,13 +503,13 @@
 function get_instance_ip {
     local vm_id=$1
     local network_name=$2
-    local nova_result
+    local addresses
     local ip
 
-    nova_result="$(nova show $vm_id)"
-    ip=$(echo "$nova_result" | grep "$network_name" | get_field 2)
+    addresses=$(openstack server show -c addresses -f value "$vm_id")
+    ip=$(echo $addresses | sed -n "s/^.*$network_name=\([0-9\.]*\).*$/\1/p")
     if [[ $ip = "" ]];then
-        echo "$nova_result"
+        echo "addresses of server $vm_id : $addresses"
         die $LINENO "[Fail] Couldn't get ipaddress of VM"
     fi
     echo $ip
diff --git a/functions-common b/functions-common
index c968531..df295a3 100644
--- a/functions-common
+++ b/functions-common
@@ -45,6 +45,7 @@
 declare -A -g GITDIR
 
 TRACK_DEPENDS=${TRACK_DEPENDS:-False}
+KILL_PATH="$(which kill)"
 
 # Save these variables to .stackenv
 STACK_ENV_VARS="BASE_SQL_CONN DATA_DIR DEST ENABLED_SERVICES HOST_IP \
@@ -385,8 +386,6 @@
         DISTRO="rhel${os_RELEASE::1}"
     elif [[ "$os_VENDOR" =~ (XenServer) ]]; then
         DISTRO="xs${os_RELEASE%.*}"
-    elif [[ "$os_VENDOR" =~ (kvmibm) ]]; then
-        DISTRO="${os_VENDOR}${os_RELEASE::1}"
     else
         # We can't make a good choice here.  Setting a sensible DISTRO
         # is part of the problem, but not the major issue -- we really
@@ -440,7 +439,7 @@
     [ "$os_VENDOR" = "Fedora" ] || [ "$os_VENDOR" = "Red Hat" ] || \
         [ "$os_VENDOR" = "RedHatEnterpriseServer" ] || \
         [ "$os_VENDOR" = "CentOS" ] || [ "$os_VENDOR" = "OracleServer" ] || \
-        [ "$os_VENDOR" = "Virtuozzo" ] || [ "$os_VENDOR" = "kvmibm" ]
+        [ "$os_VENDOR" = "Virtuozzo" ]
 }
 
 
@@ -1393,7 +1392,8 @@
     iniset -sudo $unitfile "Service" "User" "$user"
     iniset -sudo $unitfile "Service" "ExecStart" "$command"
     iniset -sudo $unitfile "Service" "KillMode" "process"
-    iniset -sudo $unitfile "Service" "TimeoutStopSec" "infinity"
+    iniset -sudo $unitfile "Service" "TimeoutStopSec" "300"
+    iniset -sudo $unitfile "Service" "ExecReload" "$KILL_PATH -HUP \$MAINPID"
     if [[ -n "$group" ]]; then
         iniset -sudo $unitfile "Service" "Group" "$group"
     fi
@@ -1415,6 +1415,7 @@
     iniset -sudo $unitfile "Service" "SyslogIdentifier" "$service"
     iniset -sudo $unitfile "Service" "User" "$user"
     iniset -sudo $unitfile "Service" "ExecStart" "$command"
+    iniset -sudo $unitfile "Service" "ExecReload" "$KILL_PATH -HUP \$MAINPID"
     iniset -sudo $unitfile "Service" "Type" "notify"
     iniset -sudo $unitfile "Service" "KillMode" "process"
     iniset -sudo $unitfile "Service" "Restart" "always"
@@ -1492,7 +1493,6 @@
 # If the command includes shell metachatacters (;<>*) it must be run using a shell
 # If an optional group is provided sg will be used to run the
 # command as that group.
-# Uses globals ``USE_SCREEN``
 # run_process service "command-line" [group] [user]
 function run_process {
     local service=$1
@@ -1513,7 +1513,7 @@
 # If a PID is available use it, kill the whole process group via TERM
 # If screen is being used kill the screen window; this will catch processes
 # that did not leave a PID behind
-# Uses globals ``SERVICE_DIR``, ``USE_SCREEN``
+# Uses globals ``SERVICE_DIR``
 # stop_process service
 function stop_process {
     local service=$1
@@ -1703,6 +1703,35 @@
     fi
 }
 
+# define_plugin <name>
+#
+# This function is a no-op.  It allows a plugin to define its name So
+# that other plugins may reference it by name.  It should generally be
+# the last component of the canonical git repo name.  E.g.,
+# openstack/devstack-foo should use "devstack-foo" as the name here.
+#
+# This function is currently a noop, but the value may still be used
+# by external tools (as in plugin_requires) and may be used by
+# devstack in the future.
+#
+# ``name`` is an arbitrary name - (aka: glusterfs, nova-docker, zaqar)
+function define_plugin {
+    :
+}
+
+# plugin_requires <name> <other>
+#
+# This function is a no-op.  It is currently used by external tools
+# (such as the devstack module for Ansible) to automatically generate
+# local.conf files.  It is not currently used by devstack itself to
+# resolve dependencies.
+#
+# ``name`` is an arbitrary name - (aka: glusterfs, nova-docker, zaqar)
+# ``other`` is the name of another plugin
+function plugin_requires {
+    :
+}
+
 
 # Service Functions
 # =================
diff --git a/inc/python b/inc/python
index 9c810ec..e074ea4 100644
--- a/inc/python
+++ b/inc/python
@@ -49,7 +49,11 @@
     fi
     $xtrace
 
-    if is_fedora || is_suse; then
+    if python3_enabled && [ "$os_VENDOR" = "Fedora" -a $os_RELEASE -gt 26 ]; then
+        # Default Python 3 install prefix changed to /usr/local in Fedora 27:
+        # https://fedoraproject.org/wiki/Changes/Making_sudo_pip_safe
+        echo "/usr/local/bin"
+    elif is_fedora || is_suse; then
         echo "/usr/bin"
     else
         echo "/usr/local/bin"
@@ -333,7 +337,7 @@
     # packages like setuptools?
     local pip_version
     pip_version=$(python -c "import pip; \
-                        print(pip.__version__.strip('.')[0])")
+                        print(pip.__version__.split('.')[0])")
     if (( pip_version<6 )); then
         die $LINENO "Currently installed pip version ${pip_version} does not" \
             "meet minimum requirements (>=6)."
@@ -406,6 +410,15 @@
 # determine if a package was installed from git
 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
     # be smart about finding the remote of the git repo the package
     # was installed from.  This doesn't work with zuul which clones
@@ -415,11 +428,11 @@
     # you the path an editable install was installed from; for example
     # in response to something like
     #  pip install -e 'git+http://git.openstack.org/openstack-dev/bashate#egg=bashate'
-    # pip list shows
-    #  bashate (0.5.2.dev19, /tmp/env/src/bashate)
-    # Thus we look for "path after a comma" to indicate we were
-    # installed from some local place
-    pip list 2>/dev/null | grep -- "$name" | grep -q -- ', .*)$'
+    # pip list --format columns shows
+    #  bashate 0.5.2.dev19 /tmp/env/src/bashate
+    # Thus we check the third column to see if we're installed from
+    # some local place.
+    [[ -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
diff --git a/lib/apache b/lib/apache
index 39d5b7b..84cec73 100644
--- a/lib/apache
+++ b/lib/apache
@@ -133,8 +133,9 @@
         sudo rm -f /etc/httpd/conf.d/000-*
         install_package httpd mod_wsgi
         # For consistency with Ubuntu, switch to the worker mpm, as
-        # the default is prefork
+        # the default is event
         sudo sed -i '/mod_mpm_prefork.so/s/^/#/g' /etc/httpd/conf.modules.d/00-mpm.conf
+        sudo sed -i '/mod_mpm_event.so/s/^/#/g' /etc/httpd/conf.modules.d/00-mpm.conf
         sudo sed -i '/mod_mpm_worker.so/s/^#//g' /etc/httpd/conf.modules.d/00-mpm.conf
     elif is_suse; then
         install_package apache2 apache2-mod_wsgi
@@ -259,7 +260,7 @@
     iniset "$file" uwsgi master true
     # Set die-on-term & exit-on-reload so that uwsgi shuts down
     iniset "$file" uwsgi die-on-term true
-    iniset "$file" uwsgi exit-on-reload true
+    iniset "$file" uwsgi exit-on-reload false
     # Set worker-reload-mercy so that worker will not exit till the time
     # configured after graceful shutdown
     iniset "$file" uwsgi worker-reload-mercy $WORKER_TIMEOUT
@@ -282,7 +283,6 @@
     else
         local apache_conf=""
         apache_conf=$(apache_site_config_for $name)
-        echo "SetEnv proxy-sendcl 1" | sudo tee $apache_conf
         iniset "$file" uwsgi socket "$socket"
         iniset "$file" uwsgi chmod-socket 666
         echo "ProxyPass \"${url}\" \"unix:${socket}|uwsgi://uwsgi-uds-${name}/\" retry=0 " | sudo tee -a $apache_conf
@@ -316,7 +316,7 @@
     iniset "$file" uwsgi master true
     # Set die-on-term & exit-on-reload so that uwsgi shuts down
     iniset "$file" uwsgi die-on-term true
-    iniset "$file" uwsgi exit-on-reload true
+    iniset "$file" uwsgi exit-on-reload false
     iniset "$file" uwsgi enable-threads true
     iniset "$file" uwsgi plugins python
     # uwsgi recommends this to prevent thundering herd on accept.
@@ -345,6 +345,7 @@
     local apache_conf=""
     apache_conf=$(apache_site_config_for $name)
     echo "KeepAlive Off" | sudo tee $apache_conf
+    echo "SetEnv proxy-sendchunked 1" | sudo tee -a $apache_conf
     echo "ProxyPass \"${url}\" \"http://127.0.0.1:$port\" retry=0 " | sudo tee -a $apache_conf
     enable_apache_site $name
     restart_apache_server
diff --git a/lib/cinder b/lib/cinder
index c35b89d..96a7d5b 100644
--- a/lib/cinder
+++ b/lib/cinder
@@ -206,8 +206,6 @@
 function configure_cinder {
     sudo install -d -o $STACK_USER -m 755 $CINDER_CONF_DIR
 
-    cp -p $CINDER_DIR/etc/cinder/policy.json $CINDER_CONF_DIR
-
     rm -f $CINDER_CONF
 
     configure_rootwrap cinder
@@ -244,6 +242,7 @@
     iniset $CINDER_CONF DEFAULT my_ip "$HOST_IP"
 
     iniset $CINDER_CONF key_manager backend cinder.keymgr.conf_key_mgr.ConfKeyManager
+    iniset $CINDER_CONF key_manager fixed_key $(openssl rand -hex 16)
 
     if is_service_enabled c-vol && [[ -n "$CINDER_ENABLED_BACKENDS" ]]; then
         local enabled_backends=""
@@ -441,7 +440,7 @@
     setup_develop $CINDER_DIR
     if [[ "$CINDER_ISCSI_HELPER" == "tgtadm" ]]; then
         install_package tgt
-    elif [[ "$CINDER_ISCI_HELPER" == "lioadm" ]]; then
+    elif [[ "$CINDER_ISCSI_HELPER" == "lioadm" ]]; then
         install_package targetcli
     fi
 }
diff --git a/lib/databases/mysql b/lib/databases/mysql
index a0cf7a4..0089663 100644
--- a/lib/databases/mysql
+++ b/lib/databases/mysql
@@ -15,10 +15,9 @@
 
 register_database mysql
 
-# Linux distros, thank you for being incredibly consistent
-MYSQL=mysql
+MYSQL_SERVICE_NAME=mysql
 if is_fedora && ! is_oraclelinux; then
-    MYSQL=mariadb
+    MYSQL_SERVICE_NAME=mariadb
 fi
 
 # Functions
@@ -34,17 +33,17 @@
 
 # Get rid of everything enough to cleanly change database backends
 function cleanup_database_mysql {
-    stop_service $MYSQL
+    stop_service $MYSQL_SERVICE_NAME
     if is_ubuntu; then
         # Get ruthless with mysql
         apt_get purge -y mysql* mariadb*
         sudo rm -rf /var/lib/mysql
         sudo rm -rf /etc/mysql
         return
-    elif is_suse || is_oraclelinux; then
+    elif is_oraclelinux; then
         uninstall_package mysql-community-server
         sudo rm -rf /var/lib/mysql
-    elif is_fedora; then
+    elif is_suse || is_fedora; then
         uninstall_package mariadb-server
         sudo rm -rf /var/lib/mysql
     else
@@ -64,12 +63,9 @@
 
     if is_ubuntu; then
         my_conf=/etc/mysql/my.cnf
-        mysql=mysql
     elif is_suse || is_oraclelinux; then
         my_conf=/etc/my.cnf
-        mysql=mysql
     elif is_fedora; then
-        mysql=mariadb
         my_conf=/etc/my.cnf
         local cracklib_conf=/etc/my.cnf.d/cracklib_password_check.cnf
         if [ -f "$cracklib_conf" ]; then
@@ -82,7 +78,7 @@
     # Start mysql-server
     if is_fedora || is_suse; then
         # service is not started by default
-        start_service $mysql
+        start_service $MYSQL_SERVICE_NAME
     fi
 
     # Set the root password - only works the first time. For Ubuntu, we already
@@ -124,7 +120,7 @@
         iniset -sudo $my_conf mysqld log-queries-not-using-indexes 1
     fi
 
-    restart_service $mysql
+    restart_service $MYSQL_SERVICE_NAME
 }
 
 function install_database_mysql {
@@ -151,13 +147,11 @@
         chmod 0600 $HOME/.my.cnf
     fi
     # Install mysql-server
-    if is_suse || is_oraclelinux; then
-        if ! is_package_installed mariadb; then
-            install_package mysql-community-server
-        fi
-    elif is_fedora; then
+    if is_oraclelinux; then
+        install_package mysql-community-server
+    elif is_fedora || is_suse; then
         install_package mariadb-server
-        sudo systemctl enable mariadb
+        sudo systemctl enable $MYSQL_SERVICE_NAME
     elif is_ubuntu; then
         install_package mysql-server
     else
diff --git a/lib/etcd3 b/lib/etcd3
index 51df8e4..d3f7226 100644
--- a/lib/etcd3
+++ b/lib/etcd3
@@ -107,9 +107,11 @@
 
         tar xzvf $etcd_file -C $FILES
         sudo cp $FILES/$ETCD_NAME/etcd $ETCD_BIN_DIR/etcd
+        sudo cp $FILES/$ETCD_NAME/etcdctl $ETCD_BIN_DIR/etcdctl
     fi
     if [ ! -f "$ETCD_BIN_DIR/etcd" ]; then
         sudo cp $FILES/$ETCD_NAME/etcd $ETCD_BIN_DIR/etcd
+        sudo cp $FILES/$ETCD_NAME/etcdctl $ETCD_BIN_DIR/etcdctl
     fi
 }
 
diff --git a/lib/glance b/lib/glance
index 74734c7..95d2450 100644
--- a/lib/glance
+++ b/lib/glance
@@ -56,6 +56,7 @@
 GLANCE_POLICY_JSON=$GLANCE_CONF_DIR/policy.json
 GLANCE_SCHEMA_JSON=$GLANCE_CONF_DIR/schema-image.json
 GLANCE_SWIFT_STORE_CONF=$GLANCE_CONF_DIR/glance-swift-store.conf
+GLANCE_IMAGE_IMPORT_CONF=$GLANCE_CONF_DIR/glance-image-import.conf
 GLANCE_V1_ENABLED=${GLANCE_V1_ENABLED:-False}
 
 if is_service_enabled tls-proxy; then
@@ -105,6 +106,11 @@
 function configure_glance {
     sudo install -d -o $STACK_USER $GLANCE_CONF_DIR $GLANCE_METADEF_DIR
 
+    # We run this here as this configures cache dirs for the auth middleware
+    # which is used in the api server and not in the registry. The api
+    # 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
     iniset $GLANCE_REGISTRY_CONF DEFAULT debug $ENABLE_DEBUG_LOG_LEVEL
@@ -227,6 +233,11 @@
     # Store specific confs
     iniset $GLANCE_CACHE_CONF glance_store filesystem_store_datadir $GLANCE_IMAGE_DIR/
 
+    # Set default configuration options for the glance-image-import
+    iniset $GLANCE_IMAGE_IMPORT_CONF image_import_opts image_import_plugins []
+    iniset $GLANCE_IMAGE_IMPORT_CONF inject_metadata_properties ignore_user_roles admin
+    iniset $GLANCE_IMAGE_IMPORT_CONF inject_metadata_properties inject
+
     cp -p $GLANCE_DIR/etc/policy.json $GLANCE_POLICY_JSON
     cp -p $GLANCE_DIR/etc/schema-image.json $GLANCE_SCHEMA_JSON
 
@@ -279,7 +290,7 @@
     fi
 }
 
-# create_glance_cache_dir() - Part of the init_glance() process
+# create_glance_cache_dir() - Part of the configure_glance() process
 function create_glance_cache_dir {
     # Create cache dir
     sudo install -d -o $STACK_USER $GLANCE_AUTH_CACHE_DIR/api $GLANCE_AUTH_CACHE_DIR/registry $GLANCE_AUTH_CACHE_DIR/search $GLANCE_AUTH_CACHE_DIR/artifact
@@ -306,8 +317,6 @@
     # Load metadata definitions
     $GLANCE_BIN_DIR/glance-manage --config-file $GLANCE_CONF_DIR/glance-api.conf db_load_metadefs
     time_stop "dbsync"
-
-    create_glance_cache_dir
 }
 
 # install_glanceclient() - Collect source and prepare
diff --git a/lib/neutron b/lib/neutron
index 21c8d4c..c5839f5 100644
--- a/lib/neutron
+++ b/lib/neutron
@@ -243,7 +243,7 @@
         cp $NEUTRON_DIR/etc/metadata_agent.ini.sample $NEUTRON_META_CONF
 
         iniset $NEUTRON_META_CONF DEFAULT debug $ENABLE_DEBUG_LOG_LEVEL
-        iniset $NEUTRON_META_CONF DEFAULT nova_metadata_ip $SERVICE_HOST
+        iniset $NEUTRON_META_CONF DEFAULT nova_metadata_host $SERVICE_HOST
         iniset $NEUTRON_META_CONF DEFAULT metadata_workers $API_WORKERS
         # TODO(ihrachys) do we really need to set rootwrap for metadata agent?
         configure_root_helper_options $NEUTRON_META_CONF
diff --git a/lib/neutron-legacy b/lib/neutron-legacy
index 0ccb17c..bb76c5f 100644
--- a/lib/neutron-legacy
+++ b/lib/neutron-legacy
@@ -757,7 +757,7 @@
     cp $NEUTRON_DIR/etc/metadata_agent.ini.sample $Q_META_CONF_FILE
 
     iniset $Q_META_CONF_FILE DEFAULT debug $ENABLE_DEBUG_LOG_LEVEL
-    iniset $Q_META_CONF_FILE DEFAULT nova_metadata_ip $Q_META_DATA_IP
+    iniset $Q_META_CONF_FILE DEFAULT nova_metadata_host $Q_META_DATA_IP
     iniset $Q_META_CONF_FILE DEFAULT metadata_workers $API_WORKERS
     iniset $Q_META_CONF_FILE AGENT root_helper "$Q_RR_COMMAND"
     if [[ "$Q_USE_ROOTWRAP_DAEMON" == "True" ]]; then
diff --git a/lib/neutron_plugins/services/l3 b/lib/neutron_plugins/services/l3
index 98315b7..41a467d 100644
--- a/lib/neutron_plugins/services/l3
+++ b/lib/neutron_plugins/services/l3
@@ -188,7 +188,7 @@
             if [ -z $SUBNETPOOL_V4_ID ]; then
                 fixed_range_v4=$FIXED_RANGE
             fi
-            SUBNET_ID=$(openstack --os-cloud devstack-admin --os-region "$REGION_NAME" subnet create --project $project_id --ip-version 4 ${ALLOCATION_POOL:+--allocation-pool $ALLOCATION_POOL} $PROVIDER_SUBNET_NAME --gateway $NETWORK_GATEWAY ${SUBNETPOOL_V4_ID:+--subnet-pool $SUBNETPOOL_V4_ID} --network $NET_ID --subnet-range $fixed_range_v4 | grep ' id ' | get_field 2)
+            SUBNET_ID=$(openstack --os-cloud devstack-admin --os-region "$REGION_NAME" subnet create --project $project_id --ip-version 4 ${ALLOCATION_POOL:+--allocation-pool $ALLOCATION_POOL} $PROVIDER_SUBNET_NAME --gateway $NETWORK_GATEWAY ${SUBNETPOOL_V4_ID:+--subnet-pool $SUBNETPOOL_V4_ID} --network $NET_ID ${fixed_range_v4:+--subnet-range $fixed_range_v4} | grep ' id ' | get_field 2)
             die_if_not_set $LINENO SUBNET_ID "Failure creating SUBNET_ID for $PROVIDER_SUBNET_NAME $project_id"
         fi
 
@@ -198,7 +198,7 @@
             if [ -z $SUBNETPOOL_V6_ID ]; then
                 fixed_range_v6=$IPV6_PROVIDER_FIXED_RANGE
             fi
-            IPV6_SUBNET_ID=$(openstack --os-cloud devstack-admin --os-region "$REGION_NAME" subnet create --project $project_id --ip-version 6 --gateway $IPV6_PROVIDER_NETWORK_GATEWAY $IPV6_PROVIDER_SUBNET_NAME ${SUBNETPOOL_V6_ID:+--subnet-pool $SUBNETPOOL_V6_ID} --network $NET_ID --subnet-range $fixed_range_v6 | grep ' id ' | get_field 2)
+            IPV6_SUBNET_ID=$(openstack --os-cloud devstack-admin --os-region "$REGION_NAME" subnet create --project $project_id --ip-version 6 --gateway $IPV6_PROVIDER_NETWORK_GATEWAY $IPV6_PROVIDER_SUBNET_NAME ${SUBNETPOOL_V6_ID:+--subnet-pool $SUBNETPOOL_V6_ID} --network $NET_ID ${fixed_range_v6:+--subnet-range $fixed_range_v6} | grep ' id ' | get_field 2)
             die_if_not_set $LINENO IPV6_SUBNET_ID "Failure creating IPV6_SUBNET_ID for $IPV6_PROVIDER_SUBNET_NAME $project_id"
         fi
 
diff --git a/lib/nova b/lib/nova
index 1112f29..c40c43a 100644
--- a/lib/nova
+++ b/lib/nova
@@ -520,8 +520,8 @@
         # 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 vncserver_listen "$VNCSERVER_LISTEN"
-        iniset $NOVA_CONF vnc vncserver_proxyclient_address "$VNCSERVER_PROXYCLIENT_ADDRESS"
+        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"
     else
@@ -589,6 +589,8 @@
             local vhost
             conf=$(conductor_conf $i)
             vhost="nova_cell${i}"
+            # clean old conductor conf
+            rm -f $conf
             iniset $conf database connection `database_connection_url nova_cell${i}`
             iniset $conf conductor workers "$API_WORKERS"
             iniset $conf DEFAULT debug "$ENABLE_DEBUG_LOG_LEVEL"
@@ -598,6 +600,12 @@
             else
                 rpc_backend_add_vhost $vhost
                 iniset_rpc_backend nova $conf DEFAULT $vhost
+                # When running in superconductor mode, the cell conductor
+                # must be configured to talk to the placement service for
+                # reschedules to work.
+                if is_service_enabled placement placement-client; then
+                    configure_placement_nova_compute $conf
+                fi
             fi
             # Format logging
             setup_logging $conf
diff --git a/lib/nova_plugins/functions-libvirt b/lib/nova_plugins/functions-libvirt
index 8d74c77..147ed8b 100644
--- a/lib/nova_plugins/functions-libvirt
+++ b/lib/nova_plugins/functions-libvirt
@@ -25,7 +25,7 @@
 DEBUG_LIBVIRT_COREDUMPS=$(trueorfalse False DEBUG_LIBVIRT_COREDUMPS)
 
 # Only Xenial is left with libvirt-bin.  Everywhere else is libvirtd
-if is_ubuntu && [ ! -f /etc/init.d/libvirtd ]; then
+if is_ubuntu && [ ${DISTRO} == "xenial" ]; then
     LIBVIRT_DAEMON=libvirt-bin
 else
     LIBVIRT_DAEMON=libvirtd
@@ -72,10 +72,12 @@
         pip_install_gr libvirt-python
         #pip_install_gr <there-si-no-guestfs-in-pypi>
     elif is_fedora || is_suse; then
-        # On "KVM for IBM z Systems", kvm does not have its own package
-        if [[ ! ${DISTRO} =~ "kvmibm1" ]]; then
-            install_package qemu-kvm
-        fi
+
+        # Note that in CentOS/RHEL this needs to come from the RDO
+        # repositories (qemu-kvm-ev ... which provides this package)
+        # as the base system version is too old.  We should have
+        # pre-installed these
+        install_package qemu-kvm
 
         install_package libvirt libvirt-devel
         pip_uninstall libvirt-python
diff --git a/lib/nova_plugins/hypervisor-libvirt b/lib/nova_plugins/hypervisor-libvirt
index 0c08a0f..3d676b9 100644
--- a/lib/nova_plugins/hypervisor-libvirt
+++ b/lib/nova_plugins/hypervisor-libvirt
@@ -71,8 +71,8 @@
         iniset $NOVA_CONF libvirt connection_uri "parallels+unix:///system"
         iniset $NOVA_CONF libvirt images_type "ploop"
         iniset $NOVA_CONF DEFAULT force_raw_images  "False"
-        iniset $NOVA_CONF vnc vncserver_proxyclient_address  $HOST_IP
-        iniset $NOVA_CONF vnc vncserver_listen $HOST_IP
+        iniset $NOVA_CONF vnc server_proxyclient_address  $HOST_IP
+        iniset $NOVA_CONF vnc server_listen $HOST_IP
         iniset $NOVA_CONF vnc keymap
     elif [[ "$NOVA_BACKEND" == "LVM" ]]; then
         iniset $NOVA_CONF libvirt images_type "lvm"
diff --git a/lib/placement b/lib/placement
index d3fb8c8..1d68f8a 100644
--- a/lib/placement
+++ b/lib/placement
@@ -71,6 +71,7 @@
 function cleanup_placement {
     sudo rm -f $(apache_site_config_for nova-placement-api)
     sudo rm -f $(apache_site_config_for placement-api)
+    remove_uwsgi_config "$PLACEMENT_UWSGI_CONF" "$PLACEMENT_UWSGI"
 }
 
 # _config_placement_apache_wsgi() - Set WSGI config files
@@ -102,14 +103,16 @@
 }
 
 function configure_placement_nova_compute {
-    iniset $NOVA_CONF placement auth_type "password"
-    iniset $NOVA_CONF placement auth_url "$KEYSTONE_SERVICE_URI"
-    iniset $NOVA_CONF placement username placement
-    iniset $NOVA_CONF placement password "$SERVICE_PASSWORD"
-    iniset $NOVA_CONF placement user_domain_name "$SERVICE_DOMAIN_NAME"
-    iniset $NOVA_CONF placement project_name "$SERVICE_TENANT_NAME"
-    iniset $NOVA_CONF placement project_domain_name "$SERVICE_DOMAIN_NAME"
-    iniset $NOVA_CONF placement os_region_name "$REGION_NAME"
+    # Use the provided config file path or default to $NOVA_CONF.
+    local conf=${1:-$NOVA_CONF}
+    iniset $conf placement auth_type "password"
+    iniset $conf placement auth_url "$KEYSTONE_SERVICE_URI"
+    iniset $conf placement username placement
+    iniset $conf placement password "$SERVICE_PASSWORD"
+    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
@@ -188,7 +191,6 @@
 function stop_placement {
     if [[ "$WSGI_MODE" == "uwsgi" ]]; then
         stop_process "placement-api"
-        remove_uwsgi_config "$PLACEMENT_UWSGI_CONF" "$PLACEMENT_UWSGI"
     else
         disable_apache_site placement-api
         restart_apache_server
diff --git a/lib/tempest b/lib/tempest
index bdbaaa5..35dbb7b 100644
--- a/lib/tempest
+++ b/lib/tempest
@@ -310,7 +310,6 @@
     fi
 
     # Image Features
-    iniset $TEMPEST_CONFIG image-feature-enabled deactivate_image True
     if [ "$GLANCE_V1_ENABLED" != "True" ]; then
         iniset $TEMPEST_CONFIG image-feature-enabled api_v1 False
     fi
diff --git a/lib/tls b/lib/tls
index 0baf86c..0bc389b 100644
--- a/lib/tls
+++ b/lib/tls
@@ -564,6 +564,20 @@
 # using tls configuration are down.
 function stop_tls_proxy {
     stop_apache_server
+
+    # NOTE(jh): Removing all tls-proxy configs is a bit of a hack, but
+    # necessary so that we can restart after an unstack.  A better
+    # solution would be to ensure that each service calling
+    # start_tls_proxy will call stop_tls_proxy with the same
+    # parameters on shutdown so we can use the disable_apache_site
+    # function and remove individual files there.
+    if is_ubuntu; then
+        sudo rm -f /etc/apache2/sites-enabled/*-tls-proxy.conf
+    else
+        for i in $APACHE_CONF_DIR/*-tls-proxy.conf; do
+            sudo mv $i $i.disabled
+        done
+    fi
 }
 
 # Clean up the CA files
diff --git a/playbooks/devstack.yaml b/playbooks/devstack.yaml
new file mode 100644
index 0000000..ede8382
--- /dev/null
+++ b/playbooks/devstack.yaml
@@ -0,0 +1,3 @@
+- hosts: all
+  roles:
+    - run-devstack
diff --git a/playbooks/post.yaml b/playbooks/post.yaml
new file mode 100644
index 0000000..0c5e83b
--- /dev/null
+++ b/playbooks/post.yaml
@@ -0,0 +1,72 @@
+- hosts: all
+  become: True
+  vars:
+    devstack_log_dir: "{{ devstack_base_dir|default('/opt/stack') }}/logs/"
+    devstack_conf_dir: "{{ devstack_base_dir|default('/opt/stack') }}/devstack/"
+    devstack_full_log: "{{ devstack_early_log|default('/opt/stack/logs/devstack-early.txt') }}"
+  tasks:
+    # NOTE(andreaf) If the tempest service is enabled, a tempest.log is
+    # generated as part of lib/tempest, as a result of verify_tempest_config
+    - name: Check if a tempest log exits
+      stat:
+        path: "{{ devstack_conf_dir }}/tempest.log"
+      register: tempest_log
+    - name: Link post-devstack tempest.log
+      file:
+        src: "{{ devstack_conf_dir }}/tempest.log"
+        dest: "{{ stage_dir }}/verify_tempest_conf.log"
+        state: hard
+      when: tempest_log.stat.exists
+  roles:
+    - export-devstack-journal
+    - apache-logs-conf
+    - devstack-project-conf
+    # capture-system-logs should be the last role before stage-output
+    - capture-system-logs
+    - role: stage-output
+      zuul_copy_output:
+        { '{{ devstack_conf_dir }}/local.conf': 'logs',
+          '{{ devstack_conf_dir }}/localrc': 'logs',
+          '{{ devstack_conf_dir }}/.stackenv': 'logs' ,
+          '{{ devstack_log_dir }}/dstat-csv.log': 'logs',
+          '{{ devstack_log_dir }}/devstacklog.txt': 'logs',
+          '{{ devstack_log_dir }}/devstacklog.txt.summary': 'logs',
+          '{{ devstack_full_log}}': 'logs',
+          '{{ stage_dir }}/verify_tempest_conf.log': 'logs',
+          '{{ stage_dir }}/apache': 'logs',
+          '{{ stage_dir }}/apache_config': 'logs',
+          '{{ stage_dir }}/etc': 'logs',
+          '/var/log/rabbitmq': 'logs',
+          '/var/log/postgresql': 'logs',
+          '/var/log/mysql.err': 'logs',
+          '/var/log/mysql.log': 'logs',
+          '/var/log/libvirt': 'logs',
+          '/etc/sudoers': 'logs',
+          '/etc/sudoers.d': 'logs',
+          '{{ stage_dir }}/iptables.txt': 'logs',
+          '{{ stage_dir }}/df.txt': 'logs',
+          '{{ stage_dir }}/pip2-freeze.txt': 'logs',
+          '{{ stage_dir }}/pip3-freeze.txt': 'logs',
+          '{{ stage_dir }}/dpkg-l.txt': 'logs',
+          '{{ stage_dir }}/rpm-qa.txt': 'logs',
+          '{{ stage_dir }}/core': 'logs',
+          '{{ stage_dir }}/listen53.txt': 'logs',
+          '{{ stage_dir }}/deprecations.log': 'logs',
+          '/var/log/ceph': 'logs',
+          '/var/log/openvswitch': 'logs',
+          '/var/log/glusterfs': 'logs',
+          '/etc/glusterfs/glusterd.vol': 'logs',
+          '/etc/resolv.conf': 'logs',
+          '/var/log/unbound.log': 'logs' }
+      extensions_to_txt:
+        - conf
+        - log
+        - localrc
+        - stackenv
+        - summary
+    # NOTE(andreaf) We need fetch-devstack-log-dir only as long as the base job
+    # starts pulling logs for us from {{ ansible_user_dir }}/logs.
+    # Meanwhile we already store things in ansible_user_dir and use
+    # fetch-devstack-log-dir setting devstack_base_dir
+    - role: fetch-devstack-log-dir
+      devstack_base_dir: "{{ ansible_user_dir }}"
diff --git a/playbooks/pre.yaml b/playbooks/pre.yaml
new file mode 100644
index 0000000..6681fb2
--- /dev/null
+++ b/playbooks/pre.yaml
@@ -0,0 +1,20 @@
+- hosts: controller
+  roles:
+    - role: test-matrix
+      test_matrix_role: primary 
+
+- hosts: subnode
+  roles:
+    - role: test-matrix
+      test_matrix_role: subnode
+
+- hosts: all
+  roles:
+    - configure-swap
+    - setup-stack-user
+    - setup-tempest-user
+    - setup-devstack-source-dirs
+    - setup-devstack-log-dir
+    - setup-devstack-cache
+    - start-fresh-logging
+    - write-devstack-local-conf
diff --git a/playbooks/tox/post.yaml b/playbooks/tox/post.yaml
new file mode 100644
index 0000000..7f0cb19
--- /dev/null
+++ b/playbooks/tox/post.yaml
@@ -0,0 +1,4 @@
+- hosts: all
+  roles:
+    - fetch-tox-output
+    - fetch-subunit-output
diff --git a/playbooks/tox/pre.yaml b/playbooks/tox/pre.yaml
new file mode 100644
index 0000000..d7e4670
--- /dev/null
+++ b/playbooks/tox/pre.yaml
@@ -0,0 +1,8 @@
+- hosts: all
+  roles:
+    # Run bindep and test-setup after devstack so that they won't interfere
+    - role: bindep
+      bindep_profile: test
+      bindep_dir: "{{ zuul_work_dir }}"
+    - test-setup
+    - ensure-tox
diff --git a/playbooks/tox/run-both.yaml b/playbooks/tox/run-both.yaml
new file mode 100644
index 0000000..e85c2ee
--- /dev/null
+++ b/playbooks/tox/run-both.yaml
@@ -0,0 +1,10 @@
+- hosts: all
+  roles:
+    - run-devstack
+    # Run bindep and test-setup after devstack so that they won't interfere
+    - role: bindep
+      bindep_profile: test
+      bindep_dir: "{{ zuul_work_dir }}"
+    - test-setup
+    - ensure-tox
+    - tox
diff --git a/playbooks/tox/run.yaml b/playbooks/tox/run.yaml
new file mode 100644
index 0000000..22f8209
--- /dev/null
+++ b/playbooks/tox/run.yaml
@@ -0,0 +1,3 @@
+- hosts: all
+  roles:
+    - tox
diff --git a/roles/apache-logs-conf/README.rst b/roles/apache-logs-conf/README.rst
new file mode 100644
index 0000000..eccee40
--- /dev/null
+++ b/roles/apache-logs-conf/README.rst
@@ -0,0 +1,12 @@
+Prepare apache configs and logs for staging
+
+Make sure apache config files and log files are available in a linux flavor
+independent location. Note that this relies on hard links, to the staging
+directory must be in the same partition where the logs and configs are.
+
+**Role Variables**
+
+.. zuul:rolevar:: stage_dir
+   :default: {{ ansible_user_dir }}
+
+   The base stage directory.
diff --git a/roles/apache-logs-conf/defaults/main.yaml b/roles/apache-logs-conf/defaults/main.yaml
new file mode 100644
index 0000000..1fb04fe
--- /dev/null
+++ b/roles/apache-logs-conf/defaults/main.yaml
@@ -0,0 +1,2 @@
+devstack_base_dir: /opt/stack
+stage_dir: "{{ ansible_user_dir }}"
diff --git a/roles/apache-logs-conf/tasks/main.yaml b/roles/apache-logs-conf/tasks/main.yaml
new file mode 100644
index 0000000..7fd490e
--- /dev/null
+++ b/roles/apache-logs-conf/tasks/main.yaml
@@ -0,0 +1,80 @@
+- name: Ensure {{ stage_dir }}/apache exists
+  file:
+    path: "{{ stage_dir }}/apache"
+    state: directory
+
+- name: Link apache logs on Debian/SuSE
+  block:
+  - name: Find logs
+    find:
+      path: "/var/log/apache2"
+      file_type: any
+    register: debian_suse_apache_logs
+  - name: Dereference files
+    stat:
+      path: "{{ item.path }}"
+    with_items: "{{ debian_suse_apache_logs.files }}"
+    register: debian_suse_apache_deref_logs
+  - name: Create hard links
+    file:
+      src: "{{ item.stat.lnk_source | default(item.stat.path) }}"
+      dest: "{{ stage_dir }}/apache/{{ item.stat.path | basename }}"
+      state: hard
+    with_items: "{{ debian_suse_apache_deref_logs.results }}"
+    when:
+      - item.stat.isreg or item.stat.islnk
+  when: ansible_os_family in ('Debian', 'Suse')
+
+- name: Link apache logs on RedHat
+  block:
+  - name: Find logs
+    find:
+      path: "/var/log/httpd"
+      file_type: any
+    register: redhat_apache_logs
+  - name: Dereference files
+    stat:
+      path: "{{ item.path }}"
+    with_items: "{{ redhat_apache_logs.files }}"
+    register: redhat_apache_deref_logs
+  - name: Create hard links
+    file:
+      src: "{{ item.stat.lnk_source | default(item.stat.path) }}"
+      dest: "{{ stage_dir }}/apache/{{ item.stat.path | basename }}"
+      state: hard
+    with_items: "{{ redhat_apache_deref_logs.results }}"
+    when:
+      - item.stat.isreg or item.stat.islnk
+  when: ansible_os_family == 'Redhat'
+
+- name: Ensure {{ stage_dir }}/apache_config apache_config exists
+  file:
+    path: "{{ stage_dir }}/apache_config"
+    state: directory
+
+- name: Define config paths
+  set_fact:
+    apache_config_paths:
+      'Debian': '/etc/apache2/sites-enabled/'
+      'Suse': '/etc/apache2/conf.d/'
+      'Redhat': '/etc/httpd/conf.d/'
+
+- name: Discover configurations
+  find:
+    path: "{{ apache_config_paths[ansible_os_family] }}"
+    file_type: any
+  register: apache_configs
+
+- name: Dereference configurations
+  stat:
+    path: "{{ item.path }}"
+  with_items: "{{ apache_configs.files }}"
+  register: apache_configs_deref
+
+- name: Link configurations
+  file:
+    src: "{{ item.stat.lnk_source | default(item.stat.path) }}"
+    dest: "{{ stage_dir }}/apache_config/{{ item.stat.path | basename }}"
+    state: hard
+  with_items: "{{ apache_configs_deref.results }}"
+  when: item.stat.isreg or item.stat.islnk
diff --git a/roles/capture-system-logs/README.rst b/roles/capture-system-logs/README.rst
new file mode 100644
index 0000000..c284124
--- /dev/null
+++ b/roles/capture-system-logs/README.rst
@@ -0,0 +1,20 @@
+Stage a number of system type logs
+
+Stage a number of different logs / reports:
+- snapshot of iptables
+- disk space available
+- pip[2|3] freeze
+- installed packages (dpkg/rpm)
+- ceph, openswitch, gluster
+- coredumps
+- dns resolver
+- listen53
+- unbound.log
+- deprecation messages
+
+**Role Variables**
+
+.. zuul:rolevar:: stage_dir
+   :default: {{ ansible_user_dir }}
+
+   The base stage directory.
diff --git a/roles/capture-system-logs/defaults/main.yaml b/roles/capture-system-logs/defaults/main.yaml
new file mode 100644
index 0000000..fea05c8
--- /dev/null
+++ b/roles/capture-system-logs/defaults/main.yaml
@@ -0,0 +1 @@
+devstack_base_dir: /opt/stack
diff --git a/roles/capture-system-logs/tasks/main.yaml b/roles/capture-system-logs/tasks/main.yaml
new file mode 100644
index 0000000..cd8f4f0
--- /dev/null
+++ b/roles/capture-system-logs/tasks/main.yaml
@@ -0,0 +1,38 @@
+# TODO(andreaf) Make this into proper Ansible
+- name: Stage various logs and reports
+  shell:
+    cmd: |
+      sudo iptables-save > {{ stage_dir }}/iptables.txt
+      df -h > {{ stage_dir }}/df.txt
+
+      for py_ver in 2 3; do
+          if [[ `which python${py_ver}` ]]; then
+              python${py_ver} -m pip freeze > {{ stage_dir }}/pip${py_ver}-freeze.txt
+          fi
+      done
+
+      if [ `command -v dpkg` ]; then
+          dpkg -l> {{ stage_dir }}/dpkg-l.txt
+      fi
+      if [ `command -v rpm` ]; then
+          rpm -qa | sort > {{ stage_dir }}/rpm-qa.txt
+      fi
+
+      # gzip and save any coredumps in /var/core
+      if [ -d /var/core ]; then
+          sudo gzip -r /var/core
+          sudo cp -r /var/core {{ stage_dir }}/
+      fi
+
+      sudo ss -lntup | grep ':53' > {{ stage_dir }}/listen53.txt
+
+      # NOTE(andreaf) Service logs are already in logs/ thanks for the
+      # export-devstack-journal log. Apache logs are under apache/ thans to the
+      # apache-logs-conf role.
+      grep -i deprecat {{ stage_dir }}/logs/*.txt {{ stage_dir }}/apache/*.log | \
+          sed -r 's/[0-9]{1,2}\:[0-9]{1,2}\:[0-9]{1,2}\.[0-9]{1,3}/ /g' | \
+          sed -r 's/[0-9]{1,2}\:[0-9]{1,2}\:[0-9]{1,2}/ /g' | \
+          sed -r 's/[0-9]{1,4}-[0-9]{1,2}-[0-9]{1,4}/ /g' |
+          sed -r 's/\[.*\]/ /g' | \
+          sed -r 's/\s[0-9]+\s/ /g' | \
+          awk '{if ($0 in seen) {seen[$0]++} else {out[++n]=$0;seen[$0]=1}} END { for (i=1; i<=n; i++) print seen[out[i]]" :: " out[i] }' > {{ stage_dir }}/deprecations.log
diff --git a/roles/devstack-project-conf/README.rst b/roles/devstack-project-conf/README.rst
new file mode 100644
index 0000000..3f2d4c9
--- /dev/null
+++ b/roles/devstack-project-conf/README.rst
@@ -0,0 +1,11 @@
+Prepare OpenStack project configurations for staging
+
+Prepare all relevant config files for staging.
+This is helpful to avoid staging the entire /etc.
+
+**Role Variables**
+
+.. zuul:rolevar:: stage_dir
+   :default: {{ ansible_user_dir }}
+
+   The base stage directory.
diff --git a/roles/devstack-project-conf/defaults/main.yaml b/roles/devstack-project-conf/defaults/main.yaml
new file mode 100644
index 0000000..f8fb8de
--- /dev/null
+++ b/roles/devstack-project-conf/defaults/main.yaml
@@ -0,0 +1 @@
+stage_dir: "{{ ansible_user_dir }}"
diff --git a/roles/devstack-project-conf/tasks/main.yaml b/roles/devstack-project-conf/tasks/main.yaml
new file mode 100644
index 0000000..9c6e06b
--- /dev/null
+++ b/roles/devstack-project-conf/tasks/main.yaml
@@ -0,0 +1,24 @@
+- name: Ensure {{ stage_dir }}/etc exists
+  file:
+    path: "{{ stage_dir }}/etc"
+    state: directory
+
+- name: Check which projects have a config folder
+  stat:
+    path: "/etc/{{ item.value.short_name }}"
+  with_dict: "{{ zuul.projects }}"
+  register: project_configs
+
+- name: Copy configuration files
+  command: cp -pRL {{ item.stat.path }} {{ stage_dir }}/etc/{{ item.item.value.short_name }}
+  when: item.stat.exists
+  with_items: "{{ project_configs.results }}"
+
+- name: Check if openstack has a config folder
+  stat:
+    path: "/etc/openstack"
+  register: openstack_configs
+
+- name: Copy configuration files
+  command: cp -pRL /etc/openstack {{ stage_dir }}/etc/
+  when: openstack_configs.stat.exists
diff --git a/roles/export-devstack-journal/README.rst b/roles/export-devstack-journal/README.rst
new file mode 100644
index 0000000..a34e070
--- /dev/null
+++ b/roles/export-devstack-journal/README.rst
@@ -0,0 +1,21 @@
+Export journal files from devstack services
+
+Export the systemd journal for every devstack service in native
+journal format as well as text.  Also, export a syslog-style file with
+kernal and sudo messages.
+
+Writes the output to the ``logs/`` subdirectory of
+``stage_dir``.
+
+**Role Variables**
+
+.. zuul:rolevar:: devstack_base_dir
+   :default: /opt/stack
+
+   The devstack base directory. This is used to obtain the
+   ``log-start-timestamp.txt``, used to filter the systemd journal.
+
+.. zuul:rolevar:: stage_dir
+   :default: {{ ansible_user_dir }}
+
+   The base stage directory.
diff --git a/roles/export-devstack-journal/defaults/main.yaml b/roles/export-devstack-journal/defaults/main.yaml
new file mode 100644
index 0000000..1fb04fe
--- /dev/null
+++ b/roles/export-devstack-journal/defaults/main.yaml
@@ -0,0 +1,2 @@
+devstack_base_dir: /opt/stack
+stage_dir: "{{ ansible_user_dir }}"
diff --git a/roles/export-devstack-journal/tasks/main.yaml b/roles/export-devstack-journal/tasks/main.yaml
new file mode 100644
index 0000000..6e760c1
--- /dev/null
+++ b/roles/export-devstack-journal/tasks/main.yaml
@@ -0,0 +1,37 @@
+# NOTE(andreaf) This bypasses the stage-output role
+- name: Ensure {{ stage_dir }}/logs exists
+  become: true
+  file:
+    path: "{{ stage_dir }}/logs"
+    state: directory
+    owner: "{{ ansible_user }}"
+
+# TODO: convert this to ansible
+- name: Export journal files
+  become: true
+  shell:
+    cmd: |
+      u=""
+      name=""
+      for u in `systemctl list-unit-files | grep devstack | awk '{print $1}'`; do
+        name=$(echo $u | sed 's/devstack@/screen-/' | sed 's/\.service//')
+        journalctl -o short-precise --unit $u | gzip - > {{ stage_dir }}/logs/$name.txt.gz
+      done
+
+      # Export the journal in export format to make it downloadable
+      # for later searching. It can then be rewritten to a journal native
+      # format locally using systemd-journal-remote. This makes a class of
+      # debugging much easier. We don't do the native conversion here as
+      # some distros do not package that tooling.
+      journalctl -u 'devstack@*' -o export | \
+          xz --threads=0 - > {{ stage_dir }}/logs/devstack.journal.xz
+
+      # The journal contains everything running under systemd, we'll
+      # build an old school version of the syslog with just the
+      # kernel and sudo messages.
+      journalctl \
+          -t kernel \
+          -t sudo \
+          --no-pager \
+          --since="$(cat {{ devstack_base_dir }}/log-start-timestamp.txt)" \
+        | gzip - > {{ stage_dir }}/logs/syslog.txt.gz
diff --git a/roles/fetch-devstack-log-dir/README.rst b/roles/fetch-devstack-log-dir/README.rst
new file mode 100644
index 0000000..360a2e3
--- /dev/null
+++ b/roles/fetch-devstack-log-dir/README.rst
@@ -0,0 +1,10 @@
+Fetch content from the devstack log directory
+
+Copy logs from every host back to the zuul executor.
+
+**Role Variables**
+
+.. zuul:rolevar:: devstack_base_dir
+   :default: /opt/stack
+
+   The devstack base directory.
diff --git a/roles/fetch-devstack-log-dir/defaults/main.yaml b/roles/fetch-devstack-log-dir/defaults/main.yaml
new file mode 100644
index 0000000..fea05c8
--- /dev/null
+++ b/roles/fetch-devstack-log-dir/defaults/main.yaml
@@ -0,0 +1 @@
+devstack_base_dir: /opt/stack
diff --git a/roles/fetch-devstack-log-dir/tasks/main.yaml b/roles/fetch-devstack-log-dir/tasks/main.yaml
new file mode 100644
index 0000000..5a198b2
--- /dev/null
+++ b/roles/fetch-devstack-log-dir/tasks/main.yaml
@@ -0,0 +1,5 @@
+- name: Collect devstack logs
+  synchronize:
+    dest: "{{ zuul.executor.log_root }}/{{ inventory_hostname }}"
+    mode: pull
+    src: "{{ devstack_base_dir }}/logs"
diff --git a/roles/run-devstack/README.rst b/roles/run-devstack/README.rst
new file mode 100644
index 0000000..e53f060
--- /dev/null
+++ b/roles/run-devstack/README.rst
@@ -0,0 +1,14 @@
+Run devstack
+
+**Role Variables**
+
+.. zuul:rolevar:: devstack_base_dir
+   :default: /opt/stack
+
+   The devstack base directory.
+
+.. zuul:rolevar:: devstack_early_log
+   :default: /opt/stack/log/devstack-early.txt
+
+   The full devstack log that includes the whatever stack.sh logs before
+   the LOGFILE variable in local.conf is honoured.
diff --git a/roles/run-devstack/defaults/main.yaml b/roles/run-devstack/defaults/main.yaml
new file mode 100644
index 0000000..dc4528f
--- /dev/null
+++ b/roles/run-devstack/defaults/main.yaml
@@ -0,0 +1,2 @@
+devstack_base_dir: /opt/stack
+devstack_early_log: /opt/stack/logs/devstack-early.txt
diff --git a/roles/run-devstack/tasks/main.yaml b/roles/run-devstack/tasks/main.yaml
new file mode 100644
index 0000000..f532129
--- /dev/null
+++ b/roles/run-devstack/tasks/main.yaml
@@ -0,0 +1,6 @@
+- name: Run devstack
+  shell: ./stack.sh 2>&1 {{ devstack_early_log }}
+  args:
+    chdir: "{{devstack_base_dir}}/devstack"
+  become: true
+  become_user: stack
diff --git a/roles/setup-devstack-cache/README.rst b/roles/setup-devstack-cache/README.rst
new file mode 100644
index 0000000..b8938c3
--- /dev/null
+++ b/roles/setup-devstack-cache/README.rst
@@ -0,0 +1,15 @@
+Set up the devstack cache directory
+
+If the node has a cache of devstack image files, copy it into place.
+
+**Role Variables**
+
+.. zuul:rolevar:: devstack_base_dir
+   :default: /opt/stack
+
+   The devstack base directory.
+
+.. zuul:rolevar:: devstack_cache_dir
+   :default: /opt/cache
+
+   The directory with the cached files.
diff --git a/roles/setup-devstack-cache/defaults/main.yaml b/roles/setup-devstack-cache/defaults/main.yaml
new file mode 100644
index 0000000..c56720b
--- /dev/null
+++ b/roles/setup-devstack-cache/defaults/main.yaml
@@ -0,0 +1,2 @@
+devstack_base_dir: /opt/stack
+devstack_cache_dir: /opt/cache
diff --git a/roles/setup-devstack-cache/tasks/main.yaml b/roles/setup-devstack-cache/tasks/main.yaml
new file mode 100644
index 0000000..84f33f0
--- /dev/null
+++ b/roles/setup-devstack-cache/tasks/main.yaml
@@ -0,0 +1,14 @@
+- name: Copy cached devstack files
+  # This uses hard links to avoid using extra space.
+  command: "find {{ devstack_cache_dir }}/files -mindepth 1 -maxdepth 1 -exec cp -l {} {{ devstack_base_dir }}/devstack/files/ ;"
+  become: true
+
+- name: Set ownership of cached files
+  file:
+    path: '{{ devstack_base_dir }}/devstack/files'
+    state: directory
+    recurse: true
+    owner: stack
+    group: stack
+    mode: a+r
+  become: yes
diff --git a/roles/setup-devstack-log-dir/README.rst b/roles/setup-devstack-log-dir/README.rst
new file mode 100644
index 0000000..9d8dba3
--- /dev/null
+++ b/roles/setup-devstack-log-dir/README.rst
@@ -0,0 +1,11 @@
+Set up the devstack log directory
+
+Create a log directory on the ephemeral disk partition to save space
+on the root device.
+
+**Role Variables**
+
+.. zuul:rolevar:: devstack_base_dir
+   :default: /opt/stack
+
+   The devstack base directory.
diff --git a/roles/setup-devstack-log-dir/defaults/main.yaml b/roles/setup-devstack-log-dir/defaults/main.yaml
new file mode 100644
index 0000000..fea05c8
--- /dev/null
+++ b/roles/setup-devstack-log-dir/defaults/main.yaml
@@ -0,0 +1 @@
+devstack_base_dir: /opt/stack
diff --git a/roles/setup-devstack-log-dir/tasks/main.yaml b/roles/setup-devstack-log-dir/tasks/main.yaml
new file mode 100644
index 0000000..b9f38df
--- /dev/null
+++ b/roles/setup-devstack-log-dir/tasks/main.yaml
@@ -0,0 +1,5 @@
+- name: Create logs directory
+  file:
+    path: '{{ devstack_base_dir }}/logs'
+    state: directory
+  become: yes
diff --git a/roles/setup-devstack-source-dirs/README.rst b/roles/setup-devstack-source-dirs/README.rst
new file mode 100644
index 0000000..4ebf839
--- /dev/null
+++ b/roles/setup-devstack-source-dirs/README.rst
@@ -0,0 +1,11 @@
+Set up the devstack source directories
+
+Ensure that the base directory exists, and then move the source repos
+into it.
+
+**Role Variables**
+
+.. zuul:rolevar:: devstack_base_dir
+   :default: /opt/stack
+
+   The devstack base directory.
diff --git a/roles/setup-devstack-source-dirs/defaults/main.yaml b/roles/setup-devstack-source-dirs/defaults/main.yaml
new file mode 100644
index 0000000..fea05c8
--- /dev/null
+++ b/roles/setup-devstack-source-dirs/defaults/main.yaml
@@ -0,0 +1 @@
+devstack_base_dir: /opt/stack
diff --git a/roles/setup-devstack-source-dirs/tasks/main.yaml b/roles/setup-devstack-source-dirs/tasks/main.yaml
new file mode 100644
index 0000000..e6bbae2
--- /dev/null
+++ b/roles/setup-devstack-source-dirs/tasks/main.yaml
@@ -0,0 +1,22 @@
+- name: Find all source repos used by this job
+  find:
+    paths:
+      - src/git.openstack.org/openstack
+      - src/git.openstack.org/openstack-dev
+      - src/git.openstack.org/openstack-infra
+    file_type: directory
+  register: found_repos
+
+- name: Copy Zuul repos into devstack working directory
+  command: rsync -a {{ item.path }} {{ devstack_base_dir }}
+  with_items: '{{ found_repos.files }}'
+  become: yes
+
+- name: Set ownership of repos
+  file:
+    path: '{{ devstack_base_dir }}'
+    state: directory
+    recurse: true
+    owner: stack
+    group: stack
+  become: yes
diff --git a/roles/setup-stack-user/README.rst b/roles/setup-stack-user/README.rst
new file mode 100644
index 0000000..80c4d39
--- /dev/null
+++ b/roles/setup-stack-user/README.rst
@@ -0,0 +1,16 @@
+Set up the `stack` user
+
+Create the stack user, set up its home directory, and allow it to
+sudo.
+
+**Role Variables**
+
+.. zuul:rolevar:: devstack_base_dir
+   :default: /opt/stack
+
+   The devstack base directory.
+
+.. zuul:rolevar:: devstack_stack_home_dir
+   :default: {{ devstack_base_dir }}
+
+   The home directory for the stack user.
diff --git a/roles/setup-stack-user/defaults/main.yaml b/roles/setup-stack-user/defaults/main.yaml
new file mode 100644
index 0000000..6d0be66
--- /dev/null
+++ b/roles/setup-stack-user/defaults/main.yaml
@@ -0,0 +1,2 @@
+devstack_base_dir: /opt/stack
+devstack_stack_home_dir: '{{ devstack_base_dir }}'
diff --git a/roles/setup-stack-user/files/50_stack_sh b/roles/setup-stack-user/files/50_stack_sh
new file mode 100644
index 0000000..4c6b46b
--- /dev/null
+++ b/roles/setup-stack-user/files/50_stack_sh
@@ -0,0 +1 @@
+stack ALL=(root) NOPASSWD:ALL
diff --git a/roles/setup-stack-user/tasks/main.yaml b/roles/setup-stack-user/tasks/main.yaml
new file mode 100644
index 0000000..8384515
--- /dev/null
+++ b/roles/setup-stack-user/tasks/main.yaml
@@ -0,0 +1,45 @@
+- name: Create stack group
+  group:
+    name: stack
+  become: yes
+
+# NOTE(andreaf) Create a user home_dir is not safe via
+# the user module since it will fail if the containing
+# folder does not exists. If the folder does exists and
+# it's empty, the skeleton is setup and ownership set.
+- name: Create the stack user home folder
+  file:
+    path: '{{ devstack_stack_home_dir }}'
+    state: directory
+  become: yes
+
+- name: Create stack user
+  user:
+    name: stack
+    shell: /bin/bash
+    home: '{{ devstack_stack_home_dir }}'
+    group: stack
+  become: yes
+
+- name: Set stack user home directory permissions
+  file:
+    path: '{{ devstack_stack_home_dir }}'
+    mode: 0755
+  become: yes
+
+- name: Copy 50_stack_sh file to /etc/sudoers.d
+  copy:
+    src: 50_stack_sh
+    dest: /etc/sudoers.d
+    mode: 0440
+    owner: root
+    group: root
+  become: yes
+
+- name: Create new/.cache folder within BASE
+  file:
+    path: '{{ devstack_stack_home_dir }}/.cache'
+    state: directory
+    owner: stack
+    group: stack
+  become: yes
diff --git a/roles/setup-tempest-user/README.rst b/roles/setup-tempest-user/README.rst
new file mode 100644
index 0000000..bb29c50
--- /dev/null
+++ b/roles/setup-tempest-user/README.rst
@@ -0,0 +1,10 @@
+Set up the `tempest` user
+
+Create the tempest user and allow it to sudo.
+
+**Role Variables**
+
+.. zuul:rolevar:: devstack_base_dir
+   :default: /opt/stack
+
+   The devstack base directory.
diff --git a/roles/setup-tempest-user/files/51_tempest_sh b/roles/setup-tempest-user/files/51_tempest_sh
new file mode 100644
index 0000000..f88ff9f
--- /dev/null
+++ b/roles/setup-tempest-user/files/51_tempest_sh
@@ -0,0 +1,3 @@
+tempest ALL=(root) NOPASSWD:/sbin/ip
+tempest ALL=(root) NOPASSWD:/sbin/iptables
+tempest ALL=(root) NOPASSWD:/usr/bin/ovsdb-client
diff --git a/roles/setup-tempest-user/tasks/main.yaml b/roles/setup-tempest-user/tasks/main.yaml
new file mode 100644
index 0000000..892eaf6
--- /dev/null
+++ b/roles/setup-tempest-user/tasks/main.yaml
@@ -0,0 +1,20 @@
+- name: Create tempest group
+  group:
+    name: tempest
+  become: yes
+
+- name: Create tempest user
+  user:
+    name: tempest
+    shell: /bin/bash
+    group: tempest
+  become: yes
+
+- name: Copy 51_tempest_sh to /etc/sudoers.d
+  copy:
+    src: 51_tempest_sh
+    dest: /etc/sudoers.d
+    owner: root
+    group: root
+    mode: 0440
+  become: yes
diff --git a/roles/start-fresh-logging/README.rst b/roles/start-fresh-logging/README.rst
new file mode 100644
index 0000000..11b029e
--- /dev/null
+++ b/roles/start-fresh-logging/README.rst
@@ -0,0 +1,11 @@
+Restart logging on all hosts
+
+Restart syslog so that the system logs only include output from the
+job.
+
+**Role Variables**
+
+.. zuul:rolevar:: devstack_base_dir
+   :default: /opt/stack
+
+   The devstack base directory.
diff --git a/roles/start-fresh-logging/defaults/main.yaml b/roles/start-fresh-logging/defaults/main.yaml
new file mode 100644
index 0000000..fea05c8
--- /dev/null
+++ b/roles/start-fresh-logging/defaults/main.yaml
@@ -0,0 +1 @@
+devstack_base_dir: /opt/stack
diff --git a/roles/start-fresh-logging/tasks/main.yaml b/roles/start-fresh-logging/tasks/main.yaml
new file mode 100644
index 0000000..6c7ba66
--- /dev/null
+++ b/roles/start-fresh-logging/tasks/main.yaml
@@ -0,0 +1,56 @@
+- name: Check for /bin/journalctl file
+  command: which journalctl
+  changed_when: False
+  failed_when: False
+  register: which_out
+
+- block:
+    - name: Get current date
+      command: date +"%Y-%m-%d %H:%M:%S"
+      register: date_out
+
+    - name: Copy current date to log-start-timestamp.txt
+      copy:
+        dest: "{{ devstack_base_dir }}/log-start-timestamp.txt"
+        content: "{{ date_out.stdout }}"
+  when: which_out.rc == 0
+  become: yes
+
+- block:
+    - name: Stop rsyslog
+      service: name=rsyslog state=stopped
+
+    - name: Save syslog file prior to devstack run
+      command: mv /var/log/syslog /var/log/syslog-pre-devstack
+
+    - name: Save kern.log file prior to devstack run
+      command: mv /var/log/kern.log /var/log/kern_log-pre-devstack
+
+    - name: Recreate syslog file
+      file: name=/var/log/syslog state=touch
+
+    - name: Recreate syslog file owner and group
+      command: chown /var/log/syslog --ref /var/log/syslog-pre-devstack
+
+    - name: Recreate syslog file permissions
+      command: chmod /var/log/syslog --ref /var/log/syslog-pre-devstack
+
+    - name: Add read permissions to all on syslog file
+      file: name=/var/log/syslog mode=a+r
+
+    - name: Recreate kern.log file
+      file: name=/var/log/kern.log state=touch
+
+    - name: Recreate kern.log file owner and group
+      command: chown /var/log/kern.log --ref /var/log/kern_log-pre-devstack
+
+    - name: Recreate kern.log file permissions
+      command: chmod /var/log/kern.log --ref /var/log/kern_log-pre-devstack
+
+    - name: Add read permissions to all on kern.log file
+      file: name=/var/log/kern.log mode=a+r
+
+    - name: Start rsyslog
+      service: name=rsyslog state=started
+  when: which_out.rc == 1
+  become: yes
diff --git a/roles/write-devstack-local-conf/README.rst b/roles/write-devstack-local-conf/README.rst
new file mode 100644
index 0000000..73f9f0d
--- /dev/null
+++ b/roles/write-devstack-local-conf/README.rst
@@ -0,0 +1,77 @@
+Write the local.conf file for use by devstack
+
+**Role Variables**
+
+.. zuul:rolevar:: devstack_base_dir
+   :default: /opt/stack
+
+   The devstack base directory.
+
+.. zuul:rolevar:: devstack_local_conf_path
+   :default: {{ devstack_base_dir }}/devstack/local.conf
+
+   The path of the local.conf file.
+
+.. zuul:rolevar:: devstack_localrc
+   :type: dict
+
+   A dictionary of variables that should be written to the localrc
+   section of local.conf.  The values (which are strings) may contain
+   bash shell variables, and will be ordered so that variables used by
+   later entries appear first.
+
+.. zuul:rolevar:: devstack_local_conf
+   :type: dict
+
+   A complex argument consisting of nested dictionaries which combine
+   to form the meta-sections of the local_conf file.  The top level is
+   a dictionary of phases, followed by dictionaries of filenames, then
+   sections, which finally contain key-value pairs for the INI file
+   entries in those sections.
+
+   The keys in this dictionary are the devstack phases.
+
+   .. zuul:rolevar:: [phase]
+      :type: dict
+
+      The keys in this dictionary are the filenames for this phase.
+
+      .. zuul:rolevar:: [filename]
+         :type: dict
+
+         The keys in this dictionary are the INI sections in this file.
+
+         .. zuul:rolevar:: [section]
+            :type: dict
+
+            This is a dictionary of key-value pairs which comprise
+            this section of the INI file.
+
+.. zuul:rolevar:: devstack_base_services
+   :type: list
+   :default: {{ base_services | default(omit) }}
+
+   A list of base services which are enabled. Services can be added or removed
+   from this list via the ``devstack_services`` variable. This is ignored if
+   ``base`` is set to ``False`` in ``devstack_services``.
+
+.. zuul:rolevar:: devstack_services
+   :type: dict
+
+   A dictionary mapping service names to boolean values.  If the
+   boolean value is ``false``, a ``disable_service`` line will be
+   emitted for the service name.  If it is ``true``, then
+   ``enable_service`` will be emitted. All other values are ignored.
+
+   The special key ``base`` can be used to enable or disable the base set of
+   services enabled by default. If ``base`` is found, it will processed before
+   all other keys. If its value is ``False`` a ``disable_all_services`` will be
+   emitted; if its value is ``True`` services from ``devstack_base_services``
+   will be emitted via ``ENABLED_SERVICES``.
+
+.. zuul:rolevar:: devstack_plugins
+   :type: dict
+
+   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.
diff --git a/roles/write-devstack-local-conf/defaults/main.yaml b/roles/write-devstack-local-conf/defaults/main.yaml
new file mode 100644
index 0000000..7bc1dec
--- /dev/null
+++ b/roles/write-devstack-local-conf/defaults/main.yaml
@@ -0,0 +1,3 @@
+devstack_base_dir: /opt/stack
+devstack_local_conf_path: "{{ devstack_base_dir }}/devstack/local.conf"
+devstack_base_services: "{{ enabled_services | default(omit) }}"
diff --git a/roles/write-devstack-local-conf/library/devstack_local_conf.py b/roles/write-devstack-local-conf/library/devstack_local_conf.py
new file mode 100644
index 0000000..55ba4af
--- /dev/null
+++ b/roles/write-devstack-local-conf/library/devstack_local_conf.py
@@ -0,0 +1,193 @@
+# 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 re
+
+
+class VarGraph(object):
+    # This is based on the JobGraph from Zuul.
+
+    def __init__(self, vars):
+        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():
+            self._addVar(k, str(v))
+
+    bash_var_re = re.compile(r'\$\{?(\w+)')
+    def getDependencies(self, value):
+        return self.bash_var_re.findall(value)
+
+    def _addVar(self, key, value):
+        if key in self.vars:
+            raise Exception("Variable {} already added".format(key))
+        self.vars[key] = value
+        # Append the dependency information
+        self._dependencies.setdefault(key, set())
+        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)
+        except Exception:
+            del self.vars[key]
+            del self._dependencies[key]
+            raise
+
+    def getVars(self):
+        ret = []
+        keys = sorted(self.vars.keys())
+        seen = set()
+        for key in keys:
+            dependencies = self.getDependentVarsRecursively(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
+
+    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
+
+
+class LocalConf(object):
+
+    def __init__(self, localrc, localconf, base_services, services, plugins):
+        self.localrc = []
+        self.meta_sections = {}
+        if plugins:
+            self.handle_plugins(plugins)
+        if services or base_services:
+            self.handle_services(base_services, services or {})
+        if localrc:
+            self.handle_localrc(localrc)
+        if localconf:
+            self.handle_localconf(localconf)
+
+    def handle_plugins(self, plugins):
+        for k, v in plugins.items():
+            if v:
+                self.localrc.append('enable_plugin {} {}'.format(k, v))
+
+    def handle_services(self, base_services, services):
+        enable_base_services = services.pop('base', True)
+        if enable_base_services and base_services:
+            self.localrc.append('ENABLED_SERVICES={}'.format(
+                ",".join(base_services)))
+        else:
+            self.localrc.append('disable_all_services')
+        for k, v in services.items():
+            if v is False:
+                self.localrc.append('disable_service {}'.format(k))
+            elif v is True:
+                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))
+
+    def handle_localconf(self, localconf):
+        for phase, phase_data in localconf.items():
+            for fn, fn_data in phase_data.items():
+                ms_name = '[[{}|{}]]'.format(phase, fn)
+                ms_data = []
+                for section, section_data in fn_data.items():
+                    ms_data.append('[{}]'.format(section))
+                    for k, v in section_data.items():
+                        ms_data.append('{} = {}'.format(k, v))
+                    ms_data.append('')
+                self.meta_sections[ms_name] = ms_data
+
+    def write(self, path):
+        with open(path, 'w') as f:
+            f.write('[[local|localrc]]\n')
+            f.write('\n'.join(self.localrc))
+            f.write('\n\n')
+            for section, lines in self.meta_sections.items():
+                f.write('{}\n'.format(section))
+                f.write('\n'.join(lines))
+
+
+def main():
+    module = AnsibleModule(
+        argument_spec=dict(
+            plugins=dict(type='dict'),
+            base_services=dict(type='list'),
+            services=dict(type='dict'),
+            localrc=dict(type='dict'),
+            local_conf=dict(type='dict'),
+            path=dict(type='str'),
+        )
+    )
+
+    p = module.params
+    lc = LocalConf(p.get('localrc'),
+                   p.get('local_conf'),
+                   p.get('base_services'),
+                   p.get('services'),
+                   p.get('plugins'))
+    lc.write(p['path'])
+
+    module.exit_json()
+
+
+from ansible.module_utils.basic import *  # noqa
+from ansible.module_utils.basic import AnsibleModule
+
+if __name__ == '__main__':
+    main()
diff --git a/roles/write-devstack-local-conf/tasks/main.yaml b/roles/write-devstack-local-conf/tasks/main.yaml
new file mode 100644
index 0000000..cc21426
--- /dev/null
+++ b/roles/write-devstack-local-conf/tasks/main.yaml
@@ -0,0 +1,10 @@
+- name: Write a job-specific local_conf file
+  become: true
+  become_user: stack
+  devstack_local_conf:
+    path: "{{ devstack_local_conf_path }}"
+    plugins: "{{ devstack_plugins|default(omit) }}"
+    base_services: "{{ devstack_base_services|default(omit) }}"
+    services: "{{ devstack_services|default(omit) }}"
+    localrc: "{{ devstack_localrc|default(omit) }}"
+    local_conf: "{{ devstack_local_conf|default(omit) }}"
diff --git a/stack.sh b/stack.sh
index c545c56..32eb43f 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|yakkety|zesty|stretch|jessie|f24|f25|f26|opensuse-42.2|opensuse-42.3|rhel7|kvmibm1) ]]; then
+if [[ ! ${DISTRO} =~ (xenial|zesty|artful|stretch|jessie|f25|f26|f27|opensuse-42.2|opensuse-42.3|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"
@@ -1006,7 +1006,7 @@
 # be memory bound not cpu bound so enable KSM by default but allow people
 # to opt out if the CPU time is more important to them.
 
-if [[ "ENABLE_KSM" == "True" ]] ; then
+if [[ $ENABLE_KSM == "True" ]] ; then
     if [[ -f /sys/kernel/mm/ksm/run ]] ; then
         sudo sh -c "echo 1 > /sys/kernel/mm/ksm/run"
     fi
diff --git a/stackrc b/stackrc
index ffe4050..a9f3c32 100644
--- a/stackrc
+++ b/stackrc
@@ -121,7 +121,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"
+export ENABLED_PYTHON3_PACKAGES="nova,glance,cinder,uwsgi,python-openstackclient,python-openstacksdk"
 
 # Explicitly list services not to run under Python 3. See
 # disable_python3_package to edit this variable.
@@ -720,11 +720,11 @@
 
 # etcd3 defaults
 ETCD_VERSION=${ETCD_VERSION:-v3.1.10}
-ETCD_SHA256_AMD64="2d335f298619c6fb02b1124773a56966e448ad9952b26fea52909da4fe80d2be"
+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_PPC64=""
-ETCD_SHA256_S390X=""
+ETCD_SHA256_ARM64=${ETCD_SHA256_ARM64:-""}
+ETCD_SHA256_PPC64=${ETCD_SHA256_PPC64:-""}
+ETCD_SHA256_S390X=${ETCD_SHA256_S390X:-""}
 # Make sure etcd3 downloads the correct architecture
 if is_arch "x86_64"; then
     ETCD_ARCH="amd64"
@@ -762,8 +762,8 @@
     fi
 done
 
-# 10Gb default volume backing file size
-VOLUME_BACKING_FILE_SIZE=${VOLUME_BACKING_FILE_SIZE:-10250M}
+# 24Gb default volume backing file size
+VOLUME_BACKING_FILE_SIZE=${VOLUME_BACKING_FILE_SIZE:-24G}
 
 # Prefixes for volume and instance names
 VOLUME_NAME_PREFIX=${VOLUME_NAME_PREFIX:-volume-}
diff --git a/tools/install_prereqs.sh b/tools/install_prereqs.sh
index 6189085..da59093 100755
--- a/tools/install_prereqs.sh
+++ b/tools/install_prereqs.sh
@@ -88,15 +88,6 @@
     export PYTHON=$(which python 2>/dev/null)
 fi
 
-if is_suse; then
-    # now reinstall cryptography from source, in order to rebuilt it against the
-    # system libssl rather than the bundled openSSL 1.1, which segfaults when combined
-    # with a system provided openSSL 1.0
-    # see https://github.com/pyca/cryptography/issues/3804 and followup issues
-    sudo pip install cryptography --no-binary :all:
-fi
-
-
 # Mark end of run
 # ---------------
 
diff --git a/tools/worlddump.py b/tools/worlddump.py
index 6fff149..7506082 100755
--- a/tools/worlddump.py
+++ b/tools/worlddump.py
@@ -164,8 +164,7 @@
     _header("Network Dump")
 
     _dump_cmd("brctl show")
-    _dump_cmd("arp -n")
-    ip_cmds = ["addr", "link", "route"]
+    ip_cmds = ["neigh", "addr", "link", "route"]
     for cmd in ip_cmds + ['netns']:
         _dump_cmd("ip %s" % cmd)
     for netns_ in _netns_list():
diff --git a/tox.ini b/tox.ini
index 46b15f4..74436b0 100644
--- a/tox.ini
+++ b/tox.ini
@@ -34,16 +34,7 @@
          -print0 | xargs -0 bashate -v -iE006 -eE005,E042"
 
 [testenv:docs]
-deps =
-   Pygments
-   docutils
-   sphinx>=1.6.2
-   pbr>=2.0.0,!=2.1.0
-   openstackdocstheme>=1.11.0
-   nwdiag
-   blockdiag
-   sphinxcontrib-blockdiag
-   sphinxcontrib-nwdiag
+deps = -r{toxinidir}/doc/requirements.txt
 whitelist_externals = bash
 setenv =
   TOP_DIR={toxinidir}
@@ -51,11 +42,5 @@
   python setup.py build_sphinx
 
 [testenv:venv]
-deps =
-   pbr>=2.0.0,!=2.1.0
-   sphinx>=1.6.2
-   openstackdocstheme>=1.11.0
-   blockdiag
-   sphinxcontrib-blockdiag
-   sphinxcontrib-nwdiag
+deps = -r{toxinidir}/doc/requirements.txt
 commands = {posargs}
diff --git a/unstack.sh b/unstack.sh
index 5d3672e..ccea0ef 100755
--- a/unstack.sh
+++ b/unstack.sh
@@ -45,6 +45,10 @@
 # Configure Projects
 # ==================
 
+# Determine what system we are running on.  This provides ``os_VENDOR``,
+# ``os_RELEASE``, ``os_PACKAGE``, ``os_CODENAME`` and ``DISTRO``
+GetDistro
+
 # Plugin Phase 0: override_defaults - allow plugins to override
 # defaults before other services are run
 run_phase override_defaults
@@ -83,10 +87,6 @@
 
 load_plugin_settings
 
-# Determine what system we are running on.  This provides ``os_VENDOR``,
-# ``os_RELEASE``, ``os_PACKAGE``, ``os_CODENAME``
-GetOSVersion
-
 set -o xtrace
 
 # Run extras