Merge "Fixed support python 2 on Fedora 27"
diff --git a/doc/source/guides/multinode-lab.rst b/doc/source/guides/multinode-lab.rst
index fb351f8..c3574ac 100644
--- a/doc/source/guides/multinode-lab.rst
+++ b/doc/source/guides/multinode-lab.rst
@@ -177,7 +177,7 @@
     GLANCE_HOSTPORT=$SERVICE_HOST:9292
     ENABLED_SERVICES=n-cpu,q-agt,n-api-meta,c-vol,placement-client
     NOVA_VNC_ENABLED=True
-    NOVNCPROXY_URL="http://$SERVICE_HOST:6080/vnc_auto.html"
+    NOVNCPROXY_URL="http://$SERVICE_HOST:6080/vnc_lite.html"
     VNCSERVER_LISTEN=$HOST_IP
     VNCSERVER_PROXYCLIENT_ADDRESS=$VNCSERVER_LISTEN
 
diff --git a/doc/source/zuul_ci_jobs_migration.rst b/doc/source/zuul_ci_jobs_migration.rst
index c00f06e..633f951 100644
--- a/doc/source/zuul_ci_jobs_migration.rst
+++ b/doc/source/zuul_ci_jobs_migration.rst
@@ -102,7 +102,6 @@
         tox_envlist: 'all'
         devstack_localrc:
           KURYR_K8S_API_PORT: 8080
-          TEMPEST_PLUGINS: '/opt/stack/kuryr-tempest-plugin'
         devstack_services:
           kubernetes-api: true
           kubernetes-controller-manager: true
@@ -114,6 +113,8 @@
           kuryr-kubernetes: https://git.openstack.org/openstack/kuryr
           devstack-plugin-container: https://git.openstack.org/openstack/devstack-plugin-container
           neutron-lbaas: https://git.openstack.org/openstack/neutron-lbaas
+        tempest_plugins:
+          - kuryr-tempest-plugin
         (...)
 
 Job variables
diff --git a/inc/python b/inc/python
index ba2048d..e2a042e 100644
--- a/inc/python
+++ b/inc/python
@@ -94,7 +94,7 @@
     cd $name
     set +e
     classifier=$(python setup.py --classifiers \
-        | grep 'Programming Language :: Python :: 3$')
+        | grep 'Programming Language :: Python :: 3')
     set -e
     echo $classifier
 }
@@ -290,6 +290,8 @@
                             echo "Automatically using $PYTHON3_VERSION version to install $package_dir based on local package settings"
                             sudo_pip="$sudo_pip LC_ALL=en_US.UTF-8"
                             cmd_pip=$(get_pip_command $PYTHON3_VERSION)
+                        else
+                            echo "WARNING: Did not find python 3 classifier for local package $package_dir"
                         fi
                     fi
                 else
@@ -300,6 +302,8 @@
                         echo "Automatically using $PYTHON3_VERSION version to install $package based on remote package settings"
                         sudo_pip="$sudo_pip LC_ALL=en_US.UTF-8"
                         cmd_pip=$(get_pip_command $PYTHON3_VERSION)
+                    else
+                        echo "WARNING: Did not find python 3 classifier for remote package $package_dir"
                     fi
                 fi
             fi
diff --git a/lib/cinder b/lib/cinder
index 48f3e45..047b25b 100644
--- a/lib/cinder
+++ b/lib/cinder
@@ -435,6 +435,9 @@
         install_package tgt
     elif [[ "$CINDER_ISCSI_HELPER" == "lioadm" ]]; then
         if [[ ${DISTRO} == "bionic" ]]; then
+            # TODO(frickler): Workaround for https://launchpad.net/bugs/1819819
+            sudo mkdir -p /etc/target
+
             install_package targetcli-fb
         else
             install_package targetcli
diff --git a/lib/neutron b/lib/neutron
index b2414cf..1066d8e 100644
--- a/lib/neutron
+++ b/lib/neutron
@@ -33,7 +33,7 @@
 # - True : Run neutron under uwsgi
 # TODO(annp): Switching to uwsgi in next cycle if things turn out to be stable
 # enough
-NEUTRON_DEPLOY_MOD_WSGI=${NEUTRON_DEPLOY_MOD_WSGI:-False}
+NEUTRON_DEPLOY_MOD_WSGI=$(trueorfalse False NEUTRON_DEPLOY_MOD_WSGI)
 NEUTRON_AGENT=${NEUTRON_AGENT:-openvswitch}
 NEUTRON_DIR=$DEST/neutron
 NEUTRON_AUTH_CACHE_DIR=${NEUTRON_AUTH_CACHE_DIR:-/var/cache/neutron}
diff --git a/lib/neutron-legacy b/lib/neutron-legacy
index 2fdb6db..8257115 100644
--- a/lib/neutron-legacy
+++ b/lib/neutron-legacy
@@ -91,7 +91,7 @@
 # - True : Run neutron under uwsgi
 # TODO(annp): Switching to uwsgi in next cycle if things turn out to be stable
 # enough
-NEUTRON_DEPLOY_MOD_WSGI=${NEUTRON_DEPLOY_MOD_WSGI:-False}
+NEUTRON_DEPLOY_MOD_WSGI=$(trueorfalse False NEUTRON_DEPLOY_MOD_WSGI)
 
 NEUTRON_UWSGI_CONF=$NEUTRON_CONF_DIR/neutron-api-uwsgi.ini
 
diff --git a/lib/nova b/lib/nova
index 033ebf3..7c6f8cd 100644
--- a/lib/nova
+++ b/lib/nova
@@ -614,7 +614,16 @@
     # All nova-compute workers need to know the vnc configuration options
     # These settings don't hurt anything if n-xvnc and n-novnc are disabled
     if is_service_enabled n-cpu; then
-        NOVNCPROXY_URL=${NOVNCPROXY_URL:-"http://$SERVICE_HOST:6080/vnc_auto.html"}
+        if [ "$NOVNC_FROM_PACKAGE" == "True" ]; then
+            # Use the old URL when installing novnc packages.
+            NOVNCPROXY_URL=${NOVNCPROXY_URL:-"http://$SERVICE_HOST:6080/vnc_auto.html"}
+        elif vercmp ${NOVNC_BRANCH} "<" "1.0.0"; then
+             # Use the old URL when installing older novnc source.
+            NOVNCPROXY_URL=${NOVNCPROXY_URL:-"http://$SERVICE_HOST:6080/vnc_auto.html"}
+        else
+            # Use the new URL when building >=v1.0.0 from source.
+            NOVNCPROXY_URL=${NOVNCPROXY_URL:-"http://$SERVICE_HOST:6080/vnc_lite.html"}
+        fi
         iniset $NOVA_CPU_CONF vnc novncproxy_base_url "$NOVNCPROXY_URL"
         XVPVNCPROXY_URL=${XVPVNCPROXY_URL:-"http://$SERVICE_HOST:6081/console"}
         iniset $NOVA_CPU_CONF vnc xvpvncproxy_base_url "$XVPVNCPROXY_URL"
@@ -665,6 +674,22 @@
             sudo mkdir -p /etc/pki/nova-novnc
             deploy_int_CA /etc/pki/nova-novnc/ca-cert.pem
             deploy_int_cert /etc/pki/nova-novnc/client-cert.pem /etc/pki/nova-novnc/client-key.pem
+            # OpenSSL 1.1.0 generates the key file with permissions: 600, by
+            # default, and the deploy_int* methods use 'sudo cp' to copy the
+            # files, making them owned by root:root.
+            # Change ownership of everything under /etc/pki/nova-novnc to
+            # $STACK_USER:$(id -g ${STACK_USER}) so that $STACK_USER can read
+            # the key file.
+            sudo chown -R $STACK_USER:$(id -g ${STACK_USER}) /etc/pki/nova-novnc
+            # This is needed to enable TLS in the proxy itself, example log:
+            # WebSocket server settings:
+            #   - Listen on 0.0.0.0:6080
+            #   - Flash security policy server
+            #   - Web server (no directory listings). Web root: /usr/share/novnc
+            #   - SSL/TLS support
+            #   - proxying from 0.0.0.0:6080 to None:None
+            iniset $conf DEFAULT key "/etc/pki/nova-novnc/client-key.pem"
+            iniset $conf DEFAULT cert "/etc/pki/nova-novnc/client-cert.pem"
         fi
     fi
 
diff --git a/lib/nova_plugins/functions-libvirt b/lib/nova_plugins/functions-libvirt
index fcb4777..4639869 100644
--- a/lib/nova_plugins/functions-libvirt
+++ b/lib/nova_plugins/functions-libvirt
@@ -155,9 +155,15 @@
             echo "vnc_tls_x509_verify = 1" | sudo tee -a $QEMU_CONF
 
             sudo mkdir -p /etc/pki/libvirt-vnc
-            sudo chown libvirt-qemu:libvirt-qemu /etc/pki/libvirt-vnc
             deploy_int_CA /etc/pki/libvirt-vnc/ca-cert.pem
             deploy_int_cert /etc/pki/libvirt-vnc/server-cert.pem /etc/pki/libvirt-vnc/server-key.pem
+            # OpenSSL 1.1.0 generates the key file with permissions: 600, by
+            # default and the deploy_int* methods use 'sudo cp' to copy the
+            # files, making them owned by root:root.
+            # Change ownership of everything under /etc/pki/libvirt-vnc to
+            # libvirt-qemu:libvirt-qemu so that libvirt-qemu can read the key
+            # file.
+            sudo chown -R libvirt-qemu:libvirt-qemu /etc/pki/libvirt-vnc
         fi
     fi
 
diff --git a/lib/tempest b/lib/tempest
index 1e344c8..a8f107a 100644
--- a/lib/tempest
+++ b/lib/tempest
@@ -255,6 +255,9 @@
     # and the public_network_id should not be set.
     if [[ "$NEUTRON_CREATE_INITIAL_NETWORKS" == "True" ]] && is_networking_extension_supported 'external-net'; then
         public_network_id=$(openstack network show -f value -c id $PUBLIC_NETWORK_NAME)
+        # make sure shared network presence does not confuses the tempest tests
+        openstack network create --share shared
+        openstack subnet create --description shared-subnet --subnet-range ${TEMPEST_SHARED_POOL:-192.168.233.0/24} --network shared shared-subnet
     fi
 
     iniset $TEMPEST_CONFIG DEFAULT use_syslog $SYSLOG
@@ -276,8 +279,6 @@
     iniset $TEMPEST_CONFIG identity user_lockout_failure_attempts $KEYSTONE_LOCKOUT_FAILURE_ATTEMPTS
     iniset $TEMPEST_CONFIG identity user_lockout_duration $KEYSTONE_LOCKOUT_DURATION
     iniset $TEMPEST_CONFIG identity user_unique_last_password_count $KEYSTONE_UNIQUE_LAST_PASSWORD_COUNT
-    # Use domain scoped tokens for admin v3 tests, v3 dynamic credentials of v3 account generation
-    iniset $TEMPEST_CONFIG identity admin_domain_scope True
     if [[ "$TEMPEST_HAS_ADMIN" == "True" ]]; then
         iniset $TEMPEST_CONFIG auth admin_username $admin_username
         iniset $TEMPEST_CONFIG auth admin_password "$password"
@@ -674,7 +675,7 @@
     pushd $TEMPEST_DIR
     if [[ $TEMPEST_PLUGINS != 0 ]] ; then
         # The requirements might be on a different branch, while tempest & tempest plugins needs master requirements.
-        (cd $REQUIREMENTS_DIR && git show master:upper-constraints.txt) > u-c-m.txt
+        (cd $REQUIREMENTS_DIR && git show origin/master:upper-constraints.txt) > u-c-m.txt
         tox -evenv-tempest -- pip install -c u-c-m.txt $TEMPEST_PLUGINS
         echo "Checking installed Tempest plugins:"
         tox -evenv-tempest -- tempest list-plugins
diff --git a/roles/write-devstack-local-conf/README.rst b/roles/write-devstack-local-conf/README.rst
index e9739cd..d0a51e7 100644
--- a/roles/write-devstack-local-conf/README.rst
+++ b/roles/write-devstack-local-conf/README.rst
@@ -88,3 +88,12 @@
    If a plugin declares a dependency on another plugin (via
    ``plugin_requires`` in the plugin's settings file), this role will
    automatically emit ``enable_plugin`` lines in the correct order.
+
+.. zuul:rolevar:: tempest_plugins
+   :type: list
+
+   A list of tempest plugins which are installed alongside tempest.
+
+   The list of values will be combined with the base devstack directory
+   and used to populate the ``TEMPEST_PLUGINS`` variable. If the variable
+   already exists, its value is *not* changed.
diff --git a/roles/write-devstack-local-conf/library/devstack_local_conf.py b/roles/write-devstack-local-conf/library/devstack_local_conf.py
index 1366a22..3a8cd58 100644
--- a/roles/write-devstack-local-conf/library/devstack_local_conf.py
+++ b/roles/write-devstack-local-conf/library/devstack_local_conf.py
@@ -207,13 +207,15 @@
 class LocalConf(object):
 
     def __init__(self, localrc, localconf, base_services, services, plugins,
-                 base_dir, projects, project):
+                 base_dir, projects, project, tempest_plugins):
         self.localrc = []
+        self.warnings = []
         self.meta_sections = {}
         self.plugin_deps = {}
         self.base_dir = base_dir
         self.projects = projects
         self.project = project
+        self.tempest_plugins = tempest_plugins
         if services or base_services:
             self.handle_services(base_services, services or {})
         self.handle_localrc(localrc)
@@ -246,12 +248,15 @@
 
     def handle_localrc(self, localrc):
         lfg = False
+        tp = False
         if localrc:
             vg = VarGraph(localrc)
             for k, v in vg.getVars():
-                self.localrc.append('{}={}'.format(k, v))
+                self.localrc.append('{}="{}"'.format(k, v))
                 if k == 'LIBS_FROM_GIT':
                     lfg = True
+                elif k == 'TEMPEST_PLUGINS':
+                    tp = True
 
         if not lfg and (self.projects or self.project):
             required_projects = []
@@ -266,6 +271,19 @@
                 self.localrc.append('LIBS_FROM_GIT={}'.format(
                     ','.join(required_projects)))
 
+        if self.tempest_plugins:
+            if not tp:
+                tp_dirs = []
+                for tempest_plugin in self.tempest_plugins:
+                    tp_dirs.append(os.path.join(self.base_dir, tempest_plugin))
+                self.localrc.append('TEMPEST_PLUGINS="{}"'.format(
+                        ' '.join(tp_dirs)))
+            else:
+                self.warnings.append('TEMPEST_PLUGINS already defined ({}),'
+                                     'requested value {} ignored'.format(
+                                         tp, self.tempest_plugins))
+
+
     def handle_localconf(self, localconf):
         for phase, phase_data in localconf.items():
             for fn, fn_data in phase_data.items():
@@ -300,6 +318,7 @@
             path=dict(type='str'),
             projects=dict(type='dict'),
             project=dict(type='dict'),
+            tempest_plugins=dict(type='list'),
         )
     )
 
@@ -311,10 +330,11 @@
                    p.get('plugins'),
                    p.get('base_dir'),
                    p.get('projects'),
-                   p.get('project'))
+                   p.get('project'),
+                   p.get('tempest_plugins'))
     lc.write(p['path'])
 
-    module.exit_json()
+    module.exit_json(warnings=lc.warnings)
 
 
 try:
diff --git a/roles/write-devstack-local-conf/library/test.py b/roles/write-devstack-local-conf/library/test.py
index 45fae3d..22bf2da 100644
--- a/roles/write-devstack-local-conf/library/test.py
+++ b/roles/write-devstack-local-conf/library/test.py
@@ -23,6 +23,20 @@
 from collections import OrderedDict
 
 class TestDevstackLocalConf(unittest.TestCase):
+
+    @staticmethod
+    def _init_localconf(p):
+        lc = LocalConf(p.get('localrc'),
+                       p.get('local_conf'),
+                       p.get('base_services'),
+                       p.get('services'),
+                       p.get('plugins'),
+                       p.get('base_dir'),
+                       p.get('projects'),
+                       p.get('project'),
+                       p.get('tempest_plugins'))
+        return lc
+
     def setUp(self):
         self.tmpdir = tempfile.mkdtemp()
 
@@ -51,14 +65,7 @@
                  plugins=plugins,
                  base_dir='./test',
                  path=os.path.join(self.tmpdir, 'test.local.conf'))
-        lc = LocalConf(p.get('localrc'),
-                       p.get('local_conf'),
-                       p.get('base_services'),
-                       p.get('services'),
-                       p.get('plugins'),
-                       p.get('base_dir'),
-                       p.get('projects'),
-                       p.get('project'))
+        lc = self._init_localconf(p)
         lc.write(p['path'])
 
         plugins = []
@@ -104,14 +111,7 @@
                  plugins=plugins,
                  base_dir=self.tmpdir,
                  path=os.path.join(self.tmpdir, 'test.local.conf'))
-        lc = LocalConf(p.get('localrc'),
-                       p.get('local_conf'),
-                       p.get('base_services'),
-                       p.get('services'),
-                       p.get('plugins'),
-                       p.get('base_dir'),
-                       p.get('projects'),
-                       p.get('project'))
+        lc = self._init_localconf(p)
         lc.write(p['path'])
 
         plugins = []
@@ -145,14 +145,7 @@
                  path=os.path.join(self.tmpdir, 'test.local.conf'),
                  projects=projects,
                  project=project)
-        lc = LocalConf(p.get('localrc'),
-                       p.get('local_conf'),
-                       p.get('base_services'),
-                       p.get('services'),
-                       p.get('plugins'),
-                       p.get('base_dir'),
-                       p.get('projects'),
-                       p.get('project'))
+        lc = self._init_localconf(p)
         lc.write(p['path'])
 
         lfg = None
@@ -184,14 +177,7 @@
                  base_dir='./test',
                  path=os.path.join(self.tmpdir, 'test.local.conf'),
                  projects=projects)
-        lc = LocalConf(p.get('localrc'),
-                       p.get('local_conf'),
-                       p.get('base_services'),
-                       p.get('services'),
-                       p.get('plugins'),
-                       p.get('base_dir'),
-                       p.get('projects'),
-                       p.get('project'))
+        lc = self._init_localconf(p)
         lc.write(p['path'])
 
         lfg = None
@@ -199,7 +185,7 @@
             for line in f:
                 if line.startswith('LIBS_FROM_GIT'):
                     lfg = line.strip().split('=')[1]
-        self.assertEqual('oslo.db', lfg)
+        self.assertEqual('"oslo.db"', lfg)
 
     def test_plugin_circular_deps(self):
         "Test that plugins with circular dependencies fail"
@@ -238,14 +224,50 @@
                  base_dir=self.tmpdir,
                  path=os.path.join(self.tmpdir, 'test.local.conf'))
         with self.assertRaises(Exception):
-            lc = LocalConf(p.get('localrc'),
-                           p.get('local_conf'),
-                           p.get('base_services'),
-                           p.get('services'),
-                           p.get('plugins'),
-                           p.get('base_dir'))
+            lc = self._init_localconf(p)
             lc.write(p['path'])
 
+    def _find_tempest_plugins_value(self, file_path):
+        tp = None
+        with open(file_path) as f:
+            for line in f:
+                if line.startswith('TEMPEST_PLUGINS'):
+                    found = line.strip().split('=')[1]
+                    self.assertIsNone(tp,
+                        "TEMPEST_PLUGIN ({}) found again ({})".format(
+                            tp, found))
+                    tp = found
+        return tp
+
+    def test_tempest_plugins(self):
+        "Test that TEMPEST_PLUGINS is correctly populated."
+        p = dict(base_services=[],
+                 base_dir='./test',
+                 path=os.path.join(self.tmpdir, 'test.local.conf'),
+                 tempest_plugins=['heat-tempest-plugin', 'sahara-tests'])
+        lc = self._init_localconf(p)
+        lc.write(p['path'])
+
+        tp = self._find_tempest_plugins_value(p['path'])
+        self.assertEqual('"./test/heat-tempest-plugin ./test/sahara-tests"', tp)
+        self.assertEqual(len(lc.warnings), 0)
+
+    def test_tempest_plugins_not_overridden(self):
+        """Test that the existing value of TEMPEST_PLUGINS is not overridden
+        by the user-provided value, but a warning is emitted."""
+        localrc = {'TEMPEST_PLUGINS': 'someplugin'}
+        p = dict(localrc=localrc,
+                 base_services=[],
+                 base_dir='./test',
+                 path=os.path.join(self.tmpdir, 'test.local.conf'),
+                 tempest_plugins=['heat-tempest-plugin', 'sahara-tests'])
+        lc = self._init_localconf(p)
+        lc.write(p['path'])
+
+        tp = self._find_tempest_plugins_value(p['path'])
+        self.assertEqual('"someplugin"', tp)
+        self.assertEqual(len(lc.warnings), 1)
+
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/roles/write-devstack-local-conf/tasks/main.yaml b/roles/write-devstack-local-conf/tasks/main.yaml
index 9a6b083..bfd0860 100644
--- a/roles/write-devstack-local-conf/tasks/main.yaml
+++ b/roles/write-devstack-local-conf/tasks/main.yaml
@@ -10,4 +10,5 @@
     local_conf: "{{ devstack_local_conf|default(omit) }}"
     base_dir: "{{ devstack_base_dir|default(omit) }}"
     projects: "{{ zuul.projects }}"
-    project: "{{ zuul.project }}"
\ No newline at end of file
+    project: "{{ zuul.project }}"
+    tempest_plugins: "{{ tempest_plugins|default(omit) }}"
diff --git a/stackrc b/stackrc
index 652f2de..c6304bb 100644
--- a/stackrc
+++ b/stackrc
@@ -607,7 +607,7 @@
 
 # a websockets/html5 or flash powered VNC console for vm instances
 NOVNC_REPO=${NOVNC_REPO:-https://github.com/novnc/noVNC.git}
-NOVNC_BRANCH=${NOVNC_BRANCH:-stable/v0.6}
+NOVNC_BRANCH=${NOVNC_BRANCH:-v1.0.0}
 
 # a websockets/html5 or flash powered SPICE console for vm instances
 SPICE_REPO=${SPICE_REPO:-http://anongit.freedesktop.org/git/spice/spice-html5.git}
@@ -736,10 +736,10 @@
 EXTRA_CACHE_URLS=""
 
 # etcd3 defaults
-ETCD_VERSION=${ETCD_VERSION:-v3.2.17}
-ETCD_SHA256_AMD64=${ETCD_SHA256_AMD64:-"0a75e794502e2e76417b19da2807a9915fa58dcbf0985e397741d570f4f305cd"}
-ETCD_SHA256_ARM64=${ETCD_SHA256_ARM64:-"0ab4621c44c79d17d94e43bd184d0f23b763a3669056ce4ae2d0b2942410a98f"}
-ETCD_SHA256_PPC64=${ETCD_SHA256_PPC64:-"69e1279c4a2a52256b78d2a8dd23346ac46b836e678b971a459f2afaef3c275e"}
+ETCD_VERSION=${ETCD_VERSION:-v3.3.12}
+ETCD_SHA256_AMD64=${ETCD_SHA256_AMD64:-"dc5d82df095dae0a2970e4d870b6929590689dd707ae3d33e7b86da0f7f211b6"}
+ETCD_SHA256_ARM64=${ETCD_SHA256_ARM64:-"170b848ac1a071fe7d495d404a868a2c0090750b2944f8a260ef1c6125b2b4f4"}
+ETCD_SHA256_PPC64=${ETCD_SHA256_PPC64:-"77f807b1b51abbf51e020bb05bdb8ce088cb58260fcd22749ea32eee710463d3"}
 # etcd v3.2.x doesn't have anything for s390x
 ETCD_SHA256_S390X=${ETCD_SHA256_S390X:-""}
 # Make sure etcd3 downloads the correct architecture