Merge "Add manila.conf parameters for NetApp Active IQ weigher"
diff --git a/babel.cfg b/babel.cfg
deleted file mode 100644
index 15cd6cb..0000000
--- a/babel.cfg
+++ /dev/null
@@ -1,2 +0,0 @@
-[python: **.py]
-
diff --git a/doc/requirements.txt b/doc/requirements.txt
index 5f0bf6a..99f7e90 100644
--- a/doc/requirements.txt
+++ b/doc/requirements.txt
@@ -1,3 +1,3 @@
-sphinx!=1.6.6,!=1.6.7,>=1.6.2 # BSD
+sphinx>=1.7.0 # BSD
 openstackdocstheme>=1.31.2 # Apache-2.0
 reno>=2.5.0 # Apache-2.0
diff --git a/doc/source/conf.py b/doc/source/conf.py
old mode 100755
new mode 100644
diff --git a/manila_tempest_tests/common/constants.py b/manila_tempest_tests/common/constants.py
index 3488bc5..416de56 100644
--- a/manila_tempest_tests/common/constants.py
+++ b/manila_tempest_tests/common/constants.py
@@ -108,3 +108,6 @@
 SERVER_STATE_UNMANAGE_STARTING = 'unmanage_starting'
 STATUS_SERVER_MIGRATING = 'server_migrating'
 STATUS_SERVER_MIGRATING_TO = 'server_migrating_to'
+
+# Share transfer
+SHARE_TRANSFER_VERSION = "2.77"
diff --git a/manila_tempest_tests/common/waiters.py b/manila_tempest_tests/common/waiters.py
index 8a97c8e..a11b620 100644
--- a/manila_tempest_tests/common/waiters.py
+++ b/manila_tempest_tests/common/waiters.py
@@ -45,7 +45,8 @@
                              resource_name='share', rule_id=None,
                              status_attr='status',
                              raise_rule_in_error_state=True,
-                             version=LATEST_MICROVERSION):
+                             version=LATEST_MICROVERSION,
+                             timeout=None):
     """Waits for a resource to reach a given status."""
 
     get_resource_action = {
@@ -59,6 +60,7 @@
         'share_group': 'get_share_group',
         'share_group_snapshot': 'get_share_group_snapshot',
         'share_replica': 'get_share_replica',
+        'share_backup': 'get_share_backup'
     }
 
     action_name = get_resource_action[resource_name]
@@ -86,6 +88,9 @@
     start = int(time.time())
 
     exp_status = status if isinstance(status, list) else [status]
+    resource_status_check_time_out = client.build_timeout
+    if timeout is not None:
+        resource_status_check_time_out = timeout
     while resource_status not in exp_status:
         time.sleep(client.build_interval)
         body = resource_action(*method_args, **method_kwargs)[rn]
@@ -102,11 +107,11 @@
             raise_method = _get_name_of_raise_method(resource_name)
             resource_exception = getattr(share_exceptions, raise_method)
             raise resource_exception(resource_id=resource_id)
-        if int(time.time()) - start >= client.build_timeout:
+        if int(time.time()) - start >= resource_status_check_time_out:
             message = ('%s %s failed to reach %s status (current %s) '
                        'within the required time (%s s).' %
                        (resource_name.replace('_', ' '), resource_id, status,
-                        resource_status, client.build_timeout))
+                        resource_status, resource_status_check_time_out))
             raise exceptions.TimeoutException(message)
 
 
diff --git a/manila_tempest_tests/config.py b/manila_tempest_tests/config.py
index 56a5b5a..03dfafb 100644
--- a/manila_tempest_tests/config.py
+++ b/manila_tempest_tests/config.py
@@ -40,7 +40,7 @@
                     "This value is only used to validate the versions "
                     "response from Manila."),
     cfg.StrOpt("max_api_microversion",
-               default="2.74",
+               default="2.85",
                help="The maximum api microversion is configured to be the "
                     "value of the latest microversion supported by Manila."),
     cfg.StrOpt("region",
@@ -265,6 +265,9 @@
                 default=False,
                 help="Enable or disable migration with "
                      "preserve_snapshots tests set to True."),
+    cfg.BoolOpt("run_driver_assisted_backup_tests",
+                default=False,
+                help="Enable or disable share backup tests."),
     cfg.BoolOpt("run_manage_unmanage_tests",
                 default=False,
                 help="Defines whether to run manage/unmanage tests or not. "
@@ -305,6 +308,7 @@
                default="manila",
                help="Image username."),
     cfg.StrOpt("image_password",
+               secret=True,
                help="Image password. Should be used for "
                     "'image_with_share_tools' without Nova Metadata support."),
     cfg.StrOpt("client_vm_flavor_ref",
@@ -314,8 +318,12 @@
                default=1500,
                help="Time to wait for share migration before "
                     "timing out (seconds)."),
+    cfg.IntOpt("share_backup_timeout",
+               default=1500,
+               help="Time to wait for share backup before "
+                    "timing out (seconds)."),
     cfg.IntOpt("share_server_migration_timeout",
-               default="1500",
+               default=1500,
                help="Time to wait for share server migration before "
                     "timing out (seconds)."),
     cfg.StrOpt("default_share_type_name",
@@ -327,6 +335,18 @@
     cfg.IntOpt("share_size",
                default=1,
                help="Default size in GB for shares created by share tests."),
+    cfg.IntOpt("additional_overflow_blocks",
+               default=0,
+               help="Additional blocks to be written "
+                    "to share in scenario tests."),
+    cfg.IntOpt("share_resize_sync_delay",
+               default=0,
+               help="Time to wait before the changes to the share size"
+                    " are propagated to the storage system."),
+    cfg.IntOpt("share_growth_size",
+               default=1,
+               help="The default increase in size sought by tests"
+                    " when validating share resizing within scenario tests."),
     cfg.BoolOpt("run_ipv6_tests",
                 default=False,
                 help="Enable or disable running IPv6 NFS scenario tests. "
@@ -336,4 +356,20 @@
                      "attempt to create an IPv6 subnet on the project network "
                      "they create for ping and SSH to the client test VM "
                      "where data path testing is performed."),
+    cfg.StrOpt("dd_input_file",
+               default="/dev/zero",
+               help="The input file (if) in the dd command specifies the "
+                    "source of data that dd will read and process, which can "
+                    "be a device, a regular file, or even standard input "
+                    "(stdin). dd copies, transforms, or performs actions on "
+                    "this data based on provided options and then writes it "
+                    "to an output file or device (of). When using /dev/zero "
+                    "in storage systems with default compression, although "
+                    "it generates highly compressible null bytes (zeros), "
+                    "writing data from /dev/zero might not yield significant "
+                    "space savings as these systems are already optimized for "
+                    "efficient compression."),
+    cfg.DictOpt("driver_assisted_backup_test_driver_options",
+                default={'dummy': True},
+                help="Share backup driver options specified as dict."),
 ]
diff --git a/manila_tempest_tests/services/share/json/shares_client.py b/manila_tempest_tests/services/share/json/shares_client.py
index 6871eda..fb03afc 100644
--- a/manila_tempest_tests/services/share/json/shares_client.py
+++ b/manila_tempest_tests/services/share/json/shares_client.py
@@ -323,6 +323,9 @@
         elif "server_id" in kwargs:
             return self._is_resource_deleted(
                 self.show_share_server, kwargs.get("server_id"))
+        elif "backup_id" in kwargs:
+            return self._is_resource_deleted(
+                self.get_share_backup, kwargs.get("backup_id"))
         else:
             raise share_exceptions.InvalidResource(
                 message=str(kwargs))
diff --git a/manila_tempest_tests/services/share/v2/json/shares_client.py b/manila_tempest_tests/services/share/v2/json/shares_client.py
index bf944d4..e3a022e 100644
--- a/manila_tempest_tests/services/share/v2/json/shares_client.py
+++ b/manila_tempest_tests/services/share/v2/json/shares_client.py
@@ -373,6 +373,61 @@
         return rest_client.ResponseBody(resp, body)
 
 ###############
+    def create_share_transfer(self, share_id, name=None,
+                              version=LATEST_MICROVERSION):
+        if name is None:
+            name = data_utils.rand_name("tempest-created-share-transfer")
+        post_body = {
+            "transfer": {
+                "share_id": share_id,
+                "name": name
+            }
+        }
+        body = json.dumps(post_body)
+        resp, body = self.post("share-transfers", body, version=version)
+        self.expected_success(202, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def delete_share_transfer(self, transfer_id, version=LATEST_MICROVERSION):
+        resp, body = self.delete("share-transfers/%s" % transfer_id,
+                                 version=version)
+        self.expected_success(200, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def list_share_transfers(self, detailed=False, params=None,
+                             version=LATEST_MICROVERSION):
+        """Get list of share transfers w/o filters."""
+        uri = 'share-transfers/detail' if detailed else 'share-transfers'
+        uri += '?%s' % parse.urlencode(params) if params else ''
+        resp, body = self.get(uri, version=version)
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def get_share_transfer(self, transfer_id, version=LATEST_MICROVERSION):
+        resp, body = self.get("share-transfers/%s" % transfer_id,
+                              version=version)
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def accept_share_transfer(self, transfer_id, auth_key,
+                              clear_access_rules=False,
+                              version=LATEST_MICROVERSION):
+        post_body = {
+            "accept": {
+                "auth_key": auth_key,
+                "clear_access_rules": clear_access_rules
+            }
+        }
+        body = json.dumps(post_body)
+        resp, body = self.post("share-transfers/%s/accept" % transfer_id,
+                               body, version=version)
+        self.expected_success(202, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+###############
 
     def get_instances_of_share(self, share_id, version=LATEST_MICROVERSION):
         resp, body = self.get("shares/%s/instances" % share_id,
@@ -764,7 +819,8 @@
     def create_access_rule(self, share_id, access_type="ip",
                            access_to="0.0.0.0", access_level=None,
                            version=LATEST_MICROVERSION, metadata=None,
-                           action_name=None):
+                           action_name=None, lock_visibility=False,
+                           lock_deletion=False):
         post_body = {
             self._get_access_action_name(version, 'os-allow_access'): {
                 "access_type": access_type,
@@ -774,6 +830,10 @@
         }
         if metadata is not None:
             post_body['allow_access']['metadata'] = metadata
+        if lock_visibility:
+            post_body['allow_access']['lock_visibility'] = True
+        if lock_deletion:
+            post_body['allow_access']['lock_deletion'] = True
         body = json.dumps(post_body)
         resp, body = self.post(
             "shares/%s/action" % share_id, body, version=version,
@@ -817,12 +877,15 @@
         return rest_client.ResponseBody(resp, body)
 
     def delete_access_rule(self, share_id, rule_id,
-                           version=LATEST_MICROVERSION, action_name=None):
+                           version=LATEST_MICROVERSION, action_name=None,
+                           unrestrict=False):
         post_body = {
             self._get_access_action_name(version, 'os-deny_access'): {
                 "access_id": rule_id,
             }
         }
+        if unrestrict:
+            post_body['deny_access']['unrestrict'] = True
         body = json.dumps(post_body)
         resp, body = self.post(
             "shares/%s/action" % share_id, body, version=version)
@@ -1781,6 +1844,109 @@
         body = json.loads(body)
         return rest_client.ResponseBody(resp, body)
 
+###############
+
+    def get_share_backup(self, backup_id, version=LATEST_MICROVERSION):
+        """Returns the details of a single backup."""
+        resp, body = self.get("share-backups/%s" % backup_id,
+                              headers=EXPERIMENTAL,
+                              extra_headers=True,
+                              version=version)
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def list_share_backups(self, share_id=None, version=LATEST_MICROVERSION):
+        """Get list of backups."""
+        uri = "share-backups/detail"
+        if share_id:
+            uri += (f'?share_id={share_id}')
+        resp, body = self.get(uri, headers=EXPERIMENTAL,
+                              extra_headers=True, version=version)
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def create_share_backup(self, share_id, name=None, description=None,
+                            backup_options=None, version=LATEST_MICROVERSION):
+        """Create a share backup."""
+        if name is None:
+            name = data_utils.rand_name("tempest-created-share-backup")
+        if description is None:
+            description = data_utils.rand_name(
+                "tempest-created-share-backup-desc")
+        post_body = {
+            'share_backup': {
+                'name': name,
+                'description': description,
+                'share_id': share_id,
+                'backup_options': backup_options,
+            }
+        }
+        body = json.dumps(post_body)
+        resp, body = self.post('share-backups', body,
+                               headers=EXPERIMENTAL,
+                               extra_headers=True,
+                               version=version)
+
+        self.expected_success(202, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def delete_share_backup(self, backup_id, version=LATEST_MICROVERSION):
+        """Delete share backup."""
+        uri = "share-backups/%s" % backup_id
+        resp, body = self.delete(uri,
+                                 headers=EXPERIMENTAL,
+                                 extra_headers=True,
+                                 version=version)
+        self.expected_success(202, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
+    def restore_share_backup(self, backup_id, version=LATEST_MICROVERSION):
+        """Restore share backup."""
+        uri = "share-backups/%s/action" % backup_id
+        body = {'restore': None}
+        resp, body = self.post(uri, json.dumps(body),
+                               headers=EXPERIMENTAL,
+                               extra_headers=True,
+                               version=version)
+        self.expected_success(202, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def update_share_backup(self, backup_id, name=None, description=None,
+                            version=LATEST_MICROVERSION):
+        """Update share backup."""
+        uri = "share-backups/%s" % backup_id
+        post_body = {}
+        if name:
+            post_body['name'] = name
+        if description:
+            post_body['description'] = description
+
+        body = json.dumps({'share_backup': post_body})
+        resp, body = self.put(uri, body,
+                              headers=EXPERIMENTAL,
+                              extra_headers=True,
+                              version=version)
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def reset_state_share_backup(self, backup_id,
+                                 status=constants.STATUS_AVAILABLE,
+                                 version=LATEST_MICROVERSION):
+
+        uri = "share-backups/%s/action" % backup_id
+        body = {'reset_status': {'status': status}}
+        resp, body = self.post(uri, json.dumps(body),
+                               headers=EXPERIMENTAL,
+                               extra_headers=True,
+                               version=LATEST_MICROVERSION)
+        self.expected_success(202, resp.status)
+        return rest_client.ResponseBody(resp, body)
+
 ################
 
     def create_snapshot_access_rule(self, snapshot_id, access_type="ip",
@@ -2149,3 +2315,68 @@
         self.expected_success(200, resp.status)
         body = json.loads(body)
         return rest_client.ResponseBody(resp, body)
+
+#################
+
+    def create_resource_lock(self, resource_id, resource_type,
+                             resource_action='delete', lock_reason=None,
+                             version=LATEST_MICROVERSION):
+        body = {
+            "resource_lock": {
+                'resource_id': resource_id,
+                'resource_type': resource_type,
+                'resource_action': resource_action,
+                'lock_reason': lock_reason,
+            },
+        }
+        body = json.dumps(body)
+        resp, body = self.post("resource-locks", body, version=version)
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def get_resource_lock(self, lock_id, version=LATEST_MICROVERSION):
+        resp, body = self.get("resource-locks/%s" % lock_id, version=version)
+
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def list_resource_locks(self, filters=None, version=LATEST_MICROVERSION):
+        uri = (
+            "resource-locks?%s" % parse.urlencode(filters)
+            if filters else "resource-locks"
+        )
+
+        resp, body = self.get(uri, version=version)
+
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def update_resource_lock(self,
+                             lock_id,
+                             resource_action=None,
+                             lock_reason=None,
+                             version=LATEST_MICROVERSION):
+        uri = 'resource-locks/%s' % lock_id
+        post_body = {}
+        if resource_action:
+            post_body['resource_action'] = resource_action
+        if lock_reason:
+            post_body['lock_reason'] = lock_reason
+        body = json.dumps({'resource_lock': post_body})
+
+        resp, body = self.put(uri, body, version=version)
+
+        self.expected_success(200, resp.status)
+        body = json.loads(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def delete_resource_lock(self, lock_id, version=LATEST_MICROVERSION):
+        uri = "resource-locks/%s" % lock_id
+
+        resp, body = self.delete(uri, version=version)
+
+        self.expected_success(204, resp.status)
+        return rest_client.ResponseBody(resp, body)
diff --git a/manila_tempest_tests/share_exceptions.py b/manila_tempest_tests/share_exceptions.py
index efa61b5..7620109 100644
--- a/manila_tempest_tests/share_exceptions.py
+++ b/manila_tempest_tests/share_exceptions.py
@@ -86,3 +86,7 @@
 class ShareServerMigrationException(exceptions.TempestException):
     message = ("Share server %(server_id)s failed to migrate and is in ERROR "
                "status")
+
+
+class ShareBackupBuildErrorException(exceptions.TempestException):
+    message = ("Share backup %(backup_id)s failed and is in ERROR status")
diff --git a/manila_tempest_tests/tests/api/admin/test_export_locations.py b/manila_tempest_tests/tests/api/admin/test_export_locations.py
index db382f2..74df8d1 100644
--- a/manila_tempest_tests/tests/api/admin/test_export_locations.py
+++ b/manila_tempest_tests/tests/api/admin/test_export_locations.py
@@ -58,6 +58,8 @@
         summary_keys = ['id', 'path']
         if utils.is_microversion_ge(version, '2.14'):
             summary_keys += ['preferred']
+        if utils.is_microversion_ge(version, '2.87'):
+            summary_keys += ['metadata']
 
         admin_summary_keys = summary_keys + [
             'share_instance_id', 'is_admin_only']
diff --git a/manila_tempest_tests/tests/api/admin/test_share_servers_manage_negative.py b/manila_tempest_tests/tests/api/admin/test_share_servers_manage_negative.py
index 1e9073d..09a40d9 100644
--- a/manila_tempest_tests/tests/api/admin/test_share_servers_manage_negative.py
+++ b/manila_tempest_tests/tests/api/admin/test_share_servers_manage_negative.py
@@ -333,7 +333,8 @@
             invalid_params)
 
         # try with part of the identifier
-        invalid_params['identifier'] = share_server['identifier'].split("-")[2]
+        invalid_params['identifier'] = (
+            share_server['identifier'].split("-")[-1])
 
         self.assertRaises(
             lib_exc.BadRequest,
diff --git a/manila_tempest_tests/tests/api/admin/test_share_servers_migration.py b/manila_tempest_tests/tests/api/admin/test_share_servers_migration.py
index 2535745..d3ec3f1 100644
--- a/manila_tempest_tests/tests/api/admin/test_share_servers_migration.py
+++ b/manila_tempest_tests/tests/api/admin/test_share_servers_migration.py
@@ -282,10 +282,11 @@
             src_server_id, dest_host, preserve_snapshots=preserve_snapshots)
 
         expected_state = constants.TASK_STATE_MIGRATION_DRIVER_PHASE1_DONE
+        timeout = CONF.share.share_server_migration_timeout
         waiters.wait_for_resource_status(
             self.shares_v2_client, src_server_id,
             expected_state, resource_name='share_server',
-            status_attr='task_state'
+            status_attr='task_state', timeout=timeout
         )
 
         # Get for the destination share server.
@@ -352,10 +353,11 @@
             preserve_snapshots=preserve_snapshots)
 
         expected_state = constants.TASK_STATE_MIGRATION_DRIVER_PHASE1_DONE
+        timeout = CONF.share.share_server_migration_timeout
         waiters.wait_for_resource_status(
             self.shares_v2_client, src_server_id,
             expected_state, resource_name='share_server',
-            status_attr='task_state'
+            status_attr='task_state', timeout=timeout
         )
         # Get for the destination share server.
         dest_server_id = self._get_share_server_destination_for_migration(
diff --git a/manila_tempest_tests/tests/api/admin/test_share_types.py b/manila_tempest_tests/tests/api/admin/test_share_types.py
index 3d46c9e..d23171a 100644
--- a/manila_tempest_tests/tests/api/admin/test_share_types.py
+++ b/manila_tempest_tests/tests/api/admin/test_share_types.py
@@ -113,22 +113,26 @@
     @utils.skip_if_microversion_not_supported("2.50")
     @decorators.idempotent_id('a9af19e1-e789-4c4f-a39b-dd8df6ed00b1')
     @tc.attr(base.TAG_POSITIVE, base.TAG_API)
-    @ddt.data(
-        ('2.50', data_utils.rand_name("type_updated"),
+    @ddt.named_data(
+        ('2_50_name_description_public', '2.50',
+         data_utils.rand_name("type_updated"), 'description_updated', True),
+        ('2_50_name', '2.50', data_utils.rand_name("type_updated"), None,
+         None),
+        ('2_50_description_public', '2.50', None, 'description_updated',
+         None),
+        ('2_50_public', '2.50', None, None, True),
+        ('2_50', '2.50', None, None, False),
+        (f'{LATEST_MICROVERSION}_name_description_public',
+         LATEST_MICROVERSION, data_utils.rand_name("type_updated"),
          'description_updated', True),
-        ('2.50', data_utils.rand_name("type_updated"), None, None),
-        ('2.50', None, 'description_updated', None),
-        ('2.50', None, None, True),
-        ('2.50', None, None, False),
-        (LATEST_MICROVERSION, data_utils.rand_name("type_updated"),
-         'description_updated', True),
-        (LATEST_MICROVERSION, data_utils.rand_name("type_updated"),
-         None, None),
-        (LATEST_MICROVERSION, None, 'description_updated', None),
-        (LATEST_MICROVERSION, None, None, True),
-        (LATEST_MICROVERSION, None, None, False),
+        (f'{LATEST_MICROVERSION}_name', LATEST_MICROVERSION,
+         data_utils.rand_name("type_updated"), None, None),
+        (f'{LATEST_MICROVERSION}_description', LATEST_MICROVERSION, None,
+         'description_updated', None),
+        (f'{LATEST_MICROVERSION}_public', LATEST_MICROVERSION, None, None,
+         True),
+        (LATEST_MICROVERSION, LATEST_MICROVERSION, None, None, False),
     )
-    @ddt.unpack
     def test_share_type_create_update(self, version, st_name,
                                       st_description, st_is_public):
         name = data_utils.rand_name("tempest-manila")
diff --git a/manila_tempest_tests/tests/api/admin/test_shares_actions.py b/manila_tempest_tests/tests/api/admin/test_shares_actions.py
index 29c7e04..7ad6b1e 100644
--- a/manila_tempest_tests/tests/api/admin/test_shares_actions.py
+++ b/manila_tempest_tests/tests/api/admin/test_shares_actions.py
@@ -164,8 +164,8 @@
         # verify response
         self.assertGreater(len(shares), 0)
         for share in shares:
-            self.assertDictContainsSubset(
-                filters['metadata'], share['metadata'])
+            self.assertLessEqual(filters['metadata'].items(),
+                                 share['metadata'].items())
         if CONF.share.capability_create_share_from_snapshot_support:
             self.assertFalse(self.shares[1]['id'] in [s['id'] for s in shares])
 
@@ -203,7 +203,8 @@
                 )
             extra_specs = self.shares_client.get_share_type_extra_specs(
                 st_id)['extra_specs']
-            self.assertDictContainsSubset(filters["extra_specs"], extra_specs)
+            self.assertLessEqual(filters["extra_specs"].items(),
+                                 extra_specs.items())
 
     @decorators.idempotent_id('76fbe8ba-f1d3-4446-b9b8-55617762a2c7')
     @tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND)
diff --git a/manila_tempest_tests/tests/api/base.py b/manila_tempest_tests/tests/api/base.py
index c5282d9..2db2353 100755
--- a/manila_tempest_tests/tests/api/base.py
+++ b/manila_tempest_tests/tests/api/base.py
@@ -705,6 +705,31 @@
         return replica
 
     @classmethod
+    def create_backup_wait_for_active(cls, share_id, client=None,
+                                      cleanup_in_class=False, cleanup=True,
+                                      version=CONF.share.max_api_microversion):
+        client = client or cls.shares_v2_client
+        backup_name = data_utils.rand_name('Backup')
+        backup_options = CONF.share.driver_assisted_backup_test_driver_options
+        backup = client.create_share_backup(
+            share_id,
+            name=backup_name,
+            backup_options=backup_options)['share_backup']
+        resource = {
+            "type": "share_backup",
+            "id": backup["id"],
+            "client": client,
+        }
+        if cleanup:
+            if cleanup_in_class:
+                cls.class_resources.insert(0, resource)
+            else:
+                cls.method_resources.insert(0, resource)
+        waiters.wait_for_resource_status(client, backup["id"], "available",
+                                         resource_name='share_backup')
+        return client.get_share_backup(backup['id'])['share_backup']
+
+    @classmethod
     def delete_share_replica(cls, replica_id, client=None,
                              version=CONF.share.max_api_microversion):
         client = client or cls.shares_v2_client
@@ -792,6 +817,30 @@
         return security_service
 
     @classmethod
+    def create_resource_lock(cls, resource_id, resource_type='share',
+                             resource_action='delete', lock_reason=None,
+                             client=None, version=LATEST_MICROVERSION,
+                             cleanup_in_class=True):
+        lock_reason = lock_reason or "locked by tempest tests"
+        client = client or cls.shares_v2_client
+
+        lock = client.create_resource_lock(resource_id,
+                                           resource_type,
+                                           resource_action=resource_action,
+                                           lock_reason=lock_reason,
+                                           version=version)['resource_lock']
+        resource = {
+            "type": "resource_lock",
+            "id": lock["id"],
+            "client": client,
+        }
+        if cleanup_in_class:
+            cls.class_resources.insert(0, resource)
+        else:
+            cls.method_resources.insert(0, resource)
+        return lock
+
+    @classmethod
     def update_share_type(cls, share_type_id, name=None,
                           is_public=None, description=None,
                           client=None):
@@ -895,6 +944,9 @@
                     elif res["type"] == "share_replica":
                         client.delete_share_replica(res_id)
                         client.wait_for_resource_deletion(replica_id=res_id)
+                    elif res["type"] == "share_backup":
+                        client.delete_share_backup(res_id)
+                        client.wait_for_resource_deletion(backup_id=res_id)
                     elif res["type"] == "share_network_subnet":
                         sn_id = res["extra_params"]["share_network_id"]
                         client.delete_subnet(sn_id, res_id)
@@ -904,6 +956,8 @@
                     elif res["type"] == "quotas":
                         user_id = res.get('user_id')
                         client.reset_quotas(res_id, user_id=user_id)
+                    elif res["type"] == "resource_lock":
+                        client.delete_resource_lock(res_id)
                     else:
                         LOG.warning("Provided unsupported resource type for "
                                     "cleanup '%s'. Skipping.", res["type"])
@@ -991,7 +1045,8 @@
     def allow_access(self, share_id, client=None, access_type=None,
                      access_level='rw', access_to=None, metadata=None,
                      version=LATEST_MICROVERSION, status='active',
-                     raise_rule_in_error_state=True, cleanup=True):
+                     raise_rule_in_error_state=True, lock_visibility=False,
+                     lock_deletion=False, cleanup=True):
 
         client = client or self.shares_v2_client
         a_type, a_to = utils.get_access_rule_data_from_config(
@@ -1004,8 +1059,15 @@
             'access_to': access_to,
             'access_level': access_level
         }
+        delete_kwargs = (
+            {'unrestrict': True} if lock_deletion else {}
+        )
         if client is self.shares_v2_client:
             kwargs.update({'metadata': metadata, 'version': version})
+        if lock_visibility:
+            kwargs.update({'lock_visibility': True})
+        if lock_deletion:
+            kwargs.update({'lock_deletion': True})
 
         rule = client.create_access_rule(share_id, **kwargs)['access']
         waiters.wait_for_resource_status(
@@ -1016,7 +1078,9 @@
             self.addCleanup(
                 client.wait_for_resource_deletion, rule_id=rule['id'],
                 share_id=share_id, version=version)
-            self.addCleanup(client.delete_access_rule, share_id, rule['id'])
+            self.addCleanup(
+                client.delete_access_rule, share_id, rule['id'],
+                **delete_kwargs)
         return rule
 
 
diff --git a/manila_tempest_tests/tests/api/test_access_rules_metadata_negative.py b/manila_tempest_tests/tests/api/test_access_rules_metadata_negative.py
index 98a1361..c212095 100644
--- a/manila_tempest_tests/tests/api/test_access_rules_metadata_negative.py
+++ b/manila_tempest_tests/tests/api/test_access_rules_metadata_negative.py
@@ -20,6 +20,7 @@
 from testtools import testcase as tc
 
 from manila_tempest_tests.common import constants
+from manila_tempest_tests.common import waiters
 from manila_tempest_tests.tests.api import base
 from manila_tempest_tests import utils
 
@@ -67,6 +68,9 @@
         cls.access = cls.shares_v2_client.create_access_rule(
             cls.share["id"], cls.access_type, cls.access_to,
             'rw', metadata={u"key1": u"value1"})['access']
+        waiters.wait_for_resource_status(
+            cls.shares_v2_client, cls.share["id"], "active",
+            resource_name='access_rule', rule_id=cls.access["id"])
 
     @decorators.idempotent_id('d2d41db8-ae00-4641-a5ec-499cee1877f1')
     @tc.attr(base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND)
diff --git a/manila_tempest_tests/tests/api/test_backup.py b/manila_tempest_tests/tests/api/test_backup.py
new file mode 100644
index 0000000..40c48ac
--- /dev/null
+++ b/manila_tempest_tests/tests/api/test_backup.py
@@ -0,0 +1,120 @@
+# Copyright 2024 Cloudification GmbH
+# 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.
+
+from tempest import config
+from tempest.lib.common.utils import data_utils
+from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
+from testtools import testcase as tc
+
+from manila_tempest_tests.common import waiters
+from manila_tempest_tests.tests.api import base
+from manila_tempest_tests import utils
+
+
+CONF = config.CONF
+_MIN_SUPPORTED_MICROVERSION = '2.80'
+
+
+class ShareBackupTest(base.BaseSharesMixedTest):
+
+    @classmethod
+    def skip_checks(cls):
+        super(ShareBackupTest, cls).skip_checks()
+        if not CONF.share.run_driver_assisted_backup_tests:
+            raise cls.skipException("Share backup tests are disabled.")
+        utils.check_skip_if_microversion_not_supported(
+            _MIN_SUPPORTED_MICROVERSION)
+
+    def setUp(self):
+        super(ShareBackupTest, self).setUp()
+        extra_specs = {
+            'snapshot_support': True,
+            'mount_snapshot_support': True,
+        }
+        share_type = self.create_share_type(extra_specs=extra_specs)
+        share = self.create_share(self.shares_v2_client.share_protocol,
+                                  share_type_id=share_type['id'])
+        self.share_id = share["id"]
+
+    @decorators.idempotent_id('12c36c97-faf4-4fec-9a9b-7cff0d2035cd')
+    @tc.attr(base.TAG_POSITIVE, base.TAG_BACKEND)
+    def test_create_share_backup(self):
+        backup = self.create_backup_wait_for_active(self.share_id)
+
+        # Verify backup create API response, we use the configured max API
+        # version to make this call
+        expected_keys = ["id", "share_id", "status",
+                         "availability_zone", "created_at", "updated_at",
+                         "size", "progress", "restore_progress",
+                         "name", "description"]
+        if utils.is_microversion_ge(CONF.share.max_api_microversion, '2.85'):
+            expected_keys.append("backup_type")
+
+        # Strict key check
+        actual_backup = self.shares_v2_client.get_share_backup(
+            backup['id'])['share_backup']
+        actual_keys = actual_backup.keys()
+        self.assertEqual(backup['id'], actual_backup['id'])
+        self.assertEqual(set(expected_keys), set(actual_keys))
+
+    @decorators.idempotent_id('34c36c97-faf4-4fec-9a9b-7cff0d2035cd')
+    @tc.attr(base.TAG_POSITIVE, base.TAG_BACKEND)
+    def test_delete_share_backup(self):
+        backup = self.create_backup_wait_for_active(
+            self.share_id, cleanup=False)
+
+        # Delete share backup
+        self.shares_v2_client.delete_share_backup(backup['id'])
+        self.shares_v2_client.wait_for_resource_deletion(
+            backup_id=backup['id'])
+        self.assertRaises(
+            lib_exc.NotFound,
+            self.shares_v2_client.get_share_backup,
+            backup['id'])
+
+    @decorators.idempotent_id('56c36c97-faf4-4fec-9a9b-7cff0d2035cd')
+    @tc.attr(base.TAG_POSITIVE, base.TAG_BACKEND)
+    def test_restore_share_backup(self):
+        backup = self.create_backup_wait_for_active(self.share_id)
+
+        # Restore share backup
+        restore = self.shares_v2_client.restore_share_backup(
+            backup['id'])['restore']
+        waiters.wait_for_resource_status(
+            self.shares_v2_client, backup['id'], 'available',
+            resource_name='share_backup')
+
+        self.assertEqual(restore['share_id'], self.share_id)
+
+    @decorators.idempotent_id('78c36c97-faf4-4fec-9a9b-7cff0d2035cd')
+    @tc.attr(base.TAG_POSITIVE, base.TAG_BACKEND)
+    def test_update_share_backup(self):
+        backup = self.create_backup_wait_for_active(self.share_id)
+
+        # Update share backup name
+        backup_name2 = data_utils.rand_name('Backup')
+        backup = self.shares_v2_client.update_share_backup(
+            backup['id'], name=backup_name2)['share_backup']
+        updated_backup = self.shares_v2_client.get_share_backup(
+            backup['id'])['share_backup']
+        self.assertEqual(backup_name2, updated_backup['name'])
+
+    @decorators.idempotent_id('19c36c97-faf4-4fec-9a9b-7cff0d2045af')
+    @tc.attr(base.TAG_POSITIVE, base.TAG_BACKEND)
+    def test_list_share_backups(self):
+        self.create_backup_wait_for_active(self.share_id)
+        backups = self.shares_v2_client.list_share_backups()
+        self.assertEqual(1, len(backups))
diff --git a/manila_tempest_tests/tests/api/test_backup_negative.py b/manila_tempest_tests/tests/api/test_backup_negative.py
new file mode 100644
index 0000000..743c195
--- /dev/null
+++ b/manila_tempest_tests/tests/api/test_backup_negative.py
@@ -0,0 +1,153 @@
+# Copyright 2024 Cloudification GmbH
+# 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.
+
+from tempest import config
+from tempest.lib.common.utils import data_utils
+from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
+from testtools import testcase as tc
+
+from manila_tempest_tests.common import waiters
+from manila_tempest_tests.tests.api import base
+from manila_tempest_tests import utils
+
+
+CONF = config.CONF
+_MIN_SUPPORTED_MICROVERSION = '2.80'
+
+
+class ShareBackupNegativeTest(base.BaseSharesMixedTest):
+
+    @classmethod
+    def skip_checks(cls):
+        super(ShareBackupNegativeTest, cls).skip_checks()
+        if not CONF.share.run_driver_assisted_backup_tests:
+            raise cls.skipException("Share backup tests are disabled.")
+        utils.check_skip_if_microversion_not_supported(
+            _MIN_SUPPORTED_MICROVERSION)
+
+    def setUp(self):
+        super(ShareBackupNegativeTest, self).setUp()
+        extra_specs = {
+            'snapshot_support': True,
+            'mount_snapshot_support': True,
+        }
+        share_type = self.create_share_type(extra_specs=extra_specs)
+        share = self.create_share(self.shares_v2_client.share_protocol,
+                                  share_type_id=share_type['id'])
+        self.share_id = share["id"]
+        self.backup_options = (
+            CONF.share.driver_assisted_backup_test_driver_options)
+
+    @decorators.idempotent_id('58c36c97-faf4-4fec-9a9b-7cff0d2035ab')
+    @tc.attr(base.TAG_NEGATIVE, base.TAG_BACKEND)
+    def test_create_backup_when_share_is_in_backup_creating_state(self):
+        backup_name1 = data_utils.rand_name('Backup')
+        backup1 = self.shares_v2_client.create_share_backup(
+            self.share_id,
+            name=backup_name1,
+            backup_options=self.backup_options)['share_backup']
+
+        # try create backup when share state is busy
+        backup_name2 = data_utils.rand_name('Backup')
+        self.assertRaises(lib_exc.BadRequest,
+                          self.shares_v2_client.create_share_backup,
+                          self.share_id,
+                          name=backup_name2,
+                          backup_options=self.backup_options)
+        waiters.wait_for_resource_status(
+            self.shares_v2_client, backup1['id'], "available",
+            resource_name='share_backup')
+
+        # delete the share backup
+        self.shares_v2_client.delete_share_backup(backup1['id'])
+        self.shares_v2_client.wait_for_resource_deletion(
+            backup_id=backup1['id'])
+
+    @decorators.idempotent_id('58c36c97-faf4-4fec-9a9b-7cff0d2012ab')
+    @tc.attr(base.TAG_NEGATIVE, base.TAG_BACKEND)
+    def test_create_backup_when_share_is_in_error_state(self):
+        self.admin_shares_v2_client.reset_state(self.share_id,
+                                                status='error')
+
+        # try create backup when share is not available
+        backup_name = data_utils.rand_name('Backup')
+        self.assertRaises(lib_exc.BadRequest,
+                          self.shares_v2_client.create_share_backup,
+                          self.share_id,
+                          name=backup_name,
+                          backup_options=self.backup_options)
+
+        self.admin_shares_v2_client.reset_state(self.share_id,
+                                                status='available')
+
+    @decorators.idempotent_id('58c36c97-faf4-4fec-9a9b-7cff0d2012de')
+    @tc.attr(base.TAG_NEGATIVE, base.TAG_BACKEND)
+    def test_create_backup_when_share_has_snapshots(self):
+        self.create_snapshot_wait_for_active(self.share_id,
+                                             cleanup_in_class=False)
+
+        # try create backup when share has snapshots
+        backup_name = data_utils.rand_name('Backup')
+        self.assertRaises(lib_exc.BadRequest,
+                          self.shares_v2_client.create_share_backup,
+                          self.share_id,
+                          name=backup_name,
+                          backup_options=self.backup_options)
+
+    @decorators.idempotent_id('58c12c97-faf4-4fec-9a9b-7cff0d2012de')
+    @tc.attr(base.TAG_NEGATIVE, base.TAG_BACKEND)
+    def test_delete_backup_when_backup_is_not_available(self):
+        backup = self.create_backup_wait_for_active(self.share_id)
+        self.admin_shares_v2_client.reset_state_share_backup(
+            backup['id'], status='creating')
+
+        # try delete backup when share backup is not available
+        self.assertRaises(lib_exc.BadRequest,
+                          self.shares_v2_client.delete_share_backup,
+                          backup['id'])
+
+        self.admin_shares_v2_client.reset_state_share_backup(
+            backup['id'], status='available')
+
+    @decorators.idempotent_id('58c56c97-faf4-4fec-9a9b-7cff0d2012de')
+    @tc.attr(base.TAG_NEGATIVE, base.TAG_BACKEND)
+    def test_restore_backup_when_share_is_not_available(self):
+        backup = self.create_backup_wait_for_active(self.share_id)
+        self.admin_shares_v2_client.reset_state(self.share_id,
+                                                status='error')
+
+        # try restore backup when share is not available
+        self.assertRaises(lib_exc.BadRequest,
+                          self.shares_v2_client.restore_share_backup,
+                          backup['id'])
+
+        self.admin_shares_v2_client.reset_state(self.share_id,
+                                                status='available')
+
+    @decorators.idempotent_id('58c12998-faf4-4fec-9a9b-7cff0d2012de')
+    @tc.attr(base.TAG_NEGATIVE, base.TAG_BACKEND)
+    def test_restore_backup_when_backup_is_not_available(self):
+        backup = self.create_backup_wait_for_active(self.share_id)
+        self.admin_shares_v2_client.reset_state_share_backup(
+            backup['id'], status='creating')
+
+        # try restore backup when backup is not available
+        self.assertRaises(lib_exc.BadRequest,
+                          self.shares_v2_client.restore_share_backup,
+                          backup['id'])
+
+        self.admin_shares_v2_client.reset_state_share_backup(
+            backup['id'], status='available')
diff --git a/manila_tempest_tests/tests/api/test_metadata.py b/manila_tempest_tests/tests/api/test_metadata.py
index bd73963..862de50 100644
--- a/manila_tempest_tests/tests/api/test_metadata.py
+++ b/manila_tempest_tests/tests/api/test_metadata.py
@@ -33,12 +33,6 @@
 
     def _verify_share_metadata(self, share, md):
 
-        # get metadata of share
-        metadata = self.shares_v2_client.get_metadata(share["id"])['metadata']
-
-        # verify metadata
-        self.assertEqual(md, metadata)
-
         # verify metadata items
         for key in md:
             get_value = self.shares_v2_client.get_metadata_item(
@@ -82,7 +76,8 @@
         # verify deletion of metadata
         get_metadata = self.shares_v2_client.get_metadata(share["id"])[
             'metadata']
-        self.assertEmpty(get_metadata)
+        for key in md.keys():
+            self.assertNotIn(key, list(get_metadata.keys()))
 
     @decorators.idempotent_id('4e5f8159-62b6-4d5c-f729-d8b1f029d7de')
     @tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND)
@@ -117,7 +112,8 @@
         # verify deletion of metadata
         get_metadata = self.shares_v2_client.get_metadata(
             share["id"])['metadata']
-        self.assertEmpty(get_metadata)
+        for key in md.keys():
+            self.assertNotIn(key, list(get_metadata.keys()))
 
     @decorators.idempotent_id('2ec70ba5-050b-3b17-c862-c149e53543c0')
     @tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND)
@@ -150,7 +146,8 @@
         # verify deletion of metadata
         get_metadata = self.shares_v2_client.get_metadata(
             share["id"])['metadata']
-        self.assertEmpty(get_metadata)
+        for key in md.keys():
+            self.assertNotIn(key, list(get_metadata.keys()))
 
     @decorators.idempotent_id('c94851f4-2559-4712-9297-9912db1da7ff')
     @tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND)
@@ -228,7 +225,7 @@
 
         body_get = self.shares_v2_client.get_metadata(
             self.share["id"])['metadata']
-        self.assertEqual(data, body_get)
+        self.assertEqual(data["k"], body_get["k"])
 
     @decorators.idempotent_id('5eff5619-b7cd-42f1-85e0-47d3d47098dd')
     @tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND)
@@ -240,7 +237,9 @@
 
         body_get = self.shares_v2_client.get_metadata(
             self.share["id"])['metadata']
-        self.assertEqual(data, body_get)
+        body_get_keys = list(body_get.keys())
+        self.assertIn(max_key, body_get_keys)
+        self.assertEqual(data[max_key], body_get[max_key])
 
     @decorators.idempotent_id('44a572f1-6b5c-49d0-8f2e-1583ec3428d8')
     @tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND)
@@ -251,7 +250,7 @@
 
         body_get = self.shares_v2_client.get_metadata(
             self.share["id"])['metadata']
-        self.assertEqual(data, body_get)
+        self.assertEqual(data["key"], body_get["key"])
 
     @decorators.idempotent_id('694d95e1-ba8c-49fc-a888-6f9f0d51d77d')
     @tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND)
@@ -263,4 +262,4 @@
 
         body_get = self.shares_v2_client.get_metadata(
             self.share["id"])['metadata']
-        self.assertEqual(data, body_get)
+        self.assertEqual(max_value, body_get["key"])
diff --git a/manila_tempest_tests/tests/api/test_replication_export_locations.py b/manila_tempest_tests/tests/api/test_replication_export_locations.py
index 2227290..bff8d2d 100644
--- a/manila_tempest_tests/tests/api/test_replication_export_locations.py
+++ b/manila_tempest_tests/tests/api/test_replication_export_locations.py
@@ -196,6 +196,9 @@
         """Validates exports from the replica export locations APIs"""
         el_summary_keys = ['id', 'path', 'replica_state',
                            'availability_zone', 'preferred']
+        if utils.is_microversion_ge(LATEST_MICROVERSION, '2.87'):
+            el_summary_keys += ['metadata']
+
         el_detail_keys = el_summary_keys + ['created_at', 'updated_at']
         share, replica, expected_primary_exports, expected_replica_exports = (
             self._create_share_and_replica_get_exports()
diff --git a/manila_tempest_tests/tests/api/test_resource_locks.py b/manila_tempest_tests/tests/api/test_resource_locks.py
new file mode 100644
index 0000000..4f88d72
--- /dev/null
+++ b/manila_tempest_tests/tests/api/test_resource_locks.py
@@ -0,0 +1,291 @@
+# 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 datetime
+
+from oslo_utils import timeutils
+from oslo_utils import uuidutils
+from tempest import config
+from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
+
+from manila_tempest_tests.tests.api import base
+from manila_tempest_tests import utils
+
+CONF = config.CONF
+
+LOCKS_MIN_API_VERSION = '2.81'
+
+RESOURCE_LOCK_FIELDS = {
+    'id',
+    'resource_id',
+    'resource_action',
+    'resource_type',
+    'user_id',
+    'project_id',
+    'lock_context',
+    'created_at',
+    'updated_at',
+    'lock_reason',
+    'links',
+}
+
+
+class ResourceLockCRUTest(base.BaseSharesMixedTest):
+
+    @classmethod
+    def skip_checks(cls):
+        super(ResourceLockCRUTest, cls).skip_checks()
+        utils.check_skip_if_microversion_not_supported(LOCKS_MIN_API_VERSION)
+
+    @classmethod
+    def resource_setup(cls):
+        super(ResourceLockCRUTest, cls).resource_setup()
+        # create share type
+        share_type = cls.create_share_type()
+        cls.share_type_id = share_type['id']
+
+        # create share and place a "delete" lock on it
+        cls.share = cls.create_share(share_type_id=cls.share_type_id)
+        cls.lock = cls.create_resource_lock(cls.share['id'])
+
+    @decorators.attr(type=[base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND])
+    @decorators.idempotent_id('f3d162a6-2ab4-433b-b8e7-6bf4f0bb6b0e')
+    def test_list_resource_locks(self):
+        locks = self.shares_v2_client.list_resource_locks()['resource_locks']
+        self.assertIsInstance(locks, list)
+        self.assertIn(self.lock['id'], [x['id'] for x in locks])
+        lock = locks[0]
+        self.assertEqual(RESOURCE_LOCK_FIELDS, set(lock.keys()))
+
+    @decorators.attr(type=[base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND])
+    @decorators.idempotent_id('72cc0d43-f676-4dd8-8a93-faa71608de98')
+    def test_list_resource_locks_sorted_and_paginated(self):
+        lock_2 = self.create_resource_lock(self.share['id'],
+                                           cleanup_in_class=False)
+        lock_3 = self.create_resource_lock(self.share['id'],
+                                           cleanup_in_class=False)
+
+        expected_order = [self.lock['id'], lock_2['id']]
+
+        filters = {'sort_key': 'created_at', 'sort_dir': 'asc', 'limit': 2}
+        body = self.shares_v2_client.list_resource_locks(filters=filters)
+        # tempest/lib/common/rest_client.py's _parse_resp checks
+        # for number of keys in response's dict, if there is only single
+        # key, it returns directly this key, otherwise it returns
+        # parsed body. If limit param is used, then API returns
+        # multiple keys in response ('resource_locks' and
+        # 'resource_lock_links')
+        locks = body['resource_locks']
+        self.assertIsInstance(locks, list)
+        actual_order = [x['id'] for x in locks]
+        self.assertEqual(2, len(actual_order))
+        self.assertNotIn(lock_3['id'], actual_order)
+        self.assertEqual(expected_order, actual_order)
+
+    @decorators.attr(type=[base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND])
+    @decorators.idempotent_id('22831edc-9d99-432d-a0b6-85af8853db98')
+    def test_list_resource_locks_filtered(self):
+        # Filter by resource_id, resource_action, lock_reason_like,
+        # created_since, created_before
+        share_2 = self.create_share(share_type_id=self.share_type_id)
+        share_1_lock_2 = self.create_resource_lock(
+            self.share['id'],
+            lock_reason="clemson tigers rule",
+            cleanup_in_class=False)
+        share_2_lock = self.create_resource_lock(share_2['id'],
+                                                 cleanup_in_class=False)
+
+        # filter by resource_type
+        expected_locks = sorted([
+            self.lock['id'],
+            share_1_lock_2['id'],
+            share_2_lock['id']
+        ])
+        actual_locks = self.shares_v2_client.list_resource_locks(
+            filters={'resource_type': 'share'})['resource_locks']
+        self.assertEqual(expected_locks,
+                         sorted([lock['id'] for lock in actual_locks]))
+
+        # filter by resource_id
+        expected_locks = sorted([self.lock['id'], share_1_lock_2['id']])
+        actual_locks = self.shares_v2_client.list_resource_locks(
+            filters={'resource_id': self.share['id']})['resource_locks']
+        self.assertEqual(expected_locks,
+                         sorted([lock['id'] for lock in actual_locks]))
+
+        # filter by inexact lock reason
+        actual_locks = self.shares_v2_client.list_resource_locks(
+            filters={'lock_reason~': "clemson"})['resource_locks']
+        self.assertEqual([share_1_lock_2['id']],
+                         [lock['id'] for lock in actual_locks])
+
+        # timestamp filters
+        created_at_1 = timeutils.parse_strtime(self.lock['created_at'])
+        created_at_2 = timeutils.parse_strtime(share_2_lock['created_at'])
+        time_1 = created_at_1 - datetime.timedelta(seconds=1)
+        time_2 = created_at_2 - datetime.timedelta(microseconds=1)
+        filters_1 = {'created_since': str(time_1)}
+
+        # should return all resource locks created by this test including
+        # self.lock
+        actual_locks = self.shares_v2_client.list_resource_locks(
+            filters=filters_1)['resource_locks']
+        actual_lock_ids = [lock['id'] for lock in actual_locks]
+        self.assertGreaterEqual(len(actual_lock_ids), 3)
+        self.assertIn(self.lock['id'], actual_lock_ids)
+        self.assertIn(share_1_lock_2['id'], actual_lock_ids)
+
+        for lock in actual_locks:
+            time_diff_with_created_since = timeutils.delta_seconds(
+                time_1, timeutils.parse_strtime(lock['created_at']))
+            self.assertGreaterEqual(time_diff_with_created_since, 0)
+
+        filters_2 = {
+            'created_since': str(time_1),
+            'created_before': str(time_2),
+        }
+
+        actual_locks = self.shares_v2_client.list_resource_locks(
+            filters=filters_2)['resource_locks']
+        self.assertIsInstance(actual_locks, list)
+        actual_lock_ids = [lock['id'] for lock in actual_locks]
+        self.assertGreaterEqual(len(actual_lock_ids), 2)
+        self.assertIn(self.lock['id'], actual_lock_ids)
+        self.assertIn(share_1_lock_2['id'], actual_lock_ids)
+        self.assertNotIn(share_2_lock['id'], actual_lock_ids)
+
+    @decorators.attr(type=[base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND])
+    @decorators.idempotent_id('8cbf7331-f3a1-4c7b-ab1e-f8b938bf135e')
+    def test_get_resource_lock(self):
+        lock = self.shares_v2_client.get_resource_lock(
+            self.lock['id'])['resource_lock']
+
+        self.assertEqual(set(RESOURCE_LOCK_FIELDS), set(lock.keys()))
+        self.assertTrue(uuidutils.is_uuid_like(lock['id']))
+        self.assertEqual('share', lock['resource_type'])
+        self.assertEqual(self.share['id'], lock['resource_id'])
+        self.assertEqual('delete', lock['resource_action'])
+        self.assertEqual('user', lock['lock_context'])
+        self.assertEqual(self.shares_v2_client.user_id, lock['user_id'])
+        self.assertEqual(self.shares_v2_client.project_id, lock['project_id'])
+
+    @decorators.attr(type=[base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND])
+    @decorators.idempotent_id('a7f0fb6a-05ac-4afa-b8d9-04d20549bbd1')
+    def test_create_resource_lock(self):
+        # testing lock creation by a different user in the same project
+        project = self.os_admin.projects_client.show_project(
+            self.shares_v2_client.project_id)['project']
+        new_user_client = self.create_user_and_get_client(project)
+
+        lock = self.create_resource_lock(
+            self.share['id'],
+            client=new_user_client.shares_v2_client,
+            cleanup_in_class=False)
+
+        self.assertEqual(set(RESOURCE_LOCK_FIELDS), set(lock.keys()))
+        self.assertTrue(uuidutils.is_uuid_like(lock['id']))
+        self.assertEqual('share', lock['resource_type'])
+        self.assertEqual(self.share['id'], lock['resource_id'])
+        self.assertEqual('delete', lock['resource_action'])
+        self.assertEqual('user', lock['lock_context'])
+        self.assertEqual(new_user_client.shares_v2_client.user_id,
+                         lock['user_id'])
+        self.assertEqual(self.shares_v2_client.project_id, lock['project_id'])
+
+        # testing lock creation by admin
+        lock = self.create_resource_lock(
+            self.share['id'],
+            client=self.admin_shares_v2_client,
+            cleanup_in_class=False)
+        self.assertEqual('admin', lock['lock_context'])
+
+    @decorators.attr(type=[base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND])
+    @decorators.idempotent_id('d7b51cde-ff4f-45ce-a237-401e8be5b4e5')
+    def test_update_resource_lock(self):
+        lock = self.shares_v2_client.update_resource_lock(
+            self.lock['id'], lock_reason="new lock reason")['resource_lock']
+
+        # update is synchronous
+        self.assertEqual("new lock reason", lock['lock_reason'])
+
+        # verify get
+        lock = self.shares_v2_client.get_resource_lock(lock['id'])
+        self.assertEqual("new lock reason",
+                         lock['resource_lock']['lock_reason'])
+
+
+class ResourceLockDeleteTest(base.BaseSharesMixedTest):
+
+    @classmethod
+    def skip_checks(cls):
+        super(ResourceLockDeleteTest, cls).skip_checks()
+        utils.check_skip_if_microversion_not_supported(LOCKS_MIN_API_VERSION)
+
+    @classmethod
+    def resource_setup(cls):
+        super(ResourceLockDeleteTest, cls).resource_setup()
+        cls.share_type_id = cls.create_share_type()['id']
+
+    @decorators.idempotent_id('835fd617-4600-40a0-9ba1-40e5e0097b01')
+    @decorators.attr(type=[base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND])
+    def test_delete_lock(self):
+        share = self.create_share(share_type_id=self.share_type_id)
+        lock_1 = self.create_resource_lock(share['id'], cleanup_in_class=False)
+        lock_2 = self.create_resource_lock(share['id'], cleanup_in_class=False)
+
+        locks = self.shares_v2_client.list_resource_locks(
+            filters={'resource_id': share['id']})['resource_locks']
+        self.assertEqual(sorted([lock_1['id'], lock_2['id']]),
+                         sorted([lock['id'] for lock in locks]))
+
+        self.shares_v2_client.delete_resource_lock(lock_1['id'])
+        locks = self.shares_v2_client.list_resource_locks(
+            filters={'resource_id': share['id']})['resource_locks']
+        self.assertEqual(1, len(locks))
+        self.assertIn(lock_2['id'], [lock['id'] for lock in locks])
+
+    @decorators.idempotent_id('a96e70c7-0afe-4335-9abc-4b45ef778bd7')
+    @decorators.attr(type=[base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND])
+    def test_delete_locked_resource(self):
+        share = self.create_share(share_type_id=self.share_type_id)
+        lock_1 = self.create_resource_lock(share['id'], cleanup_in_class=False)
+        lock_2 = self.create_resource_lock(share['id'], cleanup_in_class=False)
+
+        # share can't be deleted when a lock exists
+        self.assertRaises(lib_exc.Forbidden,
+                          self.shares_v2_client.delete_share,
+                          share['id'])
+
+        # admin can't do this either
+        self.assertRaises(lib_exc.Forbidden,
+                          self.admin_shares_v2_client.delete_share,
+                          share['id'])
+        # "the force" shouldn't work either
+        self.assertRaises(lib_exc.Forbidden,
+                          self.admin_shares_v2_client.delete_share,
+                          share['id'],
+                          params={'force': True})
+
+        self.shares_v2_client.delete_resource_lock(lock_1['id'])
+
+        # there's at least one lock, share deletion should still fail
+        self.assertRaises(lib_exc.Forbidden,
+                          self.shares_v2_client.delete_share,
+                          share['id'])
+
+        self.shares_v2_client.delete_resource_lock(lock_2['id'])
+
+        # locks are gone, share deletion should be possible
+        self.shares_v2_client.delete_share(share['id'])
+        self.shares_v2_client.wait_for_resource_deletion(
+            share_id=share["id"])
diff --git a/manila_tempest_tests/tests/api/test_resource_locks_negative.py b/manila_tempest_tests/tests/api/test_resource_locks_negative.py
new file mode 100644
index 0000000..9501d6c
--- /dev/null
+++ b/manila_tempest_tests/tests/api/test_resource_locks_negative.py
@@ -0,0 +1,127 @@
+#    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 tempest import config
+from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
+
+from manila_tempest_tests.tests.api import base
+from manila_tempest_tests import utils
+
+CONF = config.CONF
+
+LOCKS_MIN_API_VERSION = '2.81'
+
+
+class ResourceLockNegativeTestAPIOnly(base.BaseSharesMixedTest):
+
+    @classmethod
+    def skip_checks(cls):
+        super(ResourceLockNegativeTestAPIOnly, cls).skip_checks()
+        utils.check_skip_if_microversion_not_supported(LOCKS_MIN_API_VERSION)
+
+    @decorators.attr(type=[base.TAG_NEGATIVE, base.TAG_API])
+    @decorators.idempotent_id('dd978cf7-1622-49e8-a6c8-3da4ac6c6f86')
+    def test_create_resource_lock_invalid_resource(self):
+        self.assertRaises(
+            lib_exc.BadRequest,
+            self.shares_v2_client.create_resource_lock,
+            'invalid-share-id',
+            'share'
+        )
+
+    @decorators.attr(type=[base.TAG_NEGATIVE, base.TAG_API])
+    @decorators.idempotent_id('d5600bdc-72c8-43fd-9900-c112aa6c87fa')
+    def test_delete_resource_lock_invalid(self):
+        self.assertRaises(
+            lib_exc.NotFound,
+            self.shares_v2_client.delete_resource_lock,
+            'invalid-lock-id'
+        )
+
+
+class ResourceLockNegativeTestWithShares(base.BaseSharesMixedTest):
+    @classmethod
+    def skip_checks(cls):
+        super(ResourceLockNegativeTestWithShares, cls).skip_checks()
+        utils.check_skip_if_microversion_not_supported(LOCKS_MIN_API_VERSION)
+
+    @classmethod
+    def resource_setup(cls):
+        super(ResourceLockNegativeTestWithShares, cls).resource_setup()
+        share_type = cls.create_share_type()
+        cls.share = cls.create_share(share_type_id=share_type['id'])
+        cls.user_project = cls.os_admin.projects_client.show_project(
+            cls.shares_v2_client.project_id)['project']
+
+    @decorators.attr(type=[base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND])
+    @decorators.idempotent_id('658297a8-d675-471d-8a19-3d9e9af3a352')
+    def test_create_resource_lock_invalid_resource_action(self):
+        self.assertRaises(
+            lib_exc.BadRequest,
+            self.shares_v2_client.create_resource_lock,
+            self.share['id'],
+            'share',
+            resource_action='invalid-action'
+        )
+
+    @decorators.attr(type=[base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND])
+    @decorators.idempotent_id('0057b3e7-c250-492d-805b-e355dff954ed')
+    def test_create_resource_lock_invalid_lock_reason_too_long(self):
+        self.assertRaises(
+            lib_exc.BadRequest,
+            self.shares_v2_client.create_resource_lock,
+            self.share['id'],
+            'share',
+            resource_action='delete',
+            lock_reason='invalid' * 150,
+        )
+
+    @decorators.attr(type=[base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND])
+    @decorators.idempotent_id('a2db3d29-b42f-4c0b-b484-afd32f91f747')
+    def test_update_resource_lock_invalid_param(self):
+        lock = self.create_resource_lock(self.share['id'])
+        self.assertRaises(
+            lib_exc.BadRequest,
+            self.shares_v2_client.update_resource_lock,
+            lock['id'],
+            resource_action='invalid-action'
+        )
+        self.assertRaises(
+            lib_exc.BadRequest,
+            self.shares_v2_client.update_resource_lock,
+            lock['id'],
+            lock_reason='invalid' * 150,
+        )
+
+    @decorators.attr(type=[base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND])
+    @decorators.idempotent_id('45b12120-0fc3-461f-8776-fdb92e599394')
+    def test_update_resource_lock_created_by_different_user(self):
+        lock = self.create_resource_lock(self.share['id'])
+        new_user = self.create_user_and_get_client(project=self.user_project)
+        self.assertRaises(
+            lib_exc.Forbidden,
+            new_user.shares_v2_client.update_resource_lock,
+            lock['id'],
+            lock_reason="I shouldn't be able to do this",
+        )
+
+    @decorators.attr(type=[base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND])
+    @decorators.idempotent_id('00a8ef2b-8769-4aad-aefc-43fc579492f7')
+    def test_delete_resource_lock_created_by_different_user(self):
+        lock = self.create_resource_lock(self.share['id'])
+        new_user = self.create_user_and_get_client(project=self.user_project)
+        self.assertRaises(
+            lib_exc.Forbidden,
+            new_user.shares_v2_client.delete_resource_lock,
+            lock['id'],
+        )
diff --git a/manila_tempest_tests/tests/api/test_revert_to_snapshot.py b/manila_tempest_tests/tests/api/test_revert_to_snapshot.py
index dca394a..5eb6915 100644
--- a/manila_tempest_tests/tests/api/test_revert_to_snapshot.py
+++ b/manila_tempest_tests/tests/api/test_revert_to_snapshot.py
@@ -109,8 +109,8 @@
     @decorators.idempotent_id('196f2bc5-e13a-4730-ac51-61e339068a06')
     @tc.attr(base.TAG_POSITIVE, base.TAG_BACKEND)
     @ddt.data(
-        *{constants.REVERT_TO_SNAPSHOT_MICROVERSION,
-          CONF.share.max_api_microversion}
+        *utils.deduplicate([constants.REVERT_TO_SNAPSHOT_MICROVERSION,
+                            CONF.share.max_api_microversion])
     )
     def test_revert_to_latest_snapshot(self, version):
         snapshot = self.create_snapshot_wait_for_active(self.share['id'],
@@ -129,8 +129,8 @@
     @decorators.idempotent_id('09bd9942-7ef9-4d24-b2dd-f83bdda27b50')
     @tc.attr(base.TAG_POSITIVE, base.TAG_BACKEND)
     @ddt.data(
-        *{constants.REVERT_TO_SNAPSHOT_MICROVERSION,
-          CONF.share.max_api_microversion}
+        *utils.deduplicate([constants.REVERT_TO_SNAPSHOT_MICROVERSION,
+                            CONF.share.max_api_microversion])
     )
     def test_revert_to_previous_snapshot(self, version):
         snapshot1 = self.create_snapshot_wait_for_active(
@@ -157,8 +157,8 @@
     @tc.skipUnless(CONF.share.run_replication_tests,
                    'Replication tests are disabled.')
     @ddt.data(
-        *{constants.REVERT_TO_SNAPSHOT_MICROVERSION,
-          CONF.share.max_api_microversion}
+        *utils.deduplicate([constants.REVERT_TO_SNAPSHOT_MICROVERSION,
+                            CONF.share.max_api_microversion])
     )
     def test_revert_to_replicated_snapshot(self, version):
         """Test reverting to a replicated snapshot."""
diff --git a/manila_tempest_tests/tests/api/test_revert_to_snapshot_negative.py b/manila_tempest_tests/tests/api/test_revert_to_snapshot_negative.py
index 96d7f04..95e2c72 100644
--- a/manila_tempest_tests/tests/api/test_revert_to_snapshot_negative.py
+++ b/manila_tempest_tests/tests/api/test_revert_to_snapshot_negative.py
@@ -80,8 +80,8 @@
     @decorators.idempotent_id('21dd8561-8913-42a2-a95c-74b536964c94')
     @tc.attr(base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND)
     @ddt.data(
-        *{constants.REVERT_TO_SNAPSHOT_MICROVERSION,
-          CONF.share.max_api_microversion}
+        *utils.deduplicate([constants.REVERT_TO_SNAPSHOT_MICROVERSION,
+                            CONF.share.max_api_microversion])
     )
     def test_revert_to_second_latest_snapshot(self, version):
         snapshot1 = self.create_snapshot_wait_for_active(
@@ -98,8 +98,8 @@
     @decorators.idempotent_id('7360ee16-ac7d-46ce-9c81-251d64fb7434')
     @tc.attr(base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND)
     @ddt.data(
-        *{constants.REVERT_TO_SNAPSHOT_MICROVERSION,
-          CONF.share.max_api_microversion}
+        *utils.deduplicate([constants.REVERT_TO_SNAPSHOT_MICROVERSION,
+                            CONF.share.max_api_microversion])
     )
     def test_revert_to_error_snapshot(self, version):
         snapshot = self.create_snapshot_wait_for_active(self.share['id'],
@@ -118,8 +118,8 @@
     @decorators.idempotent_id('108a451e-a8e9-450f-8f75-53883d58c6be')
     @tc.attr(base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND)
     @ddt.data(
-        *{constants.REVERT_TO_SNAPSHOT_MICROVERSION,
-          CONF.share.max_api_microversion}
+        *utils.deduplicate([constants.REVERT_TO_SNAPSHOT_MICROVERSION,
+                            CONF.share.max_api_microversion])
     )
     def test_revert_error_share_to_snapshot(self, version):
         snapshot = self.create_snapshot_wait_for_active(self.share['id'],
@@ -143,8 +143,8 @@
     @decorators.idempotent_id('29024057-dbbd-4cf6-a796-207dfbd4c3ff')
     @tc.attr(base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND)
     @ddt.data(
-        *{constants.REVERT_TO_SNAPSHOT_MICROVERSION,
-          CONF.share.max_api_microversion}
+        *utils.deduplicate([constants.REVERT_TO_SNAPSHOT_MICROVERSION,
+                            CONF.share.max_api_microversion])
     )
     def test_revert_to_missing_snapshot(self, version):
         self.assertRaises(exceptions.BadRequest,
@@ -156,8 +156,8 @@
     @decorators.idempotent_id('ef706fad-5ac4-41dc-af81-5aa0331560cf')
     @tc.attr(base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND)
     @ddt.data(
-        *{constants.REVERT_TO_SNAPSHOT_MICROVERSION,
-          CONF.share.max_api_microversion}
+        *utils.deduplicate([constants.REVERT_TO_SNAPSHOT_MICROVERSION,
+                            CONF.share.max_api_microversion])
     )
     def test_revert_to_invalid_snapshot(self, version):
         snapshot = self.create_snapshot_wait_for_active(
diff --git a/manila_tempest_tests/tests/api/test_rules.py b/manila_tempest_tests/tests/api/test_rules.py
index 925b725..5f39852 100644
--- a/manila_tempest_tests/tests/api/test_rules.py
+++ b/manila_tempest_tests/tests/api/test_rules.py
@@ -28,6 +28,7 @@
 
 CONF = config.CONF
 LATEST_MICROVERSION = CONF.share.max_api_microversion
+RESTRICTED_RULES_VERSION = '2.82'
 
 
 def _create_delete_ro_access_rule(self, version):
@@ -444,6 +445,10 @@
         cls.share_type = cls.create_share_type()
         cls.share_type_id = cls.share_type['id']
         cls.share = cls.create_share(share_type_id=cls.share_type_id)
+        cls.user_project = cls.os_admin.projects_client.show_project(
+            cls.shares_v2_client.project_id)['project']
+        cls.new_user = cls.create_user_and_get_client(
+            project=cls.user_project)
 
     @decorators.idempotent_id('c52e95cc-d6ea-4d02-9b52-cd7c1913dfff')
     @tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND)
@@ -519,6 +524,92 @@
         msg = "expected id lists %s times in rule list" % (len(gen))
         self.assertEqual(1, len(gen), msg)
 
+    @decorators.idempotent_id('3bca373e-e54f-49e1-8789-99a383cf4df3')
+    @tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND)
+    @utils.skip_if_microversion_not_supported(RESTRICTED_RULES_VERSION)
+    @ddt.data(
+        *itertools.product(utils.deduplicate(
+            ["2.81", CONF.share.max_api_microversion]), (True, False))
+    )
+    @ddt.unpack
+    def test_list_restricted_rules_from_other_user(
+            self, older_version, lock_visibility):
+        # create rule
+        self.allow_access(
+            self.share["id"], client=self.shares_v2_client,
+            access_type=self.access_type, access_to=self.access_to,
+            lock_visibility=lock_visibility, lock_deletion=True)
+
+        rule = (
+            self.shares_v2_client.list_access_rules(
+                self.share["id"])["access_list"][0])
+
+        rules = self.new_user.shares_v2_client.list_access_rules(
+            self.share['id'])['access_list']
+        rules_get_lower_version = (
+            self.new_user.shares_v2_client.list_access_rules(
+                self.share['id'], version=older_version)['access_list'])
+        expected_access_to = '******' if lock_visibility else self.access_to
+        expected_access_key = (
+            '******' if lock_visibility else rule['access_key'])
+
+        # verify values
+        rule_latest_rules_api = [r for r in rules if r['id'] == rule['id']][0]
+        rule_lower_version_rules_api = [r for r in rules_get_lower_version
+                                        if r['id'] == rule['id']][0]
+        self.assertEqual(
+            expected_access_to, rule_latest_rules_api["access_to"])
+        self.assertEqual(
+            expected_access_to,
+            rule_lower_version_rules_api['access_to'])
+        if self.access_type == 'cephx':
+            self.assertEqual(expected_access_key,
+                             rule_latest_rules_api['access_key'])
+            self.assertEqual(
+                expected_access_key,
+                rule_lower_version_rules_api['access_key'])
+
+    @decorators.idempotent_id('4829265a-eb32-400d-91a0-be06ce31a2ef')
+    @tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND)
+    def test_admin_listing_restricted_rules(self):
+        utils.check_skip_if_microversion_not_supported(
+            RESTRICTED_RULES_VERSION)
+
+        # create rule
+        self.allow_access(
+            self.share["id"], client=self.shares_v2_client,
+            access_type=self.access_type, access_to=self.access_to,
+            lock_visibility=True)
+
+        rules = self.admin_shares_v2_client.list_access_rules(
+            self.share["id"])['access_list']
+
+        # ensure admin can see rules even if locked
+        self.assertEqual(self.access_to, rules[0]["access_to"])
+        if self.access_type == 'cephx':
+            self.assertIsNotNone(rules[0]['access_key'])
+            self.assertFalse(rules[0]['access_key'] == '******')
+        else:
+            self.assertIsNone(rules[0]['access_key'])
+
+    @decorators.idempotent_id('00202c6c-b4c7-4fa6-933a-562fbffde405')
+    @tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND)
+    def test_admin_delete_restricted_rules(self):
+        utils.check_skip_if_microversion_not_supported(
+            RESTRICTED_RULES_VERSION)
+
+        # create rule
+        rule = self.allow_access(
+            self.share["id"], client=self.shares_v2_client,
+            access_type=self.access_type, access_to=self.access_to,
+            lock_visibility=True, lock_deletion=True, cleanup=False)
+
+        self.admin_shares_v2_client.delete_access_rule(
+            self.share['id'], rule['id'], unrestrict=True)
+
+        self.shares_v2_client.wait_for_resource_deletion(
+            rule_id=rule['id'], share_id=self.share['id'])
+
     @decorators.idempotent_id('b77bcbda-9754-48f0-9be6-79341ad1af64')
     @tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND)
     @ddt.data(*utils.deduplicate(['1.0', '2.9', '2.27', '2.28',
diff --git a/manila_tempest_tests/tests/api/test_rules_negative.py b/manila_tempest_tests/tests/api/test_rules_negative.py
index a4b53f5..84b416d 100644
--- a/manila_tempest_tests/tests/api/test_rules_negative.py
+++ b/manila_tempest_tests/tests/api/test_rules_negative.py
@@ -28,6 +28,7 @@
 
 CONF = config.CONF
 LATEST_MICROVERSION = CONF.share.max_api_microversion
+RESTRICTED_RULES_VERSION = '2.82'
 
 
 @ddt.ddt
@@ -60,6 +61,11 @@
             # create snapshot
             cls.snap = cls.create_snapshot_wait_for_active(cls.share["id"])
 
+        cls.user_project = cls.os_admin.projects_client.show_project(
+            cls.shares_v2_client.project_id)['project']
+        cls.new_user = cls.create_user_and_get_client(
+            project=cls.user_project)
+
     @decorators.idempotent_id('16781b45-d2bb-4891-aa97-c28c0769d5bd')
     @tc.attr(base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND)
     @ddt.data('1.2.3.256',
@@ -170,6 +176,93 @@
                           self.admin_client.create_access_rule,
                           share["id"], access_type, access_to)
 
+    @decorators.idempotent_id('478d3c84-b0ea-41c8-a860-e87f182d991c')
+    @tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND)
+    def test_deny_access_unrestrict_other_user_rule(self):
+        utils.check_skip_if_microversion_not_supported(
+            RESTRICTED_RULES_VERSION)
+
+        access_type, access_to = utils.get_access_rule_data_from_config(
+            self.protocol)
+
+        # create rule
+        rule = self.allow_access(
+            self.share["id"], client=self.shares_v2_client,
+            access_type=access_type, access_to=access_to,
+            lock_visibility=True, lock_deletion=True)
+
+        self.assertRaises(
+            lib_exc.Forbidden,
+            self.new_user.shares_v2_client.delete_access_rule,
+            self.share['id'],
+            rule['id'],
+            unrestrict=True
+        )
+
+    @decorators.idempotent_id('c107b0b7-7a3e-4114-af64-ca8fe6e836c9')
+    @tc.attr(base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND)
+    @ddt.data(True, False)
+    def test_deny_access_without_unrestrict_as_owner_user(self, same_user):
+        utils.check_skip_if_microversion_not_supported(
+            RESTRICTED_RULES_VERSION)
+        access_type, access_to = utils.get_access_rule_data_from_config(
+            self.protocol)
+
+        # create rule
+        rule = self.allow_access(
+            self.share["id"], client=self.shares_v2_client,
+            access_type=access_type, access_to=access_to,
+            lock_visibility=True, lock_deletion=True)
+
+        client = (
+            self.shares_v2_client
+            if same_user else self.new_user.shares_v2_client
+        )
+        self.assertRaises(
+            lib_exc.Forbidden,
+            client.delete_access_rule,
+            self.share['id'],
+            rule['id'])
+        self.assertRaises(
+            lib_exc.Forbidden,
+            client.delete_access_rule,
+            self.share['id'],
+            rule['id'],
+            version='2.81'
+        )
+
+    @decorators.idempotent_id('f5b9e7c9-7e6b-4918-a1c4-e03c8d82c46a')
+    @tc.attr(base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND)
+    def test_allow_access_multiple_visibility_locks_not_allowed(self):
+        utils.check_skip_if_microversion_not_supported(
+            RESTRICTED_RULES_VERSION)
+        access_type, access_to = utils.get_access_rule_data_from_config(
+            self.protocol)
+
+        # create rule
+        rule = self.allow_access(
+            self.share["id"], client=self.shares_v2_client,
+            access_type=access_type, access_to=access_to,
+            lock_visibility=True, lock_deletion=True)
+
+        self.assertRaises(
+            lib_exc.Conflict,
+            self.shares_v2_client.create_resource_lock,
+            rule['id'],
+            "access_rule",
+            resource_action="show",
+            lock_reason="locked for testing"
+        )
+
+        self.assertRaises(
+            lib_exc.Conflict,
+            self.new_user.shares_v2_client.create_resource_lock,
+            rule['id'],
+            "access_rule",
+            resource_action="show",
+            lock_reason="locked for testing"
+        )
+
 
 @ddt.ddt
 class ShareIpRulesForCIFSNegativeTest(ShareIpRulesForNFSNegativeTest):
@@ -429,12 +522,13 @@
     @tc.attr(base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND)
     def test_can_apply_new_cephx_rules_when_one_is_in_error_state(self):
         # Create share on "primary" tenant
-        share_primary = self.create_share()
+        share_primary = self.create_share(share_type_id=self.share_type_id)
         # Add access rule to "Joe" by "primary" user
         self.allow_access(share_primary['id'], access_to='Joe')
 
         # Create share on "alt" tenant
-        share_alt = self.create_share(client=self.alt_shares_v2_client)
+        share_alt = self.create_share(
+            client=self.alt_shares_v2_client, share_type_id=self.share_type_id)
         # Add access rule to "Joe" by "alt" user.
         # Rule must be set to "error" status.
         rule1 = self.allow_access(share_alt['id'],
diff --git a/manila_tempest_tests/tests/api/test_security_services.py b/manila_tempest_tests/tests/api/test_security_services.py
index 85ed5fb..1e34295 100644
--- a/manila_tempest_tests/tests/api/test_security_services.py
+++ b/manila_tempest_tests/tests/api/test_security_services.py
@@ -166,7 +166,7 @@
         self.service_names = ["ldap", "kerberos", "active_directory"]
         for ss_name in self.service_names:
             ss = self.create_security_service(ss_name, **data)
-            self.assertDictContainsSubset(data, ss)
+            self.assertLessEqual(data.items(), ss.items())
             self.assertEqual(ss_name, ss["type"])
             self.shares_client.delete_security_service(ss["id"])
 
@@ -188,9 +188,9 @@
             get = self.shares_client.get_security_service(
                 ss["id"])['security_service']
 
-        self.assertDictContainsSubset(data, ss)
+        self.assertLessEqual(data.items(), ss.items())
         self.assertEqual(with_ou, 'ou' in ss)
-        self.assertDictContainsSubset(data, get)
+        self.assertLessEqual(data.items(), get.items())
         self.assertEqual(with_ou, 'ou' in get)
 
     @decorators.idempotent_id('84d47747-13c8-4ab9-9fc4-a43fbb29ad18')
@@ -198,7 +198,7 @@
     def test_update_security_service(self):
         data = utils.generate_security_service_data()
         ss = self.create_security_service(**data)
-        self.assertDictContainsSubset(data, ss)
+        self.assertLessEqual(data.items(), ss.items())
 
         upd_data = utils.generate_security_service_data()
         updated = self.shares_client.update_security_service(
@@ -206,8 +206,8 @@
 
         get = self.shares_client.get_security_service(
             ss["id"])['security_service']
-        self.assertDictContainsSubset(upd_data, updated)
-        self.assertDictContainsSubset(upd_data, get)
+        self.assertLessEqual(upd_data.items(), updated.items())
+        self.assertLessEqual(upd_data.items(), get.items())
 
         if utils.is_microversion_ge(CONF.share.max_api_microversion, '2.44'):
             # update again with ou
@@ -217,8 +217,8 @@
 
             get_ou = self.shares_v2_client.get_security_service(
                 ss["id"])['security_service']
-            self.assertDictContainsSubset(upd_data_ou, updated_ou)
-            self.assertDictContainsSubset(upd_data_ou, get_ou)
+            self.assertLessEqual(upd_data_ou.items(), updated_ou.items())
+            self.assertLessEqual(upd_data_ou.items(), get_ou.items())
 
     @decorators.idempotent_id('c3c04992-da11-4677-9098-eff3f4231a4b')
     @tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND)
@@ -259,7 +259,7 @@
         }
         updated = self.shares_client.update_security_service(
             ss["id"], **update_data)['security_service']
-        self.assertDictContainsSubset(update_data, updated)
+        self.assertLessEqual(update_data.items(), updated.items())
 
     @decorators.idempotent_id('8d9af272-df89-470d-9ff8-92ba774c9fff')
     @tc.attr(base.TAG_POSITIVE, base.TAG_API)
diff --git a/manila_tempest_tests/tests/api/test_security_services_mapping.py b/manila_tempest_tests/tests/api/test_security_services_mapping.py
index 6ce9a0a..4c2d272 100644
--- a/manila_tempest_tests/tests/api/test_security_services_mapping.py
+++ b/manila_tempest_tests/tests/api/test_security_services_mapping.py
@@ -36,13 +36,13 @@
         self.sn = self.create_share_network(client=self.cl,
                                             add_security_services=False,
                                             **data)
-        self.assertDictContainsSubset(data, self.sn)
+        self.assertLessEqual(data.items(), self.sn.items())
 
         # create security service
         data = utils.generate_security_service_data()
 
         self.ss = self.create_security_service(client=self.cl, **data)
-        self.assertDictContainsSubset(data, self.ss)
+        self.assertLessEqual(data.items(), self.ss.items())
 
         # Add security service to share network
         self.cl.add_sec_service_to_share_network(self.sn["id"], self.ss["id"])
diff --git a/manila_tempest_tests/tests/api/test_security_services_mapping_negative.py b/manila_tempest_tests/tests/api/test_security_services_mapping_negative.py
index 96506ce..f5b06e8 100644
--- a/manila_tempest_tests/tests/api/test_security_services_mapping_negative.py
+++ b/manila_tempest_tests/tests/api/test_security_services_mapping_negative.py
@@ -150,14 +150,14 @@
 
         sn = self.create_share_network(client=self.cl,
                                        add_security_services=False, **data)
-        self.assertDictContainsSubset(data, sn)
+        self.assertLessEqual(data.items(), sn.items())
 
         # create security services with same type
         security_services = []
         for i in range(2):
             data = utils.generate_security_service_data()
             ss = self.create_security_service(client=self.cl, **data)
-            self.assertDictContainsSubset(data, ss)
+            self.assertLessEqual(data.items(), ss.items())
             security_services.insert(i, ss)
 
         # Add security service to share network
@@ -177,13 +177,13 @@
 
         sn = self.create_share_network(client=self.cl,
                                        add_security_services=False, **data)
-        self.assertDictContainsSubset(data, sn)
+        self.assertLessEqual(data.items(), sn.items())
 
         # create security service
         data = utils.generate_security_service_data()
 
         ss = self.create_security_service(client=self.cl, **data)
-        self.assertDictContainsSubset(data, ss)
+        self.assertLessEqual(data.items(), ss.items())
 
         # Add security service to share network
         self.cl.add_sec_service_to_share_network(sn["id"], ss["id"])
diff --git a/manila_tempest_tests/tests/api/test_security_services_negative.py b/manila_tempest_tests/tests/api/test_security_services_negative.py
index 23c7cc1..733247e 100644
--- a/manila_tempest_tests/tests/api/test_security_services_negative.py
+++ b/manila_tempest_tests/tests/api/test_security_services_negative.py
@@ -132,7 +132,7 @@
     def test_get_deleted_security_service(self):
         data = utils.generate_security_service_data()
         ss = self.create_security_service(**data)
-        self.assertDictContainsSubset(data, ss)
+        self.assertLessEqual(data.items(), ss.items())
 
         self.shares_client.delete_security_service(ss["id"])
 
diff --git a/manila_tempest_tests/tests/api/test_share_groups.py b/manila_tempest_tests/tests/api/test_share_groups.py
index ab6b9d1..dbc673c 100644
--- a/manila_tempest_tests/tests/api/test_share_groups.py
+++ b/manila_tempest_tests/tests/api/test_share_groups.py
@@ -401,6 +401,8 @@
         # Delete share group
         self.shares_v2_client.delete_share_group(
             share_group['id'], version=constants.MIN_SHARE_GROUP_MICROVERSION)
+        self.shares_v2_client.wait_for_resource_deletion(
+            share_group_id=share_group['id'])
 
         # Delete subnet
         self.shares_v2_client.delete_subnet(
diff --git a/manila_tempest_tests/tests/api/test_share_network_subnets.py b/manila_tempest_tests/tests/api/test_share_network_subnets.py
index 1ae5351..ec5c79e 100644
--- a/manila_tempest_tests/tests/api/test_share_network_subnets.py
+++ b/manila_tempest_tests/tests/api/test_share_network_subnets.py
@@ -81,7 +81,7 @@
         # Default subnet was created during share network creation
         self.assertIsNone(default_subnet['availability_zone'])
         # Match new subnet content
-        self.assertDictContainsSubset(data, created)
+        self.assertLessEqual(data.items(), created.items())
 
         self.assertEqual(sorted(keys), sorted(list(created.keys())))
 
@@ -109,7 +109,7 @@
             created['id'], share_network['id'])['share_network_subnet']
 
         # Asserts
-        self.assertDictContainsSubset(data, shown)
+        self.assertLessEqual(data.items(), shown.items())
 
         # Deletes the created subnet
         self.shares_v2_client.delete_subnet(share_network['id'],
diff --git a/manila_tempest_tests/tests/api/test_share_network_subnets_negative.py b/manila_tempest_tests/tests/api/test_share_network_subnets_negative.py
index 52b297e..0bb6a5c 100644
--- a/manila_tempest_tests/tests/api/test_share_network_subnets_negative.py
+++ b/manila_tempest_tests/tests/api/test_share_network_subnets_negative.py
@@ -131,7 +131,7 @@
         subnet = self.create_share_network_subnet(**data)
 
         # Make sure that the created subnet contains the data
-        self.assertDictContainsSubset(data, subnet)
+        self.assertLessEqual(data.items(), subnet.items())
 
         # Delete the given subnet
         self.shares_v2_client.delete_subnet(self.share_network_id,
diff --git a/manila_tempest_tests/tests/api/test_share_networks.py b/manila_tempest_tests/tests/api/test_share_networks.py
index f990566..ac87fd7 100644
--- a/manila_tempest_tests/tests/api/test_share_networks.py
+++ b/manila_tempest_tests/tests/api/test_share_networks.py
@@ -224,7 +224,7 @@
         # create share network
         created = self.shares_client.create_share_network(
             **data)['share_network']
-        self.assertDictContainsSubset(data, created)
+        self.assertLessEqual(data.items(), created.items())
 
         # Delete share_network
         self.shares_client.delete_share_network(created["id"])
@@ -237,7 +237,7 @@
         self.assertEqual('2002-02-02T00:00:00.000000', get['created_at'])
         data = self.data_sn_with_ldap_ss.copy()
         del data['created_at']
-        self.assertDictContainsSubset(data, get)
+        self.assertLessEqual(data.items(), get.items())
 
     @decorators.idempotent_id('1837fdd3-8068-4e88-bc50-9224498f84c0')
     @tc.attr(base.TAG_POSITIVE, base.TAG_API)
@@ -246,7 +246,7 @@
         updated = self.shares_client.update_share_network(
             self.sn_with_ldap_ss["id"],
             **update_data)['share_network']
-        self.assertDictContainsSubset(update_data, updated)
+        self.assertLessEqual(update_data.items(), updated.items())
 
     @decorators.idempotent_id('198a5c08-3aaf-4623-9720-95d33ebe3376')
     @tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND)
@@ -262,7 +262,7 @@
         updated = self.shares_client.update_share_network(
             self.shares_client.share_network_id,
             **update_dict)['share_network']
-        self.assertDictContainsSubset(update_dict, updated)
+        self.assertLessEqual(update_dict.items(), updated.items())
 
     @decorators.idempotent_id('7595a844-a28e-476c-89f1-4d3193ce9d5b')
     @tc.attr(base.TAG_POSITIVE, base.TAG_API)
@@ -272,14 +272,14 @@
 
         # create share network
         sn1 = self.shares_client.create_share_network(**data)['share_network']
-        self.assertDictContainsSubset(data, sn1)
+        self.assertLessEqual(data.items(), sn1.items())
 
         # Delete first share network
         self.shares_client.delete_share_network(sn1["id"])
 
         # create second share network with same data
         sn2 = self.shares_client.create_share_network(**data)['share_network']
-        self.assertDictContainsSubset(data, sn2)
+        self.assertLessEqual(data.items(), sn2.items())
 
         # Delete second share network
         self.shares_client.delete_share_network(sn2["id"])
@@ -292,11 +292,11 @@
 
         # create first share network
         sn1 = self.create_share_network(**data)
-        self.assertDictContainsSubset(data, sn1)
+        self.assertLessEqual(data.items(), sn1.items())
 
         # create second share network
         sn2 = self.create_share_network(**data)
-        self.assertDictContainsSubset(data, sn2)
+        self.assertLessEqual(data.items(), sn2.items())
 
     @decorators.idempotent_id('2dbf91da-04ae-4f9f-a7b9-0299c6b2e02c')
     @testtools.skipUnless(CONF.share.create_networks_when_multitenancy_enabled,
diff --git a/manila_tempest_tests/tests/api/test_share_networks_negative.py b/manila_tempest_tests/tests/api/test_share_networks_negative.py
index 94fa7b8..77866dd 100644
--- a/manila_tempest_tests/tests/api/test_share_networks_negative.py
+++ b/manila_tempest_tests/tests/api/test_share_networks_negative.py
@@ -90,7 +90,7 @@
     def test_try_get_deleted_share_network(self):
         data = utils.generate_share_network_data()
         sn = self.create_share_network(**data)
-        self.assertDictContainsSubset(data, sn)
+        self.assertLessEqual(data.items(), sn.items())
 
         self.shares_client.delete_share_network(sn["id"])
 
diff --git a/manila_tempest_tests/tests/api/test_share_transfers.py b/manila_tempest_tests/tests/api/test_share_transfers.py
new file mode 100644
index 0000000..56f8e0c
--- /dev/null
+++ b/manila_tempest_tests/tests/api/test_share_transfers.py
@@ -0,0 +1,107 @@
+# Copyright (C) 2022 China Telecom Digital Intelligence.
+# 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.
+
+from tempest import config
+from tempest.lib.common.utils import data_utils
+from tempest.lib import decorators
+from testtools import testcase as tc
+
+from manila_tempest_tests.common import constants
+from manila_tempest_tests.common import waiters
+from manila_tempest_tests.tests.api import base
+from manila_tempest_tests import utils
+
+CONF = config.CONF
+
+
+class ShareTransferTest(base.BaseSharesMixedTest):
+
+    @classmethod
+    def skip_checks(cls):
+        super(ShareTransferTest, cls).skip_checks()
+        utils.check_skip_if_microversion_not_supported(
+            constants.SHARE_TRANSFER_VERSION)
+        if CONF.share.multitenancy_enabled:
+            raise cls.skipException(
+                'Only for driver_handles_share_servers = False driver mode.')
+
+    @classmethod
+    def resource_setup(cls):
+        super(ShareTransferTest, cls).resource_setup()
+        # create share_type with dhss=False
+        extra_specs = cls.add_extra_specs_to_dict()
+        cls.share_type = cls.create_share_type(extra_specs=extra_specs)
+        cls.share_type_id = cls.share_type['id']
+
+    @decorators.idempotent_id('716e71a0-8265-4410-9170-08714095d9e8')
+    @tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND)
+    def test_create_and_delete_share_transfer(self):
+        # create share
+        share_name = data_utils.rand_name("tempest-share-name")
+        share = self.create_share(name=share_name,
+                                  share_type_id=self.share_type_id,
+                                  cleanup_in_class=False)
+
+        # create share transfer
+        transfer = self.shares_v2_client.create_share_transfer(
+            share['id'], name='tempest_share_transfer')['transfer']
+        waiters.wait_for_resource_status(
+            self.shares_client, share['id'], 'awaiting_transfer')
+
+        # check transfer exists and show transfer
+        transfer_show = self.shares_v2_client.get_share_transfer(
+            transfer['id'])['transfer']
+        self.assertEqual(transfer_show['name'], 'tempest_share_transfer')
+
+        # delete share transfer
+        self.shares_v2_client.delete_share_transfer(transfer['id'])
+        waiters.wait_for_resource_status(
+            self.shares_client, share['id'], 'available')
+
+        # check transfer not in transfer list
+        transfers = self.shares_v2_client.list_share_transfers()['transfers']
+        transfer_ids = [tf['id'] for tf in transfers]
+        self.assertNotIn(transfer['id'], transfer_ids)
+
+    @decorators.idempotent_id('3c2622ab-3368-4693-afb6-e60bd27e61ef')
+    @tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND)
+    def test_create_and_accept_share_transfer(self):
+        # create share
+        share_name = data_utils.rand_name("tempest-share-name")
+        share = self.create_share(name=share_name,
+                                  share_type_id=self.share_type_id)
+
+        # create share transfer
+        transfer = self.shares_v2_client.create_share_transfer(
+            share['id'])['transfer']
+        waiters.wait_for_resource_status(
+            self.shares_client, share['id'], 'awaiting_transfer')
+
+        # accept share transfer by alt project
+        self.alt_shares_v2_client.accept_share_transfer(transfer['id'],
+                                                        transfer['auth_key'])
+        waiters.wait_for_resource_status(
+            self.alt_shares_client, share['id'], 'available')
+
+        # check share in alt project
+        shares = self.alt_shares_v2_client.list_shares(
+            detailed=True)['shares']
+        share_ids = [sh['id'] for sh in shares] if shares else []
+        self.assertIn(share['id'], share_ids)
+
+        # delete the share
+        self.alt_shares_v2_client.delete_share(share['id'])
+        self.alt_shares_v2_client.wait_for_resource_deletion(
+            share_id=share["id"])
diff --git a/manila_tempest_tests/tests/api/test_share_transfers_negative.py b/manila_tempest_tests/tests/api/test_share_transfers_negative.py
new file mode 100644
index 0000000..0672459
--- /dev/null
+++ b/manila_tempest_tests/tests/api/test_share_transfers_negative.py
@@ -0,0 +1,137 @@
+# Copyright (C) 2022 China Telecom Digital Intelligence.
+# 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.
+
+from oslo_utils import uuidutils
+from tempest import config
+from tempest.lib.common.utils import data_utils
+from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
+from testtools import testcase as tc
+
+from manila_tempest_tests.common import constants
+from manila_tempest_tests.common import waiters
+from manila_tempest_tests.tests.api import base
+from manila_tempest_tests import utils
+
+CONF = config.CONF
+
+
+class ShareTransferNegativeTest(base.BaseSharesMixedTest):
+
+    @classmethod
+    def skip_checks(cls):
+        super(ShareTransferNegativeTest, cls).skip_checks()
+        utils.check_skip_if_microversion_not_supported(
+            constants.SHARE_TRANSFER_VERSION)
+        if CONF.share.multitenancy_enabled:
+            raise cls.skipException(
+                'Only for driver_handles_share_servers = False driver mode.')
+
+    @classmethod
+    def resource_setup(cls):
+        super(ShareTransferNegativeTest, cls).resource_setup()
+        # create share_type with dhss=False
+        extra_specs = cls.add_extra_specs_to_dict()
+        cls.share_type = cls.create_share_type(extra_specs=extra_specs)
+        cls.share_type_id = cls.share_type['id']
+
+    def _create_share_transfer(self, share):
+        transfer = self.shares_v2_client.create_share_transfer(
+            share['id'])['transfer']
+        waiters.wait_for_resource_status(
+            self.shares_client, share['id'], 'awaiting_transfer')
+        self.addCleanup(waiters.wait_for_resource_status, self.shares_client,
+                        share['id'], 'available')
+        self.addCleanup(self.shares_v2_client.delete_share_transfer,
+                        transfer['id'])
+        return transfer
+
+    @decorators.idempotent_id('baf66f62-253e-40dd-a6a9-109bc7613e52')
+    @tc.attr(base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND)
+    def test_show_transfer_of_other_tenants(self):
+        # create share
+        share_name = data_utils.rand_name("tempest-share-name")
+        share = self.create_share(
+            name=share_name,
+            share_type_id=self.share_type_id)
+
+        # create share transfer
+        transfer = self._create_share_transfer(share)
+
+        self.assertRaises(lib_exc.NotFound,
+                          self.alt_shares_v2_client.get_share_transfer,
+                          transfer['id'])
+
+    @decorators.idempotent_id('4b9e75b1-4ac6-4111-b09e-e6dacd0ac2c3')
+    @tc.attr(base.TAG_NEGATIVE, base.TAG_API)
+    def test_show_nonexistent_transfer(self):
+        self.assertRaises(lib_exc.NotFound,
+                          self.shares_v2_client.get_share_transfer,
+                          str(uuidutils.generate_uuid()))
+
+    @decorators.idempotent_id('b3e26356-5eb0-4f73-b5a7-d3594cc2f30e')
+    @tc.attr(base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND)
+    def test_delete_transfer_of_other_tenants(self):
+        # create share
+        share_name = data_utils.rand_name("tempest-share-name")
+        share = self.create_share(
+            name=share_name,
+            share_type_id=self.share_type_id)
+
+        # create share transfer
+        transfer = self._create_share_transfer(share)
+
+        self.assertRaises(lib_exc.NotFound,
+                          self.alt_shares_v2_client.delete_share_transfer,
+                          transfer['id'])
+
+    @decorators.idempotent_id('085d5971-fe6e-4497-93cb-f1eb176a10da')
+    @tc.attr(base.TAG_NEGATIVE, base.TAG_API)
+    def test_delete_nonexistent_transfer(self):
+        self.assertRaises(lib_exc.NotFound,
+                          self.shares_v2_client.delete_share_transfer,
+                          str(uuidutils.generate_uuid()))
+
+    @decorators.idempotent_id('cc7af032-0504-417e-8ab9-73b37bed7f85')
+    @tc.attr(base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND)
+    def test_accept_transfer_without_auth_key(self):
+        # create share
+        share_name = data_utils.rand_name("tempest-share-name")
+        share = self.create_share(
+            name=share_name,
+            share_type_id=self.share_type_id)
+
+        # create share transfer
+        transfer = self._create_share_transfer(share)
+
+        self.assertRaises(lib_exc.BadRequest,
+                          self.alt_shares_v2_client.accept_share_transfer,
+                          transfer['id'], "")
+
+    @decorators.idempotent_id('05a6a345-7609-421f-be21-d79041970674')
+    @tc.attr(base.TAG_NEGATIVE, base.TAG_API)
+    def test_accept_transfer_with_incorrect_auth_key(self):
+        # create share
+        share_name = data_utils.rand_name("tempest-share-name")
+        share = self.create_share(
+            name=share_name,
+            share_type_id=self.share_type_id)
+
+        # create share transfer
+        transfer = self._create_share_transfer(share)
+
+        self.assertRaises(lib_exc.BadRequest,
+                          self.alt_shares_v2_client.accept_share_transfer,
+                          transfer['id'], "incorrect_auth_key")
diff --git a/manila_tempest_tests/tests/api/test_share_types_negative.py b/manila_tempest_tests/tests/api/test_share_types_negative.py
index 046af45..af4815f 100644
--- a/manila_tempest_tests/tests/api/test_share_types_negative.py
+++ b/manila_tempest_tests/tests/api/test_share_types_negative.py
@@ -83,15 +83,17 @@
     @utils.skip_if_microversion_not_supported("2.50")
     @decorators.idempotent_id('4a22945c-8988-43a1-88c9-eb86e6abcd8e')
     @tc.attr(base.TAG_NEGATIVE, base.TAG_API)
-    @ddt.data(
-        ('2.50', '', None, None),
-        (LATEST_MICROVERSION, '', None, None),
-        ('2.50', None, None, 'not_bool'),
-        (LATEST_MICROVERSION, None, None, 'not_bool'),
-        ('2.50', None, generate_long_description(256), None),
-        (LATEST_MICROVERSION, None, generate_long_description(256), None),
+    @ddt.named_data(
+        ('2_50', '2.50', '', None, None),
+        (LATEST_MICROVERSION, LATEST_MICROVERSION, '', None, None),
+        ('2_50_bad_public', '2.50', None, None, 'not_bool'),
+        (f'{LATEST_MICROVERSION}_bad_public', LATEST_MICROVERSION, None, None,
+         'not_bool'),
+        ('2_50_description', '2.50', None, generate_long_description(256),
+         None),
+        (f'{LATEST_MICROVERSION}_description', LATEST_MICROVERSION, None,
+         generate_long_description(256), None),
     )
-    @ddt.unpack
     def test_share_type_update_bad_request(
             self, version, st_name, st_description, st_is_public):
         st_id = self.st['id']
diff --git a/manila_tempest_tests/tests/api/test_shares_actions.py b/manila_tempest_tests/tests/api/test_shares_actions.py
index 1b987ae..aa60d84 100644
--- a/manila_tempest_tests/tests/api/test_shares_actions.py
+++ b/manila_tempest_tests/tests/api/test_shares_actions.py
@@ -310,8 +310,8 @@
         # verify response
         self.assertGreater(len(shares), 0)
         for share in shares:
-            self.assertDictContainsSubset(
-                filters['metadata'], share['metadata'])
+            self.assertLessEqual(filters['metadata'].items(),
+                                 share['metadata'].items())
         if CONF.share.capability_create_share_from_snapshot_support:
             self.assertFalse(self.shares[1]['id'] in [s['id'] for s in shares])
 
diff --git a/manila_tempest_tests/tests/api/test_shares_actions_negative.py b/manila_tempest_tests/tests/api/test_shares_actions_negative.py
index 977d9a1..da85f2e 100644
--- a/manila_tempest_tests/tests/api/test_shares_actions_negative.py
+++ b/manila_tempest_tests/tests/api/test_shares_actions_negative.py
@@ -288,7 +288,6 @@
 
         self.assertEqual(0, len(shares))
 
-    @decorators.skip_because(bug='1914363')
     @decorators.idempotent_id('e8f857f1-ec32-4f81-9e09-26065891dc93')
     @tc.attr(base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND)
     def test_get_share_from_other_project(self):
diff --git a/manila_tempest_tests/tests/rbac/base.py b/manila_tempest_tests/tests/rbac/base.py
index 06759ff..d3c63de 100644
--- a/manila_tempest_tests/tests/rbac/base.py
+++ b/manila_tempest_tests/tests/rbac/base.py
@@ -129,6 +129,20 @@
         return share_group_type
 
     @classmethod
+    def create_share_group(cls, client, share_group_type_id, share_type_ids):
+        name = data_utils.rand_name('share-group')
+        share_group = client.create_share_group(
+            name=name, share_group_type_id=share_group_type_id,
+            share_type_ids=share_type_ids)['share_group']
+        waiters.wait_for_resource_status(
+            client, share_group['id'], 'available',
+            resource_name='share_group')
+        cls.addClassResourceCleanup(
+            cls.delete_resource, client,
+            share_group_id=share_group['id'])
+        return share_group
+
+    @classmethod
     def get_share_type(cls):
         return cls.shares_v2_client.get_default_share_type()['share_type']
 
diff --git a/manila_tempest_tests/tests/rbac/test_share_groups.py b/manila_tempest_tests/tests/rbac/test_share_groups.py
new file mode 100644
index 0000000..b09a5e4
--- /dev/null
+++ b/manila_tempest_tests/tests/rbac/test_share_groups.py
@@ -0,0 +1,474 @@
+# Copyright 2022 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 abc
+
+from tempest import config
+from tempest.lib.common.utils import data_utils
+from tempest.lib import decorators
+from tempest.lib import exceptions as lib_exc
+from testtools import testcase as tc
+
+from manila_tempest_tests.common import waiters
+from manila_tempest_tests.tests.api import base
+from manila_tempest_tests.tests.rbac import base as rbac_base
+
+CONF = config.CONF
+
+
+class ShareRbacShareGroupsTests(rbac_base.ShareRbacBaseTests,
+                                metaclass=abc.ABCMeta):
+
+    @classmethod
+    def skip_checks(cls):
+        super(ShareRbacShareGroupsTests, cls).skip_checks()
+        if cls.protocol not in CONF.share.enable_protocols:
+            message = "%s tests are disabled" % cls.protocol
+            raise cls.skipException(message)
+
+    @classmethod
+    def setup_clients(cls):
+        super(ShareRbacShareGroupsTests, cls).setup_clients()
+        cls.persona = getattr(cls, 'os_%s' % cls.credentials[0])
+        cls.client = cls.persona.share_v2.SharesV2Client()
+        cls.admin_shares_v2_client = (
+            cls.os_project_admin.share_v2.SharesV2Client())
+        cls.alt_project_share_v2_client = (
+            cls.os_project_alt_member.share_v2.SharesV2Client())
+
+    @classmethod
+    def resource_setup(cls):
+        super(ShareRbacShareGroupsTests, cls).resource_setup()
+        cls.share_type = cls.create_share_type()
+        cls.share_group_type = cls.create_share_group_type(
+            cls.share_type['id'])
+
+    def share_group(self, share_group_type_id, share_type_ids):
+        share_group = {}
+        share_group['name'] = data_utils.rand_name('share_group')
+        share_group['share_group_type_id'] = share_group_type_id
+        share_group['share_type_ids'] = [share_type_ids]
+        return share_group
+
+    @abc.abstractmethod
+    def test_get_share_group(self):
+        pass
+
+    @abc.abstractmethod
+    def test_list_share_groups(self):
+        pass
+
+    @abc.abstractmethod
+    def test_create_share_group(self):
+        pass
+
+    @abc.abstractmethod
+    def test_delete_share_group(self):
+        pass
+
+    @abc.abstractmethod
+    def test_force_delete_share_group(self):
+        pass
+
+    @abc.abstractmethod
+    def test_update_share_group(self):
+        pass
+
+    @abc.abstractmethod
+    def test_reset_share_group(self):
+        pass
+
+
+class TestProjectAdminTestsNFS(ShareRbacShareGroupsTests, base.BaseSharesTest):
+
+    credentials = ['project_admin', 'project_alt_member']
+    protocol = 'nfs'
+
+    @classmethod
+    def setup_clients(cls):
+        super(TestProjectAdminTestsNFS, cls).setup_clients()
+        project_member = cls.setup_user_client(
+            cls.persona, project_id=cls.persona.credentials.project_id)
+        cls.share_member_client = project_member.share_v2.SharesV2Client()
+
+    @tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND)
+    @decorators.idempotent_id('0de993c5-8389-4997-8f7f-345e27f563f1')
+    def test_get_share_group(self):
+        share_group = self.create_share_group(
+            self.share_member_client, self.share_group_type['id'],
+            [self.share_type['id']])
+        self.do_request(
+            'get_share_group', expected_status=200,
+            share_group_id=share_group['id'])
+
+        alt_share_group = self.create_share_group(
+            self.alt_project_share_v2_client, self.share_group_type['id'],
+            [self.share_type['id']])
+        self.do_request(
+            'get_share_group', expected_status=200,
+            share_group_id=alt_share_group['id'])
+
+    @tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND)
+    @decorators.idempotent_id('3b277a44-dcae-46da-a58c-f5281d8abc84')
+    def test_list_share_groups(self):
+        share_group = self.create_share_group(
+            self.share_member_client, self.share_group_type['id'],
+            [self.share_type['id']])
+        alt_share_group = self.create_share_group(
+            self.alt_project_share_v2_client, self.share_group_type['id'],
+            [self.share_type['id']])
+
+        params = {"all_tenants": 1}
+        share_group_list = self.do_request(
+            'list_share_groups', expected_status=200,
+            params=params)['share_groups']
+        share_group_id_list = [
+            s['id'] for s in share_group_list
+        ]
+
+        self.assertIn(share_group['id'], share_group_id_list)
+        self.assertIn(alt_share_group['id'], share_group_id_list)
+
+    @tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND)
+    @decorators.idempotent_id('d060996e-c5f2-4dff-820b-6892a096a425')
+    def test_create_share_group(self):
+        share_group = self.do_request(
+            'create_share_group', expected_status=202,
+            **self.share_group(self.share_group_type['id'],
+                               self.share_type['id']))['share_group']
+        waiters.wait_for_resource_status(
+            self.client, share_group['id'], 'available',
+            resource_name='share_group')
+        self.addCleanup(self.client.wait_for_resource_deletion,
+                        share_group_id=share_group['id'])
+        self.addCleanup(self.client.delete_share_group, share_group['id'])
+
+    @tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND)
+    @decorators.idempotent_id('ea6cbb78-057e-4fbc-86bf-125b033cb76f')
+    def test_delete_share_group(self):
+        share_group = self.create_share_group(
+            self.share_member_client, self.share_group_type['id'],
+            [self.share_type['id']])
+        self.do_request(
+            'delete_share_group', expected_status=202,
+            share_group_id=share_group['id'])
+        self.client.wait_for_resource_deletion(
+            share_group_id=share_group['id'])
+
+        alt_share_group = self.create_share_group(
+            self.alt_project_share_v2_client, self.share_group_type['id'],
+            [self.share_type['id']])
+        self.do_request(
+            'delete_share_group', expected_status=202,
+            share_group_id=alt_share_group['id'])
+        self.client.wait_for_resource_deletion(
+            share_group_id=alt_share_group['id'])
+
+    @tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND)
+    @decorators.idempotent_id('2cb00ffb-47e3-495e-853c-007752c9e679')
+    def test_force_delete_share_group(self):
+        share_group = self.create_share_group(
+            self.share_member_client, self.share_group_type['id'],
+            [self.share_type['id']])
+        self.do_request(
+            'share_group_force_delete', expected_status=202,
+            share_group_id=share_group['id'])
+        self.client.wait_for_resource_deletion(
+            share_group_id=share_group['id'])
+
+        alt_share_group = self.create_share_group(
+            self.alt_project_share_v2_client, self.share_group_type['id'],
+            [self.share_type['id']])
+        self.do_request(
+            'share_group_force_delete', expected_status=202,
+            share_group_id=alt_share_group['id'])
+        self.client.wait_for_resource_deletion(
+            share_group_id=alt_share_group['id'])
+
+    @tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND)
+    @decorators.idempotent_id('1bab40d5-bdba-4a23-9300-807fe513bf15')
+    def test_update_share_group(self):
+        share_group = self.create_share_group(
+            self.share_member_client, self.share_group_type['id'],
+            [self.share_type['id']])
+        name = data_utils.rand_name('rename_share')
+        self.do_request(
+            'update_share_group', expected_status=200,
+            share_group_id=share_group['id'], name=name)
+
+        alt_share_group = self.create_share_group(
+            self.share_member_client, self.share_group_type['id'],
+            [self.share_type['id']])
+        name = data_utils.rand_name('rename_share')
+        self.do_request(
+            'update_share_group', expected_status=200,
+            share_group_id=alt_share_group['id'], name=name)
+
+    @tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND)
+    @decorators.idempotent_id('069bc68e-6411-44b8-abe9-399885f0eee5')
+    def test_reset_share_group(self):
+        share_group = self.create_share_group(
+            self.share_member_client, self.share_group_type['id'],
+            [self.share_type['id']])
+        self.do_request(
+            'share_group_reset_state', expected_status=202,
+            share_group_id=share_group['id'], status='error')
+
+        alt_share_group = self.create_share_group(
+            self.share_member_client, self.share_group_type['id'],
+            [self.share_type['id']])
+        self.do_request(
+            'share_group_reset_state', expected_status=202,
+            share_group_id=alt_share_group['id'], status='error')
+
+
+class TestProjectMemberTestsNFS(ShareRbacShareGroupsTests,
+                                base.BaseSharesTest):
+
+    credentials = ['project_member', 'project_admin', 'project_alt_member']
+    protocol = 'nfs'
+
+    @tc.attr(base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND)
+    @decorators.idempotent_id('a29e1a68-220e-40fc-98ea-9092fd256d07')
+    def test_get_share_group(self):
+        share_client = getattr(self, 'share_member_client', self.client)
+        share_group = self.create_share_group(
+            share_client, self.share_group_type['id'], [self.share_type['id']])
+        self.do_request(
+            'get_share_group', expected_status=200,
+            share_group_id=share_group['id'])
+
+        alt_share_group = self.create_share_group(
+            self.alt_project_share_v2_client, self.share_group_type['id'],
+            [self.share_type['id']])
+        self.do_request(
+            'get_share_group', expected_status=lib_exc.NotFound,
+            share_group_id=alt_share_group['id'])
+
+    @tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND)
+    @decorators.idempotent_id('d9c04932-c47e-46e0-bfcf-79c2af32c4c7')
+    def test_list_share_groups(self):
+        share_client = getattr(self, 'share_member_client', self.client)
+        share_group = self.create_share_group(
+            share_client, self.share_group_type['id'], [self.share_type['id']])
+        alt_share_group = self.create_share_group(
+            self.alt_project_share_v2_client, self.share_group_type['id'],
+            [self.share_type['id']])
+
+        params = {"all_tenants": 1}
+        share_group_list = self.do_request(
+            'list_share_groups', expected_status=200,
+            params=params)['share_groups']
+        share_group_id_list = [
+            s['id'] for s in share_group_list
+        ]
+
+        self.assertIn(share_group['id'], share_group_id_list)
+        self.assertNotIn(alt_share_group['id'], share_group_id_list)
+
+    @tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND)
+    @decorators.idempotent_id('ebad2242-1fb5-4d99-9a5a-281c1944e03d')
+    def test_create_share_group(self):
+        share_group = self.do_request(
+            'create_share_group', expected_status=202,
+            **self.share_group(self.share_group_type['id'],
+                               self.share_type['id']))['share_group']
+        waiters.wait_for_resource_status(
+            self.client, share_group['id'], 'available',
+            resource_name='share_group')
+        self.addCleanup(self.client.wait_for_resource_deletion,
+                        share_group_id=share_group['id'])
+        self.addCleanup(self.client.delete_share_group, share_group['id'])
+
+    @tc.attr(base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND)
+    @decorators.idempotent_id('f5c243e4-5128-4a1c-9a15-8c9f0a44437e')
+    def test_delete_share_group(self):
+        share_group = self.create_share_group(
+            self.client, self.share_group_type['id'], [self.share_type['id']])
+        self.do_request(
+            'delete_share_group', expected_status=202,
+            share_group_id=share_group['id'])
+        self.client.wait_for_resource_deletion(
+            share_group_id=share_group['id'])
+
+        alt_share_group = self.create_share_group(
+            self.alt_project_share_v2_client, self.share_group_type['id'],
+            [self.share_type['id']])
+        self.do_request(
+            'delete_share_group', expected_status=lib_exc.NotFound,
+            share_group_id=alt_share_group['id'])
+
+    @tc.attr(base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND)
+    @decorators.idempotent_id('36a58d50-1257-479f-80a2-f9b7a00814e2')
+    def test_force_delete_share_group(self):
+        share_client = getattr(self, 'share_member_client', self.client)
+        share_group = self.create_share_group(
+            share_client, self.share_group_type['id'],
+            [self.share_type['id']])
+        self.do_request(
+            'share_group_force_delete', expected_status=lib_exc.Forbidden,
+            share_group_id=share_group['id'])
+
+        alt_share_group = self.create_share_group(
+            self.alt_project_share_v2_client, self.share_group_type['id'],
+            [self.share_type['id']])
+        self.do_request(
+            'share_group_force_delete', expected_status=lib_exc.Forbidden,
+            share_group_id=alt_share_group['id'])
+
+    @tc.attr(base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND)
+    @decorators.idempotent_id('cf9e34b6-6c04-4920-a811-2dbcf07ba14e')
+    def test_update_share_group(self):
+        share_group = self.create_share_group(
+            self.client, self.share_group_type['id'], [self.share_type['id']])
+        name = data_utils.rand_name('rename_share')
+        self.do_request(
+            'update_share_group', expected_status=200,
+            share_group_id=share_group['id'], name=name)
+
+        alt_share_group = self.create_share_group(
+            self.alt_project_share_v2_client, self.share_group_type['id'],
+            [self.share_type['id']])
+        name = data_utils.rand_name('rename_share')
+        self.do_request(
+            'update_share_group', expected_status=lib_exc.NotFound,
+            share_group_id=alt_share_group['id'], name=name)
+
+    @tc.attr(base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND)
+    @decorators.idempotent_id('2108c4cd-74e0-467f-823a-e44cf8686afa')
+    def test_reset_share_group(self):
+        share_client = getattr(self, 'share_member_client', self.client)
+        share_group = self.create_share_group(
+            share_client, self.share_group_type['id'],
+            [self.share_type['id']])
+        self.do_request(
+            'share_group_reset_state', expected_status=lib_exc.Forbidden,
+            share_group_id=share_group['id'], status='error')
+
+        alt_share_group = self.create_share_group(
+            self.alt_project_share_v2_client, self.share_group_type['id'],
+            [self.share_type['id']])
+        self.do_request(
+            'share_group_reset_state', expected_status=lib_exc.Forbidden,
+            share_group_id=alt_share_group['id'], status='error')
+
+
+class TestProjectReaderTestsNFS(TestProjectMemberTestsNFS):
+    """Test suite for basic share group operations by reader user
+
+    In order to test certain share operations we must create a share group
+    resource for this. Since reader user is limited in resources creation, we
+    are forced to use admin credentials, so we can test other share operations.
+    In this class we use admin user to create a member user within reader
+    project. That way we can perform a reader actions on this resource.
+    """
+
+    credentials = ['project_reader', 'project_admin', 'project_alt_member']
+
+    @classmethod
+    def setup_clients(cls):
+        super(TestProjectReaderTestsNFS, cls).setup_clients()
+        project_member = cls.setup_user_client(
+            cls.os_project_admin,
+            project_id=cls.persona.credentials.project_id)
+        cls.share_member_client = project_member.share_v2.SharesV2Client()
+
+    @tc.attr(base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND)
+    @decorators.idempotent_id('ec0ecbb0-5d45-4624-bb26-8b2e140e2ea9')
+    def test_get_share_group(self):
+        super(TestProjectReaderTestsNFS, self).test_get_share_group()
+
+    @tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND)
+    @decorators.idempotent_id('4ac87837-5bdf-4253-ab50-dd6efdcea285')
+    def test_list_share_groups(self):
+        super(TestProjectReaderTestsNFS, self).test_list_share_groups()
+
+    @tc.attr(base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND)
+    @decorators.idempotent_id('526dcd91-e789-48f8-b209-c384d77e5803')
+    def test_create_share_group(self):
+        self.do_request(
+            'create_share_group', expected_status=lib_exc.Forbidden,
+            **self.share_group(self.share_group_type['id'],
+                               self.share_type['id']))
+
+    @tc.attr(base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND)
+    @decorators.idempotent_id('fdf4d49e-a576-441f-9a3c-e2d58c0d8679')
+    def test_delete_share_group(self):
+        share_group = self.create_share_group(
+            self.share_member_client, self.share_group_type['id'],
+            [self.share_type['id']])
+        self.do_request(
+            'delete_share_group', expected_status=lib_exc.Forbidden,
+            share_group_id=share_group['id'])
+
+        alt_share_group = self.create_share_group(
+            self.alt_project_share_v2_client, self.share_group_type['id'],
+            [self.share_type['id']])
+        self.do_request(
+            'delete_share_group', expected_status=lib_exc.Forbidden,
+            share_group_id=alt_share_group['id'])
+
+    @tc.attr(base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND)
+    @decorators.idempotent_id('eddca093-e3a1-4a79-a8c7-8fd04c77b02f')
+    def test_force_delete_share_group(self):
+        super(TestProjectReaderTestsNFS, self).test_force_delete_share_group()
+
+    @tc.attr(base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND)
+    @decorators.idempotent_id('4530c19d-0aa5-402e-ac83-a3f2333f6c71')
+    def test_update_share_group(self):
+        share_group = self.create_share_group(
+            self.share_member_client, self.share_group_type['id'],
+            [self.share_type['id']])
+        name = data_utils.rand_name('rename_share')
+        self.do_request(
+            'update_share_group', expected_status=lib_exc.Forbidden,
+            share_group_id=share_group['id'], name=name)
+
+        alt_share_group = self.create_share_group(
+            self.alt_project_share_v2_client, self.share_group_type['id'],
+            [self.share_type['id']])
+        name = data_utils.rand_name('rename_share')
+        self.do_request(
+            'update_share_group', expected_status=lib_exc.Forbidden,
+            share_group_id=alt_share_group['id'], name=name)
+
+    @tc.attr(base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND)
+    @decorators.idempotent_id('37f23531-69b5-418d-bd91-7913341586ec')
+    def test_reset_share_group(self):
+        super(TestProjectReaderTestsNFS, self).test_reset_share_group()
+
+
+class TestProjectAdminTestsCEPHFS(TestProjectAdminTestsNFS):
+    protocol = 'cephfs'
+
+
+class TestProjectMemberTestsCEPHFS(TestProjectMemberTestsNFS):
+    protocol = 'cephfs'
+
+
+class TestProjectReaderTestsCEPHFS(TestProjectReaderTestsNFS):
+    protocol = 'cephfs'
+
+
+class TestProjectAdminTestsCIFS(TestProjectAdminTestsNFS):
+    protocol = 'cifs'
+
+
+class TestProjectMemberTestsCIFS(TestProjectMemberTestsNFS):
+    protocol = 'cifs'
+
+
+class TestProjectReaderTestsCIFS(TestProjectReaderTestsNFS):
+    protocol = 'cifs'
diff --git a/manila_tempest_tests/tests/rbac/test_shares.py b/manila_tempest_tests/tests/rbac/test_shares.py
index 2ca3c92..b5ea7d9 100644
--- a/manila_tempest_tests/tests/rbac/test_shares.py
+++ b/manila_tempest_tests/tests/rbac/test_shares.py
@@ -420,7 +420,7 @@
         alt_share = self.create_share(
             self.alt_project_share_v2_client, self.share_type['id'])
         self.do_request(
-            'reset_state', expected_status=lib_exc.NotFound,
+            'reset_state', expected_status=lib_exc.Forbidden,
             s_id=alt_share['id'], status="error")
 
     @decorators.idempotent_id('56a07567-d0a9-460a-9267-fcd82306a371')
@@ -465,7 +465,7 @@
         alt_share = self.create_share(
             self.alt_project_share_v2_client, self.share_type['id'])
         self.do_request(
-            'set_metadata', expected_status=lib_exc.Forbidden,
+            'set_metadata', expected_status=lib_exc.NotFound,
             resource_id=alt_share['id'], metadata={'key': 'value'})
 
     @decorators.idempotent_id('a69a2b85-3374-4621-83a9-89937ddb520b')
@@ -482,7 +482,7 @@
             self.alt_project_share_v2_client, self.share_type['id'],
             metadata=metadata)
         self.do_request(
-            'get_metadata', expected_status=lib_exc.Forbidden,
+            'get_metadata', expected_status=lib_exc.NotFound,
             resource_id=alt_share['id'])
 
     @decorators.idempotent_id('bea5518a-338e-494d-9034-1d03658ed58b')
@@ -498,7 +498,7 @@
             self.alt_project_share_v2_client, self.share_type['id'],
             metadata={'key': 'value'})
         self.do_request(
-            'delete_metadata', expected_status=lib_exc.Forbidden,
+            'delete_metadata', expected_status=lib_exc.NotFound,
             resource_id=alt_share['id'], key='key')
 
 
@@ -551,7 +551,7 @@
         alt_share = self.create_share(
             self.alt_project_share_v2_client, self.share_type['id'])
         self.do_request(
-            'delete_share', expected_status=lib_exc.NotFound,
+            'delete_share', expected_status=lib_exc.Forbidden,
             share_id=alt_share['id'])
 
     @decorators.idempotent_id('cb040955-5897-409f-aea0-84b6ae16b77e')
@@ -596,7 +596,7 @@
             self.alt_project_share_v2_client, self.share_type['id'],
             size=CONF.share.share_size + 1)
         self.do_request(
-            'shrink_share', expected_status=lib_exc.NotFound,
+            'shrink_share', expected_status=lib_exc.Forbidden,
             share_id=alt_share['id'], new_size=CONF.share.share_size)
 
     @decorators.idempotent_id('0b57aedb-6b68-498f-814e-173c47e6c307')
@@ -612,7 +612,7 @@
         alt_share = self.create_share(
             self.alt_project_share_v2_client, self.share_type['id'])
         self.do_request(
-            'extend_share', expected_status=lib_exc.NotFound,
+            'extend_share', expected_status=lib_exc.Forbidden,
             share_id=alt_share['id'], new_size=CONF.share.share_size + 1)
 
     @decorators.idempotent_id('3def3f4e-33fc-4726-8818-6cffbc2cab51')
diff --git a/manila_tempest_tests/tests/rbac/test_snapshots.py b/manila_tempest_tests/tests/rbac/test_snapshots.py
index 810ba9b..1978415 100644
--- a/manila_tempest_tests/tests/rbac/test_snapshots.py
+++ b/manila_tempest_tests/tests/rbac/test_snapshots.py
@@ -497,7 +497,7 @@
         alt_snap = self.create_snapshot(
             self.alt_project_share_v2_client, self.alt_share['id'])
         self.do_request(
-            'delete_snapshot', expected_status=lib_exc.NotFound,
+            'delete_snapshot', expected_status=lib_exc.Forbidden,
             snap_id=alt_snap['id'])
 
     @decorators.idempotent_id('ed0af390-e3d0-432b-9147-c0d569181b92')
@@ -517,7 +517,7 @@
         alt_snap = self.create_snapshot(
             self.alt_project_share_v2_client, self.alt_share['id'])
         self.do_request(
-            'rename_snapshot', expected_status=lib_exc.NotFound,
+            'rename_snapshot', expected_status=lib_exc.Forbidden,
             snapshot_id=alt_snap['id'], name=name)
 
     @decorators.idempotent_id('b8c9c9a4-3b2a-4b1c-80d8-2ec87d708111')
diff --git a/manila_tempest_tests/tests/scenario/manager_share.py b/manila_tempest_tests/tests/scenario/manager_share.py
index a847217..3e06323 100644
--- a/manila_tempest_tests/tests/scenario/manager_share.py
+++ b/manila_tempest_tests/tests/scenario/manager_share.py
@@ -84,7 +84,7 @@
         if CONF.share.image_with_share_tools == 'centos':
             self.image_ref = self._create_centos_based_glance_image()
         elif CONF.share.image_with_share_tools:
-            images = self.compute_images_client.list_images()["images"]
+            images = self.image_client.list_images()["images"]
             for img in images:
                 if img["name"] == CONF.share.image_with_share_tools:
                     self.image_id = img['id']
@@ -186,8 +186,7 @@
                         storage_net_nic[0]['addr']
                     )
             # Attach a floating IP
-            self.compute_floating_ips_client.associate_floating_ip_to_server(
-                floating_ip['floating_ip_address'], instance['id'])
+            self.associate_floating_ip(floating_ip, instance)
 
         self.assertIsNotNone(server_ip)
         # Check ssh
@@ -230,8 +229,7 @@
     def write_data_to_mounted_share_using_dd(self, remote_client,
                                              output_file,
                                              block_size,
-                                             block_count,
-                                             input_file='/dev/zero'):
+                                             block_count):
         """Writes data to mounted share using dd command
 
         Example Usage for writing 512Mb to a file on /mnt/
@@ -244,13 +242,12 @@
         :param block_size: The size of an individual block in bytes
         :param block_count: The number of blocks to write
         :param output_file: Path to the file to be written
-        :param input_file: Path to the file to read from
         """
         block_count = int(block_count)
         remote_client.exec_command(
-            "sudo sh -c \"dd bs={} count={} if={} of={} conv=fsync"
-            " iflag=fullblock\""
-            .format(block_size, block_count, input_file, output_file))
+            "sudo sh -c \"dd bs={} count={} if={} of={} iflag=fullblock\""
+            .format(block_size, block_count, CONF.share.dd_input_file,
+                    output_file))
 
     def read_data_from_mounted_share(self,
                                      remote_client,
diff --git a/manila_tempest_tests/tests/scenario/test_share_extend.py b/manila_tempest_tests/tests/scenario/test_share_extend.py
index 595ddd4..39098dc 100644
--- a/manila_tempest_tests/tests/scenario/test_share_extend.py
+++ b/manila_tempest_tests/tests/scenario/test_share_extend.py
@@ -9,6 +9,7 @@
 #    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 ddt
 from oslo_log import log as logging
@@ -50,6 +51,8 @@
     @tc.attr(base.TAG_POSITIVE, base.TAG_BACKEND)
     def test_create_extend_and_write(self):
         default_share_size = CONF.share.share_size
+        additional_overflow_blocks = CONF.share.additional_overflow_blocks
+        share_growth_size = CONF.share.share_growth_size
 
         LOG.debug('Step 1 - create instance')
         instance = self.boot_instance(wait_until="BUILD")
@@ -76,33 +79,37 @@
                   .format(three_quarter_blocks))
         self.write_data_to_mounted_share_using_dd(remote_client,
                                                   '/mnt/t1', '64M',
-                                                  three_quarter_blocks,
-                                                  '/dev/urandom')
+                                                  three_quarter_blocks)
+        time.sleep(CONF.share.share_resize_sync_delay)
         ls_result = remote_client.exec_command("sudo ls -lAh /mnt/")
         LOG.debug(ls_result)
 
-        over_one_quarter_blocks = total_blocks - three_quarter_blocks + 5
+        # Additional blocks that exceed the share capacity, defaulting to 5.
+        overflow_blocks = (
+            total_blocks - three_quarter_blocks +
+            additional_overflow_blocks or 5)
         LOG.debug('Step 6b - Write more data, should fail')
         self.assertRaises(
             exceptions.SSHExecCommandFailed,
             self.write_data_to_mounted_share_using_dd,
-            remote_client, '/mnt/t2', '64M', over_one_quarter_blocks,
-            '/dev/urandom')
+            remote_client, '/mnt/t2', '64M', overflow_blocks)
+        time.sleep(CONF.share.share_resize_sync_delay)
         ls_result = remote_client.exec_command("sudo ls -lAh /mnt/")
         LOG.debug(ls_result)
 
         LOG.debug('Step 7 - extend and wait')
-        extended_share_size = default_share_size + 1
+        extended_share_size = default_share_size + share_growth_size
         self.shares_v2_client.extend_share(share["id"],
                                            new_size=extended_share_size)
         waiters.wait_for_resource_status(
             self.shares_v2_client, share["id"], constants.STATUS_AVAILABLE)
         share = self.shares_v2_client.get_share(share["id"])['share']
         self.assertEqual(extended_share_size, int(share["size"]))
+        time.sleep(CONF.share.share_resize_sync_delay)
 
         LOG.debug('Step 8 - writing more data, should succeed')
         self.write_data_with_remount(location, remote_client, '/mnt/t3',
-                                     '64M', over_one_quarter_blocks)
+                                     '64M', overflow_blocks)
         ls_result = remote_client.exec_command("sudo ls -lAh /mnt/")
         LOG.debug(ls_result)
 
@@ -129,8 +136,7 @@
             self.write_data_to_mounted_share_using_dd(remote_client,
                                                       output_file,
                                                       block_size,
-                                                      block_count,
-                                                      '/dev/urandom')
+                                                      block_count)
         except exceptions.SSHExecCommandFailed as e:
             if 'stale file handle' in str(e).lower():
                 LOG.warning("Client was disconnected during extend process")
@@ -139,8 +145,7 @@
                 self.write_data_to_mounted_share_using_dd(remote_client,
                                                           output_file,
                                                           block_size,
-                                                          block_count,
-                                                          '/dev/urandom')
+                                                          block_count)
             else:
                 raise
 
diff --git a/manila_tempest_tests/tests/scenario/test_share_manage_unmanage.py b/manila_tempest_tests/tests/scenario/test_share_manage_unmanage.py
index ef2a6af..bbf5ba6 100644
--- a/manila_tempest_tests/tests/scenario/test_share_manage_unmanage.py
+++ b/manila_tempest_tests/tests/scenario/test_share_manage_unmanage.py
@@ -105,7 +105,7 @@
         LOG.debug('Step 6b - writing 640mb')
         self.write_data_to_mounted_share_using_dd(remote_client,
                                                   '/mnt/t1', 1024,
-                                                  2048, '/dev/zero')
+                                                  2048)
         ls_result = remote_client.exec_command("sudo ls -lA /mnt/")
         LOG.debug(ls_result)
 
diff --git a/manila_tempest_tests/tests/scenario/test_share_shrink.py b/manila_tempest_tests/tests/scenario/test_share_shrink.py
index a4e59e8..431bfc2 100644
--- a/manila_tempest_tests/tests/scenario/test_share_shrink.py
+++ b/manila_tempest_tests/tests/scenario/test_share_shrink.py
@@ -79,7 +79,8 @@
         LOG.debug('Step 6 - writing {} * 64MB blocks'.format(blocks))
         self.write_data_to_mounted_share_using_dd(remote_client,
                                                   '/mnt/t1', '64M',
-                                                  blocks, '/dev/urandom')
+                                                  blocks)
+        time.sleep(CONF.share.share_resize_sync_delay)
         ls_result = remote_client.exec_command("sudo ls -lAh /mnt/")
         LOG.debug(ls_result)
 
@@ -117,10 +118,11 @@
         self.assertEqual(default_share_size, int(share["size"]))
 
         LOG.debug('Step 11 - write more data than allocated, should fail')
+        overflow_blocks = blocks + CONF.share.additional_overflow_blocks
         self.assertRaises(
             exceptions.SSHExecCommandFailed,
             self.write_data_to_mounted_share_using_dd,
-            remote_client, '/mnt/t1', '64M', blocks, '/dev/urandom')
+            remote_client, '/mnt/t1', '64M', overflow_blocks)
 
         LOG.debug('Step 12 - unmount')
         self.unmount_share(remote_client)
diff --git a/releasenotes/notes/drop-python38-support-07c1fab194bad54a.yaml b/releasenotes/notes/drop-python38-support-07c1fab194bad54a.yaml
new file mode 100644
index 0000000..70f3814
--- /dev/null
+++ b/releasenotes/notes/drop-python38-support-07c1fab194bad54a.yaml
@@ -0,0 +1,6 @@
+---
+upgrade:
+  - |
+    Python 3.8 support has been dropped. Last release of
+    manila-tempest-plugin to support python 3.8 is 2.5.0.
+    The minimum version of Python now supported is Python 3.9.
diff --git a/requirements.txt b/requirements.txt
index 95ad247..ae5ea16 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,9 +1,5 @@
-# The order of packages is significant, because pip processes them in the order
-# of appearance. Changing the order has an impact on the overall integration
-# process, which may cause wedges in the gate later.
+pbr>=3.0.0 # Apache-2.0
 
-pbr!=2.1.0,>=2.0.0 # Apache-2.0
-
-ddt>=1.0.1 # MIT
+ddt>=1.6.0 # MIT
 oslo.log>=3.36.0 # Apache-2.0
 tempest>=31.1.0 # Apache-2.0
diff --git a/setup.cfg b/setup.cfg
index 457bfc2..f1c8312 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/manila/latest/
+python_requires = >=3.9
 classifier =
     Environment :: OpenStack
     Intended Audience :: Information Technology
@@ -14,10 +15,10 @@
     Operating System :: POSIX :: Linux
     Programming Language :: Python
     Programming Language :: Python :: 3
-    Programming Language :: Python :: 3.5
-    Programming Language :: Python :: 3.6
-    Programming Language :: Python :: 3.7
-    Programming Language :: Python :: 3.8
+    Programming Language :: Python :: 3.9
+    Programming Language :: Python :: 3.10
+    Programming Language :: Python :: 3.11
+    Programming Language :: Python :: 3.12
 
 [files]
 packages =
diff --git a/test-requirements.txt b/test-requirements.txt
index e422e93..761bf70 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -1,10 +1,6 @@
-# The order of packages is significant, because pip processes them in the order
-# of appearance. Changing the order has an impact on the overall integration
-# process, which may cause wedges in the gate later.
+hacking>=3.0.1,<3.1.0 # Apache-2.0
 
-hacking>=3.0.1,<3.1.0;python_version>'3' # Apache-2.0
-
-coverage!=4.4,>=4.0 # Apache-2.0
+coverage>=4.4.1 # Apache-2.0
 python-subunit>=1.0.0 # Apache-2.0/BSD
 oslotest>=3.2.0 # Apache-2.0
 stestr>=1.0.0 # Apache-2.0
diff --git a/zuul.d/manila-tempest-jobs.yaml b/zuul.d/manila-tempest-jobs.yaml
index d11676e..208bec3 100644
--- a/zuul.d/manila-tempest-jobs.yaml
+++ b/zuul.d/manila-tempest-jobs.yaml
@@ -16,6 +16,7 @@
       - ^doc/.*$
       - ^manila/hacking/.*$
       - ^manila/tests/.*$
+      - ^\.pre-commit-config\.yaml$
       - ^releasenotes/.*$
       - ^setup.cfg$
       - ^tools/.*$
@@ -157,23 +158,25 @@
               backend_names: LONDON,PARIS
               multi_backend: true
               image_password: manila
+              dd_input_file: /dev/urandom
 
 - job:
     name: manila-tempest-plugin-zfsonlinux
     description: Test ZFSOnLinux multibackend (DHSS=False) with postgresql db
     parent: manila-tempest-plugin-zfsonlinux-base
-    branches: &ubuntu_jammy_test_image_branches ^(?!stable/(yoga|xena|wallaby|victoria|ussuri)).*$
+    branches: &ubuntu_jammy_test_image_branches
+      regex: ^stable/(yoga|xena|wallaby|victoria|ussuri)$
+      negate: true
 
 - job:
     name: manila-tempest-plugin-lvm-base
-    nodeset: openstack-single-node-focal
     description: |
       Test LVM multibackend (DHSS=False) in a 4+6 (dual-stack) devstack
       environment with IPv6 control plane endpoints.
     parent: manila-tempest-plugin-ipv6-base
     abstract: true
-    required-projects:
-      - openstack/neutron-dynamic-routing
+    # TODO(carloss): enable neutron-dynamic-routing setup when LP #1998489
+    # is fixed.
     vars:
       tempest_test_regex: '(^manila_tempest_tests.tests)(?=.*\[.*\bbackend\b.*\])'
       devstack_services: &devstack-with-ovs
@@ -192,6 +195,7 @@
         q-l3: true
         q-meta: true
         q-metering: true
+        openstack-cli-server: true
       devstack_localrc:
         # NOTE(gouthamr): LP#1940324 prevents bgp usage with OVN, use OVS
         Q_AGENT: openvswitch
@@ -211,8 +215,8 @@
         MANILA_SETUP_IPV6: true
         NEUTRON_CREATE_INITIAL_NETWORKS: false
         MANILA_RESTORE_IPV6_DEFAULT_ROUTE: false
-      devstack_plugins:
-        neutron-dynamic-routing: https://opendev.org/openstack/neutron-dynamic-routing
+      # TODO(carloss): enable neutron-dynamic-routing setup when LP #1998489
+      # is fixed.
       devstack_local_conf:
         test-config:
           $TEMPEST_CONFIG:
@@ -237,6 +241,14 @@
       environment with IPv6 control plane endpoints.
     branches: *ubuntu_jammy_test_image_branches
     parent: manila-tempest-plugin-lvm-base
+    vars:
+      # TODO(gouthamr): some tests are disabled due to bugs
+      # IPv6 Tests: https://bugs.launchpad.net/manila/+bug/1998489
+      # drop these overrides once we address that bug.
+      tempest_exclude_regex: '(^manila_tempest_tests.tests.scenario.*IPv6.*)'
+      devstack_localrc:
+        MANILA_SETUP_IPV6: false
+        NEUTRON_CREATE_INITIAL_NETWORKS: true
 
 - job:
     name: manila-tempest-plugin-container
@@ -307,6 +319,7 @@
         MANILA_USE_SERVICE_INSTANCE_PASSWORD: true
         MANILA_DEFAULT_SHARE_TYPE_EXTRA_SPECS: 'snapshot_support=True create_share_from_snapshot_support=True'
         TEMPEST_USE_TEST_ACCOUNTS: true
+        GLANCE_ENFORCE_SCOPE: false
       devstack_services:
         cinder: true
       devstack_local_conf:
@@ -393,7 +406,6 @@
 
 - job:
     name: manila-tempest-plugin-cephfs-native
-    nodeset: openstack-single-node-focal
     description: Test CephFS Native (DHSS=False)
     parent: manila-tempest-plugin-cephfs-native-base
     branches: *ubuntu_jammy_test_image_branches
@@ -405,10 +417,14 @@
     vars:
       configure_swap_size: 8192
       tempest_test_regex: manila_tempest_tests.tests
-      tempest_exclude_regex: '(^manila_tempest_tests.tests.scenario.*ceph_fuse.*)'
+      # NOTE(gouthamr): Avoiding ceph fuse tests to conserve resources;
+      # we're hoping we'd get sufficient coverage through kernel tests.
+      # test_extend_share: https://bugs.launchpad.net/manila/+bug/2075981
+      tempest_exclude_regex: "\
+        (^manila_tempest_tests.tests.scenario.*ceph_fuse.*)|\
+        (^manila_tempest_tests.tests.scenario.test_share_extend.*)"
       devstack_localrc:
         ENABLE_CEPH_NOVA: false
-        CONTAINER_IMAGE: 'quay.io/ceph/ceph:v17.2.5'
         CEPHADM_DEPLOY: true
         CEPHADM_DEV_OSD: true
         TARGET_DEV_OSD_DIR: /opt/stack
@@ -424,7 +440,8 @@
     parent: manila-tempest-plugin-base
     required-projects:
       - openstack/devstack-plugin-ceph
-      - openstack/neutron-dynamic-routing
+      # TODO(carloss): enable neutron-dynamic-routing setup when LP #1998489
+      # is fixed.
     vars:
       tempest_concurrency: 2
       # turning off some tests due to exhaustion of disk space
@@ -435,7 +452,8 @@
       devstack_services: *devstack-with-ovs # LP 1940324
       devstack_plugins:
         devstack-plugin-ceph: https://opendev.org/openstack/devstack-plugin-ceph
-        neutron-dynamic-routing: https://opendev.org/openstack/neutron-dynamic-routing
+        # TODO(carloss): enable neutron-dynamic-routing setup when LP #1998489
+        # is fixed.
       devstack_localrc:
         # NOTE(gouthamr): LP#1940324 prevents bgp usage with OVN, use OVS
         Q_AGENT: openvswitch
@@ -477,20 +495,129 @@
     description: Test CephFS NFS (DHSS=False)
     parent: manila-tempest-plugin-cephfs-nfs-base
     branches: *ubuntu_jammy_test_image_branches
-    nodeset: devstack-single-node-centos-9-stream
     vars:
+      tempest_concurrency: 1
       # TODO(gouthamr): some tests are disabled due to bugs
       # IPv6 Tests: https://bugs.launchpad.net/manila/+bug/1998489
-      # snapshot clone fs sync: https://bugs.launchpad.net/manila/+bug/1989273
+      # test_share_extend: https://bugs.launchpad.net/manila/+bug/2075981
       tempest_exclude_regex: "\
-          (^manila_tempest_tests.tests.scenario.*IPv6.*)|\
-          (^manila_tempest_tests.tests.scenario.test_share_basic_ops.TestShareBasicOpsNFS.test_write_data_to_share_created_from_snapshot)"
+        (^manila_tempest_tests.tests.scenario.*IPv6.*)|\
+        (^manila_tempest_tests.tests.scenario.test_share_extend.*)"
+      tempest_test_regex: '(^manila_tempest_tests.tests)(?=.*\[.*\bbackend\b.*\])'
       devstack_localrc:
+        CEPHADM_DEPLOY: True
+        CEPHADM_DEV_OSD: True
+        CEPH_LOOPBACK_DISK_SIZE: 40GB
+        CEPHADM_DEPLOY_NFS: False
+        TARGET_DEV_OSD_DIR: /opt/stack
+        ENABLED_SHARE_PROTOCOLS: NFS
         MANILA_OPTGROUP_cephfsnfs_cephfs_ganesha_server_ip: "{{ hostvars[inventory_hostname]['nodepool']['private_ipv4'] }}"
-        CEPH_RELEASE: "quincy"
+        CEPH_RELEASE: "reef"
         MANILA_SETUP_IPV6: false
         NEUTRON_CREATE_INITIAL_NETWORKS: true
         IP_VERSION: 4
+        CEPH_INGRESS_IP: "{{hostvars['controller'].ansible_default_ipv6.address}}"
+
+- job:
+    name: manila-tempest-plugin-multinode-base
+    abstract: true
+    description: |
+      Base job for testing multinode with Manila. Manila is enabled in
+      the controller node; and we have an additional compute node.
+    parent: tempest-multinode-full-py3
+    timeout: 10800
+    irrelevant-files: *irrelevant-files
+    required-projects: *manila-tempest-required-projects
+    vars:
+      tox_envlist: all
+      tempest_test_regex: manila_tempest_tests
+      tempest_plugins:
+        - manila-tempest-plugin
+      tempest_concurrency: 8
+      devstack_services:
+        cinder: false
+        c-bak: false
+        s-account: false
+        s-container: false
+        s-object: false
+        s-proxy: false
+        horizon: false
+        tls-proxy: true
+      devstack_localrc:
+        MANILA_USE_DOWNGRADE_MIGRATIONS: false
+        MANILA_INSTALL_TEMPEST_PLUGIN_SYSTEMWIDE: false
+        MANILA_ALLOW_NAS_SERVER_PORTS_ON_HOST: true
+        MANILA_SHARE_MIGRATION_PERIOD_TASK_INTERVAL: 1
+        MANILA_SERVER_MIGRATION_PERIOD_TASK_INTERVAL: 10
+        MANILA_REPLICA_STATE_UPDATE_INTERVAL: 10
+    group-vars:
+      tempest:
+        devstack_plugins:
+          manila: https://opendev.org/openstack/manila
+      subnode:
+        devstack_services:
+          cinder: false
+          c-bak: false
+
+- job:
+    name: manila-tempest-plugin-multinode-cephfs-nfs-cephadm
+    description: Test CephFS NFS (DHSS=False) in a Multinode devstack env
+    parent: manila-tempest-plugin-multinode-base
+    required-projects:
+      - openstack/devstack-plugin-ceph
+    vars:
+      configure_swap_size: 8192
+      tempest_concurrency: 2
+        # TODO(gouthamr): some tests are disabled due to bugs
+        # IPv6 Tests: https://bugs.launchpad.net/manila/+bug/1998489
+        # test_share_extend: https://bugs.launchpad.net/manila/+bug/2075981
+      tempest_exclude_regex: "\
+        (^manila_tempest_tests.tests.scenario.*IPv6.*)|\
+        (^manila_tempest_tests.tests.scenario.test_share_extend.*)"
+      devstack_localrc:
+        MYSQL_REDUCE_MEMORY: True
+        CEPHADM_DEPLOY: True
+        CEPHADM_DEV_OSD: true
+        CEPH_LOOPBACK_DISK_SIZE: 40GB
+        ENABLED_SHARE_PROTOCOLS: NFS
+        ENABLE_CEPH_MANILA: True
+        ENABLE_CEPH_NOVA: False
+        MANILA_CEPH_DRIVER: cephfsnfs
+        MANILA_CONFIGURE_DEFAULT_TYPES: true
+        MANILA_DEFAULT_SHARE_TYPE_EXTRA_SPECS: 'snapshot_support=True create_share_from_snapshot_support=True'
+        MANILA_ENABLED_BACKENDS: cephfsnfs
+        MANILA_OPTGROUP_cephfsnfs_cephfs_auth_id: manila
+        MANILA_OPTGROUP_cephfsnfs_cephfs_conf_path: /etc/ceph/ceph.conf
+        MANILA_OPTGROUP_cephfsnfs_cephfs_nfs_cluster_id: cephfs
+        MANILA_OPTGROUP_cephfsnfs_cephfs_protocol_helper_type: NFS
+        MANILA_OPTGROUP_cephfsnfs_driver_handles_share_servers: false
+        MANILA_OPTGROUP_cephfsnfs_share_driver: manila.share.drivers.cephfs.driver.CephFSDriver
+        MANILA_SERVICE_IMAGE_ENABLED: True
+        MANILA_SETUP_IPV6: false
+        SHARE_DRIVER: manila.share.drivers.cephfs.driver.CephFSDriver
+        TARGET_DEV_OSD_DIR: /opt/stack
+        CEPH_INGRESS_IP: "{{hostvars['controller'].ansible_default_ipv6.address}}"
+      devstack_local_conf:
+        test-config:
+          $TEMPEST_CONFIG:
+            share:
+              backend_names: cephfsnfs
+              capability_storage_protocol: NFS
+              default_share_type_name: default
+              enable_protocols: nfs
+              image_password: manila
+              multitenancy_enabled: false
+              run_share_group_tests: false
+    group-vars:
+      subnode:
+        devstack_plugins:
+          devstack-plugin-ceph: https://opendev.org/openstack/devstack-plugin-ceph
+        devstack_localrc:
+          REMOTE_CEPH: True
+          ENABLE_CEPH_NOVA: False
+      tempest:
+        devstack_plugins:
+          devstack-plugin-ceph: https://opendev.org/openstack/devstack-plugin-ceph
 
 - job:
     name: manila-tempest-plugin-dummy-no-dhss
@@ -530,6 +657,8 @@
         MANILA_OPTGROUP_membernet_standalone_network_plugin_mask: 24
         MANILA_OPTGROUP_membernet_standalone_network_plugin_network_type: vlan
         MANILA_OPTGROUP_membernet_standalone_network_plugin_segmentation_id: 1010
+        MANILA_CREATE_BACKUP_CONTINUE_TASK_INTERVAL: 30
+        MANILA_RESTORE_BACKUP_CONTINUE_TASK_INTERVAL: 30
       devstack_local_conf:
         test-config:
           "$TEMPEST_CONFIG":
@@ -543,6 +672,7 @@
               enable_user_rules_for_protocols: cifs
               multi_backend: true
               multitenancy_enabled: false
+              run_driver_assisted_backup_tests: true
               run_driver_assisted_migration_tests: true
               run_manage_unmanage_snapshot_tests: true
               run_manage_unmanage_tests: true
@@ -631,7 +761,6 @@
     description: |
       Test the GlusterFS driver (DHSS=False) with the native GlusterFS protocol
     parent: manila-tempest-plugin-standalone-base
-    nodeset: openstack-single-node-focal
     required-projects:
       - x/devstack-plugin-glusterfs
     vars:
@@ -664,7 +793,6 @@
     description: |
       Test the GlusterFS driver (DHSS=False) with the native NFS protocol
     parent: manila-tempest-plugin-standalone-base
-    nodeset: openstack-single-node-focal
     required-projects:
       - x/devstack-plugin-glusterfs
     vars:
@@ -735,7 +863,9 @@
 - job:
     name: manila-tempest-plugin-lvm-fips
     parent: manila-tempest-plugin-lvm-fips-base
-    branches: ^(?!stable/(yoga|xena|wallaby|victoria|ussuri)).*$
+    branches:
+      regex: ^stable/(yoga|xena|wallaby|victoria|ussuri)$
+      negate: true
 
 - project-template:
     name: manila-tempest-plugin-jobs-using-service-image
@@ -751,7 +881,7 @@
             voting: false
         - manila-tempest-plugin-cephfs-native-cephadm:
             voting: false
-        - manila-tempest-plugin-cephfs-nfs:
+        - manila-tempest-plugin-multinode-cephfs-nfs-cephadm:
             voting: false
         - manila-tempest-plugin-zfsonlinux:
             voting: false
diff --git a/zuul.d/manila-tempest-stable-jobs.yaml b/zuul.d/manila-tempest-stable-jobs.yaml
index 28db229..c5a692b 100644
--- a/zuul.d/manila-tempest-stable-jobs.yaml
+++ b/zuul.d/manila-tempest-stable-jobs.yaml
@@ -1,5 +1,5 @@
-# Stable xena / yoga / zed branch jobs to test the trunk version of
-# manila-tempest-plugin against those released stable branches of manila
+# Stable branch jobs to test the trunk version of manila-tempest-plugin
+# against those released stable branches of manila
 
 - job:
     name: manila-tempest-plugin-generic-scenario-stable
@@ -7,7 +7,7 @@
       Test the scenario test cases on the generic driver multibackend
       (DHSS=True) with NFS and CIFS
     parent: manila-tempest-plugin-generic-scenario-base
-    branches: &manila_tempest_image_pinned_branches ^(stable/(zed|yoga|xena)).*$
+    branches: &manila_tempest_image_pinned_branches ^stable/(2023.1|zed|yoga|xena)$
     vars: &manila_tempest_image_pinned_vars
       devstack_localrc:
         # NOTE(carloss): Pinning manila service image to a Focal version,
@@ -27,15 +27,12 @@
 
 - job:
     name: manila-tempest-plugin-lvm-stable
-    # NOTE(carloss): we are aware that focal is the current default, but
-    # in order to avoid breakages when devstack-minimal switches to a newer
-    # branch, we are pinning focal here.
-    nodeset: openstack-single-node-focal
     description: |
       Test LVM multibackend (DHSS=False) in a 4+6 (dual-stack) devstack
       environment with IPv6 control plane endpoints.
     branches: *manila_tempest_image_pinned_branches
     parent: manila-tempest-plugin-lvm-base
+    nodeset: openstack-single-node-focal
     vars: *manila_tempest_image_pinned_vars
 
 - job:
@@ -68,22 +65,47 @@
 - job:
     name: manila-tempest-plugin-lvm-fips-stable
     parent: manila-tempest-plugin-lvm-fips-base
-    branches: ^(stable/(yoga|xena)).*$
+    branches: ^stable/(yoga|xena)$
     vars: *manila_tempest_image_pinned_vars
 
 - job:
-    name: manila-tempest-plugin-lvm-yoga
+    name: manila-tempest-plugin-lvm-2024.2
     parent: manila-tempest-plugin-lvm-base
-    nodeset: openstack-single-node-focal
-    override-checkout: stable/yoga
-    vars: *manila_tempest_image_pinned_vars
+    override-checkout: stable/2024.2
+    vars:
+      # TODO(gouthamr): some tests are disabled due to bugs
+      # IPv6 Tests: https://bugs.launchpad.net/manila/+bug/1998489
+      # drop these overrides once we address that bug.
+      tempest_exclude_regex: '(^manila_tempest_tests.tests.scenario.*IPv6.*)'
+      devstack_localrc:
+        MANILA_SETUP_IPV6: false
+        NEUTRON_CREATE_INITIAL_NETWORKS: true
 
 - job:
-    name: manila-tempest-plugin-lvm-xena
+    name: manila-tempest-plugin-lvm-2024.1
     parent: manila-tempest-plugin-lvm-base
-    nodeset: openstack-single-node-focal
-    override-checkout: stable/xena
-    vars: *manila_tempest_image_pinned_vars
+    override-checkout: stable/2024.1
+    vars:
+      # TODO(gouthamr): some tests are disabled due to bugs
+      # IPv6 Tests: https://bugs.launchpad.net/manila/+bug/1998489
+      # drop these overrides once we address that bug.
+      tempest_exclude_regex: '(^manila_tempest_tests.tests.scenario.*IPv6.*)'
+      devstack_localrc:
+        MANILA_SETUP_IPV6: false
+        NEUTRON_CREATE_INITIAL_NETWORKS: true
+
+- job:
+    name: manila-tempest-plugin-lvm-2023.2
+    parent: manila-tempest-plugin-lvm-base
+    override-checkout: stable/2023.2
+    vars:
+      # TODO(gouthamr): some tests are disabled due to bugs
+      # IPv6 Tests: https://bugs.launchpad.net/manila/+bug/1998489
+      # drop these overrides once we address that bug.
+      tempest_exclude_regex: '(^manila_tempest_tests.tests.scenario.*IPv6.*)'
+      devstack_localrc:
+        MANILA_SETUP_IPV6: false
+        NEUTRON_CREATE_INITIAL_NETWORKS: true
 
 - project-template:
     name: manila-tempest-plugin-jobs-using-service-image-stable
diff --git a/zuul.d/project.yaml b/zuul.d/project.yaml
index 48d3d4c..d2a654e 100644
--- a/zuul.d/project.yaml
+++ b/zuul.d/project.yaml
@@ -8,18 +8,13 @@
       jobs:
         - manila-tempest-plugin-dummy-no-dhss
         - manila-tempest-plugin-dummy-dhss
-        - manila-tempest-plugin-lvm-yoga
-        - manila-tempest-plugin-lvm-xena
+        - manila-tempest-plugin-lvm-2024.2
+        - manila-tempest-plugin-lvm-2024.1
+        - manila-tempest-plugin-lvm-2023.2
         - manila-tempest-plugin-dummy-no-dhss-rbac
         - manila-tempest-plugin-container:
             voting: false
-        - manila-tempest-plugin-glusterfs-nfs:
-            voting: false
     gate:
       jobs:
         - manila-tempest-plugin-dummy-no-dhss
         - manila-tempest-plugin-dummy-dhss
-    experimental:
-      jobs:
-        - manila-tempest-plugin-glusterfs-native:
-            voting: false