Create scenario tests for load balancers

This patch implements the tempest plugin for
for testing load balancer creation in Octavia.

Co-Authored-By: Jude Cross <jcross@godaddy.com>
Co-Authored-By: Lingxian Kong <anlin.kong@gmail.com>

Depends-On: https://review.openstack.org/557856
Change-Id: I57064f8e0834efba8859a780394a1c69851cc917
diff --git a/octavia_tempest_plugin/tests/waiters.py b/octavia_tempest_plugin/tests/waiters.py
new file mode 100644
index 0000000..7825782
--- /dev/null
+++ b/octavia_tempest_plugin/tests/waiters.py
@@ -0,0 +1,174 @@
+#   Copyright 2017 GoDaddy
+# Copyright 2018 Rackspace US Inc.  All rights reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+import time
+
+from oslo_log import log as logging
+from tempest import config
+from tempest.lib.common.utils import test_utils
+from tempest.lib import exceptions
+
+from octavia_tempest_plugin.common import constants as const
+
+CONF = config.CONF
+LOG = logging.getLogger(__name__)
+
+
+def wait_for_status(show_client, id, status_key, status,
+                    check_interval, check_timeout, root_tag=None):
+    """Waits for an object to reach a specific status.
+
+    :param show_client: The tempest service client show method.
+                        Ex. cls.os_primary.servers_client.show_server
+    :param id: The id of the object to query.
+    :param status_key: The key of the status field in the response.
+                       Ex. provisioning_status
+    :param status: The status to wait for. Ex. "ACTIVE"
+    :check_interval: How often to check the status, in seconds.
+    :check_timeout: The maximum time, in seconds, to check the status.
+    :root_tag: The root tag on the response to remove, if any.
+    :raises CommandFailed: Raised if the object goes into ERROR and ERROR was
+                           not the desired status.
+    :raises TimeoutException: The object did not achieve the status or ERROR in
+                              the check_timeout period.
+    :returns: The object details from the show client.
+    """
+    start = int(time.time())
+    LOG.info('Waiting for {name} status to update to {status}'.format(
+        name=show_client.__name__, status=status))
+    while True:
+        response = show_client(id)
+        if root_tag:
+            object_details = response[root_tag]
+        else:
+            object_details = response
+
+        if object_details[status_key] == status:
+            LOG.info('{name}\'s status updated to {status}.'.format(
+                name=show_client.__name__, status=status))
+            return object_details
+        elif object_details[status_key] == 'ERROR':
+            message = ('{name} {field} updated to an invalid state of '
+                       'ERROR'.format(name=show_client.__name__,
+                                      field=status_key))
+            caller = test_utils.find_test_caller()
+            if caller:
+                message = '({caller}) {message}'.format(caller=caller,
+                                                        message=message)
+            raise exceptions.UnexpectedResponseCode(message)
+        elif int(time.time()) - start >= check_timeout:
+            message = (
+                '{name} {field} failed to update to {expected_status} within '
+                'the required time {timeout}. Current status of {name}: '
+                '{status}'.format(
+                    name=show_client.__name__,
+                    timeout=check_timeout,
+                    status=object_details[status_key],
+                    expected_status=status,
+                    field=status_key
+                ))
+            caller = test_utils.find_test_caller()
+            if caller:
+                message = '({caller}) {message}'.format(caller=caller,
+                                                        message=message)
+            raise exceptions.TimeoutException(message)
+
+        time.sleep(check_interval)
+
+
+def wait_for_not_found(delete_func, show_func, *args, **kwargs):
+    """Call the delete function, then wait for it to be 'NotFound'
+
+    :param delete_func: The delete function to call.
+    :param show_func: The show function to call looking for 'NotFound'.
+    :param ID: The ID of the object to delete/show.
+    :raises TimeoutException: The object did not achieve the status or ERROR in
+                              the check_timeout period.
+    :returns: None
+    """
+    try:
+        return delete_func(*args, **kwargs)
+    except exceptions.NotFound:
+        return
+    start = int(time.time())
+    LOG.info('Waiting for object to be NotFound')
+    while True:
+        try:
+            show_func(*args, **kwargs)
+        except exceptions.NotFound:
+            return
+        if int(time.time()) - start >= CONF.load_balancer.check_timeout:
+            message = ('{name} did not raise NotFound in {timeout} '
+                       'seconds.'.format(
+                           name=show_func.__name__,
+                           timeout=CONF.load_balancer.check_timeout))
+            raise exceptions.TimeoutException(message)
+        time.sleep(CONF.load_balancer.check_interval)
+
+
+def wait_for_deleted_status_or_not_found(
+        show_client, id, status_key, check_interval, check_timeout,
+        root_tag=None):
+    """Waits for an object to reach a DELETED status or be not found (404).
+
+    :param show_client: The tempest service client show method.
+                        Ex. cls.os_primary.servers_client.show_server
+    :param id: The id of the object to query.
+    :param status_key: The key of the status field in the response.
+                       Ex. provisioning_status
+    :check_interval: How often to check the status, in seconds.
+    :check_timeout: The maximum time, in seconds, to check the status.
+    :root_tag: The root tag on the response to remove, if any.
+    :raises CommandFailed: Raised if the object goes into ERROR and ERROR was
+                           not the desired status.
+    :raises TimeoutException: The object did not achieve the status or ERROR in
+                              the check_timeout period.
+    :returns: None
+    """
+    start = int(time.time())
+    LOG.info('Waiting for {name} status to update to DELETED or be not '
+             'found(404)'.format(name=show_client.__name__))
+    while True:
+        try:
+            response = show_client(id)
+        except exceptions.NotFound:
+            return
+
+        if root_tag:
+            object_details = response[root_tag]
+        else:
+            object_details = response
+
+        if object_details[status_key] == const.DELETED:
+            LOG.info('{name}\'s status updated to DELETED.'.format(
+                name=show_client.__name__))
+            return
+        elif int(time.time()) - start >= check_timeout:
+            message = (
+                '{name} {field} failed to update to DELETED or become not '
+                'found (404) within the required time {timeout}. Current '
+                'status of {name}: {status}'.format(
+                    name=show_client.__name__,
+                    timeout=check_timeout,
+                    status=object_details[status_key],
+                    field=status_key
+                ))
+            caller = test_utils.find_test_caller()
+            if caller:
+                message = '({caller}) {message}'.format(caller=caller,
+                                                        message=message)
+            raise exceptions.TimeoutException(message)
+
+        time.sleep(check_interval)