Moving resource waiters to a new module

Currently, resource waiters methods are placed under
service clients modules. There are three duplicated
waiters in both v1 and v2 modules:
- wait_for_share_status
- wait_for_snapshot_status
- wait_for_access_rule_status

This patch suggests to separate the waiter functions
from client modules and collect them in one place.

Change-Id: I9f0d50a325139e6067e4339533d4b01a322df7a8
diff --git a/manila_tempest_tests/common/waiters.py b/manila_tempest_tests/common/waiters.py
new file mode 100644
index 0000000..d3dbcb4
--- /dev/null
+++ b/manila_tempest_tests/common/waiters.py
@@ -0,0 +1,374 @@
+# Copyright 2021 Red Hat, 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
+
+import six
+from tempest import config
+from tempest.lib import exceptions
+
+from manila_tempest_tests.common import constants
+from manila_tempest_tests.services.share.v2.json import shares_client
+from manila_tempest_tests import share_exceptions
+
+CONF = config.CONF
+LATEST_MICROVERSION = CONF.share.max_api_microversion
+
+
+def wait_for_share_instance_status(client, instance_id, status,
+                                   version=LATEST_MICROVERSION):
+    """Waits for a share to reach a given status."""
+    body = client.get_share_instance(instance_id, version=version)
+    instance_status = body['status']
+    start = int(time.time())
+
+    while instance_status != status:
+        time.sleep(client.build_interval)
+        body = client.get_share_instance(instance_id)
+        instance_status = body['status']
+        if instance_status == status:
+            return
+        elif 'error' in instance_status.lower():
+            raise share_exceptions.ShareInstanceBuildErrorException(
+                id=instance_id)
+
+        if int(time.time()) - start >= client.build_timeout:
+            message = ('Share instance %s failed to reach %s status within'
+                       ' the required time (%s s).' %
+                       (instance_id, status, client.build_timeout))
+            raise exceptions.TimeoutException(message)
+
+
+def wait_for_share_status(client, share_id, status, status_attr='status',
+                          version=LATEST_MICROVERSION):
+    """Waits for a share to reach a given status."""
+    if isinstance(client, shares_client.SharesV2Client):
+        body = client.get_share(share_id, version=version)
+    else:
+        body = client.get_share(share_id)
+    share_status = body[status_attr]
+    start = int(time.time())
+
+    exp_status = status if isinstance(status, list) else [status]
+    while share_status not in exp_status:
+        time.sleep(client.build_interval)
+        if isinstance(client, shares_client.SharesV2Client):
+            body = client.get_share(share_id, version=version)
+        else:
+            body = client.get_share(share_id)
+        share_status = body[status_attr]
+        if share_status in exp_status:
+            return
+        elif 'error' in share_status.lower():
+            raise share_exceptions.ShareBuildErrorException(
+                share_id=share_id)
+        if int(time.time()) - start >= client.build_timeout:
+            message = ("Share's %(status_attr)s failed to transition to "
+                       "%(status)s within the required "
+                       "time %(seconds)s." %
+                       {"status_attr": status_attr, "status": exp_status,
+                        "seconds": client.build_timeout})
+            raise exceptions.TimeoutException(message)
+
+
+def wait_for_snapshot_status(client, snapshot_id, status,
+                             version=LATEST_MICROVERSION):
+    """Waits for a snapshot to reach a given status."""
+    if isinstance(client, shares_client.SharesV2Client):
+        body = client.get_snapshot(snapshot_id, version=version)
+    else:
+        body = client.get_snapshot(snapshot_id)
+    snapshot_name = body['name']
+    snapshot_status = body['status']
+    start = int(time.time())
+
+    while snapshot_status != status:
+        time.sleep(client.build_interval)
+        if isinstance(client, shares_client.SharesV2Client):
+            body = client.get_snapshot(snapshot_id, version=version)
+        else:
+            body = client.get_snapshot(snapshot_id)
+        snapshot_status = body['status']
+        if snapshot_status in status:
+            return
+        if 'error' in snapshot_status:
+            raise (share_exceptions.
+                   SnapshotBuildErrorException(snapshot_id=snapshot_id))
+
+        if int(time.time()) - start >= client.build_timeout:
+            message = ('Share Snapshot %s failed to reach %s status '
+                       'within the required time (%s s).' %
+                       (snapshot_name, status, client.build_timeout))
+            raise exceptions.TimeoutException(message)
+
+
+def wait_for_access_rule_status(client, share_id, rule_id, status,
+                                raise_rule_in_error_state=True):
+    """Waits for an access rule to reach a given status."""
+    rule_status = "new"
+    start = int(time.time())
+    while rule_status != status:
+        time.sleep(client.build_interval)
+        rules = client.list_access_rules(share_id)
+        for rule in rules:
+            if rule["id"] in rule_id:
+                rule_status = rule['state']
+                break
+        if 'error' in rule_status and raise_rule_in_error_state:
+            raise share_exceptions.AccessRuleBuildErrorException(
+                rule_id=rule_id)
+
+        if int(time.time()) - start >= client.build_timeout:
+            message = ('Share Access Rule %s failed to reach %s status '
+                       'within the required time (%s s).' %
+                       (rule_id, status, client.build_timeout))
+            raise exceptions.TimeoutException(message)
+
+
+def wait_for_snapshot_instance_status(client, instance_id, expected_status):
+    """Waits for a snapshot instance status to reach a given status."""
+    body = client.get_snapshot_instance(instance_id)
+    instance_status = body['status']
+    start = int(time.time())
+
+    while instance_status != expected_status:
+        time.sleep(client.build_interval)
+        body = client.get_snapshot_instance(instance_id)
+        instance_status = body['status']
+        if instance_status == expected_status:
+            return
+        if 'error' in instance_status:
+            raise share_exceptions.SnapshotInstanceBuildErrorException(
+                id=instance_id)
+
+        if int(time.time()) - start >= client.build_timeout:
+            message = ('The status of snapshot instance %(id)s failed to '
+                       'reach %(expected_status)s status within the '
+                       'required time (%(time)ss). Current '
+                       'status: %(current_status)s.' %
+                       {
+                           'expected_status': expected_status,
+                           'time': client.build_timeout,
+                           'id': instance_id,
+                           'current_status': instance_status,
+                       })
+            raise exceptions.TimeoutException(message)
+
+
+def wait_for_share_group_status(client, share_group_id, status):
+    """Waits for a share group to reach a given status."""
+    body = client.get_share_group(share_group_id)
+    sg_name = body['name']
+    sg_status = body['status']
+    start = int(time.time())
+
+    while sg_status != status:
+        time.sleep(client.build_interval)
+        body = client.get_share_group(share_group_id)
+        sg_status = body['status']
+        if 'error' in sg_status and status != 'error':
+            raise share_exceptions.ShareGroupBuildErrorException(
+                share_group_id=share_group_id)
+
+        if int(time.time()) - start >= client.build_timeout:
+            sg_name = sg_name or share_group_id
+            message = ('Share Group %s failed to reach %s status '
+                       'within the required time (%s s). '
+                       'Current status: %s' %
+                       (sg_name, status, client.build_timeout, sg_status))
+            raise exceptions.TimeoutException(message)
+
+
+def wait_for_share_group_snapshot_status(client, share_group_snapshot_id,
+                                         status):
+    """Waits for a share group snapshot to reach a given status."""
+    body = client.get_share_group_snapshot(share_group_snapshot_id)
+    sg_snapshot_name = body['name']
+    sg_snapshot_status = body['status']
+    start = int(time.time())
+
+    while sg_snapshot_status != status:
+        time.sleep(client.build_interval)
+        body = client.get_share_group_snapshot(share_group_snapshot_id)
+        sg_snapshot_status = body['status']
+        if 'error' in sg_snapshot_status and status != 'error':
+            raise share_exceptions.ShareGroupSnapshotBuildErrorException(
+                share_group_snapshot_id=share_group_snapshot_id)
+
+        if int(time.time()) - start >= client.build_timeout:
+            message = ('Share Group Snapshot %s failed to reach %s status '
+                       'within the required time (%s s).' %
+                       (sg_snapshot_name, status, client.build_timeout))
+            raise exceptions.TimeoutException(message)
+
+
+def wait_for_share_server_status(client, server_id, status,
+                                 status_attr='status'):
+    """Waits for a share to reach a given status."""
+    body = client.show_share_server(server_id)
+    server_status = body[status_attr]
+    start = int(time.time())
+
+    while server_status != status:
+        time.sleep(client.build_interval)
+        body = client.show_share_server(server_id)
+        server_status = body[status_attr]
+        if server_status in status:
+            return
+        elif constants.STATUS_ERROR in server_status.lower():
+            raise share_exceptions.ShareServerBuildErrorException(
+                server_id=server_id)
+
+        if int(time.time()) - start >= client.build_timeout:
+            message = ("Share server's %(status_attr)s failed to "
+                       "transition to %(status)s within the required "
+                       "time %(seconds)s." %
+                       {"status_attr": status_attr, "status": status,
+                        "seconds": client.build_timeout})
+            raise exceptions.TimeoutException(message)
+
+
+def wait_for_share_replica_status(client, replica_id, expected_status,
+                                  status_attr='status'):
+    """Waits for a replica's status_attr to reach a given status."""
+    body = client.get_share_replica(replica_id)
+    replica_status = body[status_attr]
+    start = int(time.time())
+
+    while replica_status != expected_status:
+        time.sleep(client.build_interval)
+        body = client.get_share_replica(replica_id)
+        replica_status = body[status_attr]
+        if replica_status == expected_status:
+            return
+        if ('error' in replica_status
+                and expected_status != constants.STATUS_ERROR):
+            raise share_exceptions.ShareInstanceBuildErrorException(
+                id=replica_id)
+
+        if int(time.time()) - start >= client.build_timeout:
+            message = ('The %(status_attr)s of Replica %(id)s failed to '
+                       'reach %(expected_status)s status within the '
+                       'required time (%(time)ss). Current '
+                       '%(status_attr)s: %(current_status)s.' %
+                       {
+                           'status_attr': status_attr,
+                           'expected_status': expected_status,
+                           'time': client.build_timeout,
+                           'id': replica_id,
+                           'current_status': replica_status,
+                       })
+            raise exceptions.TimeoutException(message)
+
+
+def wait_for_snapshot_access_rule_status(client, snapshot_id, rule_id,
+                                         expected_state='active'):
+    rule = client.get_snapshot_access_rule(snapshot_id, rule_id)
+    state = rule['state']
+    start = int(time.time())
+
+    while state != expected_state:
+        time.sleep(client.build_interval)
+        rule = client.get_snapshot_access_rule(snapshot_id, rule_id)
+        state = rule['state']
+        if state == expected_state:
+            return
+        if 'error' in state:
+            raise share_exceptions.AccessRuleBuildErrorException(
+                snapshot_id)
+
+        if int(time.time()) - start >= client.build_timeout:
+            message = ('The status of snapshot access rule %(id)s failed '
+                       'to reach %(expected_state)s state within the '
+                       'required time (%(time)ss). Current '
+                       'state: %(current_state)s.' %
+                       {
+                           'expected_state': expected_state,
+                           'time': client.build_timeout,
+                           'id': rule_id,
+                           'current_state': state,
+                       })
+            raise exceptions.TimeoutException(message)
+
+
+def wait_for_migration_status(client, share_id, dest_host, status_to_wait,
+                              version=LATEST_MICROVERSION):
+    """Waits for a share to migrate to a certain host."""
+    statuses = ((status_to_wait,)
+                if not isinstance(status_to_wait, (tuple, list, set))
+                else status_to_wait)
+    share = client.get_share(share_id, version=version)
+    migration_timeout = CONF.share.migration_timeout
+    start = int(time.time())
+    while share['task_state'] not in statuses:
+        time.sleep(client.build_interval)
+        share = client.get_share(share_id, version=version)
+        if share['task_state'] in statuses:
+            break
+        elif share['task_state'] == 'migration_error':
+            raise share_exceptions.ShareMigrationException(
+                share_id=share['id'], src=share['host'], dest=dest_host)
+        elif int(time.time()) - start >= migration_timeout:
+            message = ('Share %(share_id)s failed to reach a status in'
+                       '%(status)s when migrating from host %(src)s to '
+                       'host %(dest)s within the required time '
+                       '%(timeout)s.' % {
+                           'src': share['host'],
+                           'dest': dest_host,
+                           'share_id': share['id'],
+                           'timeout': client.build_timeout,
+                           'status': six.text_type(statuses),
+                       })
+            raise exceptions.TimeoutException(message)
+    return share
+
+
+def wait_for_snapshot_access_rule_deletion(client, snapshot_id, rule_id):
+    rule = client.get_snapshot_access_rule(snapshot_id, rule_id)
+    start = int(time.time())
+
+    while rule is not None:
+        time.sleep(client.build_interval)
+
+        rule = client.get_snapshot_access_rule(snapshot_id, rule_id)
+
+        if rule is None:
+            return
+        if int(time.time()) - start >= client.build_timeout:
+            message = ('The snapshot access rule %(id)s failed to delete '
+                       'within the required time (%(time)ss).' %
+                       {
+                           'time': client.build_timeout,
+                           'id': rule_id,
+                       })
+            raise exceptions.TimeoutException(message)
+
+
+def wait_for_message(client, resource_id):
+    """Waits until a message for a resource with given id exists"""
+    start = int(time.time())
+    message = None
+
+    while not message:
+        time.sleep(client.build_interval)
+        for msg in client.list_messages():
+            if msg['resource_id'] == resource_id:
+                return msg
+
+        if int(time.time()) - start >= client.build_timeout:
+            message = ('No message for resource with id %s was created in'
+                       ' the required time (%s s).' %
+                       (resource_id, client.build_timeout))
+            raise exceptions.TimeoutException(message)