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