Merge "Change to use memory_tracker variable"
diff --git a/devstack/README.rst b/devstack/README.rst
new file mode 100644
index 0000000..d1755e5
--- /dev/null
+++ b/devstack/README.rst
@@ -0,0 +1,17 @@
+====================
+Enabling in Devstack
+====================
+
+1. Download DevStack::
+
+    git clone https://opendev.org/openstack/devstack.git
+    cd devstack
+
+2. Add this repo as an external repository::
+
+     > cat local.conf
+     [[local|localrc]]
+     enable_plugin octavia-tempest-plugin https://opendev.org/openstack/octavia-tempest-plugin
+
+3. run ``stack.sh``
+
diff --git a/devstack/plugin.sh b/devstack/plugin.sh
new file mode 100644
index 0000000..babf033
--- /dev/null
+++ b/devstack/plugin.sh
@@ -0,0 +1,14 @@
+
+# install_octavia_tempest_plugin
+function install_octavia_tempest_plugin {
+    setup_dev_lib "octavia-tempest-plugin"
+}
+
+if [[ "$1" == "stack" ]]; then
+    case "$2" in
+        install)
+            echo_summary "Installing octavia-tempest-plugin"
+            install_octavia_tempest_plugin
+            ;;
+    esac
+fi
diff --git a/devstack/settings b/devstack/settings
new file mode 100644
index 0000000..a905fac
--- /dev/null
+++ b/devstack/settings
@@ -0,0 +1,3 @@
+GITREPO["octavia-tempest-plugin"]=${OCTAVIA_TEMPEST_REPO:-${GIT_BASE}/openstack/octavia-tempest-plugin.git}
+GITDIR["octavia-tempest-plugin"]=$DEST/octavia-tempest-plugin
+GITBRANCH["octavia-tempest-plugin"]=${OCTAVIA_TEMPEST_REF:-master}
diff --git a/doc/requirements.txt b/doc/requirements.txt
index 90ae115..2a94e8b 100644
--- a/doc/requirements.txt
+++ b/doc/requirements.txt
@@ -3,12 +3,11 @@
 # process, which may cause wedges in the gate later.
 
 sphinxcontrib-apidoc>=0.2.0 # BSD
-sphinx!=1.6.6,!=1.6.7,>=1.6.2,<2.0.0;python_version=='2.7'  # BSD
-sphinx!=1.6.6,!=1.6.7,>=1.6.2,!=2.1.0;python_version>='3.4'  # BSD
-openstackdocstheme>=1.18.1 # Apache-2.0
+sphinx>=2.0.0,!=2.1.0 # BSD
+openstackdocstheme>=2.2.1 # Apache-2.0
 
 # releasenotes
-reno>=2.5.0 # Apache-2.0
+reno>=3.1.0 # Apache-2.0
 
 # PDF Docs
 sphinxcontrib-svg2pdfconverter>=0.1.0 # BSD
diff --git a/doc/source/conf.py b/doc/source/conf.py
index 5bbe295..ff6a826 100755
--- a/doc/source/conf.py
+++ b/doc/source/conf.py
@@ -16,8 +16,6 @@
 import os
 import sys
 
-import openstackdocstheme
-
 sys.path.insert(0, os.path.abspath('../..'))
 sys.path.insert(0, os.path.abspath('.'))
 
@@ -49,16 +47,6 @@
 project = u'octavia-tempest-plugin'
 copyright = u'2017-2019, OpenStack Foundation'
 
-# The version info for the project you're documenting, acts as replacement for
-# |version| and |release|, also used in various other places throughout the
-# built documents.
-#
-# Version info
-from octavia_tempest_plugin.version import version_info as octavia_tempest_ver
-release = octavia_tempest_ver.release_string()
-# The short X.Y version.
-version = octavia_tempest_ver.version_string()
-
 # If true, '()' will be appended to :func: etc. cross-reference text.
 add_function_parentheses = True
 
@@ -67,14 +55,14 @@
 add_module_names = False
 
 # The name of the Pygments (syntax highlighting) style to use.
-pygments_style = 'sphinx'
+pygments_style = 'native'
 
 # A list of ignored prefixes for module index sorting.
 modindex_common_prefix = ['octavia_tempest_plugin.']
 
-repository_name = 'openstack/octavia-tempest-plugin'
-bug_project = '910'
-bug_tag = 'docs'
+openstackdocs_repo_name = 'openstack/octavia-tempest-plugin'
+openstackdocs_pdf_link = True
+openstackdocs_use_storyboard = True
 
 apidoc_output_dir = '_build/modules'
 apidoc_module_dir = '../../octavia_tempest_plugin'
@@ -90,7 +78,6 @@
 
 html_theme = 'openstackdocs'
 
-html_last_updated_fmt = '%Y-%m-%d %H:%M'
 
 # Output file base name for HTML help builder.
 htmlhelp_basename = '%sdoc' % project
diff --git a/octavia_tempest_plugin/contrib/test_server/README.rst b/octavia_tempest_plugin/contrib/test_server/README.rst
index da719b7..ba959f9 100644
--- a/octavia_tempest_plugin/contrib/test_server/README.rst
+++ b/octavia_tempest_plugin/contrib/test_server/README.rst
@@ -2,8 +2,8 @@
 Amphorae test server
 ====================
 
-test_server is a static application that simulates an HTTP and a UDP server.
-
+test_server.bin is a static application that simulates HTTP, HTTPS, and UDP
+servers. This server can properly handle concurrent requests.
 
 Building
 --------
@@ -12,15 +12,55 @@
 
 Install dependencies for Ubuntu/Debian:
 
+::
+
     sudo apt-get install -y golang
 
 Install dependencies for Centos (use golang 1.10 from go-toolset-7) and launch
 a shell into the new environment:
 
+::
+
     sudo yum install -y centos-release-scl
     sudo yum install -y go-toolset-7-golang-bin glibc-static openssl-static zlib-static
     scl enable go-toolset-7 bash
 
 Build the binary:
 
+::
+
     CGO_ENABLED=0 GOOS=linux go build -a -ldflags '-s -w -extldflags -static' -o test_server.bin test_server.go
+
+
+Usage
+-----
+
+The usage string can be output from the command by running:
+
+::
+
+    ./test_server.bin --help
+
+Example output:
+
+::
+
+  Usage of ./test_server.bin:
+    -cert string
+          Server side PEM format certificate.
+    -client_ca string
+          Client side PEM format CA certificate.
+    -https_port int
+          HTTPS port to listen on, -1 is disabled. (default -1)
+    -id string
+          Server ID (default "1")
+    -key string
+          Server side PEM format key.
+    -port int
+          Port to listen on (default 8080)
+
+If -https_port is not specified, the server will not accept HTTPS requests.
+When --https_port is specified, -cert and -key are required parameters.
+If -https_port is specified, the -client_ca parameter is optional. When
+-client_ca is specified, it will configure the HTTPS port to require a valid
+client certificate to connect.
diff --git a/octavia_tempest_plugin/contrib/test_server/test_server.bin b/octavia_tempest_plugin/contrib/test_server/test_server.bin
index e3cc7ba..75ec2f2 100755
--- a/octavia_tempest_plugin/contrib/test_server/test_server.bin
+++ b/octavia_tempest_plugin/contrib/test_server/test_server.bin
Binary files differ
diff --git a/octavia_tempest_plugin/contrib/test_server/test_server.go b/octavia_tempest_plugin/contrib/test_server/test_server.go
index 8139580..f8bc1e0 100644
--- a/octavia_tempest_plugin/contrib/test_server/test_server.go
+++ b/octavia_tempest_plugin/contrib/test_server/test_server.go
@@ -1,11 +1,17 @@
 package main
 
 import (
+	"crypto/rand"
+	"crypto/tls"
+	"crypto/x509"
 	"flag"
 	"fmt"
 	"io"
+	"io/ioutil"
+	"log"
 	"net"
 	"net/http"
+	"os"
 	"sync"
 	"time"
 )
@@ -83,13 +89,23 @@
 	fmt.Fprintf(w, "max_conn=%d\ntotal_conn=%d\n", max_conn, total_conn)
 }
 
+func https_wrapper(base_handler func(http.ResponseWriter,
+	*http.Request)) http.Handler {
+	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+
+		w.Header().Add("Strict-Transport-Security",
+			"max-age=66012000; includeSubDomains")
+		base_handler(w, r)
+	})
+}
+
 func reset_handler(w http.ResponseWriter, r *http.Request) {
 	http.SetCookie(w, &sess_cookie)
 	scoreboard.reset()
 	fmt.Fprintf(w, "reset\n")
 }
 
-func http_serve(port int, id string) {
+func http_setup(id string) {
 	sess_cookie.Name = "JSESSIONID"
 	sess_cookie.Value = id
 
@@ -97,8 +113,65 @@
 	http.HandleFunc("/slow", slow_handler)
 	http.HandleFunc("/stats", stats_handler)
 	http.HandleFunc("/reset", reset_handler)
+}
+
+func http_serve(port int, id string) {
 	portStr := fmt.Sprintf(":%d", port)
-	http.ListenAndServe(portStr, nil)
+	log.Fatal(http.ListenAndServe(portStr, nil))
+}
+
+func https_serve(port int, id string, cert tls.Certificate,
+	certpool *x509.CertPool, server_cert_pem string,
+	server_key_pem string) {
+	mux := http.NewServeMux()
+	mux.Handle("/", https_wrapper(root_handler))
+	mux.Handle("/slow", https_wrapper(slow_handler))
+	mux.Handle("/stats", https_wrapper(stats_handler))
+	mux.Handle("/reset", https_wrapper(reset_handler))
+
+	var tls_config *tls.Config
+	if certpool != nil {
+		tls_config = &tls.Config{
+			Certificates: []tls.Certificate{cert},
+			ClientAuth:   tls.RequireAndVerifyClientCert,
+			ClientCAs:    certpool,
+			MinVersion:   tls.VersionTLS12,
+			CurvePreferences: []tls.CurveID{tls.CurveP521, tls.CurveP384,
+				tls.CurveP256},
+			PreferServerCipherSuites: true,
+			CipherSuites: []uint16{
+				tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
+				tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
+				tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
+				tls.TLS_RSA_WITH_AES_256_CBC_SHA,
+			},
+		}
+	} else {
+		tls_config = &tls.Config{
+			Certificates: []tls.Certificate{cert},
+			ClientAuth:   tls.NoClientCert,
+			MinVersion:   tls.VersionTLS12,
+			CurvePreferences: []tls.CurveID{tls.CurveP521, tls.CurveP384,
+				tls.CurveP256},
+			PreferServerCipherSuites: true,
+			CipherSuites: []uint16{
+				tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
+				tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
+				tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
+				tls.TLS_RSA_WITH_AES_256_CBC_SHA,
+			},
+		}
+	}
+	tls_config.Rand = rand.Reader
+	portStr := fmt.Sprintf(":%d", port)
+	srv := &http.Server{
+		Addr:      portStr,
+		Handler:   mux,
+		TLSConfig: tls_config,
+		TLSNextProto: make(map[string]func(*http.Server, *tls.Conn,
+			http.Handler), 0),
+	}
+	log.Fatal(srv.ListenAndServeTLS(server_cert_pem, server_key_pem))
 }
 
 func udp_serve(port int, id string) {
@@ -129,11 +202,44 @@
 func main() {
 	portPtr := flag.Int("port", 8080, "Port to listen on")
 	idPtr := flag.String("id", "1", "Server ID")
+	https_portPtr := flag.Int("https_port", -1,
+		"HTTPS port to listen on, -1 is disabled.")
+	server_cert_pem := flag.String("cert", "",
+		"Server side PEM format certificate.")
+	server_key := flag.String("key", "", "Server side PEM format key.")
+	client_ca_cert_pem := flag.String("client_ca", "",
+		"Client side PEM format CA certificate.")
 
 	flag.Parse()
 
 	resp = fmt.Sprintf("%s", *idPtr)
 
+	http_setup(*idPtr)
+
+	if *https_portPtr > -1 {
+		cert, err := tls.LoadX509KeyPair(*server_cert_pem, *server_key)
+		if err != nil {
+			fmt.Println("Error load server certificate and key.\n")
+			os.Exit(1)
+		}
+		certpool := x509.NewCertPool()
+		if *client_ca_cert_pem != "" {
+			ca_pem, err := ioutil.ReadFile(*client_ca_cert_pem)
+			if err != nil {
+				fmt.Println("Error load client side CA cert.\n")
+				os.Exit(1)
+			}
+			if !certpool.AppendCertsFromPEM(ca_pem) {
+				fmt.Println("Can't parse client side certificate authority")
+				os.Exit(1)
+			}
+		} else {
+			certpool = nil
+		}
+		go https_serve(*https_portPtr, *idPtr, cert, certpool,
+			*server_cert_pem, *server_key)
+	}
+
 	go http_serve(*portPtr, *idPtr)
 	udp_serve(*portPtr, *idPtr)
 }
diff --git a/octavia_tempest_plugin/hacking/__init__.py b/octavia_tempest_plugin/hacking/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/octavia_tempest_plugin/hacking/__init__.py
diff --git a/octavia_tempest_plugin/hacking/checks.py b/octavia_tempest_plugin/hacking/checks.py
new file mode 100644
index 0000000..eec7476
--- /dev/null
+++ b/octavia_tempest_plugin/hacking/checks.py
@@ -0,0 +1,277 @@
+# Copyright (c) 2014 OpenStack Foundation.
+#
+# 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.
+
+
+"""
+Guidelines for writing new hacking checks
+
+ - Use only for Octavia specific tests. OpenStack general tests
+   should be submitted to the common 'hacking' module.
+ - Pick numbers in the range O3xx. Find the current test with
+   the highest allocated number and then pick the next value.
+ - Keep the test method code in the source file ordered based
+   on the O3xx value.
+ - List the new rule in the top level HACKING.rst file
+ - Add test cases for each new rule to
+   octavia/tests/unit/test_hacking.py
+
+"""
+
+import re
+
+from hacking import core
+
+
+_all_log_levels = {'critical', 'error', 'exception', 'info', 'warning'}
+_all_hints = {'_LC', '_LE', '_LI', '_', '_LW'}
+
+_log_translation_hint = re.compile(
+    r".*LOG\.(%(levels)s)\(\s*(%(hints)s)\(" % {
+        'levels': '|'.join(_all_log_levels),
+        'hints': '|'.join(_all_hints),
+    })
+
+assert_trueinst_re = re.compile(
+    r"(.)*assertTrue\(isinstance\((\w|\.|\'|\"|\[|\])+, "
+    r"(\w|\.|\'|\"|\[|\])+\)\)")
+assert_equal_in_end_with_true_or_false_re = re.compile(
+    r"assertEqual\((\w|[][.'\"])+ in (\w|[][.'\", ])+, (True|False)\)")
+assert_equal_in_start_with_true_or_false_re = re.compile(
+    r"assertEqual\((True|False), (\w|[][.'\"])+ in (\w|[][.'\", ])+\)")
+assert_equal_with_true_re = re.compile(
+    r"assertEqual\(True,")
+assert_equal_with_false_re = re.compile(
+    r"assertEqual\(False,")
+mutable_default_args = re.compile(r"^\s*def .+\((.+=\{\}|.+=\[\])")
+assert_equal_end_with_none_re = re.compile(r"(.)*assertEqual\(.+, None\)")
+assert_equal_start_with_none_re = re.compile(r".*assertEqual\(None, .+\)")
+assert_not_equal_end_with_none_re = re.compile(
+    r"(.)*assertNotEqual\(.+, None\)")
+assert_not_equal_start_with_none_re = re.compile(
+    r"(.)*assertNotEqual\(None, .+\)")
+revert_must_have_kwargs_re = re.compile(
+    r'[ ]*def revert\(.+,[ ](?!\*\*kwargs)\w+\):')
+untranslated_exception_re = re.compile(r"raise (?:\w*)\((.*)\)")
+no_eventlet_re = re.compile(r'(import|from)\s+[(]?eventlet')
+no_line_continuation_backslash_re = re.compile(r'.*(\\)\n')
+no_logging_re = re.compile(r'(import|from)\s+[(]?logging')
+import_mock_re = re.compile(r"\bimport[\s]+mock\b")
+import_from_mock_re = re.compile(r"\bfrom[\s]+mock[\s]+import\b")
+
+
+def _translation_checks_not_enforced(filename):
+    # Do not do these validations on tests
+    return any(pat in filename for pat in ["/tests/", "rally-jobs/plugins/"])
+
+
+@core.flake8ext
+def assert_true_instance(logical_line):
+    """Check for assertTrue(isinstance(a, b)) sentences
+
+    O316
+    """
+    if assert_trueinst_re.match(logical_line):
+        yield (0, "O316: assertTrue(isinstance(a, b)) sentences not allowed. "
+               "Use assertIsInstance instead.")
+
+
+@core.flake8ext
+def assert_equal_or_not_none(logical_line):
+    """Check for assertEqual(A, None) or assertEqual(None, A) sentences,
+
+    assertNotEqual(A, None) or assertNotEqual(None, A) sentences
+
+    O318
+    """
+    msg = ("O318: assertEqual/assertNotEqual(A, None) or "
+           "assertEqual/assertNotEqual(None, A) sentences not allowed")
+    res = (assert_equal_start_with_none_re.match(logical_line) or
+           assert_equal_end_with_none_re.match(logical_line) or
+           assert_not_equal_start_with_none_re.match(logical_line) or
+           assert_not_equal_end_with_none_re.match(logical_line))
+    if res:
+        yield (0, msg)
+
+
+@core.flake8ext
+def assert_equal_true_or_false(logical_line):
+    """Check for assertEqual(True, A) or assertEqual(False, A) sentences
+
+    O323
+    """
+    res = (assert_equal_with_true_re.search(logical_line) or
+           assert_equal_with_false_re.search(logical_line))
+    if res:
+        yield (0, "O323: assertEqual(True, A) or assertEqual(False, A) "
+               "sentences not allowed")
+
+
+@core.flake8ext
+def no_mutable_default_args(logical_line):
+    msg = "O324: Method's default argument shouldn't be mutable!"
+    if mutable_default_args.match(logical_line):
+        yield (0, msg)
+
+
+@core.flake8ext
+def assert_equal_in(logical_line):
+    """Check for assertEqual(A in B, True), assertEqual(True, A in B),
+
+    assertEqual(A in B, False) or assertEqual(False, A in B) sentences
+
+    O338
+    """
+    res = (assert_equal_in_start_with_true_or_false_re.search(logical_line) or
+           assert_equal_in_end_with_true_or_false_re.search(logical_line))
+    if res:
+        yield (0, "O338: Use assertIn/NotIn(A, B) rather than "
+               "assertEqual(A in B, True/False) when checking collection "
+               "contents.")
+
+
+@core.flake8ext
+def no_log_warn(logical_line):
+    """Disallow 'LOG.warn('
+
+    O339
+    """
+    if logical_line.startswith('LOG.warn('):
+        yield(0, "O339:Use LOG.warning() rather than LOG.warn()")
+
+
+@core.flake8ext
+def no_translate_logs(logical_line, filename):
+    """O341 - Don't translate logs.
+
+    Check for 'LOG.*(_(' and 'LOG.*(_Lx('
+
+    Translators don't provide translations for log messages, and operators
+    asked not to translate them.
+
+    * This check assumes that 'LOG' is a logger.
+
+    :param logical_line: The logical line to check.
+    :param filename: The file name where the logical line exists.
+    :returns: None if the logical line passes the check, otherwise a tuple
+              is yielded that contains the offending index in logical line
+              and a message describe the check validation failure.
+    """
+    if _translation_checks_not_enforced(filename):
+        return
+
+    msg = "O341: Log messages should not be translated!"
+    match = _log_translation_hint.match(logical_line)
+    if match:
+        yield (logical_line.index(match.group()), msg)
+
+
+@core.flake8ext
+def check_raised_localized_exceptions(logical_line, filename):
+    """O342 - Untranslated exception message.
+
+    :param logical_line: The logical line to check.
+    :param filename: The file name where the logical line exists.
+    :returns: None if the logical line passes the check, otherwise a tuple
+              is yielded that contains the offending index in logical line
+              and a message describe the check validation failure.
+    """
+    if _translation_checks_not_enforced(filename):
+        return
+
+    logical_line = logical_line.strip()
+    raised_search = untranslated_exception_re.match(logical_line)
+    if raised_search:
+        exception_msg = raised_search.groups()[0]
+        if exception_msg.startswith("\"") or exception_msg.startswith("\'"):
+            msg = "O342: Untranslated exception message."
+            yield (logical_line.index(exception_msg), msg)
+
+
+@core.flake8ext
+def check_no_eventlet_imports(logical_line):
+    """O345 - Usage of Python eventlet module not allowed.
+
+    :param logical_line: The logical line to check.
+    :returns: None if the logical line passes the check, otherwise a tuple
+              is yielded that contains the offending index in logical line
+              and a message describe the check validation failure.
+    """
+    if no_eventlet_re.match(logical_line):
+        msg = 'O345 Usage of Python eventlet module not allowed'
+        yield logical_line.index('eventlet'), msg
+
+
+@core.flake8ext
+def check_line_continuation_no_backslash(logical_line, tokens):
+    """O346 - Don't use backslashes for line continuation.
+
+    :param logical_line: The logical line to check. Not actually used.
+    :param tokens: List of tokens to check.
+    :returns: None if the tokens don't contain any issues, otherwise a tuple
+              is yielded that contains the offending index in the logical
+              line and a message describe the check validation failure.
+    """
+    backslash = None
+    for token_type, text, start, end, orig_line in tokens:
+        m = no_line_continuation_backslash_re.match(orig_line)
+        if m:
+            backslash = (start[0], m.start(1))
+            break
+
+    if backslash is not None:
+        msg = 'O346 Backslash line continuations not allowed'
+        yield backslash, msg
+
+
+@core.flake8ext
+def revert_must_have_kwargs(logical_line):
+    """O347 - Taskflow revert methods must have \\*\\*kwargs.
+
+    :param logical_line: The logical line to check.
+    :returns: None if the logical line passes the check, otherwise a tuple
+              is yielded that contains the offending index in logical line
+              and a message describe the check validation failure.
+    """
+    if revert_must_have_kwargs_re.match(logical_line):
+        msg = 'O347 Taskflow revert methods must have **kwargs'
+        yield 0, msg
+
+
+@core.flake8ext
+def check_no_logging_imports(logical_line):
+    """O348 - Usage of Python logging module not allowed.
+
+    :param logical_line: The logical line to check.
+    :returns: None if the logical line passes the check, otherwise a tuple
+              is yielded that contains the offending index in logical line
+              and a message describe the check validation failure.
+    """
+    if no_logging_re.match(logical_line):
+        msg = 'O348 Usage of Python logging module not allowed, use oslo_log'
+        yield logical_line.index('logging'), msg
+
+
+@core.flake8ext
+def check_no_import_mock(logical_line):
+    """O349 - Test code must not import mock library.
+
+    :param logical_line: The logical line to check.
+    :returns: None if the logical line passes the check, otherwise a tuple
+              is yielded that contains the offending index in logical line
+              and a message describe the check validation failure.
+    """
+    if (import_mock_re.match(logical_line) or
+            import_from_mock_re.match(logical_line)):
+        msg = 'O349 Test code must not import mock library, use unittest.mock'
+        yield 0, msg
diff --git a/octavia_tempest_plugin/services/load_balancer/v2/__init__.py b/octavia_tempest_plugin/services/load_balancer/v2/__init__.py
index d31d6cf..04cb473 100644
--- a/octavia_tempest_plugin/services/load_balancer/v2/__init__.py
+++ b/octavia_tempest_plugin/services/load_balancer/v2/__init__.py
@@ -12,7 +12,6 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from octavia_tempest_plugin.services.load_balancer.v2.loadbalancer_client \
-    import LoadbalancerClient
+from .loadbalancer_client import LoadbalancerClient
 
 __all__ = ['LoadbalancerClient']
diff --git a/octavia_tempest_plugin/tests/act_stdby_scenario/v2/test_active_standby.py b/octavia_tempest_plugin/tests/act_stdby_scenario/v2/test_active_standby.py
index 612bbe2..5655b3f 100644
--- a/octavia_tempest_plugin/tests/act_stdby_scenario/v2/test_active_standby.py
+++ b/octavia_tempest_plugin/tests/act_stdby_scenario/v2/test_active_standby.py
@@ -270,7 +270,7 @@
         connections = 0
         for listener in amphora_stats:
             connections += listener[const.TOTAL_CONNECTIONS]
-        self.assertTrue(connections > 0)
+        self.assertGreater(connections, 0)
         LOG.info('Backup amphora is now Master.')
         # Wait for the amphora failover to start
         waiters.wait_for_status(
diff --git a/octavia_tempest_plugin/tests/api/v2/test_amphora.py b/octavia_tempest_plugin/tests/api/v2/test_amphora.py
index 7cf77dd..d1106e6 100644
--- a/octavia_tempest_plugin/tests/api/v2/test_amphora.py
+++ b/octavia_tempest_plugin/tests/api/v2/test_amphora.py
@@ -12,6 +12,9 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+from uuid import UUID
+
+from dateutil import parser
 from tempest import config
 from tempest.lib.common.utils import data_utils
 from tempest.lib import decorators
@@ -57,6 +60,98 @@
                                 CONF.load_balancer.lb_build_interval,
                                 CONF.load_balancer.lb_build_timeout)
 
+    def _expected_amp_count(self, amp_list):
+        self.assertNotEmpty(amp_list)
+        if amp_list[0][const.ROLE] in (const.ROLE_MASTER, const.ROLE_BACKUP):
+            return 2
+        return 1
+
+    @decorators.idempotent_id('a0e9ff99-2c4f-45d5-81c9-78d3107c236f')
+    def test_amphora_list_and_show(self):
+        """Tests amphora show API.
+
+        * Show amphora details.
+        * Validate the show reflects the requested values.
+        * Validates that other accounts cannot see the amphora.
+        """
+        lb_name = data_utils.rand_name("lb_member_lb2_amphora-list")
+        lb = self.mem_lb_client.create_loadbalancer(
+            name=lb_name, provider=CONF.load_balancer.provider,
+            vip_network_id=self.lb_member_vip_net[const.ID])
+        lb_id = lb[const.ID]
+        self.addCleanup(self.mem_lb_client.cleanup_loadbalancer, lb_id)
+
+        waiters.wait_for_status(self.mem_lb_client.show_loadbalancer,
+                                lb_id,
+                                const.PROVISIONING_STATUS,
+                                const.ACTIVE,
+                                CONF.load_balancer.lb_build_interval,
+                                CONF.load_balancer.lb_build_timeout)
+
+        # Test that a user, without the load balancer member role, cannot
+        # list amphorae
+        if CONF.load_balancer.RBAC_test_type == const.ADVANCED:
+            self.assertRaises(
+                exceptions.Forbidden,
+                self.os_primary.amphora_client.list_amphorae)
+
+        # Get an actual list of the amphorae
+        amphorae = self.lb_admin_amphora_client.list_amphorae()
+
+        # There should be AT LEAST 2, there may be more depending on the
+        # configured topology
+        self.assertGreaterEqual(
+            len(amphorae), 2 * self._expected_amp_count(amphorae))
+
+        # Test filtering by loadbalancer_id
+        amphorae = self.lb_admin_amphora_client.list_amphorae(
+            query_params='{loadbalancer_id}={lb_id}'.format(
+                loadbalancer_id=const.LOADBALANCER_ID, lb_id=self.lb_id))
+        self.assertEqual(self._expected_amp_count(amphorae), len(amphorae))
+        self.assertEqual(self.lb_id, amphorae[0][const.LOADBALANCER_ID])
+
+        # Test that a different user, with load balancer member role, cannot
+        # see this amphora
+        if not CONF.load_balancer.RBAC_test_type == const.NONE:
+            member2_client = self.os_roles_lb_member2.amphora_client
+            self.assertRaises(exceptions.Forbidden,
+                              member2_client.show_amphora,
+                              amphora_id=amphorae[0][const.ID])
+
+        show_amphora_response_fields = const.SHOW_AMPHORA_RESPONSE_FIELDS
+        if self.lb_admin_amphora_client.is_version_supported(
+                self.api_version, '2.1'):
+            show_amphora_response_fields.append('created_at')
+            show_amphora_response_fields.append('updated_at')
+            show_amphora_response_fields.append('image_id')
+
+        for amp in amphorae:
+
+            # Make sure all of the fields exist on the amp list records
+            for field in show_amphora_response_fields:
+                self.assertIn(field, amp)
+
+            # Verify a few of the fields are the right type
+            if self.lb_admin_amphora_client.is_version_supported(
+                    self.api_version, '2.1'):
+                parser.parse(amp[const.CREATED_AT])
+                parser.parse(amp[const.UPDATED_AT])
+
+            UUID(amp[const.ID])
+            UUID(amp[const.HA_PORT_ID])
+            UUID(amp[const.LOADBALANCER_ID])
+            UUID(amp[const.COMPUTE_ID])
+            UUID(amp[const.VRRP_PORT_ID])
+            self.assertEqual(amp[const.STATUS], const.STATUS_ALLOCATED)
+            self.assertIn(amp[const.ROLE], const.AMPHORA_ROLES)
+
+            # Test that all of the fields from the amp list match those
+            # from a show for the LB we created.
+            amp_obj = self.lb_admin_amphora_client.show_amphora(
+                amphora_id=amp[const.ID])
+            for field in show_amphora_response_fields:
+                self.assertEqual(amp[field], amp_obj[field])
+
     @decorators.idempotent_id('b7fc231b-dcfa-47a5-99f3-ec5ddcc48f30')
     def test_amphora_update(self):
         """Tests the amphora agent configuration update API
diff --git a/octavia_tempest_plugin/tests/api/v2/test_l7rule.py b/octavia_tempest_plugin/tests/api/v2/test_l7rule.py
index aaad8a0..9a73034 100644
--- a/octavia_tempest_plugin/tests/api/v2/test_l7rule.py
+++ b/octavia_tempest_plugin/tests/api/v2/test_l7rule.py
@@ -573,7 +573,7 @@
         l7rule_check = self.mem_l7rule_client.show_l7rule(
             l7rule[const.ID], l7policy_id=self.l7policy_id)
         self.assertEqual(const.ACTIVE, l7rule_check[const.PROVISIONING_STATUS])
-        self.assertEqual(False, l7rule_check[const.ADMIN_STATE_UP])
+        self.assertFalse(l7rule_check[const.ADMIN_STATE_UP])
 
         # Test that a user, without the load balancer member role, cannot
         # update this l7rule
@@ -588,7 +588,7 @@
         l7rule_check = self.mem_l7rule_client.show_l7rule(
             l7rule[const.ID], l7policy_id=self.l7policy_id)
         self.assertEqual(const.ACTIVE, l7rule_check[const.PROVISIONING_STATUS])
-        self.assertEqual(False, l7rule_check[const.ADMIN_STATE_UP])
+        self.assertFalse(l7rule_check[const.ADMIN_STATE_UP])
 
         l7rule_update_kwargs = {
             const.L7POLICY_ID: self.l7policy_id,
diff --git a/octavia_tempest_plugin/tests/barbican_scenario/v2/test_tls_barbican.py b/octavia_tempest_plugin/tests/barbican_scenario/v2/test_tls_barbican.py
index 84bfc20..a753a5c 100644
--- a/octavia_tempest_plugin/tests/barbican_scenario/v2/test_tls_barbican.py
+++ b/octavia_tempest_plugin/tests/barbican_scenario/v2/test_tls_barbican.py
@@ -749,8 +749,8 @@
                                 CONF.load_balancer.build_timeout)
 
         # Test that no client certificate fails to connect
-        self.assertRaisesRegex(
-            requests.exceptions.SSLError, ".*certificate required.*",
+        self.assertRaises(
+            requests.exceptions.SSLError,
             requests.get,
             'https://{0}:{1}'.format(self.lb_vip_address, LISTENER1_TCP_PORT),
             timeout=12, verify=False)
@@ -764,8 +764,8 @@
                     serialization.Encoding.PEM,
                     serialization.PrivateFormat.TraditionalOpenSSL,
                     serialization.NoEncryption()))
-                self.assertRaisesRegex(
-                    requests.exceptions.SSLError, ".*revoked.*", requests.get,
+                self.assertRaises(
+                    requests.exceptions.SSLError, requests.get,
                     'https://{0}:{1}'.format(self.lb_vip_address,
                                              LISTENER1_TCP_PORT),
                     timeout=12, verify=False, cert=(cert_file.name,
@@ -836,8 +836,8 @@
                     serialization.Encoding.PEM,
                     serialization.PrivateFormat.TraditionalOpenSSL,
                     serialization.NoEncryption()))
-                self.assertRaisesRegex(
-                    requests.exceptions.SSLError, ".*revoked.*", requests.get,
+                self.assertRaises(
+                    requests.exceptions.SSLError, requests.get,
                     'https://{0}:{1}'.format(self.lb_vip_address,
                                              LISTENER1_TCP_PORT),
                     timeout=12, verify=False, cert=(cert_file.name,
@@ -954,15 +954,15 @@
                                 CONF.load_balancer.build_timeout)
 
         # Test that no client certificate fails to connect to listener1
-        self.assertRaisesRegex(
-            requests.exceptions.SSLError, ".*certificate required.*",
+        self.assertRaises(
+            requests.exceptions.SSLError,
             requests.get,
             'https://{0}:{1}'.format(self.lb_vip_address, LISTENER1_TCP_PORT),
             timeout=12, verify=False)
 
         # Test that no client certificate fails to connect to listener2
-        self.assertRaisesRegex(
-            requests.exceptions.SSLError, ".*certificate required.*",
+        self.assertRaises(
+            requests.exceptions.SSLError,
             requests.get,
             'https://{0}:{1}'.format(self.lb_vip_address, LISTENER2_TCP_PORT),
             timeout=12, verify=False)
@@ -976,8 +976,8 @@
                     serialization.Encoding.PEM,
                     serialization.PrivateFormat.TraditionalOpenSSL,
                     serialization.NoEncryption()))
-                self.assertRaisesRegex(
-                    requests.exceptions.SSLError, ".*revoked.*", requests.get,
+                self.assertRaises(
+                    requests.exceptions.SSLError, requests.get,
                     'https://{0}:{1}'.format(self.lb_vip_address,
                                              LISTENER1_TCP_PORT),
                     timeout=12, verify=False, cert=(cert_file.name,
@@ -992,8 +992,8 @@
                     serialization.Encoding.PEM,
                     serialization.PrivateFormat.TraditionalOpenSSL,
                     serialization.NoEncryption()))
-                self.assertRaisesRegex(
-                    requests.exceptions.SSLError, ".*revoked.*", requests.get,
+                self.assertRaises(
+                    requests.exceptions.SSLError, requests.get,
                     'https://{0}:{1}'.format(self.lb_vip_address,
                                              LISTENER2_TCP_PORT),
                     timeout=12, verify=False, cert=(cert_file.name,
@@ -1040,8 +1040,8 @@
                     serialization.Encoding.PEM,
                     serialization.PrivateFormat.TraditionalOpenSSL,
                     serialization.NoEncryption()))
-                self.assertRaisesRegex(
-                    requests.exceptions.SSLError, ".*decrypt error.*",
+                self.assertRaises(
+                    requests.exceptions.SSLError,
                     requests.get, 'https://{0}:{1}'.format(self.lb_vip_address,
                                                            LISTENER2_TCP_PORT),
                     timeout=12, verify=False, cert=(cert_file.name,
@@ -1056,8 +1056,8 @@
                     serialization.Encoding.PEM,
                     serialization.PrivateFormat.TraditionalOpenSSL,
                     serialization.NoEncryption()))
-                self.assertRaisesRegex(
-                    requests.exceptions.SSLError, ".*decrypt error.*",
+                self.assertRaises(
+                    requests.exceptions.SSLError,
                     requests.get, 'https://{0}:{1}'.format(self.lb_vip_address,
                                                            LISTENER1_TCP_PORT),
                     timeout=12, verify=False, cert=(cert_file.name,
@@ -1072,8 +1072,8 @@
                     serialization.Encoding.PEM,
                     serialization.PrivateFormat.TraditionalOpenSSL,
                     serialization.NoEncryption()))
-                self.assertRaisesRegex(
-                    requests.exceptions.SSLError, ".*decrypt error.*",
+                self.assertRaises(
+                    requests.exceptions.SSLError,
                     requests.get, 'https://{0}:{1}'.format(self.lb_vip_address,
                                                            LISTENER2_TCP_PORT),
                     timeout=12, verify=False, cert=(cert_file.name,
@@ -1088,8 +1088,8 @@
                     serialization.Encoding.PEM,
                     serialization.PrivateFormat.TraditionalOpenSSL,
                     serialization.NoEncryption()))
-                self.assertRaisesRegex(
-                    requests.exceptions.SSLError, ".*decrypt error.*",
+                self.assertRaises(
+                    requests.exceptions.SSLError,
                     requests.get, 'https://{0}:{1}'.format(self.lb_vip_address,
                                                            LISTENER1_TCP_PORT),
                     timeout=12, verify=False, cert=(cert_file.name,
diff --git a/octavia_tempest_plugin/tests/scenario/v2/test_amphora.py b/octavia_tempest_plugin/tests/scenario/v2/test_amphora.py
deleted file mode 100644
index 9101321..0000000
--- a/octavia_tempest_plugin/tests/scenario/v2/test_amphora.py
+++ /dev/null
@@ -1,188 +0,0 @@
-# Copyright 2018 GoDaddy
-#
-#    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.
-
-from uuid import UUID
-
-from dateutil import parser
-from tempest import config
-from tempest.lib.common.utils import data_utils
-from tempest.lib import decorators
-from tempest.lib import exceptions
-
-from octavia_tempest_plugin.common import constants as const
-from octavia_tempest_plugin.tests import test_base
-from octavia_tempest_plugin.tests import waiters
-
-CONF = config.CONF
-
-
-class AmphoraScenarioTest(test_base.LoadBalancerBaseTest):
-    """Test the amphora object API."""
-
-    @classmethod
-    def skip_checks(cls):
-        super(AmphoraScenarioTest, cls).skip_checks()
-
-        if CONF.load_balancer.provider not in const.AMPHORA_PROVIDERS:
-            raise cls.skipException("Amphora tests require provider 'amphora' "
-                                    "or 'octavia' (alias to 'amphora', "
-                                    " deprecated) set")
-
-    @classmethod
-    def resource_setup(cls):
-        """Setup resources needed by the tests."""
-        super(AmphoraScenarioTest, cls).resource_setup()
-
-        lb_name = data_utils.rand_name("lb_member_lb1_amphora")
-        lb_kwargs = {const.PROVIDER: CONF.load_balancer.provider,
-                     const.NAME: lb_name}
-
-        cls._setup_lb_network_kwargs(lb_kwargs)
-
-        lb = cls.mem_lb_client.create_loadbalancer(**lb_kwargs)
-        cls.lb_id = lb[const.ID]
-        cls.addClassResourceCleanup(
-            cls.mem_lb_client.cleanup_loadbalancer,
-            cls.lb_id)
-
-        waiters.wait_for_status(cls.mem_lb_client.show_loadbalancer,
-                                cls.lb_id, const.PROVISIONING_STATUS,
-                                const.ACTIVE,
-                                CONF.load_balancer.lb_build_interval,
-                                CONF.load_balancer.lb_build_timeout)
-
-    def _expected_amp_count(self, amp_list):
-        self.assertNotEmpty(amp_list)
-        if amp_list[0][const.ROLE] in (const.ROLE_MASTER, const.ROLE_BACKUP):
-            return 2
-        return 1
-
-    @decorators.idempotent_id('a0e9ff99-2c4f-45d5-81c9-78d3107c236f')
-    def test_amphora_list_and_show(self):
-        """Tests amphora show API.
-
-        * Show amphora details.
-        * Validate the show reflects the requested values.
-        * Validates that other accounts cannot see the amphora.
-        """
-        lb_name = data_utils.rand_name("lb_member_lb2_amphora-list")
-        lb = self.mem_lb_client.create_loadbalancer(
-            name=lb_name, provider=CONF.load_balancer.provider,
-            vip_network_id=self.lb_member_vip_net[const.ID])
-        lb_id = lb[const.ID]
-        self.addCleanup(
-            self.mem_lb_client.cleanup_loadbalancer,
-            lb_id)
-
-        waiters.wait_for_status(self.mem_lb_client.show_loadbalancer,
-                                lb_id,
-                                const.PROVISIONING_STATUS,
-                                const.ACTIVE,
-                                CONF.load_balancer.lb_build_interval,
-                                CONF.load_balancer.lb_build_timeout)
-
-        # Test that a user with lb_admin role can list the amphora
-        if CONF.load_balancer.RBAC_test_type == const.ADVANCED:
-            amphora_client = self.os_roles_lb_admin.amphora_client
-            amphora_adm = amphora_client.list_amphorae()
-            self.assertTrue(
-                len(amphora_adm) >= 2 * self._expected_amp_count(amphora_adm))
-
-        # Test that a different user, with load balancer member role, cannot
-        # see this amphora
-        if not CONF.load_balancer.RBAC_test_type == const.NONE:
-            member2_client = self.os_roles_lb_member2.amphora_client
-            self.assertRaises(exceptions.Forbidden,
-                              member2_client.list_amphorae)
-
-        # Test that a user, without the load balancer member role, cannot
-        # list amphorae
-        if CONF.load_balancer.RBAC_test_type == const.ADVANCED:
-            self.assertRaises(
-                exceptions.Forbidden,
-                self.os_primary.amphora_client.list_amphorae)
-
-        # Test that a user with cloud admin role can list the amphorae
-        if not CONF.load_balancer.RBAC_test_type == const.NONE:
-            adm = self.lb_admin_amphora_client.list_amphorae()
-            self.assertTrue(len(adm) >= 2 * self._expected_amp_count(adm))
-
-        # Get an actual list of the amphorae
-        amphorae = self.lb_admin_amphora_client.list_amphorae()
-
-        # There should be AT LEAST 2, there may be more depending on the
-        # configured topology, or if there are other LBs created besides ours
-        self.assertTrue(
-            len(amphorae) >= 2 * self._expected_amp_count(amphorae))
-
-        show_amphora_response_fields = const.SHOW_AMPHORA_RESPONSE_FIELDS
-        if self.lb_admin_amphora_client.is_version_supported(
-                self.api_version, '2.1'):
-            show_amphora_response_fields.append('created_at')
-            show_amphora_response_fields.append('updated_at')
-            show_amphora_response_fields.append('image_id')
-
-        for amp in amphorae:
-
-            # Make sure all of the fields exist on the amp list records
-            for field in show_amphora_response_fields:
-                self.assertIn(field, amp)
-
-            amp_id = amp[const.ID]
-            amp_obj = self.lb_admin_amphora_client.show_amphora(
-                amphora_id=amp_id)
-
-            # Make sure all of the fields exist on the amp show record
-            for field in show_amphora_response_fields:
-                self.assertIn(field, amp_obj)
-
-            # Verify a few of the fields are the right type
-            if self.lb_admin_amphora_client.is_version_supported(
-                    self.api_version, '2.1'):
-                parser.parse(amp_obj[const.CREATED_AT])
-                parser.parse(amp_obj[const.UPDATED_AT])
-            UUID(amp_obj[const.ID])
-            self.assertIn(amp_obj[const.STATUS], const.AMPHORA_STATUSES)
-
-            # We might have gotten unassigned/spare amps?
-            if amp_obj[const.STATUS] == const.STATUS_ALLOCATED:
-                # Only check the state of fields for the LB we created,
-                # otherwise some fields (HA_PORT_ID) may not yet be
-                # populated in amps for parallel tests.
-                if lb_id == amp_obj[const.LOADBALANCER_ID]:
-                    UUID(amp_obj[const.HA_PORT_ID])
-                    UUID(amp_obj[const.LOADBALANCER_ID])
-                    UUID(amp_obj[const.COMPUTE_ID])
-                    UUID(amp_obj[const.VRRP_PORT_ID])
-                    self.assertIn(amp_obj[const.ROLE], const.AMPHORA_ROLES)
-            else:
-                self.assertIsNone(amp_obj[const.ROLE])
-
-            # Test that all of the fields from the amp list match those
-            # from a show
-            for field in show_amphora_response_fields:
-                self.assertEqual(amp[field], amp_obj[field])
-
-        # Test filtering by loadbalancer_id
-        amphorae = self.lb_admin_amphora_client.list_amphorae(
-            query_params='{loadbalancer_id}={lb_id}'.format(
-                loadbalancer_id=const.LOADBALANCER_ID, lb_id=self.lb_id))
-        self.assertEqual(self._expected_amp_count(amphorae), len(amphorae))
-        self.assertEqual(self.lb_id, amphorae[0][const.LOADBALANCER_ID])
-
-        amphorae = self.lb_admin_amphora_client.list_amphorae(
-            query_params='{loadbalancer_id}={lb_id}'.format(
-                loadbalancer_id=const.LOADBALANCER_ID, lb_id=lb_id))
-        self.assertEqual(self._expected_amp_count(amphorae), len(amphorae))
-        self.assertEqual(lb_id, amphorae[0][const.LOADBALANCER_ID])
diff --git a/octavia_tempest_plugin/tests/scenario/v2/test_pool.py b/octavia_tempest_plugin/tests/scenario/v2/test_pool.py
index 5e0622c..720e80a 100644
--- a/octavia_tempest_plugin/tests/scenario/v2/test_pool.py
+++ b/octavia_tempest_plugin/tests/scenario/v2/test_pool.py
@@ -167,8 +167,8 @@
         }
 
         if self.lb_feature_enabled.pool_algorithms_enabled:
-            pool_update_kwargs[const.LB_ALGORITHM] = \
-                const.LB_ALGORITHM_LEAST_CONNECTIONS
+            pool_update_kwargs[const.LB_ALGORITHM] = (
+                const.LB_ALGORITHM_LEAST_CONNECTIONS)
 
         if self.protocol == const.HTTP and (
                 self.lb_feature_enabled.session_persistence_enabled):
diff --git a/octavia_tempest_plugin/tests/test_base.py b/octavia_tempest_plugin/tests/test_base.py
index aab78c1..741bb1c 100644
--- a/octavia_tempest_plugin/tests/test_base.py
+++ b/octavia_tempest_plugin/tests/test_base.py
@@ -302,8 +302,8 @@
         network_kwargs = {
             'name': data_utils.rand_name("lb_member_vip_network")}
         if CONF.network_feature_enabled.port_security:
-                # Note: Allowed Address Pairs requires port security
-                network_kwargs['port_security_enabled'] = True
+            # Note: Allowed Address Pairs requires port security
+            network_kwargs['port_security_enabled'] = True
         result = cls.lb_mem_net_client.create_network(**network_kwargs)
         cls.lb_member_vip_net = result['network']
         LOG.info('lb_member_vip_net: {}'.format(cls.lb_member_vip_net))
diff --git a/playbooks/Octavia-DSVM/pre.yaml b/playbooks/Octavia-DSVM/pre.yaml
deleted file mode 100644
index 9d6beb7..0000000
--- a/playbooks/Octavia-DSVM/pre.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-- hosts: all
-  name: Octavia DSVM jobs pre-run playbook
-  tasks:
-    - shell:
-        executable: /bin/bash
-        cmd: |
-          set -e
-          set -x
-          if $(egrep --quiet '(vmx|svm)' /proc/cpuinfo) && [[ ( ! $(hostname) =~ "ovh" && ! $(hostname) =~ "limestone" ) ]]; then
-              export DEVSTACK_GATE_LIBVIRT_TYPE=kvm
-          fi
-
diff --git a/releasenotes/source/conf.py b/releasenotes/source/conf.py
index a346f01..8437073 100644
--- a/releasenotes/source/conf.py
+++ b/releasenotes/source/conf.py
@@ -43,8 +43,9 @@
 ]
 
 # openstackdocstheme options
-repository_name = 'openstack/octavia-tempest-plugin'
-use_storyboard = True
+openstackdocs_repo_name = 'openstack/octavia-tempest-plugin'
+openstackdocs_auto_name = False
+openstackdocs_use_storyboard = True
 
 # Add any paths that contain templates here, relative to this directory.
 templates_path = ['_templates']
@@ -102,7 +103,7 @@
 # show_authors = False
 
 # The name of the Pygments (syntax highlighting) style to use.
-pygments_style = 'sphinx'
+pygments_style = 'native'
 
 # A list of ignored prefixes for module index sorting.
 # modindex_common_prefix = []
diff --git a/requirements.txt b/requirements.txt
index 8020630..b30b450 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -4,7 +4,6 @@
 
 cryptography>=2.1 # BSD/Apache-2.0
 python-dateutil>=2.5.3 # BSD
-ipaddress>=1.0.17;python_version<'3.3' # PSF
 pbr!=2.1.0,>=2.0.0 # Apache-2.0
 oslo.config>=5.2.0 # Apache-2.0
 oslo.log>=3.36.0  # Apache-2.0
diff --git a/setup.cfg b/setup.cfg
index 5da1d1d..d7d3196 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -6,6 +6,7 @@
 author = OpenStack
 author-email = openstack-discuss@lists.openstack.org
 home-page = https://docs.openstack.org/octavia-tempest-plugin/latest/
+python-requires = >=3.6
 classifier =
     Environment :: OpenStack
     Intended Audience :: Information Technology
@@ -13,10 +14,12 @@
     License :: OSI Approved :: Apache Software License
     Operating System :: POSIX :: Linux
     Programming Language :: Python
-    Programming Language :: Python :: 2
-    Programming Language :: Python :: 2.7
+    Programming Language :: Python :: Implementation :: CPython
+    Programming Language :: Python :: 3 :: Only
     Programming Language :: Python :: 3
-    Programming Language :: Python :: 3.5
+    Programming Language :: Python :: 3.6
+    Programming Language :: Python :: 3.7
+    Programming Language :: Python :: 3.8
 
 [global]
 setup-hooks =
@@ -26,14 +29,6 @@
 packages =
     octavia_tempest_plugin
 
-[build_sphinx]
-source-dir = doc/source
-build-dir = doc/build
-all_files = 1
-
-[upload_sphinx]
-upload-dir = doc/build/html
-
 [compile_catalog]
 directory = octavia_tempest_plugin/locale
 domain = octavia_tempest_plugin
diff --git a/test-requirements.txt b/test-requirements.txt
index 47c128f..2125ea0 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -2,7 +2,7 @@
 # of appearance. Changing the order has an impact on the overall integration
 # process, which may cause wedges in the gate later.
 
-hacking!=0.13.0,<0.14,>=0.12.0 # Apache-2.0
+hacking>=3.0.1,<3.1.0;python_version>='3.5' # Apache-2.0
 
 coverage!=4.4,>=4.0 # Apache-2.0
 python-subunit>=1.0.0 # Apache-2.0/BSD
diff --git a/tox.ini b/tox.ini
index 4a61222..a419c62 100644
--- a/tox.ini
+++ b/tox.ini
@@ -43,6 +43,7 @@
 deps =
     -c{env:UPPER_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master}
     -r{toxinidir}/requirements.txt
+    -r{toxinidir}/test-requirements.txt
     -r{toxinidir}/doc/requirements.txt
 whitelist_externals = rm
 commands =
@@ -85,6 +86,24 @@
 # [H904]: Delay string interpolations at logging calls
 enable-extensions=H106,H203,H204,H205,H904
 
+[flake8:local-plugins]
+extension =
+  O316 = checks:assert_true_instance
+  O318 = checks:assert_equal_or_not_none
+  O323 = checks:assert_equal_true_or_false
+  O324 = checks:no_mutable_default_args
+  O338 = checks:assert_equal_in
+  O339 = checks:no_log_warn
+  O341 = checks:no_translate_logs
+  O342 = checks:check_raised_localized_exceptions
+  O345 = checks:check_no_eventlet_imports
+  O346 = checks:check_line_continuation_no_backslash
+  O347 = checks:revert_must_have_kwargs
+  O348 = checks:check_no_logging_imports
+  O349 = checks:check_no_import_mock
+paths =
+  ./octavia_tempest_plugin/hacking
+
 [testenv:genconfig]
 basepython = python3
 whitelist_externals = mkdir
diff --git a/zuul.d/jobs.yaml b/zuul.d/jobs.yaml
index 3e1c9f6..033dbae 100644
--- a/zuul.d/jobs.yaml
+++ b/zuul.d/jobs.yaml
@@ -36,11 +36,11 @@
     parent: devstack-tempest
     timeout: 7800
     required-projects:
+      - openstack/devstack
       - openstack/octavia
       - openstack/octavia-lib
       - openstack/octavia-tempest-plugin
       - openstack/python-octaviaclient
-    pre-run: playbooks/Octavia-DSVM/pre.yaml
     irrelevant-files:
       - ^.*\.rst$
       - ^api-ref/.*$
@@ -51,6 +51,8 @@
       devstack_localrc:
         TEMPEST_PLUGINS: /opt/stack/octavia-tempest-plugin
         USE_PYTHON3: true
+        LIBVIRT_TYPE: kvm
+        LIBVIRT_CPU_MODE: host-passthrough
       devstack_local_conf:
         post-config:
           $OCTAVIA_CONF:
@@ -88,11 +90,11 @@
     parent: devstack-tempest-ipv6
     timeout: 7800
     required-projects:
+      - openstack/devstack
       - openstack/octavia
       - openstack/octavia-lib
       - openstack/octavia-tempest-plugin
       - openstack/python-octaviaclient
-    pre-run: playbooks/Octavia-DSVM/pre.yaml
     irrelevant-files:
       - ^.*\.rst$
       - ^api-ref/.*$
@@ -103,6 +105,8 @@
       devstack_localrc:
         TEMPEST_PLUGINS: /opt/stack/octavia-tempest-plugin
         USE_PYTHON3: true
+        LIBVIRT_TYPE: kvm
+        LIBVIRT_CPU_MODE: host-passthrough
       devstack_local_conf:
         post-config:
           $OCTAVIA_CONF:
@@ -144,6 +148,8 @@
     vars:
       devstack_localrc:
         DIB_LOCAL_ELEMENTS: openstack-ci-mirrors
+        LIBVIRT_TYPE: kvm
+        LIBVIRT_CPU_MODE: host-passthrough
       devstack_local_conf:
         post-config:
           $OCTAVIA_CONF:
@@ -231,6 +237,8 @@
           HOST_IP: "{{ hostvars['controller']['nodepool']['private_ipv4'] }}"
           TEMPEST_PLUGINS: /opt/stack/octavia-tempest-plugin
           USE_PYTHON3: true
+          LIBVIRT_TYPE: kvm
+          LIBVIRT_CPU_MODE: host-passthrough
           # Octavia specific settings
           OCTAVIA_CONTROLLER_IP_PORT_LIST: 192.168.0.3:5555,192.168.0.4:5555
           OCTAVIA_MGMT_PORT_IP: 192.168.0.3
@@ -271,6 +279,8 @@
           RABBIT_HOST: "{{ hostvars['controller']['nodepool']['private_ipv4'] }}"
           GLANCE_HOSTPORT: "{{ hostvars['controller']['nodepool']['private_ipv4'] }}:9292"
           USE_PYTHON3: true
+          LIBVIRT_TYPE: kvm
+          LIBVIRT_CPU_MODE: host-passthrough
           # Octavia specific settings
           OCTAVIA_CONTROLLER_IP_PORT_LIST: 192.168.0.3:5555,192.168.0.4:5555
           OCTAVIA_NODE: subnode
@@ -408,16 +418,6 @@
     override-checkout: stable/stein
 
 - job:
-    name: octavia-v2-dsvm-noop-py2-api-stable-rocky
-    parent: octavia-v2-dsvm-noop-py2-api
-    override-checkout: stable/rocky
-
-- job:
-    name: octavia-v2-dsvm-noop-py2-api-stable-queens
-    parent: octavia-v2-dsvm-noop-py2-api
-    override-checkout: stable/queens
-
-- job:
     name: octavia-v2-dsvm-scenario
     parent: octavia-dsvm-live-base
     vars:
@@ -463,6 +463,9 @@
     vars:
       devstack_localrc:
         USE_PYTHON3: False
+    required-projects:
+      - name: openstack/diskimage-builder
+        override-checkout: 2.30.0
 
 - job:
     name: octavia-v2-dsvm-scenario-stable-train
@@ -473,16 +476,9 @@
     name: octavia-v2-dsvm-scenario-stable-stein
     parent: octavia-v2-dsvm-scenario
     override-checkout: stable/stein
-
-- job:
-    name: octavia-v2-dsvm-py2-scenario-stable-rocky
-    parent: octavia-v2-dsvm-py2-scenario
-    override-checkout: stable/rocky
-
-- job:
-    name: octavia-v2-dsvm-py2-scenario-stable-queens
-    parent: octavia-v2-dsvm-py2-scenario
-    override-checkout: stable/queens
+    required-projects:
+      - name: openstack/diskimage-builder
+        override-checkout: 2.30.0
 
 # Legacy jobs for the transition to the act-stdby two node jobs
 - job:
@@ -507,9 +503,16 @@
       controller:
         devstack_localrc:
           USE_PYTHON3: False
+          LIBVIRT_TYPE: kvm
+          LIBVIRT_CPU_MODE: host-passthrough
       controller2:
         devstack_localrc:
           USE_PYTHON3: False
+          LIBVIRT_TYPE: kvm
+          LIBVIRT_CPU_MODE: host-passthrough
+    required-projects:
+      - name: openstack/diskimage-builder
+        override-checkout: 2.30.0
 
 - job:
     name: octavia-v2-act-stdby-dsvm-scenario-two-node
@@ -560,6 +563,7 @@
         OCTAVIA_AMP_BASE_OS: ubuntu
         OCTAVIA_AMP_DISTRIBUTION_RELEASE_ID: xenial
         USE_PYTHON3: false
+        TEMPEST_BRANCH: 23.0.0
 
 - job:
     name: octavia-v2-dsvm-tls-barbican
@@ -594,6 +598,9 @@
     name: octavia-v2-dsvm-tls-barbican-stable-stein
     parent: octavia-v2-dsvm-tls-barbican
     override-checkout: stable/stein
+    required-projects:
+      - name: openstack/diskimage-builder
+        override-checkout: 2.30.0
 
 - job:
     name: octavia-v2-dsvm-tls-barbican-stable-rocky
@@ -626,6 +633,9 @@
     vars:
       devstack_localrc:
         USE_PYTHON3: False
+    required-projects:
+      - name: openstack/diskimage-builder
+        override-checkout: 2.30.0
 
 - job:
     name: octavia-v2-dsvm-spare-pool-stable-train
@@ -636,22 +646,16 @@
     name: octavia-v2-dsvm-spare-pool-stable-stein
     parent: octavia-v2-dsvm-spare-pool
     override-checkout: stable/stein
-
-- job:
-    name: octavia-v2-dsvm-py2-spare-pool-stable-rocky
-    parent: octavia-v2-dsvm-py2-spare-pool
-    override-checkout: stable/rocky
-
-- job:
-    name: octavia-v2-dsvm-py2-spare-pool-stable-queens
-    parent: octavia-v2-dsvm-py2-spare-pool
-    override-checkout: stable/queens
+    required-projects:
+      - name: openstack/diskimage-builder
+        override-checkout: 2.30.0
 
 - job:
     name: octavia-v2-dsvm-cinder-amphora
     parent: octavia-v2-dsvm-scenario
     required-projects:
       - openstack/cinder
+      - openstack/devstack
       - openstack/diskimage-builder
       - openstack/octavia
       - openstack/octavia-lib
@@ -737,6 +741,9 @@
     vars:
       devstack_localrc:
         USE_PYTHON3: False
+    required-projects:
+      - name: openstack/diskimage-builder
+        override-checkout: 2.30.0
 
 - job:
     name: octavia-v2-act-stdby-iptables-dsvm-py2-scenario-centos-7
@@ -755,16 +762,6 @@
               amphora_ssh_user: centos
 
 - job:
-    name: octavia-v2-act-stdby-iptables-dsvm-py2-scenario-stable-rocky
-    parent: octavia-v2-act-stdby-iptables-dsvm-py2-scenario
-    override-checkout: stable/rocky
-
-- job:
-    name: octavia-v2-act-stdby-iptables-dsvm-py2-scenario-stable-queens
-    parent: octavia-v2-act-stdby-iptables-dsvm-py2-scenario
-    override-checkout: stable/queens
-
-- job:
     name: octavia-v2-act-stdby-dsvm-scenario
     parent: octavia-dsvm-live-base
     vars:
@@ -790,3 +787,6 @@
     name: octavia-v2-act-stdby-dsvm-scenario-stable-stein
     parent: octavia-v2-act-stdby-dsvm-scenario
     override-checkout: stable/stein
+    required-projects:
+      - name: openstack/diskimage-builder
+        override-checkout: 2.30.0
diff --git a/zuul.d/projects.yaml b/zuul.d/projects.yaml
index be12bba..6b1fcc0 100644
--- a/zuul.d/projects.yaml
+++ b/zuul.d/projects.yaml
@@ -11,23 +11,15 @@
         - octavia-v2-dsvm-noop-api
         - octavia-v2-dsvm-noop-api-stable-train
         - octavia-v2-dsvm-noop-api-stable-stein
-        - octavia-v2-dsvm-noop-py2-api-stable-rocky
-        - octavia-v2-dsvm-noop-py2-api-stable-queens
         - octavia-v2-dsvm-scenario
         - octavia-v2-dsvm-scenario-stable-train
         - octavia-v2-dsvm-scenario-stable-stein
-        - octavia-v2-dsvm-py2-scenario-stable-rocky
-        - octavia-v2-dsvm-py2-scenario-stable-queens
         - octavia-v2-dsvm-scenario-ipv6-only:
             voting: false
         - octavia-v2-dsvm-scenario-centos-8:
             voting: false
         - octavia-v2-act-stdby-dsvm-scenario-two-node:
             voting: false
-        - octavia-v2-act-stdby-iptables-dsvm-py2-scenario-stable-rocky:
-            voting: false
-        - octavia-v2-act-stdby-iptables-dsvm-py2-scenario-stable-queens:
-            voting: false
         - octavia-v2-act-stdby-dsvm-scenario:
             voting: false
         - octavia-v2-act-stdby-dsvm-scenario-stable-train:
@@ -40,20 +32,12 @@
             voting: false
         - octavia-v2-dsvm-tls-barbican-stable-stein:
             voting: false
-        - octavia-v2-dsvm-tls-barbican-stable-rocky:
-            voting: false
-        - octavia-v2-dsvm-tls-barbican-stable-queens:
-            voting: false
         - octavia-v2-dsvm-spare-pool:
             voting: false
         - octavia-v2-dsvm-spare-pool-stable-train:
             voting: false
         - octavia-v2-dsvm-spare-pool-stable-stein:
             voting: false
-        - octavia-v2-dsvm-py2-spare-pool-stable-rocky:
-            voting: false
-        - octavia-v2-dsvm-py2-spare-pool-stable-queens:
-            voting: false
         - octavia-v2-dsvm-cinder-amphora:
             voting: false
     gate:
@@ -63,10 +47,6 @@
         - octavia-v2-dsvm-noop-api
         - octavia-v2-dsvm-noop-api-stable-train
         - octavia-v2-dsvm-noop-api-stable-stein
-        - octavia-v2-dsvm-noop-py2-api-stable-rocky
-        - octavia-v2-dsvm-noop-py2-api-stable-queens
         - octavia-v2-dsvm-scenario
         - octavia-v2-dsvm-scenario-stable-train
         - octavia-v2-dsvm-scenario-stable-stein
-        - octavia-v2-dsvm-py2-scenario-stable-rocky
-        - octavia-v2-dsvm-py2-scenario-stable-queens