Merge "Remove GLANCE_V1_ENABLED option"
diff --git a/.zuul.yaml b/.zuul.yaml
index f78f3f5..43e5d4c 100644
--- a/.zuul.yaml
+++ b/.zuul.yaml
@@ -558,9 +558,6 @@
       devstack_localrc:
         SERVICE_IP_VERSION: 6
         SERVICE_HOST: ""
-        # IPv6 and certificates known issue with python2
-        # https://bugs.launchpad.net/devstack/+bug/1794929
-        USE_PYTHON3: true
 
 - job:
     name: devstack-multinode
@@ -587,6 +584,7 @@
     parent: tempest-full-py3
     description: openSUSE 15.x platform test
     nodeset: devstack-single-node-opensuse-15
+    voting: false
 
 - job:
     name: devstack-platform-focal
@@ -714,6 +712,11 @@
             irrelevant-files:
               - ^.*\.rst$
               - ^doc/.*$
+        - neutron-ovn-tempest-ovs-release:
+            voting: false
+            irrelevant-files:
+              - ^.*\.rst$
+              - ^doc/.*$
         - tempest-multinode-full-py3:
             voting: false
             irrelevant-files:
diff --git a/doc/source/configuration.rst b/doc/source/configuration.rst
index 45f4ffe..22f5999 100644
--- a/doc/source/configuration.rst
+++ b/doc/source/configuration.rst
@@ -430,17 +430,6 @@
 
   ADDITIONAL_VENV_PACKAGES="python-foo, python-bar"
 
-Use python3
-------------
-
-By default ``stack.sh`` uses python2 (the exact version set by the
-``PYTHON2_VERSION``). This can be overriden so devstack will run
-python3 (the exact version set by ``PYTHON3_VERSION``).
-
-::
-
-  USE_PYTHON3=True
-
 A clean install every time
 --------------------------
 
diff --git a/doc/source/guides/devstack-with-lbaas-v2.rst b/doc/source/guides/devstack-with-lbaas-v2.rst
index 7fde6f1..5d96ca7 100644
--- a/doc/source/guides/devstack-with-lbaas-v2.rst
+++ b/doc/source/guides/devstack-with-lbaas-v2.rst
@@ -41,9 +41,6 @@
     # If you are enabling barbican for TLS offload in Octavia, include it here.
     # enable_plugin barbican https://opendev.org/openstack/barbican
 
-    # If you have python3 available:
-    # USE_PYTHON3=True
-
     # ===== BEGIN localrc =====
     DATABASE_PASSWORD=password
     ADMIN_PASSWORD=password
diff --git a/files/debs/ovn b/files/debs/ovn
new file mode 100644
index 0000000..81eea5e
--- /dev/null
+++ b/files/debs/ovn
@@ -0,0 +1,3 @@
+ovn-central
+ovn-controller-vtep
+ovn-host
diff --git a/files/rpms/ovn b/files/rpms/ovn
new file mode 100644
index 0000000..698e57b
--- /dev/null
+++ b/files/rpms/ovn
@@ -0,0 +1,3 @@
+ovn-central
+ovn-host
+ovn-vtep
diff --git a/functions-common b/functions-common
index dea5aa9..6595c3d 100644
--- a/functions-common
+++ b/functions-common
@@ -47,7 +47,7 @@
 
 # Save these variables to .stackenv
 STACK_ENV_VARS="BASE_SQL_CONN DATA_DIR DEST ENABLED_SERVICES HOST_IP \
-    KEYSTONE_AUTH_URI KEYSTONE_SERVICE_URI \
+    KEYSTONE_SERVICE_URI \
     LOGFILE OS_CACERT SERVICE_HOST STACK_USER TLS_IP \
     HOST_IPV6 SERVICE_IP_VERSION"
 
diff --git a/inc/python b/inc/python
index 08f9959..f98d28d 100644
--- a/inc/python
+++ b/inc/python
@@ -62,7 +62,7 @@
     $xtrace
 
     local PYTHON_PATH=/usr/local/bin
-    ( is_fedora && ! python3_enabled ) || is_suse && PYTHON_PATH=/usr/bin
+    is_suse && PYTHON_PATH=/usr/bin
     echo $PYTHON_PATH
 }
 
@@ -169,16 +169,9 @@
         local cmd_pip=$PIP_VIRTUAL_ENV/bin/pip
         local sudo_pip="env"
     else
-        local cmd_pip
-        local sudo_pip="sudo -H"
-        if python3_enabled; then
-            echo "Using python $PYTHON3_VERSION to install $package_dir because python3_enabled=True"
-            sudo_pip="$sudo_pip LC_ALL=en_US.UTF-8"
-            cmd_pip="python$PYTHON3_VERSION -m pip"
-        else
-            echo "Using python $PYTHON2_VERSION to install $package_dir because python3_enabled=False"
-            cmd_pip=$(get_pip_command $PYTHON2_VERSION)
-        fi
+        local cmd_pip="python$PYTHON3_VERSION -m pip"
+        local sudo_pip="sudo -H LC_ALL=en_US.UTF-8"
+        echo "Using python $PYTHON3_VERSION to install $package_dir"
     fi
 
     cmd_pip="$cmd_pip install"
@@ -213,14 +206,8 @@
         local cmd_pip=$PIP_VIRTUAL_ENV/bin/pip
         local sudo_pip="env"
     else
-        local cmd_pip
-        local sudo_pip="sudo -H"
-        if python3_enabled; then
-            sudo_pip="$sudo_pip LC_ALL=en_US.UTF-8"
-            cmd_pip="python$PYTHON3_VERSION -m pip"
-        else
-            cmd_pip=$(get_pip_command $PYTHON2_VERSION)
-        fi
+        local cmd_pip="python$PYTHON3_VERSION -m pip"
+        local sudo_pip="sudo -H LC_ALL=en_US.UTF-8"
     fi
     # don't error if we can't uninstall, it might not be there
     $sudo_pip $cmd_pip uninstall -y $name || /bin/true
@@ -457,37 +444,15 @@
 }
 
 # Report whether python 3 should be used
+# TODO(frickler): drop this once all legacy uses are removed
 function python3_enabled {
-    if [[ $USE_PYTHON3 == "True" ]]; then
-        return 0
-    else
-        return 1
-    fi
+    return 0
 }
 
 # Provide requested python version and sets PYTHON variable
 function install_python {
-    # NOTE: install_python function should finally just do what install_python3
-    # does as soon Python 2 support has been dropped
-    if python3_enabled; then
-        install_python3
-        export PYTHON=$(which python${PYTHON3_VERSION} 2>/dev/null ||
-                        which python3 2>/dev/null)
-        if [[ "${DISTRO}" =~ (rhel8) ]]; then
-            # Use Python 3 as default python command so that we have only one
-            # python alternative to use on the system for either python and
-            # python3
-            sudo alternatives --set python "${PYTHON}"
-        else
-            # Install anyway Python 2 for legacy scripts that still requires
-            # python instead of python3 command
-            install_package python
-        fi
-    else
-        echo "WARNING - Python 2 support has been deprecated in favor of Python 3"
-        install_package python
-        export PYTHON=$(which python 2>/dev/null)
-    fi
+    install_python3
+    export PYTHON=$(which python${PYTHON3_VERSION} 2>/dev/null)
 }
 
 # Install python3 packages
diff --git a/lib/apache b/lib/apache
index a5fbf75..6670219 100644
--- a/lib/apache
+++ b/lib/apache
@@ -89,15 +89,11 @@
     #
     # For package installs, the distro ships both plugins and you need
     # to select the right one ... it will not be autodetected.
-    if python3_enabled; then
-        UWSGI_PYTHON_PLUGIN=python3
-    else
-        UWSGI_PYTHON_PLUGIN=python
-    fi
+    UWSGI_PYTHON_PLUGIN=python3
 
     if is_ubuntu; then
         local pkg_list="uwsgi uwsgi-plugin-python3 libapache2-mod-proxy-uwsgi"
-        if "$DISTRO" == 'bionic'; then
+        if [[ "$DISTRO" == 'bionic' ]]; then
             pkg_list="${pkg_list} uwsgi-plugin-python"
         fi
         install_package ${pkg_list}
@@ -150,14 +146,10 @@
     if is_ubuntu; then
         # Install apache2, which is NOPRIME'd
         install_package apache2
-        if python3_enabled; then
-            if is_package_installed libapache2-mod-wsgi; then
-                uninstall_package libapache2-mod-wsgi
-            fi
-            install_package libapache2-mod-wsgi-py3
-        else
-            install_package libapache2-mod-wsgi
+        if is_package_installed libapache2-mod-wsgi; then
+            uninstall_package libapache2-mod-wsgi
         fi
+        install_package libapache2-mod-wsgi-py3
     elif is_fedora; then
         sudo rm -f /etc/httpd/conf.d/000-*
         install_package httpd mod_wsgi
diff --git a/lib/cinder b/lib/cinder
index b1ed593..b892b91 100644
--- a/lib/cinder
+++ b/lib/cinder
@@ -415,7 +415,7 @@
     if [[ "$CINDER_ISCSI_HELPER" == "tgtadm" ]]; then
         install_package tgt
     elif [[ "$CINDER_ISCSI_HELPER" == "lioadm" ]]; then
-        if [[ ${DISTRO} == "bionic" ]]; then
+        if is_ubuntu; then
             # TODO(frickler): Workaround for https://launchpad.net/bugs/1819819
             sudo mkdir -p /etc/target
 
diff --git a/lib/glance b/lib/glance
index 97af6e6..b9c23aa 100644
--- a/lib/glance
+++ b/lib/glance
@@ -201,8 +201,7 @@
 
     if is_service_enabled tls-proxy; then
         iniset $GLANCE_API_CONF DEFAULT bind_port $GLANCE_SERVICE_PORT_INT
-
-        iniset $GLANCE_API_CONF keystone_authtoken identity_uri $KEYSTONE_AUTH_URI
+        iniset $GLANCE_API_CONF keystone_authtoken identity_uri $KEYSTONE_SERVICE_URI
     fi
 
     # Format logging
@@ -214,7 +213,7 @@
     iniset $GLANCE_CACHE_CONF DEFAULT debug $ENABLE_DEBUG_LOG_LEVEL
     iniset $GLANCE_CACHE_CONF DEFAULT use_syslog $SYSLOG
     iniset $GLANCE_CACHE_CONF DEFAULT image_cache_dir $GLANCE_CACHE_DIR/
-    iniset $GLANCE_CACHE_CONF DEFAULT auth_url $KEYSTONE_AUTH_URI
+    iniset $GLANCE_CACHE_CONF DEFAULT auth_url $KEYSTONE_SERVICE_URI
     iniset $GLANCE_CACHE_CONF DEFAULT admin_tenant_name $SERVICE_PROJECT_NAME
     iniset $GLANCE_CACHE_CONF DEFAULT admin_user glance
     iniset $GLANCE_CACHE_CONF DEFAULT admin_password $SERVICE_PASSWORD
diff --git a/lib/keystone b/lib/keystone
index 1910f34..d4c7b06 100644
--- a/lib/keystone
+++ b/lib/keystone
@@ -115,7 +115,7 @@
 KEYSTONE_AUTH_URI=$KEYSTONE_SERVICE_URI
 
 # V3 URIs
-KEYSTONE_AUTH_URI_V3=$KEYSTONE_AUTH_URI/v3
+KEYSTONE_AUTH_URI_V3=$KEYSTONE_SERVICE_URI/v3
 KEYSTONE_SERVICE_URI_V3=$KEYSTONE_SERVICE_URI/v3
 
 # Security compliance
@@ -413,6 +413,7 @@
     local section=${3:-keystone_authtoken}
 
     iniset $conf_file $section auth_type password
+    iniset $conf_file $section interface public
     iniset $conf_file $section auth_url $KEYSTONE_SERVICE_URI
     iniset $conf_file $section username $admin_user
     iniset $conf_file $section password $SERVICE_PASSWORD
@@ -561,7 +562,6 @@
 # - ``KEYSTONE_BIN_DIR``
 # - ``ADMIN_PASSWORD``
 # - ``IDENTITY_API_VERSION``
-# - ``KEYSTONE_AUTH_URI``
 # - ``REGION_NAME``
 # - ``KEYSTONE_SERVICE_PROTOCOL``
 # - ``KEYSTONE_SERVICE_HOST``
diff --git a/lib/neutron-legacy b/lib/neutron-legacy
index bb1536a..59649ef 100644
--- a/lib/neutron-legacy
+++ b/lib/neutron-legacy
@@ -372,7 +372,7 @@
 function create_nova_conf_neutron {
     local conf=${1:-$NOVA_CONF}
     iniset $conf neutron auth_type "password"
-    iniset $conf neutron auth_url "$KEYSTONE_AUTH_URI"
+    iniset $conf neutron auth_url "$KEYSTONE_SERVICE_URI"
     iniset $conf neutron username "$Q_ADMIN_USERNAME"
     iniset $conf neutron password "$SERVICE_PASSWORD"
     iniset $conf neutron user_domain_name "$SERVICE_DOMAIN_NAME"
@@ -622,7 +622,7 @@
             IP_UP="sudo ip link set $to_intf up"
             if [[ "$af" == "inet" ]]; then
                 IP=$(echo $IP_BRD | awk '{ print $1; exit }' | grep -o -E '(.*)/' | cut -d "/" -f1)
-                ARP_CMD="sudo arping -A -c 3 -w 4.5 -I $to_intf $IP "
+                ARP_CMD="sudo arping -A -c 3 -w 5 -I $to_intf $IP "
             fi
         fi
 
diff --git a/lib/nova_plugins/hypervisor-ironic b/lib/nova_plugins/hypervisor-ironic
index b147c43..bda6ef6 100644
--- a/lib/nova_plugins/hypervisor-ironic
+++ b/lib/nova_plugins/hypervisor-ironic
@@ -46,7 +46,7 @@
     iniset $NOVA_CONF ironic auth_type password
     iniset $NOVA_CONF ironic username admin
     iniset $NOVA_CONF ironic password $ADMIN_PASSWORD
-    iniset $NOVA_CONF ironic auth_url $KEYSTONE_AUTH_URI
+    iniset $NOVA_CONF ironic auth_url $KEYSTONE_SERVICE_URI
     iniset $NOVA_CONF ironic project_domain_id default
     iniset $NOVA_CONF ironic user_domain_id default
     iniset $NOVA_CONF ironic project_name demo
diff --git a/lib/swift b/lib/swift
index 3c121ca..a981dfc 100644
--- a/lib/swift
+++ b/lib/swift
@@ -527,7 +527,7 @@
         else
             iniset ${testfile} func_test auth_port 80
         fi
-        iniset ${testfile} func_test auth_uri ${KEYSTONE_AUTH_URI}
+        iniset ${testfile} func_test auth_uri ${KEYSTONE_SERVICE_URI}
         if [[ "$auth_vers" == "3" ]]; then
             iniset ${testfile} func_test auth_prefix /identity/v3/
         else
diff --git a/lib/tempest b/lib/tempest
index 1ce2350..47bdc22 100644
--- a/lib/tempest
+++ b/lib/tempest
@@ -428,8 +428,7 @@
         SCENARIO_IMAGE_DIR=${SCENARIO_IMAGE_DIR:-$FILES}
         SCENARIO_IMAGE_FILE=$DEFAULT_IMAGE_FILE_NAME
     fi
-    iniset $TEMPEST_CONFIG scenario img_dir $SCENARIO_IMAGE_DIR
-    iniset $TEMPEST_CONFIG scenario img_file $SCENARIO_IMAGE_FILE
+    iniset $TEMPEST_CONFIG scenario img_file $SCENARIO_IMAGE_DIR/$SCENARIO_IMAGE_FILE
 
     # If using provider networking, use the physical network for validation rather than private
     TEMPEST_SSH_NETWORK_NAME=$PRIVATE_NETWORK_NAME
diff --git a/lib/tls b/lib/tls
index d05536b..861496d 100644
--- a/lib/tls
+++ b/lib/tls
@@ -227,13 +227,7 @@
 function init_cert {
     if [[ ! -r $DEVSTACK_CERT ]]; then
         if [[ -n "$TLS_IP" ]]; then
-            if python3_enabled; then
-                TLS_IP="IP:$TLS_IP"
-            else
-                # Lie to let incomplete match routines work with python2
-                # see https://bugs.python.org/issue23239
-                TLS_IP="DNS:$TLS_IP,IP:$TLS_IP"
-            fi
+            TLS_IP="IP:$TLS_IP"
             if [[ -n "$HOST_IPV6" ]]; then
                 TLS_IP="$TLS_IP,IP:$HOST_IPV6"
             fi
@@ -255,7 +249,11 @@
 
     if [ "$common_name" != "$SERVICE_HOST" ]; then
         if is_ipv4_address "$SERVICE_HOST" ; then
-            alt_names="$alt_names,IP:$SERVICE_HOST"
+            if [[ -z "$alt_names" ]]; then
+                alt_names="IP:$SERVICE_HOST"
+            else
+                alt_names="$alt_names,IP:$SERVICE_HOST"
+            fi
         fi
     fi
 
diff --git a/openrc b/openrc
index 99d3351..beeaebe 100644
--- a/openrc
+++ b/openrc
@@ -87,9 +87,9 @@
 
 # If you don't have a working .stackenv, this is the backup position
 KEYSTONE_BACKUP=$SERVICE_PROTOCOL://$SERVICE_HOST:5000
-KEYSTONE_AUTH_URI=${KEYSTONE_AUTH_URI:-$KEYSTONE_BACKUP}
+KEYSTONE_SERVICE_URI=${KEYSTONE_SERVICE_URI:-$KEYSTONE_BACKUP}
 
-export OS_AUTH_URL=${OS_AUTH_URL:-$KEYSTONE_AUTH_URI}
+export OS_AUTH_URL=${OS_AUTH_URL:-$KEYSTONE_SERVICE_URI}
 
 # Currently, in order to use openstackclient with Identity API v3,
 # we need to set the domain which the user and project belong to.
diff --git a/roles/process-stackviz/README.rst b/roles/process-stackviz/README.rst
new file mode 100644
index 0000000..a8447d2
--- /dev/null
+++ b/roles/process-stackviz/README.rst
@@ -0,0 +1,22 @@
+Generate stackviz report.
+
+Generate stackviz report using subunit and dstat data, using
+the stackviz archive embedded in test images.
+
+**Role Variables**
+
+.. zuul:rolevar:: devstack_base_dir
+   :default: /opt/stack
+
+   The devstack base directory.
+
+.. zuul:rolevar:: stage_dir
+   :default: "{{ ansible_user_dir }}"
+
+   The stage directory where the input data can be found and
+   the output will be produced.
+
+.. zuul:rolevar:: zuul_work_dir
+   :default: {{ devstack_base_dir }}/tempest
+
+   Directory to work in. It has to be a fully qualified path.
diff --git a/roles/process-stackviz/defaults/main.yaml b/roles/process-stackviz/defaults/main.yaml
new file mode 100644
index 0000000..f3bc32b
--- /dev/null
+++ b/roles/process-stackviz/defaults/main.yaml
@@ -0,0 +1,3 @@
+devstack_base_dir: /opt/stack
+stage_dir: "{{ ansible_user_dir }}"
+zuul_work_dir: "{{ devstack_base_dir }}/tempest"
diff --git a/roles/process-stackviz/tasks/main.yaml b/roles/process-stackviz/tasks/main.yaml
new file mode 100644
index 0000000..c51c66c
--- /dev/null
+++ b/roles/process-stackviz/tasks/main.yaml
@@ -0,0 +1,70 @@
+- name: Devstack checks if stackviz archive exists
+  stat:
+    path: "/opt/cache/files/stackviz-latest.tar.gz"
+  register: stackviz_archive
+
+- debug:
+    msg: "Stackviz archive could not be found in /opt/cache/files/stackviz-latest.tar.gz"
+  when: not stackviz_archive.stat.exists
+
+- name: Check if subunit data exists
+  stat:
+    path: "{{ zuul_work_dir }}/testrepository.subunit"
+  register: subunit_input
+
+- debug:
+    msg: "Subunit file could not be found at {{ zuul_work_dir }}/testrepository.subunit"
+  when: not subunit_input.stat.exists
+
+- name: Install stackviz
+  when:
+    - stackviz_archive.stat.exists
+    - subunit_input.stat.exists
+  block:
+    - include_role:
+        name: ensure-pip
+
+    - pip:
+        name: "file://{{ stackviz_archive.stat.path }}"
+        virtualenv: /tmp/stackviz
+        virtualenv_command: '{{ ensure_pip_virtualenv_command }}'
+        extra_args: -U
+
+- name: Deploy stackviz static html+js
+  command: cp -pR /tmp/stackviz/share/stackviz-html {{ stage_dir }}/stackviz
+  when:
+    - stackviz_archive.stat.exists
+    - subunit_input.stat.exists
+
+- name: Check if dstat data exists
+  stat:
+    path: "{{ devstack_base_dir }}/logs/dstat-csv.log"
+  register: dstat_input
+  when:
+    - stackviz_archive.stat.exists
+    - subunit_input.stat.exists
+
+- name: Run stackviz with dstat
+  shell: |
+    cat {{ subunit_input.stat.path }} | \
+      /tmp/stackviz/bin/stackviz-export \
+        --dstat "{{ devstack_base_dir }}/logs/dstat-csv.log" \
+        --env --stdin \
+        {{ stage_dir }}/stackviz/data
+  when:
+    - stackviz_archive.stat.exists
+    - subunit_input.stat.exists
+    - dstat_input.stat.exists
+  failed_when: False
+
+- name: Run stackviz without dstat
+  shell: |
+    cat {{ subunit_input.stat.path }} | \
+      /tmp/stackviz/bin/stackviz-export \
+        --env --stdin \
+        {{ stage_dir }}/stackviz/data
+  when:
+    - stackviz_archive.stat.exists
+    - subunit_input.stat.exists
+    - not dstat_input.stat.exists
+  failed_when: False
diff --git a/stack.sh b/stack.sh
index 709b97b..37e7518 100755
--- a/stack.sh
+++ b/stack.sh
@@ -1053,7 +1053,7 @@
 
 # Set up password auth credentials now that Keystone is bootstrapped
 export OS_IDENTITY_API_VERSION=3
-export OS_AUTH_URL=$KEYSTONE_AUTH_URI
+export OS_AUTH_URL=$KEYSTONE_SERVICE_URI
 export OS_USERNAME=admin
 export OS_USER_DOMAIN_ID=default
 export OS_PASSWORD=$ADMIN_PASSWORD
diff --git a/stackrc b/stackrc
index e323cee..4ffd537 100644
--- a/stackrc
+++ b/stackrc
@@ -134,25 +134,17 @@
 fi
 
 # Control whether Python 3 should be used at all.
-export USE_PYTHON3=$(trueorfalse True USE_PYTHON3)
+# TODO(frickler): Drop this when all consumers are fixed
+export USE_PYTHON3=True
 
-# When Python 3 is supported by an application, adding the specific
-# version of Python 3 to this variable will install the app using that
-# version of the interpreter instead of 2.7.
+# Adding the specific version of Python 3 to this variable will install
+# the app using that version of the interpreter instead of just 3.
 _DEFAULT_PYTHON3_VERSION="$(_get_python_version python3)"
 export PYTHON3_VERSION=${PYTHON3_VERSION:-${_DEFAULT_PYTHON3_VERSION:-3}}
 
-# Just to be more explicit on the Python 2 version to use.
-_DEFAULT_PYTHON2_VERSION="$(_get_python_version python2)"
-export PYTHON2_VERSION=${PYTHON2_VERSION:-${_DEFAULT_PYTHON2_VERSION:-2.7}}
-
 # Create a virtualenv with this
-if [[ ${USE_PYTHON3} == True ]]; then
-    # Use the built-in venv to avoid more dependencies
-    export VIRTUALENV_CMD="python3 -m venv"
-else
-    export VIRTUALENV_CMD="virtualenv "
-fi
+# Use the built-in venv to avoid more dependencies
+export VIRTUALENV_CMD="python3 -m venv"
 
 # Default for log coloring is based on interactive-or-not.
 # Baseline assumption is that non-interactive invocations are for CI,
diff --git a/tools/install_pip.sh b/tools/install_pip.sh
index 517669e..f3fd1e2 100755
--- a/tools/install_pip.sh
+++ b/tools/install_pip.sh
@@ -92,9 +92,6 @@
         touch $LOCAL_PIP.downloaded
     fi
     sudo -H -E python${PYTHON3_VERSION} $LOCAL_PIP
-    if ! python3_enabled; then
-        sudo -H -E python $LOCAL_PIP
-    fi
 }
 
 
@@ -142,9 +139,6 @@
 # results in a nonfunctional system. pip on fedora installs to /usr so pip
 # can safely override the system pip for all versions of fedora
 if ! is_fedora  && ! is_suse; then
-    if is_package_installed python-pip ; then
-        uninstall_package python-pip
-    fi
     if is_package_installed python3-pip ; then
         uninstall_package python3-pip
     fi
diff --git a/unstack.sh b/unstack.sh
index ccea0ef..276111e 100755
--- a/unstack.sh
+++ b/unstack.sh
@@ -99,6 +99,7 @@
 
 if is_service_enabled nova; then
     stop_nova
+    cleanup_nova
 fi
 
 if is_service_enabled placement; then