Merge "Fix scenario tests"
diff --git a/manila_tempest_tests/common/__init__.py b/manila_tempest_tests/common/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/manila_tempest_tests/common/__init__.py
diff --git a/manila_tempest_tests/common/constants.py b/manila_tempest_tests/common/constants.py
new file mode 100644
index 0000000..bef35a5
--- /dev/null
+++ b/manila_tempest_tests/common/constants.py
@@ -0,0 +1,36 @@
+# 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.
+
+STATUS_ERROR = 'error'
+STATUS_AVAILABLE = 'available'
+STATUS_ERROR_DELETING = 'error_deleting'
+
+TEMPEST_MANILA_PREFIX = 'tempest-manila'
+REPLICATION_STYLE_READABLE = 'readable'
+REPLICATION_STYLE_WRITABLE = 'writable'
+REPLICATION_STYLE_DR = 'dr'
+REPLICATION_TYPE_CHOICES = (
+ REPLICATION_STYLE_READABLE,
+ REPLICATION_STYLE_WRITABLE,
+ REPLICATION_STYLE_DR,
+)
+REPLICATION_PROMOTION_CHOICES = (
+ REPLICATION_STYLE_READABLE,
+ REPLICATION_STYLE_DR,
+)
+REPLICATION_STATE_ACTIVE = 'active'
+REPLICATION_STATE_IN_SYNC = 'in_sync'
+REPLICATION_STATE_OUT_OF_SYNC = 'out_of_sync'
+
+RULE_STATE_ACTIVE = 'active'
+RULE_STATE_OUT_OF_SYNC = 'out_of_sync'
+RULE_STATE_ERROR = 'error'
diff --git a/manila_tempest_tests/config.py b/manila_tempest_tests/config.py
index e52ca42..94ffb5f 100644
--- a/manila_tempest_tests/config.py
+++ b/manila_tempest_tests/config.py
@@ -36,7 +36,7 @@
help="The minimum api microversion is configured to be the "
"value of the minimum microversion supported by Manila."),
cfg.StrOpt("max_api_microversion",
- default="2.11",
+ default="2.15",
help="The maximum api microversion is configured to be the "
"value of the latest microversion supported by Manila."),
cfg.StrOpt("region",
@@ -73,6 +73,9 @@
cfg.ListOpt("enable_cert_rules_for_protocols",
default=["glusterfs", ],
help="Protocols that should be covered with cert rule tests."),
+ cfg.ListOpt("enable_cephx_rules_for_protocols",
+ default=["cephfs", ],
+ help="Protocols to be covered with cephx rule tests."),
cfg.StrOpt("username_for_user_rules",
default="Administrator",
help="Username, that will be used in user tests."),
@@ -128,13 +131,11 @@
help="Whether to suppress errors with clean up operation "
"or not. There are cases when we may want to skip "
"such errors and catch only test errors."),
- cfg.BoolOpt("run_manage_unmanage_tests",
- default=False,
- help="Defines whether to run manage/unmanage tests or not. "
- "These test may leave orphaned resources, so be careful "
- "enabling this opt."),
# Switching ON/OFF test suites filtered by features
+ cfg.BoolOpt("run_quota_tests",
+ default=True,
+ help="Defines whether to run quota tests or not."),
cfg.BoolOpt("run_extend_tests",
default=True,
help="Defines whether to run share extend tests or not. "
@@ -155,9 +156,24 @@
help="Defines whether to run consistency group tests or not. "
"Disable this feature if used driver doesn't support "
"it."),
+ cfg.BoolOpt("run_replication_tests",
+ default=False,
+ help="Defines whether to run replication tests or not. "
+ "Enable this feature if the driver is configured "
+ "for replication."),
cfg.BoolOpt("run_migration_tests",
default=False,
help="Enable or disable migration tests."),
+ cfg.BoolOpt("run_manage_unmanage_tests",
+ default=False,
+ help="Defines whether to run manage/unmanage tests or not. "
+ "These test may leave orphaned resources, so be careful "
+ "enabling this opt."),
+ cfg.BoolOpt("run_manage_unmanage_snapshot_tests",
+ default=False,
+ help="Defines whether to run manage/unmanage snapshot tests "
+ "or not. These tests may leave orphaned resources, so be "
+ "careful enabling this opt."),
cfg.StrOpt("image_with_share_tools",
default="manila-service-image",
@@ -172,9 +188,13 @@
default="100",
help="Flavor used for client vm in scenario tests."),
cfg.IntOpt("migration_timeout",
- default=1200,
+ default=1500,
help="Time to wait for share migration before "
"timing out (seconds)."),
cfg.StrOpt("default_share_type_name",
help="Default share type name to use in tempest tests."),
+ cfg.StrOpt("backend_replication_type",
+ default='none',
+ choices=['none', 'writable', 'readable', 'dr'],
+ help="Specify the replication type supported by the backend."),
]
diff --git a/manila_tempest_tests/services/share/json/shares_client.py b/manila_tempest_tests/services/share/json/shares_client.py
index 040b541..bf8ce2a 100644
--- a/manila_tempest_tests/services/share/json/shares_client.py
+++ b/manila_tempest_tests/services/share/json/shares_client.py
@@ -20,9 +20,9 @@
from six.moves.urllib import parse as urlparse
from tempest import config # noqa
-from tempest_lib.common import rest_client
-from tempest_lib.common.utils import data_utils
-from tempest_lib import exceptions
+from tempest.lib.common import rest_client
+from tempest.lib.common.utils import data_utils
+from tempest.lib import exceptions
from manila_tempest_tests import share_exceptions
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 7a454fa..3b45bf3 100644
--- a/manila_tempest_tests/services/share/v2/json/shares_client.py
+++ b/manila_tempest_tests/services/share/v2/json/shares_client.py
@@ -18,9 +18,10 @@
from six.moves.urllib import parse as urlparse
from tempest import config
-from tempest_lib.common.utils import data_utils
-from tempest_lib import exceptions
+from tempest.lib.common.utils import data_utils
+from tempest.lib import exceptions
+from manila_tempest_tests.common import constants
from manila_tempest_tests.services.share.json import shares_client
from manila_tempest_tests import share_exceptions
from manila_tempest_tests import utils
@@ -177,6 +178,9 @@
elif "cgsnapshot_id" in kwargs:
return self._is_resource_deleted(
self.get_cgsnapshot, kwargs.get("cgsnapshot_id"))
+ elif "replica_id" in kwargs:
+ return self._is_resource_deleted(
+ self.get_share_replica, kwargs.get("replica_id"))
else:
return super(SharesV2Client, self).is_resource_deleted(
*args, **kwargs)
@@ -439,6 +443,113 @@
###############
+ def create_snapshot(self, share_id, name=None, description=None,
+ force=False, version=LATEST_MICROVERSION):
+ if name is None:
+ name = data_utils.rand_name("tempest-created-share-snap")
+ if description is None:
+ description = data_utils.rand_name(
+ "tempest-created-share-snap-desc")
+ post_body = {
+ "snapshot": {
+ "name": name,
+ "force": force,
+ "description": description,
+ "share_id": share_id,
+ }
+ }
+ body = json.dumps(post_body)
+ resp, body = self.post("snapshots", body, version=version)
+ self.expected_success(202, resp.status)
+ return self._parse_resp(body)
+
+ def get_snapshot(self, snapshot_id, version=LATEST_MICROVERSION):
+ resp, body = self.get("snapshots/%s" % snapshot_id, version=version)
+ self.expected_success(200, resp.status)
+ return self._parse_resp(body)
+
+ def list_snapshots(self, detailed=False, params=None,
+ version=LATEST_MICROVERSION):
+ """Get list of share snapshots w/o filters."""
+ uri = 'snapshots/detail' if detailed else 'snapshots'
+ uri += '?%s' % urlparse.urlencode(params) if params else ''
+ resp, body = self.get(uri, version=version)
+ self.expected_success(200, resp.status)
+ return self._parse_resp(body)
+
+ def list_snapshots_with_detail(self, params=None,
+ version=LATEST_MICROVERSION):
+ """Get detailed list of share snapshots w/o filters."""
+ return self.list_snapshots(detailed=True, params=params,
+ version=version)
+
+ def delete_snapshot(self, snap_id, version=LATEST_MICROVERSION):
+ resp, body = self.delete("snapshots/%s" % snap_id, version=version)
+ self.expected_success(202, resp.status)
+ return body
+
+ def wait_for_snapshot_status(self, snapshot_id, status,
+ version=LATEST_MICROVERSION):
+ """Waits for a snapshot to reach a given status."""
+ body = self.get_snapshot(snapshot_id, version=version)
+ snapshot_name = body['name']
+ snapshot_status = body['status']
+ start = int(time.time())
+
+ while snapshot_status != status:
+ time.sleep(self.build_interval)
+ body = self.get_snapshot(snapshot_id, version=version)
+ snapshot_status = body['status']
+ if 'error' in snapshot_status:
+ raise (share_exceptions.
+ SnapshotBuildErrorException(snapshot_id=snapshot_id))
+
+ if int(time.time()) - start >= self.build_timeout:
+ message = ('Share Snapshot %s failed to reach %s status '
+ 'within the required time (%s s).' %
+ (snapshot_name, status, self.build_timeout))
+ raise exceptions.TimeoutException(message)
+
+ def manage_snapshot(self, share_id, provider_location,
+ name=None, description=None,
+ version=LATEST_MICROVERSION,
+ driver_options=None):
+ if name is None:
+ name = data_utils.rand_name("tempest-manage-snapshot")
+ if description is None:
+ description = data_utils.rand_name("tempest-manage-snapshot-desc")
+ post_body = {
+ "snapshot": {
+ "share_id": share_id,
+ "provider_location": provider_location,
+ "name": name,
+ "description": description,
+ "driver_options": driver_options if driver_options else {},
+ }
+ }
+ url = 'snapshots/manage'
+ body = json.dumps(post_body)
+ resp, body = self.post(url, body, version=version)
+ self.expected_success(202, resp.status)
+ return self._parse_resp(body)
+
+ def unmanage_snapshot(self, snapshot_id, version=LATEST_MICROVERSION,
+ body=None):
+ url = 'snapshots'
+ action_name = 'action'
+ if body is None:
+ body = json.dumps({'unmanage': {}})
+ resp, body = self.post(
+ "%(url)s/%(snapshot_id)s/%(action_name)s" % {
+ 'url': url, 'snapshot_id': snapshot_id,
+ 'action_name': action_name},
+ body,
+ version=version)
+ self.expected_success(202, resp.status)
+ return body
+
+###############
+
def _get_access_action_name(self, version, action):
if utils.is_microversion_gt(version, "2.6"):
return action.split('os-')[-1]
@@ -838,16 +949,19 @@
###############
- def migrate_share(self, share_id, host, version=LATEST_MICROVERSION,
- action_name=None):
+ def migrate_share(self, share_id, host, notify,
+ version=LATEST_MICROVERSION, action_name=None):
if action_name is None:
- if utils.is_microversion_gt(version, "2.6"):
+ if utils.is_microversion_lt(version, "2.7"):
+ action_name = 'os-migrate_share'
+ elif utils.is_microversion_lt(version, "2.15"):
action_name = 'migrate_share'
else:
- action_name = 'os-migrate_share'
+ action_name = 'migration_start'
post_body = {
action_name: {
'host': host,
+ 'notify': notify,
}
}
body = json.dumps(post_body)
@@ -855,27 +969,242 @@
headers=EXPERIMENTAL, extra_headers=True,
version=version)
- def wait_for_migration_completed(self, share_id, dest_host,
- version=LATEST_MICROVERSION):
+ def migration_complete(self, share_id, version=LATEST_MICROVERSION,
+ action_name='migration_complete'):
+ post_body = {
+ action_name: None,
+ }
+ body = json.dumps(post_body)
+ return self.post('shares/%s/action' % share_id, body,
+ headers=EXPERIMENTAL, extra_headers=True,
+ version=version)
+
+ def migration_cancel(self, share_id, version=LATEST_MICROVERSION,
+ action_name='migration_cancel'):
+ post_body = {
+ action_name: None,
+ }
+ body = json.dumps(post_body)
+ return self.post('shares/%s/action' % share_id, body,
+ headers=EXPERIMENTAL, extra_headers=True,
+ version=version)
+
+ def migration_get_progress(self, share_id, version=LATEST_MICROVERSION,
+ action_name='migration_get_progress'):
+ post_body = {
+ action_name: None,
+ }
+ body = json.dumps(post_body)
+ return self.post('shares/%s/action' % share_id, body,
+ headers=EXPERIMENTAL, extra_headers=True,
+ version=version)
+
+ def reset_task_state(
+ self, share_id, task_state, version=LATEST_MICROVERSION,
+ action_name='reset_task_state'):
+ post_body = {
+ action_name: {
+ 'task_state': task_state,
+ }
+ }
+ body = json.dumps(post_body)
+ return self.post('shares/%s/action' % share_id, body,
+ headers=EXPERIMENTAL, extra_headers=True,
+ version=version)
+
+ def wait_for_migration_status(self, share_id, dest_host, status,
+ version=LATEST_MICROVERSION):
"""Waits for a share to migrate to a certain host."""
share = self.get_share(share_id, version=version)
migration_timeout = CONF.share.migration_timeout
start = int(time.time())
- while share['task_state'] != 'migration_success':
+ while share['task_state'] != status:
time.sleep(self.build_interval)
share = self.get_share(share_id, version=version)
- if share['task_state'] == 'migration_success':
+ if share['task_state'] == status:
return share
elif share['task_state'] == 'migration_error':
raise share_exceptions.ShareMigrationException(
share_id=share['id'], src=share['host'], dest=dest_host)
elif int(time.time()) - start >= migration_timeout:
- message = ('Share %(share_id)s failed to migrate from '
- 'host %(src)s to host %(dest)s within the required '
- 'time %(timeout)s.' % {
+ message = ('Share %(share_id)s failed to reach status '
+ '%(status)s when migrating from host %(src)s to '
+ 'host %(dest)s within the required time '
+ '%(timeout)s.' % {
'src': share['host'],
'dest': dest_host,
'share_id': share['id'],
- 'timeout': self.build_timeout
+ 'timeout': self.build_timeout,
+ 'status': status,
})
raise exceptions.TimeoutException(message)
+
+################
+
+ def create_share_replica(self, share_id, availability_zone=None,
+ version=LATEST_MICROVERSION):
+ """Add a share replica of an existing share."""
+ uri = "share-replicas"
+ post_body = {
+ 'share_id': share_id,
+ 'availability_zone': availability_zone,
+ }
+
+ body = json.dumps({'share_replica': post_body})
+ resp, body = self.post(uri, body,
+ headers=EXPERIMENTAL,
+ extra_headers=True,
+ version=version)
+ self.expected_success(202, resp.status)
+ return self._parse_resp(body)
+
+ def get_share_replica(self, replica_id, version=LATEST_MICROVERSION):
+ """Get the details of share_replica."""
+ resp, body = self.get("share-replicas/%s" % replica_id,
+ headers=EXPERIMENTAL,
+ extra_headers=True,
+ version=version)
+ self.expected_success(200, resp.status)
+ return self._parse_resp(body)
+
+ def list_share_replicas(self, share_id=None, version=LATEST_MICROVERSION):
+ """Get list of replicas."""
+ uri = "share-replicas/detail"
+ uri += ("?share_id=%s" % share_id) if share_id is not None else ''
+ resp, body = self.get(uri, headers=EXPERIMENTAL,
+ extra_headers=True, version=version)
+ self.expected_success(200, resp.status)
+ return self._parse_resp(body)
+
+ def list_share_replicas_summary(self, share_id=None,
+ version=LATEST_MICROVERSION):
+ """Get summary list of replicas."""
+ uri = "share-replicas"
+ uri += ("?share_id=%s" % share_id) if share_id is not None else ''
+ resp, body = self.get(uri, headers=EXPERIMENTAL,
+ extra_headers=True, version=version)
+ self.expected_success(200, resp.status)
+ return self._parse_resp(body)
+
+ def delete_share_replica(self, replica_id, version=LATEST_MICROVERSION):
+ """Delete share_replica."""
+ uri = "share-replicas/%s" % replica_id
+ resp, body = self.delete(uri,
+ headers=EXPERIMENTAL,
+ extra_headers=True,
+ version=version)
+ self.expected_success(202, resp.status)
+ return body
+
+ def promote_share_replica(self, replica_id, expected_status=202,
+ version=LATEST_MICROVERSION):
+ """Promote a share replica to active state."""
+ uri = "share-replicas/%s/action" % replica_id
+ post_body = {
+ 'promote': None,
+ }
+ body = json.dumps(post_body)
+ resp, body = self.post(uri, body,
+ headers=EXPERIMENTAL,
+ extra_headers=True,
+ version=version)
+ self.expected_success(expected_status, resp.status)
+ return self._parse_resp(body)
+
+ def wait_for_share_replica_status(self, replica_id, expected_status,
+ status_attr='status'):
+ """Waits for a replica's status_attr to reach a given status."""
+ body = self.get_share_replica(replica_id)
+ replica_status = body[status_attr]
+ start = int(time.time())
+
+ while replica_status != expected_status:
+ time.sleep(self.build_interval)
+ body = self.get_share_replica(replica_id)
+ replica_status = body[status_attr]
+ if replica_status == expected_status:
+ return
+ if ('error' in replica_status
+ and expected_status != constants.STATUS_ERROR):
+ raise share_exceptions.ShareInstanceBuildErrorException(
+ id=replica_id)
+
+ if int(time.time()) - start >= self.build_timeout:
+ message = ('The %(status_attr)s of Replica %(id)s failed to '
+ 'reach %(expected_status)s status within the '
+ 'required time (%(time)ss). Current '
+ '%(status_attr)s: %(current_status)s.' %
+ {
+ 'status_attr': status_attr,
+ 'expected_status': expected_status,
+ 'time': self.build_timeout,
+ 'id': replica_id,
+ 'current_status': replica_status,
+ })
+ raise exceptions.TimeoutException(message)
+
+ def reset_share_replica_status(self, replica_id,
+ status=constants.STATUS_AVAILABLE,
+ version=LATEST_MICROVERSION):
+ """Reset the status."""
+ uri = 'share-replicas/%s/action' % replica_id
+ post_body = {
+ 'reset_status': {
+ 'status': status
+ }
+ }
+ body = json.dumps(post_body)
+ resp, body = self.post(uri, body,
+ headers=EXPERIMENTAL,
+ extra_headers=True,
+ version=version)
+ self.expected_success(202, resp.status)
+ return self._parse_resp(body)
+
+ def reset_share_replica_state(self, replica_id,
+ state=constants.REPLICATION_STATE_ACTIVE,
+ version=LATEST_MICROVERSION):
+ """Reset the replication state of a replica."""
+ uri = 'share-replicas/%s/action' % replica_id
+ post_body = {
+ 'reset_replica_state': {
+ 'replica_state': state
+ }
+ }
+ body = json.dumps(post_body)
+ resp, body = self.post(uri, body,
+ headers=EXPERIMENTAL,
+ extra_headers=True,
+ version=version)
+ self.expected_success(202, resp.status)
+ return self._parse_resp(body)
+
+ def resync_share_replica(self, replica_id, expected_result=202,
+ version=LATEST_MICROVERSION):
+ """Force an immediate resync of the replica."""
+ uri = 'share-replicas/%s/action' % replica_id
+ post_body = {
+ 'resync': None
+ }
+ body = json.dumps(post_body)
+ resp, body = self.post(uri, body,
+ headers=EXPERIMENTAL,
+ extra_headers=True,
+ version=version)
+ self.expected_success(expected_result, resp.status)
+ return self._parse_resp(body)
+
+ def force_delete_share_replica(self, replica_id,
+ version=LATEST_MICROVERSION):
+ """Force delete a replica."""
+ uri = 'share-replicas/%s/action' % replica_id
+ post_body = {
+ 'force_delete': None
+ }
+ body = json.dumps(post_body)
+ resp, body = self.post(uri, body,
+ headers=EXPERIMENTAL,
+ extra_headers=True,
+ version=version)
+ self.expected_success(202, resp.status)
+ return self._parse_resp(body)
diff --git a/manila_tempest_tests/share_exceptions.py b/manila_tempest_tests/share_exceptions.py
index aa688e4..3a11531 100644
--- a/manila_tempest_tests/share_exceptions.py
+++ b/manila_tempest_tests/share_exceptions.py
@@ -13,7 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest_lib import exceptions
+from tempest.lib import exceptions
class ShareBuildErrorException(exceptions.TempestException):
@@ -65,3 +65,8 @@
class ResourceReleaseFailed(exceptions.TempestException):
message = "Failed to release resource '%(res_type)s' with id '%(res_id)s'."
+
+
+class ShareReplicationTypeException(exceptions.TempestException):
+ message = ("Option backend_replication_type is set to incorrect value: "
+ "%(replication_type)s")
diff --git a/manila_tempest_tests/tests/api/admin/test_admin_actions.py b/manila_tempest_tests/tests/api/admin/test_admin_actions.py
index 9ac085a..108173e 100644
--- a/manila_tempest_tests/tests/api/admin/test_admin_actions.py
+++ b/manila_tempest_tests/tests/api/admin/test_admin_actions.py
@@ -29,6 +29,8 @@
def resource_setup(cls):
super(AdminActionsTest, cls).resource_setup()
cls.states = ["error", "available"]
+ cls.task_states = ["migration_starting", "data_copying_in_progress",
+ "migration_success"]
cls.bad_status = "error_deleting"
cls.sh = cls.create_share()
cls.sh_instance = (
@@ -116,3 +118,11 @@
# Snapshot with status 'error_deleting' should be deleted
self.shares_v2_client.force_delete(sn["id"], s_type="snapshots")
self.shares_v2_client.wait_for_resource_deletion(snapshot_id=sn["id"])
+
+ @test.attr(type=["gate", ])
+ @base.skip_if_microversion_lt("2.15")
+ def test_reset_share_task_state(self):
+ for task_state in self.task_states:
+ self.shares_v2_client.reset_task_state(self.sh["id"], task_state)
+ self.shares_v2_client.wait_for_share_status(
+ self.sh["id"], task_state, 'task_state')
diff --git a/manila_tempest_tests/tests/api/admin/test_admin_actions_negative.py b/manila_tempest_tests/tests/api/admin/test_admin_actions_negative.py
index a1ff4f7..82fcd5a 100644
--- a/manila_tempest_tests/tests/api/admin/test_admin_actions_negative.py
+++ b/manila_tempest_tests/tests/api/admin/test_admin_actions_negative.py
@@ -14,8 +14,8 @@
# under the License.
from tempest import config # noqa
+from tempest.lib import exceptions as lib_exc # noqa
from tempest import test # noqa
-from tempest_lib import exceptions as lib_exc # noqa
import testtools # noqa
from manila_tempest_tests import clients_share as clients
@@ -166,3 +166,24 @@
self.assertRaises(lib_exc.Forbidden,
self.member_shares_v2_client.get_instances_of_share,
self.sh['id'])
+
+ @test.attr(type=["gate", "negative", ])
+ @base.skip_if_microversion_lt("2.15")
+ def test_reset_task_state_share_not_found(self):
+ self.assertRaises(
+ lib_exc.NotFound, self.shares_v2_client.reset_task_state,
+ 'fake_share', 'migration_error')
+
+ @test.attr(type=["gate", "negative", ])
+ @base.skip_if_microversion_lt("2.15")
+ def test_reset_task_state_empty(self):
+ self.assertRaises(
+ lib_exc.BadRequest, self.shares_v2_client.reset_task_state,
+ self.sh['id'], None)
+
+ @test.attr(type=["gate", "negative", ])
+ @base.skip_if_microversion_lt("2.15")
+ def test_reset_task_state_invalid_state(self):
+ self.assertRaises(
+ lib_exc.BadRequest, self.shares_v2_client.reset_task_state,
+ self.sh['id'], 'fake_state')
diff --git a/manila_tempest_tests/tests/api/admin/test_consistency_group_actions.py b/manila_tempest_tests/tests/api/admin/test_consistency_group_actions.py
index c057c36..085cb1e 100644
--- a/manila_tempest_tests/tests/api/admin/test_consistency_group_actions.py
+++ b/manila_tempest_tests/tests/api/admin/test_consistency_group_actions.py
@@ -14,8 +14,8 @@
# under the License.
from tempest import config
+from tempest.lib.common.utils import data_utils
from tempest import test
-from tempest_lib.common.utils import data_utils
import testtools
from manila_tempest_tests.tests.api import base
diff --git a/manila_tempest_tests/tests/api/admin/test_consistency_groups.py b/manila_tempest_tests/tests/api/admin/test_consistency_groups.py
index d562e45..a121c57 100644
--- a/manila_tempest_tests/tests/api/admin/test_consistency_groups.py
+++ b/manila_tempest_tests/tests/api/admin/test_consistency_groups.py
@@ -14,8 +14,8 @@
# under the License.
from tempest import config
+from tempest.lib.common.utils import data_utils
from tempest import test
-from tempest_lib.common.utils import data_utils
import testtools
from manila_tempest_tests.tests.api import base
diff --git a/manila_tempest_tests/tests/api/admin/test_consistency_groups_negative.py b/manila_tempest_tests/tests/api/admin/test_consistency_groups_negative.py
index eafb5e8..18cf5db 100644
--- a/manila_tempest_tests/tests/api/admin/test_consistency_groups_negative.py
+++ b/manila_tempest_tests/tests/api/admin/test_consistency_groups_negative.py
@@ -14,9 +14,9 @@
# under the License.
from tempest import config
+from tempest.lib.common.utils import data_utils
+from tempest.lib import exceptions
from tempest import test
-from tempest_lib.common.utils import data_utils
-from tempest_lib import exceptions
import testtools
from manila_tempest_tests.tests.api import base
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 4791cb0..a7b45a8 100644
--- a/manila_tempest_tests/tests/api/admin/test_export_locations.py
+++ b/manila_tempest_tests/tests/api/admin/test_export_locations.py
@@ -13,6 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
+import ddt
from oslo_utils import timeutils
from oslo_utils import uuidutils
import six
@@ -21,11 +22,14 @@
from manila_tempest_tests import clients_share as clients
from manila_tempest_tests.tests.api import base
+from manila_tempest_tests import utils
CONF = config.CONF
+LATEST_MICROVERSION = CONF.share.max_api_microversion
@base.skip_if_microversion_not_supported("2.9")
+@ddt.ddt
class ExportLocationsTest(base.BaseSharesAdminTest):
@classmethod
@@ -38,42 +42,82 @@
cls.share_instances = cls.shares_v2_client.get_instances_of_share(
cls.share['id'])
- def _verify_export_location_structure(self, export_locations,
- role='admin'):
- expected_keys = [
- 'created_at', 'updated_at', 'path', 'uuid',
- ]
- if role == 'admin':
- expected_keys.extend(['is_admin_only', 'share_instance_id'])
+ def _verify_export_location_structure(
+ self, export_locations, role='admin', version=LATEST_MICROVERSION,
+ format='summary'):
+
+ # Determine which keys to expect based on role, version and format
+ summary_keys = ['id', 'path']
+ if utils.is_microversion_ge(version, '2.14'):
+ summary_keys += ['preferred']
+
+ admin_summary_keys = summary_keys + [
+ 'share_instance_id', 'is_admin_only']
+
+ detail_keys = summary_keys + ['created_at', 'updated_at']
+
+ admin_detail_keys = admin_summary_keys + ['created_at', 'updated_at']
+
+ if format == 'summary':
+ if role == 'admin':
+ expected_keys = admin_summary_keys
+ else:
+ expected_keys = summary_keys
+ else:
+ if role == 'admin':
+ expected_keys = admin_detail_keys
+ else:
+ expected_keys = detail_keys
if not isinstance(export_locations, (list, tuple, set)):
export_locations = (export_locations, )
for export_location in export_locations:
+
+ # Check that the correct keys are present
self.assertEqual(len(expected_keys), len(export_location))
for key in expected_keys:
self.assertIn(key, export_location)
+
+ # Check the format of ever-present summary keys
+ self.assertTrue(uuidutils.is_uuid_like(export_location['id']))
+ self.assertTrue(isinstance(export_location['path'],
+ six.string_types))
+
+ if utils.is_microversion_ge(version, '2.14'):
+ self.assertIn(export_location['preferred'], (True, False))
+
if role == 'admin':
self.assertIn(export_location['is_admin_only'], (True, False))
- self.assertTrue(
- uuidutils.is_uuid_like(
- export_location['share_instance_id']))
- self.assertTrue(uuidutils.is_uuid_like(export_location['uuid']))
- self.assertTrue(
- isinstance(export_location['path'], six.string_types))
- for time in (export_location['created_at'],
- export_location['updated_at']):
- # If var 'time' has incorrect value then ValueError exception
- # is expected to be raised. So, just try parse it making
- # assertion that it has proper date value.
- timeutils.parse_strtime(time)
+ self.assertTrue(uuidutils.is_uuid_like(
+ export_location['share_instance_id']))
+
+ # Check the format of the detail keys
+ if format == 'detail':
+ for time in (export_location['created_at'],
+ export_location['updated_at']):
+ # If var 'time' has incorrect value then ValueError
+ # exception is expected to be raised. So, just try parse
+ # it making assertion that it has proper date value.
+ timeutils.parse_strtime(time)
@test.attr(type=["gate", ])
+ @utils.skip_if_microversion_not_supported('2.13')
def test_list_share_export_locations(self):
export_locations = self.admin_client.list_share_export_locations(
- self.share['id'])
+ self.share['id'], version='2.13')
- self._verify_export_location_structure(export_locations)
+ self._verify_export_location_structure(export_locations,
+ version='2.13')
+
+ @test.attr(type=["gate", ])
+ @utils.skip_if_microversion_not_supported('2.14')
+ def test_list_share_export_locations_with_preferred_flag(self):
+ export_locations = self.admin_client.list_share_export_locations(
+ self.share['id'], version='2.14')
+
+ self._verify_export_location_structure(export_locations,
+ version='2.14')
@test.attr(type=["gate", ])
def test_get_share_export_location(self):
@@ -82,15 +126,15 @@
for export_location in export_locations:
el = self.admin_client.get_share_export_location(
- self.share['id'], export_location['uuid'])
- self._verify_export_location_structure(el)
+ self.share['id'], export_location['id'])
+ self._verify_export_location_structure(el, format='detail')
@test.attr(type=["gate", ])
def test_list_share_export_locations_by_member(self):
export_locations = self.member_client.list_share_export_locations(
self.share['id'])
- self._verify_export_location_structure(export_locations, 'member')
+ self._verify_export_location_structure(export_locations, role='member')
@test.attr(type=["gate", ])
def test_get_share_export_location_by_member(self):
@@ -101,16 +145,29 @@
if export_location['is_admin_only']:
continue
el = self.member_client.get_share_export_location(
- self.share['id'], export_location['uuid'])
- self._verify_export_location_structure(el, 'member')
+ self.share['id'], export_location['id'])
+ self._verify_export_location_structure(el, role='member',
+ format='detail')
@test.attr(type=["gate", ])
+ @utils.skip_if_microversion_not_supported('2.13')
def test_list_share_instance_export_locations(self):
for share_instance in self.share_instances:
export_locations = (
self.admin_client.list_share_instance_export_locations(
- share_instance['id']))
- self._verify_export_location_structure(export_locations)
+ share_instance['id'], version='2.13'))
+ self._verify_export_location_structure(export_locations,
+ version='2.13')
+
+ @test.attr(type=["gate", ])
+ @utils.skip_if_microversion_not_supported('2.14')
+ def test_list_share_instance_export_locations_with_preferred_flag(self):
+ for share_instance in self.share_instances:
+ export_locations = (
+ self.admin_client.list_share_instance_export_locations(
+ share_instance['id'], version='2.14'))
+ self._verify_export_location_structure(export_locations,
+ version='2.14')
@test.attr(type=["gate", ])
def test_get_share_instance_export_location(self):
@@ -120,8 +177,8 @@
share_instance['id']))
for el in export_locations:
el = self.admin_client.get_share_instance_export_location(
- share_instance['id'], el['uuid'])
- self._verify_export_location_structure(el)
+ share_instance['id'], el['id'])
+ self._verify_export_location_structure(el, format='detail')
@test.attr(type=["gate", ])
def test_share_contains_all_export_locations_of_all_share_instances(self):
@@ -140,6 +197,6 @@
len(share_instances_export_locations)
)
self.assertEqual(
- sorted(share_export_locations, key=lambda el: el['uuid']),
- sorted(share_instances_export_locations, key=lambda el: el['uuid'])
+ sorted(share_export_locations, key=lambda el: el['id']),
+ sorted(share_instances_export_locations, key=lambda el: el['id'])
)
diff --git a/manila_tempest_tests/tests/api/admin/test_export_locations_negative.py b/manila_tempest_tests/tests/api/admin/test_export_locations_negative.py
index 9d53373..93ff5f0 100644
--- a/manila_tempest_tests/tests/api/admin/test_export_locations_negative.py
+++ b/manila_tempest_tests/tests/api/admin/test_export_locations_negative.py
@@ -14,8 +14,8 @@
# under the License.
from tempest import config
+from tempest.lib import exceptions as lib_exc
from tempest import test
-from tempest_lib import exceptions as lib_exc
from manila_tempest_tests import clients_share as clients
from manila_tempest_tests.tests.api import base
@@ -90,5 +90,5 @@
self.assertRaises(
lib_exc.Forbidden,
self.member_client.get_share_instance_export_location,
- share_instance['id'], el['uuid'],
+ share_instance['id'], el['id'],
)
diff --git a/manila_tempest_tests/tests/api/admin/test_migration.py b/manila_tempest_tests/tests/api/admin/test_migration.py
index 517f43d..96f657a 100644
--- a/manila_tempest_tests/tests/api/admin/test_migration.py
+++ b/manila_tempest_tests/tests/api/admin/test_migration.py
@@ -17,6 +17,7 @@
from tempest import test # noqa
from manila_tempest_tests.tests.api import base
+from manila_tempest_tests import utils
CONF = config.CONF
@@ -39,8 +40,45 @@
raise cls.skipException("Migration tests disabled. Skipping.")
@test.attr(type=["gate", ])
+ @base.skip_if_microversion_lt("2.5")
def test_migration_empty_v2_5(self):
+ share, dest_pool = self._setup_migration()
+
+ old_exports = share['export_locations']
+
+ share = self.migrate_share(share['id'], dest_pool, version='2.5')
+
+ self._validate_migration_successful(dest_pool, share, old_exports,
+ version='2.5')
+
+ @test.attr(type=["gate", ])
+ @base.skip_if_microversion_lt("2.15")
+ def test_migration_completion_empty_v2_15(self):
+
+ share, dest_pool = self._setup_migration()
+
+ old_exports = self.shares_v2_client.list_share_export_locations(
+ share['id'], version='2.15')
+ self.assertNotEmpty(old_exports)
+ old_exports = [x['path'] for x in old_exports
+ if x['is_admin_only'] is False]
+ self.assertNotEmpty(old_exports)
+
+ share = self.migrate_share(
+ share['id'], dest_pool, version='2.15', notify=False,
+ wait_for_status='data_copying_completed')
+
+ self._validate_migration_successful(dest_pool, share,
+ old_exports, '2.15', notify=False)
+
+ share = self.migration_complete(share['id'], dest_pool, version='2.15')
+
+ self._validate_migration_successful(dest_pool, share, old_exports,
+ version='2.15')
+
+ def _setup_migration(self):
+
pools = self.shares_client.list_pools()['pools']
if len(pools) < 2:
@@ -51,6 +89,18 @@
share = self.create_share(self.protocol)
share = self.shares_client.get_share(share['id'])
+ self.shares_v2_client.create_access_rule(
+ share['id'], access_to="50.50.50.50", access_level="rw")
+
+ self.shares_v2_client.wait_for_share_status(
+ share['id'], 'active', status_attr='access_rules_status')
+
+ self.shares_v2_client.create_access_rule(
+ share['id'], access_to="51.51.51.51", access_level="ro")
+
+ self.shares_v2_client.wait_for_share_status(
+ share['id'], 'active', status_attr='access_rules_status')
+
dest_pool = next((x for x in pools if x['name'] != share['host']),
None)
@@ -59,10 +109,30 @@
dest_pool = dest_pool['name']
- old_export_location = share['export_locations'][0]
+ return share, dest_pool
- share = self.migrate_share(share['id'], dest_pool, version='2.5')
+ def _validate_migration_successful(self, dest_pool, share,
+ old_exports, version, notify=True):
+ if utils.is_microversion_lt(version, '2.9'):
+ new_exports = share['export_locations']
+ self.assertNotEmpty(new_exports)
+ else:
+ new_exports = self.shares_v2_client.list_share_export_locations(
+ share['id'], version='2.9')
+ self.assertNotEmpty(new_exports)
+ new_exports = [x['path'] for x in new_exports if
+ x['is_admin_only'] is False]
+ self.assertNotEmpty(new_exports)
- self.assertEqual(dest_pool, share['host'])
- self.assertNotEqual(old_export_location, share['export_locations'][0])
- self.assertEqual('migration_success', share['task_state'])
+ # Share migrated
+ if notify:
+ self.assertEqual(dest_pool, share['host'])
+ for export in old_exports:
+ self.assertFalse(export in new_exports)
+ self.assertEqual('migration_success', share['task_state'])
+ # Share not migrated yet
+ else:
+ self.assertNotEqual(dest_pool, share['host'])
+ for export in old_exports:
+ self.assertTrue(export in new_exports)
+ self.assertEqual('data_copying_completed', share['task_state'])
diff --git a/manila_tempest_tests/tests/api/admin/test_migration_negative.py b/manila_tempest_tests/tests/api/admin/test_migration_negative.py
new file mode 100644
index 0000000..b7d75c4
--- /dev/null
+++ b/manila_tempest_tests/tests/api/admin/test_migration_negative.py
@@ -0,0 +1,97 @@
+# Copyright 2015 Hitachi Data Systems.
+# 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 # noqa
+from tempest.lib import exceptions as lib_exc # noqa
+from tempest import test # noqa
+
+from manila_tempest_tests.tests.api import base
+
+CONF = config.CONF
+
+
+class MigrationNFSTest(base.BaseSharesAdminTest):
+ """Tests Share Migration.
+
+ Tests migration in multi-backend environment.
+ """
+
+ protocol = "nfs"
+
+ @classmethod
+ def resource_setup(cls):
+ super(MigrationNFSTest, cls).resource_setup()
+ if not CONF.share.run_migration_tests:
+ raise cls.skipException("Migration tests disabled. Skipping.")
+
+ cls.share = cls.create_share(cls.protocol)
+ cls.share = cls.shares_client.get_share(cls.share['id'])
+ pools = cls.shares_client.list_pools()['pools']
+
+ if len(pools) < 2:
+ raise cls.skipException("At least two different pool entries "
+ "are needed to run migration tests. "
+ "Skipping.")
+ cls.dest_pool = next((x for x in pools
+ if x['name'] != cls.share['host']), None)
+
+ @test.attr(type=["gate", "negative", ])
+ @base.skip_if_microversion_lt("2.15")
+ def test_migration_cancel_invalid(self):
+ self.assertRaises(
+ lib_exc.BadRequest, self.shares_v2_client.migration_cancel,
+ self.share['id'])
+
+ @test.attr(type=["gate", "negative", ])
+ @base.skip_if_microversion_lt("2.15")
+ def test_migration_get_progress_invalid(self):
+ self.assertRaises(
+ lib_exc.BadRequest, self.shares_v2_client.migration_get_progress,
+ self.share['id'])
+
+ @test.attr(type=["gate", "negative", ])
+ @base.skip_if_microversion_lt("2.15")
+ def test_migration_complete_invalid(self):
+ self.assertRaises(
+ lib_exc.BadRequest, self.shares_v2_client.migration_complete,
+ self.share['id'])
+
+ @test.attr(type=["gate", "negative", ])
+ @base.skip_if_microversion_lt("2.5")
+ def test_migrate_share_with_snapshot_v2_5(self):
+ snap = self.create_snapshot_wait_for_active(self.share['id'])
+ self.assertRaises(
+ lib_exc.BadRequest, self.shares_v2_client.migrate_share,
+ self.share['id'], self.dest_pool, True, version='2.5')
+ self.shares_client.delete_snapshot(snap['id'])
+ self.shares_client.wait_for_resource_deletion(snapshot_id=snap["id"])
+
+ @test.attr(type=["gate", "negative", ])
+ @base.skip_if_microversion_lt("2.5")
+ def test_migrate_share_same_host_v2_5(self):
+ self.assertRaises(
+ lib_exc.BadRequest, self.shares_v2_client.migrate_share,
+ self.share['id'], self.share['host'], True, version='2.5')
+
+ @test.attr(type=["gate", "negative", ])
+ @base.skip_if_microversion_lt("2.5")
+ def test_migrate_share_not_available_v2_5(self):
+ self.shares_client.reset_state(self.share['id'], 'error')
+ self.shares_client.wait_for_share_status(self.share['id'], 'error')
+ self.assertRaises(
+ lib_exc.BadRequest, self.shares_v2_client.migrate_share,
+ self.share['id'], self.dest_pool, True, version='2.5')
+ self.shares_client.reset_state(self.share['id'], 'available')
+ self.shares_client.wait_for_share_status(self.share['id'], 'available')
diff --git a/manila_tempest_tests/tests/api/admin/test_multi_backend.py b/manila_tempest_tests/tests/api/admin/test_multi_backend.py
index 4e69eda..59f56c8 100644
--- a/manila_tempest_tests/tests/api/admin/test_multi_backend.py
+++ b/manila_tempest_tests/tests/api/admin/test_multi_backend.py
@@ -14,8 +14,8 @@
# under the License.
from tempest import config # noqa
+from tempest.lib.common.utils import data_utils # noqa
from tempest import test # noqa
-from tempest_lib.common.utils import data_utils # noqa
from manila_tempest_tests.tests.api import base
diff --git a/manila_tempest_tests/tests/api/admin/test_quotas.py b/manila_tempest_tests/tests/api/admin/test_quotas.py
index badedc2..3c99083 100644
--- a/manila_tempest_tests/tests/api/admin/test_quotas.py
+++ b/manila_tempest_tests/tests/api/admin/test_quotas.py
@@ -26,6 +26,9 @@
@classmethod
def resource_setup(cls):
+ if not CONF.share.run_quota_tests:
+ msg = "Quota tests are disabled."
+ raise cls.skipException(msg)
cls.os = clients.AdminManager()
super(SharesAdminQuotasTest, cls).resource_setup()
cls.user_id = cls.shares_v2_client.user_id
@@ -65,6 +68,14 @@
force_tenant_isolation = True
client_version = '2'
+ @classmethod
+ def resource_setup(cls):
+ if not CONF.share.run_quota_tests:
+ msg = "Quota tests are disabled."
+ raise cls.skipException(msg)
+ cls.os = clients.AdminManager()
+ super(SharesAdminQuotasUpdateTest, cls).resource_setup()
+
def setUp(self):
super(self.__class__, self).setUp()
self.client = self.get_client_with_isolated_creds(
diff --git a/manila_tempest_tests/tests/api/admin/test_quotas_negative.py b/manila_tempest_tests/tests/api/admin/test_quotas_negative.py
index 7850a2d..985bef4 100644
--- a/manila_tempest_tests/tests/api/admin/test_quotas_negative.py
+++ b/manila_tempest_tests/tests/api/admin/test_quotas_negative.py
@@ -14,8 +14,8 @@
# under the License.
from tempest import config # noqa
+from tempest.lib import exceptions as lib_exc # noqa
from tempest import test # noqa
-from tempest_lib import exceptions as lib_exc # noqa
import testtools # noqa
from manila_tempest_tests import clients_share as clients
@@ -30,6 +30,9 @@
@classmethod
def resource_setup(cls):
+ if not CONF.share.run_quota_tests:
+ msg = "Quota tests are disabled."
+ raise cls.skipException(msg)
cls.os = clients.AdminManager()
super(SharesAdminQuotasNegativeTest, cls).resource_setup()
cls.user_id = cls.shares_client.user_id
diff --git a/manila_tempest_tests/tests/api/admin/test_replication.py b/manila_tempest_tests/tests/api/admin/test_replication.py
new file mode 100644
index 0000000..605656c
--- /dev/null
+++ b/manila_tempest_tests/tests/api/admin/test_replication.py
@@ -0,0 +1,173 @@
+# Copyright 2015 Yogesh Kshirsagar
+# 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 import test
+import testtools
+
+from manila_tempest_tests import clients_share as clients
+from manila_tempest_tests.common import constants
+from manila_tempest_tests import share_exceptions
+from manila_tempest_tests.tests.api import base
+
+CONF = config.CONF
+_MIN_SUPPORTED_MICROVERSION = '2.11'
+
+
+@testtools.skipUnless(CONF.share.run_replication_tests,
+ 'Replication tests are disabled.')
+@base.skip_if_microversion_lt(_MIN_SUPPORTED_MICROVERSION)
+class ReplicationAdminTest(base.BaseSharesAdminTest):
+
+ @classmethod
+ def resource_setup(cls):
+ super(ReplicationAdminTest, cls).resource_setup()
+ # Create share_type
+ name = data_utils.rand_name(constants.TEMPEST_MANILA_PREFIX)
+ cls.admin_client = clients.AdminManager().shares_v2_client
+ cls.replication_type = CONF.share.backend_replication_type
+
+ if cls.replication_type not in constants.REPLICATION_TYPE_CHOICES:
+ raise share_exceptions.ShareReplicationTypeException(
+ replication_type=cls.replication_type
+ )
+ cls.zones = cls.get_availability_zones(client=cls.admin_client)
+ cls.share_zone = cls.zones[0]
+ cls.replica_zone = cls.zones[-1]
+
+ cls.extra_specs = cls.add_required_extra_specs_to_dict(
+ {"replication_type": cls.replication_type})
+ share_type = cls.create_share_type(
+ name,
+ extra_specs=cls.extra_specs,
+ client=cls.admin_client)
+ cls.share_type = share_type["share_type"]
+ # Create share with above share_type
+ cls.share = cls.create_share(share_type_id=cls.share_type["id"],
+ availability_zone=cls.share_zone,)
+ cls.replica = cls.shares_v2_client.list_share_replicas(
+ share_id=cls.share['id'])[0]
+
+ @staticmethod
+ def _filter_share_replica_list(replica_list, r_state):
+ # Iterate through replica list to filter based on replica_state
+ return [replica['id'] for replica in replica_list
+ if replica['replica_state'] == r_state]
+
+ @test.attr(type=["gate", ])
+ def test_promote_out_of_sync_share_replica(self):
+ """Test promote 'out_of_sync' share replica to active state."""
+ if (self.replication_type
+ not in constants.REPLICATION_PROMOTION_CHOICES):
+ msg = "Option backend_replication_type should be one of (%s)!"
+ raise self.skipException(
+ msg % ','.join(constants.REPLICATION_PROMOTION_CHOICES))
+ share = self.create_share(share_type_id=self.share_type['id'])
+ original_replica = self.shares_v2_client.list_share_replicas(
+ share_id=share['id'])[0]
+
+ # NOTE(Yogi1): Cleanup needs to be disabled for replica that is
+ # being promoted since it will become the 'primary'/'active' replica.
+ replica = self.create_share_replica(share["id"], self.replica_zone,
+ cleanup=False)
+
+ # List replicas
+ replica_list = self.admin_client.list_share_replicas(
+ share_id=share['id'])
+
+ # Check if there is only 1 'active' replica before promotion.
+ active_replicas = self._filter_share_replica_list(
+ replica_list, constants.REPLICATION_STATE_ACTIVE)
+ self.assertEqual(1, len(active_replicas))
+
+ # Set replica_state to 'out_of_sync'
+ self.admin_client.reset_share_replica_state(
+ replica['id'], constants.REPLICATION_STATE_OUT_OF_SYNC)
+ self.shares_v2_client.wait_for_share_replica_status(
+ replica['id'], constants.REPLICATION_STATE_OUT_OF_SYNC,
+ status_attr='replica_state')
+
+ # Promote 'out_of_sync' replica to 'active' state.
+ self.promote_share_replica(replica['id'], self.admin_client)
+ # Original replica will need to be cleaned up before the promoted
+ # replica can be deleted.
+ self.addCleanup(self.delete_share_replica, original_replica['id'])
+
+ # Check if there is still only 1 'active' replica after promotion.
+ replica_list = self.shares_v2_client.list_share_replicas(
+ share_id=self.share["id"])
+ new_active_replicas = self._filter_share_replica_list(
+ replica_list, constants.REPLICATION_STATE_ACTIVE)
+ self.assertEqual(1, len(new_active_replicas))
+
+ @test.attr(type=["gate", ])
+ def test_force_delete_share_replica(self):
+ """Test force deleting a replica that is in 'error_deleting' status."""
+ replica = self.create_share_replica(self.share['id'],
+ self.replica_zone,
+ cleanup_in_class=False)
+ self.admin_client.reset_share_replica_status(
+ replica['id'], constants.STATUS_ERROR_DELETING)
+ self.shares_v2_client.wait_for_share_replica_status(
+ replica['id'], constants.STATUS_ERROR_DELETING)
+ self.admin_client.force_delete_share_replica(replica['id'])
+ self.shares_v2_client.wait_for_resource_deletion(
+ replica_id=replica['id'])
+
+ @test.attr(type=["gate", ])
+ def test_reset_share_replica_status(self):
+ """Test resetting a replica's 'status' attribute."""
+ replica = self.create_share_replica(self.share['id'],
+ self.replica_zone,
+ cleanup_in_class=False)
+ self.admin_client.reset_share_replica_status(replica['id'],
+ constants.STATUS_ERROR)
+ self.shares_v2_client.wait_for_share_replica_status(
+ replica['id'], constants.STATUS_ERROR)
+
+ @test.attr(type=["gate", ])
+ def test_reset_share_replica_state(self):
+ """Test resetting a replica's 'replica_state' attribute."""
+ replica = self.create_share_replica(self.share['id'],
+ self.replica_zone,
+ cleanup_in_class=False)
+ self.admin_client.reset_share_replica_state(replica['id'],
+ constants.STATUS_ERROR)
+ self.shares_v2_client.wait_for_share_replica_status(
+ replica['id'], constants.STATUS_ERROR, status_attr='replica_state')
+
+ @test.attr(type=["gate", ])
+ def test_resync_share_replica(self):
+ """Test resyncing a replica."""
+ replica = self.create_share_replica(self.share['id'],
+ self.replica_zone,
+ cleanup_in_class=False)
+ self.shares_v2_client.wait_for_share_replica_status(
+ replica['id'], constants.REPLICATION_STATE_IN_SYNC,
+ status_attr='replica_state')
+
+ # Set replica_state to 'out_of_sync'.
+ self.admin_client.reset_share_replica_state(
+ replica['id'], constants.REPLICATION_STATE_OUT_OF_SYNC)
+ self.shares_v2_client.wait_for_share_replica_status(
+ replica['id'], constants.REPLICATION_STATE_OUT_OF_SYNC,
+ status_attr='replica_state')
+
+ # Attempt resync
+ self.admin_client.resync_share_replica(replica['id'])
+ self.shares_v2_client.wait_for_share_replica_status(
+ replica['id'], constants.REPLICATION_STATE_IN_SYNC,
+ status_attr='replica_state')
diff --git a/manila_tempest_tests/tests/api/admin/test_scheduler_stats.py b/manila_tempest_tests/tests/api/admin/test_scheduler_stats.py
index 973e52e..d1ef6ef 100644
--- a/manila_tempest_tests/tests/api/admin/test_scheduler_stats.py
+++ b/manila_tempest_tests/tests/api/admin/test_scheduler_stats.py
@@ -13,8 +13,8 @@
# under the License.
from tempest import config # noqa
+from tempest.lib import exceptions as lib_exc # noqa
from tempest import test # noqa
-from tempest_lib import exceptions as lib_exc # noqa
from manila_tempest_tests.tests.api import base
diff --git a/manila_tempest_tests/tests/api/admin/test_services_negative.py b/manila_tempest_tests/tests/api/admin/test_services_negative.py
index a229c45..b32ee5a 100644
--- a/manila_tempest_tests/tests/api/admin/test_services_negative.py
+++ b/manila_tempest_tests/tests/api/admin/test_services_negative.py
@@ -14,8 +14,8 @@
# under the License.
import ddt
+from tempest.lib import exceptions as lib_exc
from tempest import test
-from tempest_lib import exceptions as lib_exc
from manila_tempest_tests import clients_share as clients
from manila_tempest_tests.tests.api import base
diff --git a/manila_tempest_tests/tests/api/admin/test_share_manage.py b/manila_tempest_tests/tests/api/admin/test_share_manage.py
index bbd98bc..9908148 100644
--- a/manila_tempest_tests/tests/api/admin/test_share_manage.py
+++ b/manila_tempest_tests/tests/api/admin/test_share_manage.py
@@ -15,9 +15,9 @@
import six
from tempest import config
+from tempest.lib.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest import test
-from tempest_lib.common.utils import data_utils
-from tempest_lib import exceptions as lib_exc
import testtools
from manila_tempest_tests.tests.api import base
@@ -196,3 +196,7 @@
class ManageHDFSShareTest(ManageNFSShareTest):
protocol = 'hdfs'
+
+
+class ManageCephFSShareTest(ManageNFSShareTest):
+ protocol = 'cephfs'
diff --git a/manila_tempest_tests/tests/api/admin/test_share_servers.py b/manila_tempest_tests/tests/api/admin/test_share_servers.py
index fb803fc..df13ce9 100644
--- a/manila_tempest_tests/tests/api/admin/test_share_servers.py
+++ b/manila_tempest_tests/tests/api/admin/test_share_servers.py
@@ -17,8 +17,8 @@
import six # noqa
from tempest import config # noqa
+from tempest.lib import exceptions as lib_exc # noqa
from tempest import test # noqa
-from tempest_lib import exceptions as lib_exc # noqa
from manila_tempest_tests.tests.api import base
@@ -219,7 +219,7 @@
neutron_subnet_id=self.share_network['neutron_subnet_id'])
# Create server with share
- share = self.create_share(share_network_id=new_sn['id'])
+ self.create_share(share_network_id=new_sn['id'])
# List share servers, filtered by share_network_id
search_opts = {"share_network": new_sn["id"]}
@@ -238,7 +238,6 @@
shares = self.shares_client.list_shares_with_detail(params)
for s in shares:
self.assertEqual(new_sn["id"], s["share_network_id"])
- self.assertTrue(any(share["id"] == s["id"] for s in shares))
# Delete shares, so we will have share server without shares
for s in shares:
diff --git a/manila_tempest_tests/tests/api/admin/test_share_servers_negative.py b/manila_tempest_tests/tests/api/admin/test_share_servers_negative.py
index a308ccf..e4712aa 100644
--- a/manila_tempest_tests/tests/api/admin/test_share_servers_negative.py
+++ b/manila_tempest_tests/tests/api/admin/test_share_servers_negative.py
@@ -13,9 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
+from tempest.lib.common.utils import data_utils # noqa
+from tempest.lib import exceptions as lib_exc # noqa
from tempest import test # noqa
-from tempest_lib.common.utils import data_utils # noqa
-from tempest_lib import exceptions as lib_exc # noqa
from manila_tempest_tests import clients_share as clients
from manila_tempest_tests.tests.api import base
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 a521aad..c1a17cd 100644
--- a/manila_tempest_tests/tests/api/admin/test_share_types.py
+++ b/manila_tempest_tests/tests/api/admin/test_share_types.py
@@ -15,9 +15,9 @@
import ddt
from tempest import config # noqa
+from tempest.lib.common.utils import data_utils # noqa
+from tempest.lib import exceptions as lib_exc # noqa
from tempest import test # noqa
-from tempest_lib.common.utils import data_utils # noqa
-from tempest_lib import exceptions as lib_exc # noqa
from manila_tempest_tests.tests.api import base
from manila_tempest_tests import utils
diff --git a/manila_tempest_tests/tests/api/admin/test_share_types_extra_specs.py b/manila_tempest_tests/tests/api/admin/test_share_types_extra_specs.py
index c4be5a8..c387765 100644
--- a/manila_tempest_tests/tests/api/admin/test_share_types_extra_specs.py
+++ b/manila_tempest_tests/tests/api/admin/test_share_types_extra_specs.py
@@ -15,8 +15,8 @@
import copy
+from tempest.lib.common.utils import data_utils # noqa
from tempest import test # noqa
-from tempest_lib.common.utils import data_utils # noqa
from manila_tempest_tests.tests.api import base
diff --git a/manila_tempest_tests/tests/api/admin/test_share_types_extra_specs_negative.py b/manila_tempest_tests/tests/api/admin/test_share_types_extra_specs_negative.py
index 600072a..771f240 100644
--- a/manila_tempest_tests/tests/api/admin/test_share_types_extra_specs_negative.py
+++ b/manila_tempest_tests/tests/api/admin/test_share_types_extra_specs_negative.py
@@ -13,9 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
+from tempest.lib.common.utils import data_utils # noqa
+from tempest.lib import exceptions as lib_exc # noqa
from tempest import test # noqa
-from tempest_lib.common.utils import data_utils # noqa
-from tempest_lib import exceptions as lib_exc # noqa
from manila_tempest_tests import clients_share as clients
from manila_tempest_tests.tests.api import base
diff --git a/manila_tempest_tests/tests/api/admin/test_share_types_negative.py b/manila_tempest_tests/tests/api/admin/test_share_types_negative.py
index 32c9620..2f41e76 100644
--- a/manila_tempest_tests/tests/api/admin/test_share_types_negative.py
+++ b/manila_tempest_tests/tests/api/admin/test_share_types_negative.py
@@ -13,9 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
+from tempest.lib.common.utils import data_utils # noqa
+from tempest.lib import exceptions as lib_exc # noqa
from tempest import test # noqa
-from tempest_lib.common.utils import data_utils # noqa
-from tempest_lib import exceptions as lib_exc # noqa
from manila_tempest_tests import clients_share as clients
from manila_tempest_tests.tests.api import base
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 e630123..696eecb 100644
--- a/manila_tempest_tests/tests/api/admin/test_shares_actions.py
+++ b/manila_tempest_tests/tests/api/admin/test_shares_actions.py
@@ -14,8 +14,8 @@
# under the License.
from tempest import config # noqa
+from tempest.lib.common.utils import data_utils # noqa
from tempest import test # noqa
-from tempest_lib.common.utils import data_utils # noqa
import testtools # noqa
from manila_tempest_tests.tests.api import base
diff --git a/manila_tempest_tests/tests/api/admin/test_snapshot_manage.py b/manila_tempest_tests/tests/api/admin/test_snapshot_manage.py
new file mode 100644
index 0000000..26ff1af
--- /dev/null
+++ b/manila_tempest_tests/tests/api/admin/test_snapshot_manage.py
@@ -0,0 +1,143 @@
+# Copyright 2015 EMC Corporation.
+# 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 six
+from tempest import config
+from tempest.lib.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
+from tempest import test
+import testtools
+
+from manila_tempest_tests.tests.api import base
+
+CONF = config.CONF
+
+
+class ManageNFSSnapshotTest(base.BaseSharesAdminTest):
+ protocol = 'nfs'
+
+ # NOTE(vponomaryov): be careful running these tests using generic driver
+ # because cinder volume snapshots won't be deleted.
+
+ @classmethod
+ @base.skip_if_microversion_lt("2.12")
+ @testtools.skipIf(
+ CONF.share.multitenancy_enabled,
+ "Only for driver_handles_share_servers = False driver mode.")
+ @testtools.skipUnless(
+ CONF.share.run_manage_unmanage_snapshot_tests,
+ "Manage/unmanage snapshot tests are disabled.")
+ def resource_setup(cls):
+ super(ManageNFSSnapshotTest, cls).resource_setup()
+ if cls.protocol not in CONF.share.enable_protocols:
+ message = "%s tests are disabled" % cls.protocol
+ raise cls.skipException(message)
+
+ # Create share type
+ cls.st_name = data_utils.rand_name("tempest-manage-st-name")
+ cls.extra_specs = {
+ 'storage_protocol': CONF.share.capability_storage_protocol,
+ 'driver_handles_share_servers': False,
+ 'snapshot_support': six.text_type(
+ CONF.share.capability_snapshot_support),
+ }
+
+ cls.st = cls.create_share_type(
+ name=cls.st_name,
+ cleanup_in_class=True,
+ extra_specs=cls.extra_specs)
+
+ creation_data = {'kwargs': {
+ 'share_type_id': cls.st['share_type']['id'],
+ 'share_protocol': cls.protocol,
+ }}
+
+ # Data for creating shares
+ data = [creation_data]
+ shares_created = cls.create_shares(data)
+
+ cls.snapshot = None
+ cls.shares = []
+ # Load all share data (host, etc.)
+ for share in shares_created:
+ cls.shares.append(cls.shares_v2_client.get_share(share['id']))
+ # Create snapshot
+ snap_name = data_utils.rand_name("tempest-snapshot-name")
+ snap_desc = data_utils.rand_name(
+ "tempest-snapshot-description")
+ snap = cls.create_snapshot_wait_for_active(
+ share['id'], snap_name, snap_desc)
+ cls.snapshot = cls.shares_v2_client.get_snapshot(snap['id'])
+ # Unmanage snapshot
+ cls.shares_v2_client.unmanage_snapshot(snap['id'])
+ cls.shares_client.wait_for_resource_deletion(
+ snapshot_id=snap['id'])
+
+ def _test_manage(self, snapshot, version=CONF.share.max_api_microversion):
+ name = ("Name for 'managed' snapshot that had ID %s" %
+ snapshot['id'])
+ description = "Description for 'managed' snapshot"
+
+ # Manage snapshot
+ share_id = snapshot['share_id']
+ snapshot = self.shares_v2_client.manage_snapshot(
+ share_id,
+ snapshot['provider_location'],
+ name=name,
+ description=description,
+ driver_options={}
+ )
+
+ # Add managed snapshot to cleanup queue
+ self.method_resources.insert(
+ 0, {'type': 'snapshot', 'id': snapshot['id'],
+ 'client': self.shares_v2_client})
+
+ # Wait for success
+ self.shares_v2_client.wait_for_snapshot_status(snapshot['id'],
+ 'available')
+
+ # Verify data of managed snapshot
+ get_snapshot = self.shares_v2_client.get_snapshot(snapshot['id'])
+ self.assertEqual(name, get_snapshot['name'])
+ self.assertEqual(description, get_snapshot['description'])
+ self.assertEqual(snapshot['share_id'], get_snapshot['share_id'])
+ self.assertEqual(snapshot['provider_location'],
+ get_snapshot['provider_location'])
+
+ # Delete snapshot
+ self.shares_v2_client.delete_snapshot(get_snapshot['id'])
+ self.shares_client.wait_for_resource_deletion(
+ snapshot_id=get_snapshot['id'])
+ self.assertRaises(lib_exc.NotFound,
+ self.shares_v2_client.get_snapshot,
+ get_snapshot['id'])
+
+ @test.attr(type=["gate", "smoke"])
+ def test_manage(self):
+ # Manage snapshot
+ self._test_manage(snapshot=self.snapshot)
+
+
+class ManageCIFSSnapshotTest(ManageNFSSnapshotTest):
+ protocol = 'cifs'
+
+
+class ManageGLUSTERFSSnapshotTest(ManageNFSSnapshotTest):
+ protocol = 'glusterfs'
+
+
+class ManageHDFSSnapshotTest(ManageNFSSnapshotTest):
+ protocol = 'hdfs'
diff --git a/manila_tempest_tests/tests/api/admin/test_snapshot_manage_negative.py b/manila_tempest_tests/tests/api/admin/test_snapshot_manage_negative.py
new file mode 100644
index 0000000..2aba07c
--- /dev/null
+++ b/manila_tempest_tests/tests/api/admin/test_snapshot_manage_negative.py
@@ -0,0 +1,109 @@
+# Copyright 2015 EMC Corporation.
+# 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 six
+from tempest import config
+from tempest.lib.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
+from tempest import test
+import testtools
+
+from manila_tempest_tests.tests.api import base
+
+CONF = config.CONF
+
+
+class ManageNFSSnapshotNegativeTest(base.BaseSharesAdminTest):
+ protocol = 'nfs'
+
+ @classmethod
+ @base.skip_if_microversion_lt("2.12")
+ @testtools.skipIf(
+ CONF.share.multitenancy_enabled,
+ "Only for driver_handles_share_servers = False driver mode.")
+ @testtools.skipUnless(
+ CONF.share.run_manage_unmanage_snapshot_tests,
+ "Manage/unmanage snapshot tests are disabled.")
+ def resource_setup(cls):
+ super(ManageNFSSnapshotNegativeTest, cls).resource_setup()
+ if cls.protocol not in CONF.share.enable_protocols:
+ message = "%s tests are disabled" % cls.protocol
+ raise cls.skipException(message)
+
+ # Create share type
+ cls.st_name = data_utils.rand_name("tempest-manage-st-name")
+ cls.extra_specs = {
+ 'storage_protocol': CONF.share.capability_storage_protocol,
+ 'driver_handles_share_servers': False,
+ 'snapshot_support': six.text_type(
+ CONF.share.capability_snapshot_support),
+ }
+
+ cls.st = cls.create_share_type(
+ name=cls.st_name,
+ cleanup_in_class=True,
+ extra_specs=cls.extra_specs)
+
+ # Create share
+ cls.share = cls.create_share(
+ share_type_id=cls.st['share_type']['id'],
+ share_protocol=cls.protocol
+ )
+
+ @test.attr(type=["gate", "smoke", "negative", ])
+ def test_manage_not_found(self):
+ # Manage snapshot fails
+ self.assertRaises(lib_exc.NotFound,
+ self.shares_v2_client.manage_snapshot,
+ 'fake-share-id',
+ 'fake-vol-snap-id',
+ driver_options={})
+
+ @test.attr(type=["gate", "smoke", "negative", ])
+ def test_manage_already_exists(self):
+ # Manage already existing snapshot fails
+
+ # Create snapshot
+ snap = self.create_snapshot_wait_for_active(self.share['id'])
+ get_snap = self.shares_v2_client.get_snapshot(snap['id'])
+ self.assertEqual(self.share['id'], get_snap['share_id'])
+ self.assertIsNotNone(get_snap['provider_location'])
+
+ # Manage snapshot fails
+ self.assertRaises(lib_exc.Conflict,
+ self.shares_v2_client.manage_snapshot,
+ self.share['id'],
+ get_snap['provider_location'],
+ driver_options={})
+
+ # Delete snapshot
+ self.shares_v2_client.delete_snapshot(get_snap['id'])
+ self.shares_client.wait_for_resource_deletion(
+ snapshot_id=get_snap['id'])
+ self.assertRaises(lib_exc.NotFound,
+ self.shares_v2_client.get_snapshot,
+ get_snap['id'])
+
+
+class ManageCIFSSnapshotNegativeTest(ManageNFSSnapshotNegativeTest):
+ protocol = 'cifs'
+
+
+class ManageGLUSTERFSSnapshotNegativeTest(ManageNFSSnapshotNegativeTest):
+ protocol = 'glusterfs'
+
+
+class ManageHDFSSnapshotNegativeTest(ManageNFSSnapshotNegativeTest):
+ protocol = 'hdfs'
diff --git a/manila_tempest_tests/tests/api/base.py b/manila_tempest_tests/tests/api/base.py
index f9a678d..0395251 100644
--- a/manila_tempest_tests/tests/api/base.py
+++ b/manila_tempest_tests/tests/api/base.py
@@ -23,11 +23,12 @@
from tempest.common import credentials_factory as common_creds
from tempest.common import dynamic_creds
from tempest import config
+from tempest.lib.common.utils import data_utils
+from tempest.lib import exceptions
from tempest import test
-from tempest_lib.common.utils import data_utils
-from tempest_lib import exceptions
from manila_tempest_tests import clients_share as clients
+from manila_tempest_tests.common import constants
from manila_tempest_tests import share_exceptions
from manila_tempest_tests import utils
@@ -79,13 +80,14 @@
skip_if_microversion_not_supported = utils.skip_if_microversion_not_supported
+skip_if_microversion_lt = utils.skip_if_microversion_lt
class BaseSharesTest(test.BaseTestCase):
"""Base test case class for all Manila API tests."""
force_tenant_isolation = False
- protocols = ["nfs", "cifs", "glusterfs", "hdfs"]
+ protocols = ["nfs", "cifs", "glusterfs", "hdfs", "cephfs"]
# Will be cleaned up in resource_cleanup
class_resources = []
@@ -104,6 +106,13 @@
raise self.skipException(
"Microversion '%s' is not supported." % microversion)
+ def skip_if_microversion_lt(self, microversion):
+ if utils.is_microversion_lt(CONF.share.max_api_microversion,
+ microversion):
+ raise self.skipException(
+ "Microversion must be greater than or equal to '%s'." %
+ microversion)
+
@classmethod
def get_client_with_isolated_creds(cls,
name=None,
@@ -335,11 +344,22 @@
return share
@classmethod
- def migrate_share(cls, share_id, dest_host, client=None, **kwargs):
+ def migrate_share(cls, share_id, dest_host, client=None, notify=True,
+ wait_for_status='migration_success', **kwargs):
client = client or cls.shares_v2_client
- client.migrate_share(share_id, dest_host, **kwargs)
- share = client.wait_for_migration_completed(
- share_id, dest_host, version=kwargs.get('version'))
+ client.migrate_share(share_id, dest_host, notify, **kwargs)
+ share = client.wait_for_migration_status(
+ share_id, dest_host, wait_for_status,
+ version=kwargs.get('version'))
+ return share
+
+ @classmethod
+ def migration_complete(cls, share_id, dest_host, client=None, **kwargs):
+ client = client or cls.shares_v2_client
+ client.migration_complete(share_id, **kwargs)
+ share = client.wait_for_migration_status(
+ share_id, dest_host, 'migration_success',
+ version=kwargs.get('version'))
return share
@classmethod
@@ -489,6 +509,56 @@
return cgsnapshot
@classmethod
+ def get_availability_zones(cls, client=None):
+ """List the availability zones for "manila-share" services
+
+ that are currently in "up" state.
+ """
+ client = client or cls.shares_v2_client
+ cls.services = client.list_services()
+ zones = [service['zone'] for service in cls.services if
+ service['binary'] == "manila-share" and
+ service['state'] == 'up']
+ return zones
+
+ @classmethod
+ def create_share_replica(cls, share_id, availability_zone, client=None,
+ cleanup_in_class=False, cleanup=True):
+ client = client or cls.shares_v2_client
+ replica = client.create_share_replica(share_id, availability_zone)
+ resource = {
+ "type": "share_replica",
+ "id": replica["id"],
+ "client": client,
+ "share_id": share_id,
+ }
+ # NOTE(Yogi1): Cleanup needs to be disabled during promotion tests.
+ if cleanup:
+ if cleanup_in_class:
+ cls.class_resources.insert(0, resource)
+ else:
+ cls.method_resources.insert(0, resource)
+ client.wait_for_share_replica_status(
+ replica["id"], constants.STATUS_AVAILABLE)
+ return replica
+
+ @classmethod
+ def delete_share_replica(cls, replica_id, client=None):
+ client = client or cls.shares_v2_client
+ client.delete_share_replica(replica_id)
+ client.wait_for_resource_deletion(replica_id=replica_id)
+
+ @classmethod
+ def promote_share_replica(cls, replica_id, client=None):
+ client = client or cls.shares_v2_client
+ replica = client.promote_share_replica(replica_id)
+ client.wait_for_share_replica_status(
+ replica["id"],
+ constants.REPLICATION_STATE_ACTIVE,
+ status_attr="replica_state")
+ return replica
+
+ @classmethod
def create_share_network(cls, client=None,
cleanup_in_class=False, **kwargs):
if client is None:
@@ -613,6 +683,9 @@
elif res["type"] is "cgsnapshot":
client.delete_cgsnapshot(res_id)
client.wait_for_resource_deletion(cgsnapshot_id=res_id)
+ elif res["type"] is "share_replica":
+ client.delete_share_replica(res_id)
+ client.wait_for_resource_deletion(replica_id=res_id)
else:
LOG.warning("Provided unsupported resource type for "
"cleanup '%s'. Skipping." % res["type"])
diff --git a/manila_tempest_tests/tests/api/test_availability_zones_negative.py b/manila_tempest_tests/tests/api/test_availability_zones_negative.py
index 0a562c6..ffe4096 100644
--- a/manila_tempest_tests/tests/api/test_availability_zones_negative.py
+++ b/manila_tempest_tests/tests/api/test_availability_zones_negative.py
@@ -13,8 +13,8 @@
# License for the specific language governing permissions and limitations
# under the License.
+from tempest.lib import exceptions as lib_exc
from tempest import test
-from tempest_lib import exceptions as lib_exc
from manila_tempest_tests.tests.api import base
diff --git a/manila_tempest_tests/tests/api/test_consistency_group_actions.py b/manila_tempest_tests/tests/api/test_consistency_group_actions.py
index a932fdf..59e07be 100644
--- a/manila_tempest_tests/tests/api/test_consistency_group_actions.py
+++ b/manila_tempest_tests/tests/api/test_consistency_group_actions.py
@@ -15,8 +15,8 @@
# under the License.
from tempest import config
+from tempest.lib.common.utils import data_utils
from tempest import test
-from tempest_lib.common.utils import data_utils
import testtools
from manila_tempest_tests.tests.api import base
diff --git a/manila_tempest_tests/tests/api/test_consistency_groups.py b/manila_tempest_tests/tests/api/test_consistency_groups.py
index 33ea334..bebd88f 100644
--- a/manila_tempest_tests/tests/api/test_consistency_groups.py
+++ b/manila_tempest_tests/tests/api/test_consistency_groups.py
@@ -14,8 +14,8 @@
# under the License.
from tempest import config # noqa
+from tempest.lib import exceptions as lib_exc # noqa
from tempest import test # noqa
-from tempest_lib import exceptions as lib_exc # noqa
import testtools # noqa
from manila_tempest_tests.tests.api import base
diff --git a/manila_tempest_tests/tests/api/test_consistency_groups_negative.py b/manila_tempest_tests/tests/api/test_consistency_groups_negative.py
index 34c0a6d..9e9c696 100644
--- a/manila_tempest_tests/tests/api/test_consistency_groups_negative.py
+++ b/manila_tempest_tests/tests/api/test_consistency_groups_negative.py
@@ -14,9 +14,9 @@
# under the License.
from tempest import config
+from tempest.lib.common.utils import data_utils
+from tempest.lib import exceptions as lib_exc
from tempest import test
-from tempest_lib.common.utils import data_utils
-from tempest_lib import exceptions as lib_exc
import testtools
from manila_tempest_tests.tests.api import base
diff --git a/manila_tempest_tests/tests/api/test_metadata_negative.py b/manila_tempest_tests/tests/api/test_metadata_negative.py
index 7401a41..4da60a6 100644
--- a/manila_tempest_tests/tests/api/test_metadata_negative.py
+++ b/manila_tempest_tests/tests/api/test_metadata_negative.py
@@ -13,8 +13,8 @@
# License for the specific language governing permissions and limitations
# under the License.
+from tempest.lib import exceptions as lib_exc # noqa
from tempest import test # noqa
-from tempest_lib import exceptions as lib_exc # noqa
from manila_tempest_tests.tests.api import base
diff --git a/manila_tempest_tests/tests/api/test_quotas.py b/manila_tempest_tests/tests/api/test_quotas.py
index da6dcce..77eae42 100644
--- a/manila_tempest_tests/tests/api/test_quotas.py
+++ b/manila_tempest_tests/tests/api/test_quotas.py
@@ -14,16 +14,22 @@
# under the License.
import ddt
-from tempest import test # noqa
+from tempest import config
+from tempest import test
from manila_tempest_tests.tests.api import base
+CONF = config.CONF
+
@ddt.data
class SharesQuotasTest(base.BaseSharesTest):
@classmethod
def resource_setup(cls):
+ if not CONF.share.run_quota_tests:
+ msg = "Quota tests are disabled."
+ raise cls.skipException(msg)
super(SharesQuotasTest, cls).resource_setup()
cls.user_id = cls.shares_v2_client.user_id
cls.tenant_id = cls.shares_v2_client.tenant_id
diff --git a/manila_tempest_tests/tests/api/test_quotas_negative.py b/manila_tempest_tests/tests/api/test_quotas_negative.py
index e2d2c37..bf64290 100644
--- a/manila_tempest_tests/tests/api/test_quotas_negative.py
+++ b/manila_tempest_tests/tests/api/test_quotas_negative.py
@@ -14,15 +14,25 @@
# under the License.
import ddt
+from tempest import config
+from tempest.lib import exceptions as lib_exc
from tempest import test
-from tempest_lib import exceptions as lib_exc
from manila_tempest_tests.tests.api import base
+CONF = config.CONF
+
@ddt.ddt
class SharesQuotasNegativeTest(base.BaseSharesTest):
+ @classmethod
+ def resource_setup(cls):
+ if not CONF.share.run_quota_tests:
+ msg = "Quota tests are disabled."
+ raise cls.skipException(msg)
+ super(SharesQuotasNegativeTest, cls).resource_setup()
+
@test.attr(type=["gate", "smoke", "negative"])
def test_get_quotas_with_empty_tenant_id(self):
self.assertRaises(lib_exc.NotFound,
diff --git a/manila_tempest_tests/tests/api/test_replication.py b/manila_tempest_tests/tests/api/test_replication.py
new file mode 100644
index 0000000..4f4268c
--- /dev/null
+++ b/manila_tempest_tests/tests/api/test_replication.py
@@ -0,0 +1,406 @@
+# Copyright 2015 Yogesh Kshirsagar
+# 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 import test
+import testtools
+
+from manila_tempest_tests import clients_share as clients
+from manila_tempest_tests.common import constants
+from manila_tempest_tests import share_exceptions
+from manila_tempest_tests.tests.api import base
+from manila_tempest_tests import utils
+
+CONF = config.CONF
+_MIN_SUPPORTED_MICROVERSION = '2.11'
+SUMMARY_KEYS = ['share_id', 'id', 'replica_state', 'status']
+DETAIL_KEYS = SUMMARY_KEYS + ['availability_zone', 'host', 'updated_at',
+ 'share_network_id', 'created_at']
+
+
+@testtools.skipUnless(CONF.share.run_replication_tests,
+ 'Replication tests are disabled.')
+@base.skip_if_microversion_lt(_MIN_SUPPORTED_MICROVERSION)
+class ReplicationTest(base.BaseSharesTest):
+
+ @classmethod
+ def resource_setup(cls):
+ super(ReplicationTest, cls).resource_setup()
+ # Create share_type
+ name = data_utils.rand_name(constants.TEMPEST_MANILA_PREFIX)
+ cls.admin_client = clients.AdminManager().shares_v2_client
+ cls.replication_type = CONF.share.backend_replication_type
+
+ if cls.replication_type not in constants.REPLICATION_TYPE_CHOICES:
+ raise share_exceptions.ShareReplicationTypeException(
+ replication_type=cls.replication_type
+ )
+ cls.zones = cls.get_availability_zones(client=cls.admin_client)
+ cls.share_zone = cls.zones[0]
+ cls.replica_zone = cls.zones[-1]
+
+ cls.extra_specs = cls.add_required_extra_specs_to_dict(
+ {"replication_type": cls.replication_type})
+ share_type = cls.create_share_type(
+ name,
+ extra_specs=cls.extra_specs,
+ client=cls.admin_client)
+ cls.share_type = share_type["share_type"]
+ # Create share with above share_type
+ cls.creation_data = {'kwargs': {
+ 'share_type_id': cls.share_type['id'],
+ 'availability_zone': cls.share_zone,
+ }}
+
+ # Data for creating shares in parallel
+ data = [cls.creation_data, cls.creation_data]
+ cls.shares = cls.create_shares(data)
+ cls.shares = [cls.shares_v2_client.get_share(s['id']) for s in
+ cls.shares]
+ cls.instance_id1 = cls._get_instance(cls.shares[0])
+ cls.instance_id2 = cls._get_instance(cls.shares[1])
+
+ cls.access_type = "ip"
+ cls.access_to = utils.rand_ip()
+
+ @classmethod
+ def _get_instance(cls, share):
+ share_instances = cls.admin_client.get_instances_of_share(share["id"])
+ return share_instances[0]["id"]
+
+ def _verify_create_replica(self):
+ # Create the replica
+ share_replica = self.create_share_replica(self.shares[0]["id"],
+ self.replica_zone,
+ cleanup_in_class=False)
+ share_replicas = self.shares_v2_client.list_share_replicas(
+ share_id=self.shares[0]["id"])
+ # Ensure replica is created successfully.
+ replica_ids = [replica["id"] for replica in share_replicas]
+ self.assertIn(share_replica["id"], replica_ids)
+ return share_replica
+
+ def _verify_active_replica_count(self, share_id):
+ # List replicas
+ replica_list = self.shares_v2_client.list_share_replicas(
+ share_id=share_id)
+
+ # Check if there is only 1 'active' replica before promotion.
+ active_replicas = self._filter_replica_list(
+ replica_list, constants.REPLICATION_STATE_ACTIVE)
+ self.assertEqual(1, len(active_replicas))
+
+ def _filter_replica_list(self, replica_list, r_state):
+ # Iterate through replica list to filter based on replica_state
+ return [replica for replica in replica_list
+ if replica['replica_state'] == r_state]
+
+ def _get_pools_for_replication_domain(self):
+ # Get the list of pools for the replication domain
+ pools = self.admin_client.list_pools(detail=True)['pools']
+ instance_host = self.shares[0]['host']
+ host_pool = [p for p in pools if p['name'] == instance_host][0]
+ rep_domain = host_pool['capabilities']['replication_domain']
+ pools_in_rep_domain = [p for p in pools if p['capabilities'][
+ 'replication_domain'] == rep_domain]
+ return rep_domain, pools_in_rep_domain
+
+ def _verify_config_and_set_access_rule_data(self):
+ """Verify the access rule configuration is enabled for NFS.
+
+ Set the data after verification.
+ """
+ protocol = self.shares_v2_client.share_protocol
+
+ # TODO(Yogi1): Add access rules for other protocols.
+ if not ((protocol.lower() == 'nfs') and
+ (protocol in CONF.share.enable_ip_rules_for_protocols) and
+ CONF.share.enable_ip_rules_for_protocols):
+ message = "IP access rules are not supported for this protocol."
+ raise self.skipException(message)
+
+ access_type = "ip"
+ access_to = utils.rand_ip()
+
+ return access_type, access_to
+
+ @test.attr(type=["gate", ])
+ def test_add_delete_share_replica(self):
+ # Create the replica
+ share_replica = self._verify_create_replica()
+
+ # Delete the replica
+ self.delete_share_replica(share_replica["id"])
+
+ @test.attr(type=["gate", ])
+ def test_add_access_rule_create_replica_delete_rule(self):
+ # Add access rule to the share
+ access_type, access_to = self._verify_config_and_set_access_rule_data()
+ rule = self.shares_v2_client.create_access_rule(
+ self.shares[0]["id"], access_type, access_to, 'ro')
+ self.shares_v2_client.wait_for_access_rule_status(
+ self.shares[0]["id"], rule["id"], constants.RULE_STATE_ACTIVE)
+
+ # Create the replica
+ self._verify_create_replica()
+
+ # Verify access rule transitions to 'active' state.
+ self.shares_v2_client.wait_for_access_rule_status(
+ self.shares[0]["id"], rule["id"], constants.RULE_STATE_ACTIVE)
+
+ # Delete rule and wait for deletion
+ self.shares_v2_client.delete_access_rule(self.shares[0]["id"],
+ rule["id"])
+ self.shares_v2_client.wait_for_resource_deletion(
+ rule_id=rule["id"], share_id=self.shares[0]['id'])
+
+ @test.attr(type=["gate", ])
+ def test_create_replica_add_access_rule_delete_replica(self):
+ access_type, access_to = self._verify_config_and_set_access_rule_data()
+ # Create the replica
+ share_replica = self._verify_create_replica()
+
+ # Add access rule
+ rule = self.shares_v2_client.create_access_rule(
+ self.shares[0]["id"], access_type, access_to, 'ro')
+ self.shares_v2_client.wait_for_access_rule_status(
+ self.shares[0]["id"], rule["id"], constants.RULE_STATE_ACTIVE)
+
+ # Delete the replica
+ self.delete_share_replica(share_replica["id"])
+
+ @test.attr(type=["gate", ])
+ def test_add_multiple_share_replicas(self):
+ rep_domain, pools = self._get_pools_for_replication_domain()
+ if len(pools) < 3:
+ msg = ("Replication domain %(domain)s has only %(count)s pools. "
+ "Need at least 3 pools to run this test." %
+ {"domain": rep_domain, "count": len(pools)})
+ raise self.skipException(msg)
+ # Add the replicas
+ share_replica1 = self.create_share_replica(self.shares[0]["id"],
+ self.replica_zone,
+ cleanup_in_class=False)
+ share_replica2 = self.create_share_replica(self.shares[0]["id"],
+ self.replica_zone,
+ cleanup_in_class=False)
+ self.shares_v2_client.get_share_replica(share_replica2['id'])
+
+ share_replicas = self.shares_v2_client.list_share_replicas(
+ share_id=self.shares[0]["id"])
+ replica_host_set = {r['host'] for r in share_replicas}
+
+ # Assert that replicas are created on different pools.
+ msg = "More than one replica is created on the same pool."
+ self.assertEqual(3, len(replica_host_set), msg)
+ # Verify replicas are in the replica list
+ replica_ids = [replica["id"] for replica in share_replicas]
+ self.assertIn(share_replica1["id"], replica_ids)
+ self.assertIn(share_replica2["id"], replica_ids)
+
+ @test.attr(type=["gate", ])
+ def test_promote_in_sync_share_replica(self):
+ # Test promote 'in_sync' share_replica to 'active' state
+ if (self.replication_type
+ not in constants.REPLICATION_PROMOTION_CHOICES):
+ msg = "Option backend_replication_type should be one of (%s)!"
+ raise self.skipException(
+ msg % ','.join(constants.REPLICATION_PROMOTION_CHOICES))
+ share = self.create_shares([self.creation_data])[0]
+ original_replica = self.shares_v2_client.list_share_replicas(
+ share["id"])[0]
+ # NOTE(Yogi1): Cleanup needs to be disabled for replica that is
+ # being promoted since it will become the 'primary'/'active' replica.
+ replica = self.create_share_replica(share["id"], self.replica_zone,
+ cleanup=False)
+ # Wait for replica state to update after creation
+ self.shares_v2_client.wait_for_share_replica_status(
+ replica['id'], constants.REPLICATION_STATE_IN_SYNC,
+ status_attr='replica_state')
+ # Promote the first in_sync replica to active state
+ promoted_replica = self.promote_share_replica(replica['id'])
+ # Delete the demoted replica so promoted replica can be cleaned
+ # during the cleanup of the share.
+ self.addCleanup(self.delete_share_replica, original_replica['id'])
+ self._verify_active_replica_count(share["id"])
+ # Verify the replica_state for promoted replica
+ promoted_replica = self.shares_v2_client.get_share_replica(
+ promoted_replica["id"])
+ self.assertEqual(constants.REPLICATION_STATE_ACTIVE,
+ promoted_replica["replica_state"])
+
+ @test.attr(type=["gate", ])
+ def test_promote_and_promote_back(self):
+ # Test promote back and forth between 2 share replicas
+ if (self.replication_type
+ not in constants.REPLICATION_PROMOTION_CHOICES):
+ msg = "Option backend_replication_type should be one of (%s)!"
+ raise self.skipException(
+ msg % ','.join(constants.REPLICATION_PROMOTION_CHOICES))
+
+ # Create a new share
+ share = self.create_shares([self.creation_data])[0]
+
+ # Discover the original replica
+ initial_replicas = self.shares_v2_client.list_share_replicas(
+ share_id=share['id'])
+ self.assertEqual(1, len(initial_replicas),
+ '%s replicas initially created for share %s' %
+ (len(initial_replicas), share['id']))
+ original_replica = initial_replicas[0]
+
+ # Create a new replica
+ new_replica = self.create_share_replica(share["id"],
+ self.replica_zone,
+ cleanup_in_class=False)
+ self.shares_v2_client.wait_for_share_replica_status(
+ new_replica['id'], constants.REPLICATION_STATE_IN_SYNC,
+ status_attr='replica_state')
+
+ # Promote the new replica to active and verify the replica states
+ self.promote_share_replica(new_replica['id'])
+ self._verify_active_replica_count(share["id"])
+ self.shares_v2_client.wait_for_share_replica_status(
+ original_replica['id'], constants.REPLICATION_STATE_IN_SYNC,
+ status_attr='replica_state')
+
+ # Promote the original replica back to active
+ self.promote_share_replica(original_replica['id'])
+ self._verify_active_replica_count(share["id"])
+ self.shares_v2_client.wait_for_share_replica_status(
+ new_replica['id'], constants.REPLICATION_STATE_IN_SYNC,
+ status_attr='replica_state')
+
+ @test.attr(type=["gate", ])
+ def test_active_replication_state(self):
+ # Verify the replica_state of first instance is set to active.
+ replica = self.shares_v2_client.get_share_replica(self.instance_id1)
+ self.assertEqual(
+ constants.REPLICATION_STATE_ACTIVE, replica['replica_state'])
+
+
+@testtools.skipUnless(CONF.share.run_replication_tests,
+ 'Replication tests are disabled.')
+@base.skip_if_microversion_lt(_MIN_SUPPORTED_MICROVERSION)
+class ReplicationActionsTest(base.BaseSharesTest):
+
+ @classmethod
+ def resource_setup(cls):
+ super(ReplicationActionsTest, cls).resource_setup()
+ # Create share_type
+ name = data_utils.rand_name(constants.TEMPEST_MANILA_PREFIX)
+ cls.admin_client = clients.AdminManager().shares_v2_client
+ cls.replication_type = CONF.share.backend_replication_type
+
+ if cls.replication_type not in constants.REPLICATION_TYPE_CHOICES:
+ raise share_exceptions.ShareReplicationTypeException(
+ replication_type=cls.replication_type
+ )
+ cls.zones = cls.get_availability_zones(client=cls.admin_client)
+ cls.share_zone = cls.zones[0]
+ cls.replica_zone = cls.zones[-1]
+
+ cls.extra_specs = cls.add_required_extra_specs_to_dict(
+ {"replication_type": cls.replication_type})
+ share_type = cls.create_share_type(
+ name,
+ extra_specs=cls.extra_specs,
+ client=cls.admin_client)
+ cls.share_type = share_type["share_type"]
+ # Create share with above share_type
+ cls.creation_data = {'kwargs': {
+ 'share_type_id': cls.share_type['id'],
+ 'availability_zone': cls.share_zone,
+ }}
+
+ # Data for creating shares in parallel
+ data = [cls.creation_data, cls.creation_data]
+ cls.shares = cls.create_shares(data)
+ cls.shares = [cls.shares_v2_client.get_share(s['id']) for s in
+ cls.shares]
+ cls.instance_id1 = cls._get_instance(cls.shares[0])
+ cls.instance_id2 = cls._get_instance(cls.shares[1])
+
+ # Create replicas to 2 shares
+ cls.replica1 = cls.create_share_replica(cls.shares[0]["id"],
+ cls.replica_zone,
+ cleanup_in_class=True)
+ cls.replica2 = cls.create_share_replica(cls.shares[1]["id"],
+ cls.replica_zone,
+ cleanup_in_class=True)
+
+ @classmethod
+ def _get_instance(cls, share):
+ share_instances = cls.admin_client.get_instances_of_share(share["id"])
+ return share_instances[0]["id"]
+
+ def _validate_replica_list(self, replica_list, detail=True):
+ # Verify keys
+ if detail:
+ keys = DETAIL_KEYS
+ else:
+ keys = SUMMARY_KEYS
+ for replica in replica_list:
+ self.assertEqual(sorted(keys), sorted(replica.keys()))
+ # Check for duplicates
+ replica_id_list = [sr["id"] for sr in replica_list
+ if sr["id"] == replica["id"]]
+ msg = "Replica %s appears %s times in replica list." % (
+ replica['id'], len(replica_id_list))
+ self.assertEqual(1, len(replica_id_list), msg)
+
+ @test.attr(type=["gate", ])
+ def test_show_share_replica(self):
+ replica = self.shares_v2_client.get_share_replica(self.replica1["id"])
+
+ actual_keys = sorted(list(replica.keys()))
+ detail_keys = sorted(DETAIL_KEYS)
+ self.assertEqual(detail_keys, actual_keys,
+ 'Share Replica %s has incorrect keys; '
+ 'expected %s, got %s.' % (replica["id"],
+ detail_keys, actual_keys))
+
+ @test.attr(type=["gate", ])
+ def test_detail_list_share_replicas_for_share(self):
+ # List replicas for share
+ replica_list = self.shares_v2_client.list_share_replicas(
+ share_id=self.shares[0]["id"])
+ replica_ids_list = [rep['id'] for rep in replica_list]
+ self.assertIn(self.replica1['id'], replica_ids_list,
+ 'Replica %s was not returned in the list of replicas: %s'
+ % (self.replica1['id'], replica_list))
+ # Verify keys
+ self._validate_replica_list(replica_list)
+
+ @test.attr(type=["gate", ])
+ def test_detail_list_share_replicas_for_all_shares(self):
+ # List replicas for all available shares
+ replica_list = self.shares_v2_client.list_share_replicas()
+ replica_ids_list = [rep['id'] for rep in replica_list]
+ for replica in [self.replica1, self.replica2]:
+ self.assertIn(replica['id'], replica_ids_list,
+ 'Replica %s was not returned in the list of '
+ 'replicas: %s' % (replica['id'], replica_list))
+ # Verify keys
+ self._validate_replica_list(replica_list)
+
+ @test.attr(type=["gate", ])
+ def test_summary_list_share_replicas_for_all_shares(self):
+ # List replicas
+ replica_list = self.shares_v2_client.list_share_replicas_summary()
+
+ # Verify keys
+ self._validate_replica_list(replica_list, detail=False)
diff --git a/manila_tempest_tests/tests/api/test_replication_negative.py b/manila_tempest_tests/tests/api/test_replication_negative.py
new file mode 100644
index 0000000..2587dc0
--- /dev/null
+++ b/manila_tempest_tests/tests/api/test_replication_negative.py
@@ -0,0 +1,169 @@
+# Copyright 2015 Yogesh Kshirsagar
+# 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 exceptions as lib_exc
+from tempest import test
+import testtools
+
+from manila_tempest_tests import clients_share as clients
+from manila_tempest_tests.common import constants
+from manila_tempest_tests import share_exceptions
+from manila_tempest_tests.tests.api import base
+
+CONF = config.CONF
+_MIN_SUPPORTED_MICROVERSION = '2.11'
+
+
+@testtools.skipUnless(CONF.share.run_replication_tests,
+ 'Replication tests are disabled.')
+@base.skip_if_microversion_lt(_MIN_SUPPORTED_MICROVERSION)
+class ReplicationNegativeTest(base.BaseSharesTest):
+
+ @classmethod
+ def resource_setup(cls):
+ super(ReplicationNegativeTest, cls).resource_setup()
+ # Create share_type
+ name = data_utils.rand_name(constants.TEMPEST_MANILA_PREFIX)
+ cls.admin_client = clients.AdminManager().shares_v2_client
+ cls.replication_type = CONF.share.backend_replication_type
+
+ if cls.replication_type not in constants.REPLICATION_TYPE_CHOICES:
+ raise share_exceptions.ShareReplicationTypeException(
+ replication_type=cls.replication_type
+ )
+ cls.zones = cls.get_availability_zones(client=cls.admin_client)
+ cls.share_zone = cls.zones[0]
+ cls.replica_zone = cls.zones[-1]
+
+ cls.extra_specs = cls.add_required_extra_specs_to_dict(
+ {"replication_type": cls.replication_type})
+ share_type = cls.create_share_type(
+ name,
+ extra_specs=cls.extra_specs,
+ client=cls.admin_client)
+ cls.share_type = share_type["share_type"]
+ # Create share with above share_type
+ cls.share1, cls.instance_id1 = cls._create_share_get_instance()
+
+ @classmethod
+ def _create_share_get_instance(cls):
+ share = cls.create_share(share_type_id=cls.share_type["id"],
+ availability_zone=cls.share_zone,)
+ share_instances = cls.admin_client.get_instances_of_share(
+ share["id"], version=_MIN_SUPPORTED_MICROVERSION
+ )
+ instance_id = share_instances[0]["id"]
+ return share, instance_id
+
+ def _is_replication_type_promotable(self):
+ if (self.replication_type
+ not in constants.REPLICATION_PROMOTION_CHOICES):
+ msg = "Option backend_replication_type should be one of (%s)!"
+ raise self.skipException(
+ msg % ','.join(constants.REPLICATION_PROMOTION_CHOICES))
+
+ @test.attr(type=["gate", "negative", ])
+ def test_try_add_replica_to_share_with_no_replication_share_type(self):
+ # Create share without replication type
+ share = self.create_share()
+ self.assertRaises(lib_exc.BadRequest,
+ self.create_share_replica,
+ share['id'],
+ self.replica_zone)
+
+ @test.attr(type=["gate", "negative", ])
+ def test_add_replica_to_share_with_error_state(self):
+ # Set "error" state
+ self.admin_client.reset_state(
+ self.share1['id'], constants.STATUS_ERROR)
+ self.addCleanup(self.admin_client.reset_state,
+ self.share1['id'],
+ constants.STATUS_AVAILABLE)
+ self.assertRaises(lib_exc.BadRequest,
+ self.create_share_replica,
+ self.share1['id'],
+ self.replica_zone)
+
+ @test.attr(type=["gate", "negative", ])
+ def test_get_replica_by_nonexistent_id(self):
+ self.assertRaises(lib_exc.NotFound,
+ self.shares_v2_client.get_share_replica,
+ data_utils.rand_uuid())
+
+ @test.attr(type=["gate", "negative", ])
+ def test_try_delete_replica_by_nonexistent_id(self):
+ self.assertRaises(lib_exc.NotFound,
+ self.shares_v2_client.delete_share_replica,
+ data_utils.rand_uuid())
+
+ @test.attr(type=["gate", "negative", ])
+ def test_try_delete_last_active_replica(self):
+ self.assertRaises(lib_exc.BadRequest,
+ self.shares_v2_client.delete_share_replica,
+ self.instance_id1)
+
+ @test.attr(type=["gate", "negative", ])
+ def test_try_delete_share_having_replica(self):
+ self.create_share_replica(self.share1["id"], self.replica_zone,
+ cleanup_in_class=False)
+ self.assertRaises(lib_exc.Conflict,
+ self.shares_v2_client.delete_share,
+ self.share1["id"])
+
+ @test.attr(type=["negative", "gate", ])
+ def test_promote_out_of_sync_share_replica(self):
+ # Test promoting an out_of_sync share_replica to active state
+ self._is_replication_type_promotable()
+ share, instance_id = self._create_share_get_instance()
+ replica = self.create_share_replica(share["id"], self.replica_zone,
+ cleanup_in_class=False)
+ # Set replica state to out of sync
+ self.admin_client.reset_share_replica_state(
+ replica['id'], constants.REPLICATION_STATE_OUT_OF_SYNC)
+ self.shares_v2_client.wait_for_share_replica_status(
+ replica['id'], constants.REPLICATION_STATE_OUT_OF_SYNC,
+ status_attr='replica_state')
+ # Try promoting the first out_of_sync replica to active state
+ self.assertRaises(lib_exc.Forbidden,
+ self.shares_v2_client.promote_share_replica,
+ replica['id'])
+
+ @test.attr(type=["negative", "gate", ])
+ def test_promote_active_share_replica(self):
+ # Test promote active share_replica
+ self._is_replication_type_promotable()
+
+ # Try promoting the active replica
+ self.shares_v2_client.promote_share_replica(self.instance_id1,
+ expected_status=200)
+
+ @test.attr(type=["negative", "gate", ])
+ def test_promote_share_replica_for_writable_share_type(self):
+ # Test promote active share_replica for writable share
+ if self.replication_type != "writable":
+ raise self.skipException("Option backend_replication_type "
+ "should be writable!")
+ share, instance_id = self._create_share_get_instance()
+ replica = self.create_share_replica(share["id"], self.replica_zone,
+ cleanup_in_class=False)
+ # By default, 'writable' replica is expected to be in active state
+ self.shares_v2_client.wait_for_share_replica_status(
+ replica["id"], constants.REPLICATION_STATE_ACTIVE,
+ status_attr='replica_state')
+
+ # Try promoting the replica
+ self.shares_v2_client.promote_share_replica(replica['id'])
diff --git a/manila_tempest_tests/tests/api/test_rules.py b/manila_tempest_tests/tests/api/test_rules.py
index 0b0bc08..fce3e44 100644
--- a/manila_tempest_tests/tests/api/test_rules.py
+++ b/manila_tempest_tests/tests/api/test_rules.py
@@ -15,8 +15,8 @@
import ddt
from tempest import config
+from tempest.lib import exceptions as lib_exc
from tempest import test
-from tempest_lib import exceptions as lib_exc
import testtools
from manila_tempest_tests.tests.api import base
@@ -359,6 +359,41 @@
@ddt.ddt
+class ShareCephxRulesForCephFSTest(base.BaseSharesTest):
+ protocol = "cephfs"
+
+ @classmethod
+ def resource_setup(cls):
+ super(ShareCephxRulesForCephFSTest, cls).resource_setup()
+ if (cls.protocol not in CONF.share.enable_protocols or
+ cls.protocol not in
+ CONF.share.enable_cephx_rules_for_protocols):
+ msg = ("Cephx rule tests for %s protocol are disabled." %
+ cls.protocol)
+ raise cls.skipException(msg)
+ cls.share = cls.create_share(cls.protocol)
+ cls.access_type = "cephx"
+ # Provide access to a client identified by a cephx auth id.
+ cls.access_to = "bob"
+
+ @test.attr(type=["gate", ])
+ @ddt.data("alice", "alice_bob", "alice bob")
+ def test_create_delete_cephx_rule(self, access_to):
+ rule = self.shares_v2_client.create_access_rule(
+ self.share["id"], self.access_type, access_to)
+
+ self.assertEqual('rw', rule['access_level'])
+ for key in ('deleted', 'deleted_at', 'instance_mappings'):
+ self.assertNotIn(key, rule.keys())
+ self.shares_v2_client.wait_for_access_rule_status(
+ self.share["id"], rule["id"], "active")
+
+ self.shares_v2_client.delete_access_rule(self.share["id"], rule["id"])
+ self.shares_v2_client.wait_for_resource_deletion(
+ rule_id=rule["id"], share_id=self.share['id'])
+
+
+@ddt.ddt
class ShareRulesTest(base.BaseSharesTest):
@classmethod
@@ -369,6 +404,8 @@
any(p in CONF.share.enable_user_rules_for_protocols
for p in cls.protocols) or
any(p in CONF.share.enable_cert_rules_for_protocols
+ for p in cls.protocols) or
+ any(p in CONF.share.enable_cephx_rules_for_protocols
for p in cls.protocols)):
cls.message = "Rule tests are disabled"
raise cls.skipException(cls.message)
@@ -384,12 +421,21 @@
cls.protocol = CONF.share.enable_cert_rules_for_protocols[0]
cls.access_type = "cert"
cls.access_to = "client3.com"
+ elif CONF.share.enable_cephx_rules_for_protocols:
+ cls.protocol = CONF.share.enable_cephx_rules_for_protocols[0]
+ cls.access_type = "cephx"
+ cls.access_to = "alice"
cls.shares_v2_client.share_protocol = cls.protocol
cls.share = cls.create_share()
@test.attr(type=["gate", ])
@ddt.data('1.0', '2.9', LATEST_MICROVERSION)
def test_list_access_rules(self, version):
+ if (utils.is_microversion_lt(version, '2.13') and
+ CONF.share.enable_cephx_rules_for_protocols):
+ msg = ("API version %s does not support cephx access type, "
+ "need version greater than 2.13." % version)
+ raise self.skipException(msg)
# create rule
if utils.is_microversion_eq(version, '1.0'):
@@ -447,6 +493,11 @@
@test.attr(type=["gate", ])
@ddt.data('1.0', '2.9', LATEST_MICROVERSION)
def test_access_rules_deleted_if_share_deleted(self, version):
+ if (utils.is_microversion_lt(version, '2.13') and
+ CONF.share.enable_cephx_rules_for_protocols):
+ msg = ("API version %s does not support cephx access type, "
+ "need version greater than 2.13." % version)
+ raise self.skipException(msg)
# create share
share = self.create_share()
diff --git a/manila_tempest_tests/tests/api/test_rules_negative.py b/manila_tempest_tests/tests/api/test_rules_negative.py
index 47f5a52..a35c18a 100644
--- a/manila_tempest_tests/tests/api/test_rules_negative.py
+++ b/manila_tempest_tests/tests/api/test_rules_negative.py
@@ -15,10 +15,11 @@
import ddt
from tempest import config
+from tempest.lib import exceptions as lib_exc
from tempest import test
-from tempest_lib import exceptions as lib_exc
import testtools
+from manila_tempest_tests import share_exceptions
from manila_tempest_tests.tests.api import base
from manila_tempest_tests import utils
@@ -317,6 +318,48 @@
@ddt.ddt
+class ShareCephxRulesForCephFSNegativeTest(base.BaseSharesTest):
+ protocol = "cephfs"
+
+ @classmethod
+ def resource_setup(cls):
+ super(ShareCephxRulesForCephFSNegativeTest, cls).resource_setup()
+ if not (cls.protocol in CONF.share.enable_protocols and
+ cls.protocol in CONF.share.enable_cephx_rules_for_protocols):
+ msg = ("CEPHX rule tests for %s protocol are disabled" %
+ cls.protocol)
+ raise cls.skipException(msg)
+ # create share
+ cls.share = cls.create_share(cls.protocol)
+ cls.access_type = "cephx"
+ cls.access_to = "david"
+
+ @test.attr(type=["negative", "gate", ])
+ @ddt.data('jane.doe', u"bj\u00F6rn")
+ def test_create_access_rule_cephx_with_invalid_cephx_id(self, access_to):
+ self.assertRaises(lib_exc.BadRequest,
+ self.shares_v2_client.create_access_rule,
+ self.share["id"], self.access_type, access_to)
+
+ @test.attr(type=["negative", "gate", ])
+ def test_create_access_rule_cephx_with_wrong_level(self):
+ self.assertRaises(lib_exc.BadRequest,
+ self.shares_v2_client.create_access_rule,
+ self.share["id"], self.access_type, self.access_to,
+ access_level="su")
+
+ @test.attr(type=["negative", "gate", ])
+ def test_create_access_rule_cephx_with_unsupported_access_level_ro(self):
+ rule = self.shares_v2_client.create_access_rule(
+ self.share["id"], self.access_type, self.access_to,
+ access_level="ro")
+ self.assertRaises(
+ share_exceptions.AccessRuleBuildErrorException,
+ self.shares_client.wait_for_access_rule_status,
+ self.share['id'], rule['id'], "active")
+
+
+@ddt.ddt
class ShareRulesNegativeTest(base.BaseSharesTest):
# Tests independent from rule type and share protocol
@@ -328,6 +371,8 @@
any(p in CONF.share.enable_user_rules_for_protocols
for p in cls.protocols) or
any(p in CONF.share.enable_cert_rules_for_protocols
+ for p in cls.protocols) or
+ any(p in CONF.share.enable_cephx_rules_for_protocols
for p in cls.protocols)):
cls.message = "Rule tests are disabled"
raise cls.skipException(cls.message)
@@ -337,9 +382,21 @@
# create snapshot
cls.snap = cls.create_snapshot_wait_for_active(cls.share["id"])
+ def skip_if_cephx_access_type_not_supported_by_client(self, client):
+ if client == 'shares_client':
+ version = '1.0'
+ else:
+ version = LATEST_MICROVERSION
+ if (CONF.share.enable_cephx_rules_for_protocols and
+ utils.is_microversion_lt(version, '2.13')):
+ msg = ("API version %s does not support cephx access type, "
+ "need version greater than 2.13." % version)
+ raise self.skipException(msg)
+
@test.attr(type=["negative", "gate", ])
@ddt.data('shares_client', 'shares_v2_client')
def test_delete_access_rule_with_wrong_id(self, client_name):
+ self.skip_if_cephx_access_type_not_supported_by_client(client_name)
self.assertRaises(lib_exc.NotFound,
getattr(self, client_name).delete_access_rule,
self.share["id"], "wrong_rule_id")
@@ -347,6 +404,7 @@
@test.attr(type=["negative", "gate", ])
@ddt.data('shares_client', 'shares_v2_client')
def test_create_access_rule_ip_with_wrong_type(self, client_name):
+ self.skip_if_cephx_access_type_not_supported_by_client(client_name)
self.assertRaises(lib_exc.BadRequest,
getattr(self, client_name).create_access_rule,
self.share["id"], "wrong_type", "1.2.3.4")
@@ -354,6 +412,7 @@
@test.attr(type=["negative", "gate", ])
@ddt.data('shares_client', 'shares_v2_client')
def test_create_access_rule_ip_with_wrong_share_id(self, client_name):
+ self.skip_if_cephx_access_type_not_supported_by_client(client_name)
self.assertRaises(lib_exc.NotFound,
getattr(self, client_name).create_access_rule,
"wrong_share_id")
@@ -363,6 +422,7 @@
@testtools.skipUnless(CONF.share.run_snapshot_tests,
"Snapshot tests are disabled.")
def test_create_access_rule_ip_to_snapshot(self, client_name):
+ self.skip_if_cephx_access_type_not_supported_by_client(client_name)
self.assertRaises(lib_exc.NotFound,
getattr(self, client_name).create_access_rule,
self.snap["id"])
diff --git a/manila_tempest_tests/tests/api/test_scheduler_stats_negative.py b/manila_tempest_tests/tests/api/test_scheduler_stats_negative.py
index 9be8f64..dd7a498 100644
--- a/manila_tempest_tests/tests/api/test_scheduler_stats_negative.py
+++ b/manila_tempest_tests/tests/api/test_scheduler_stats_negative.py
@@ -13,8 +13,8 @@
# License for the specific language governing permissions and limitations
# under the License.
+from tempest.lib import exceptions as lib_exc # noqa
from tempest import test # noqa
-from tempest_lib import exceptions as lib_exc # noqa
from manila_tempest_tests.tests.api import base
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 834deb5..6a5f18f 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
@@ -16,8 +16,8 @@
from oslo_log import log # noqa
import six # noqa
from tempest import config # noqa
+from tempest.lib import exceptions as lib_exc # noqa
from tempest import test # noqa
-from tempest_lib import exceptions as lib_exc # noqa
import testtools # noqa
from manila_tempest_tests.tests.api import base
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 5908f81..80139b8 100644
--- a/manila_tempest_tests/tests/api/test_security_services_negative.py
+++ b/manila_tempest_tests/tests/api/test_security_services_negative.py
@@ -16,8 +16,8 @@
from oslo_log import log # noqa
import six # noqa
from tempest import config # noqa
+from tempest.lib import exceptions as lib_exc # noqa
from tempest import test # noqa
-from tempest_lib import exceptions as lib_exc # noqa
import testtools # noqa
from manila_tempest_tests.tests.api import base
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 d0036c3..d337d4a 100644
--- a/manila_tempest_tests/tests/api/test_share_networks_negative.py
+++ b/manila_tempest_tests/tests/api/test_share_networks_negative.py
@@ -14,8 +14,8 @@
# under the License.
from tempest import config # noqa
+from tempest.lib import exceptions as lib_exc # noqa
from tempest import test # noqa
-from tempest_lib import exceptions as lib_exc # noqa
import testtools # noqa
from manila_tempest_tests.tests.api import base
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 4d2c189..9be479b 100644
--- a/manila_tempest_tests/tests/api/test_share_types_negative.py
+++ b/manila_tempest_tests/tests/api/test_share_types_negative.py
@@ -13,9 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
+from tempest.lib.common.utils import data_utils # noqa
+from tempest.lib import exceptions as lib_exc # noqa
from tempest import test # noqa
-from tempest_lib.common.utils import data_utils # noqa
-from tempest_lib import exceptions as lib_exc # noqa
from manila_tempest_tests import clients_share as clients
from manila_tempest_tests.tests.api import base
diff --git a/manila_tempest_tests/tests/api/test_shares.py b/manila_tempest_tests/tests/api/test_shares.py
index 46d1558..2057d22 100644
--- a/manila_tempest_tests/tests/api/test_shares.py
+++ b/manila_tempest_tests/tests/api/test_shares.py
@@ -14,8 +14,8 @@
# under the License.
from tempest import config
+from tempest.lib import exceptions as lib_exc
from tempest import test
-from tempest_lib import exceptions as lib_exc
import testtools
from manila_tempest_tests.tests.api import base
@@ -187,3 +187,8 @@
class SharesHDFSTest(SharesNFSTest):
"""Covers share functionality that is related to HDFS share type."""
protocol = "hdfs"
+
+
+class SharesCephFSTest(SharesNFSTest):
+ """Covers share functionality that is related to CEPHFS share type."""
+ protocol = "cephfs"
diff --git a/manila_tempest_tests/tests/api/test_shares_actions.py b/manila_tempest_tests/tests/api/test_shares_actions.py
index 703dc71..1f37c2a 100644
--- a/manila_tempest_tests/tests/api/test_shares_actions.py
+++ b/manila_tempest_tests/tests/api/test_shares_actions.py
@@ -15,8 +15,8 @@
import six
from tempest import config
+from tempest.lib.common.utils import data_utils
from tempest import test
-from tempest_lib.common.utils import data_utils
import testtools
from manila_tempest_tests.tests.api import base
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 624bf31..5b50a72 100644
--- a/manila_tempest_tests/tests/api/test_shares_actions_negative.py
+++ b/manila_tempest_tests/tests/api/test_shares_actions_negative.py
@@ -14,8 +14,8 @@
# under the License.
from tempest import config # noqa
+from tempest.lib import exceptions as lib_exc # noqa
from tempest import test # noqa
-from tempest_lib import exceptions as lib_exc # noqa
import testtools # noqa
from manila_tempest_tests import clients_share as clients
diff --git a/manila_tempest_tests/tests/api/test_shares_negative.py b/manila_tempest_tests/tests/api/test_shares_negative.py
index ec8ac3b..0e91302 100644
--- a/manila_tempest_tests/tests/api/test_shares_negative.py
+++ b/manila_tempest_tests/tests/api/test_shares_negative.py
@@ -14,8 +14,8 @@
# under the License.
from tempest import config # noqa
+from tempest.lib import exceptions as lib_exc # noqa
from tempest import test # noqa
-from tempest_lib import exceptions as lib_exc # noqa
import testtools # noqa
from manila_tempest_tests import share_exceptions
diff --git a/manila_tempest_tests/tests/scenario/manager_share.py b/manila_tempest_tests/tests/scenario/manager_share.py
index f5f0c1f..37b346e 100644
--- a/manila_tempest_tests/tests/scenario/manager_share.py
+++ b/manila_tempest_tests/tests/scenario/manager_share.py
@@ -18,8 +18,8 @@
from tempest.common.utils.linux import remote_client # noqa
from tempest import config # noqa
+from tempest.lib.common.utils import data_utils
from tempest.scenario import manager # noqa
-from tempest_lib.common.utils import data_utils
from manila_tempest_tests import clients_share
@@ -157,10 +157,9 @@
tenant_id = client.tenant_id
if not router_id:
router_id = self._get_router()['id']
- client.add_router_interface_with_subnet_id(router_id,
- subnet_id)
- self.addCleanup(client.remove_router_interface_with_subnet_id,
- router_id, subnet_id)
+ client.add_router_interface(router_id, subnet_id=subnet_id)
+ self.addCleanup(
+ client.remove_router_interface, router_id, subnet_id=subnet_id)
def get_remote_client(self, *args, **kwargs):
if not CONF.share.image_with_share_tools:
@@ -196,8 +195,9 @@
def _migrate_share(self, share_id, dest_host, client=None):
client = client or self.shares_admin_v2_client
- client.migrate_share(share_id, dest_host)
- share = client.wait_for_migration_completed(share_id, dest_host)
+ client.migrate_share(share_id, dest_host, True)
+ share = client.wait_for_migration_status(share_id, dest_host,
+ 'migration_success')
return share
def _create_share_type(self, name, is_public=True, **kwargs):
diff --git a/manila_tempest_tests/tests/scenario/test_share_basic_ops.py b/manila_tempest_tests/tests/scenario/test_share_basic_ops.py
index 4b1daf4..7dde3ad 100644
--- a/manila_tempest_tests/tests/scenario/test_share_basic_ops.py
+++ b/manila_tempest_tests/tests/scenario/test_share_basic_ops.py
@@ -15,9 +15,9 @@
from oslo_log import log as logging
from tempest import config # noqa
+from tempest.lib.common.utils import data_utils
+from tempest.lib import exceptions
from tempest import test # noqa
-from tempest_lib.common.utils import data_utils
-from tempest_lib import exceptions
from manila_tempest_tests.tests.scenario import manager_share as manager
from manila_tempest_tests import utils
@@ -176,7 +176,15 @@
self.allow_access_ip(self.share['id'], instance=instance,
cleanup=False)
ssh_client = self.init_ssh(instance)
- for location in self.share['export_locations']:
+
+ if utils.is_microversion_lt(CONF.share.max_api_microversion, "2.9"):
+ locations = self.share['export_locations']
+ else:
+ exports = self.shares_v2_client.list_share_export_locations(
+ self.share['id'])
+ locations = [x['path'] for x in exports]
+
+ for location in locations:
self.mount_share(location, ssh_client)
self.umount_share(ssh_client)
self.servers_client.delete_server(instance['id'])
@@ -194,10 +202,14 @@
cleanup=False)
ssh_client_inst1 = self.init_ssh(instance1)
- # TODO(vponomaryov): use separate API for getting export location for
- # share when "v2" client is used.
- first_location = self.share['export_locations'][0]
- self.mount_share(first_location, ssh_client_inst1)
+ if utils.is_microversion_lt(CONF.share.max_api_microversion, "2.9"):
+ locations = self.share['export_locations']
+ else:
+ exports = self.shares_v2_client.list_share_export_locations(
+ self.share['id'])
+ locations = [x['path'] for x in exports]
+
+ self.mount_share(locations[0], ssh_client_inst1)
self.addCleanup(self.umount_share,
ssh_client_inst1)
self.write_data(test_data, ssh_client_inst1)
@@ -206,7 +218,7 @@
instance2 = self.boot_instance()
self.allow_access_ip(self.share['id'], instance=instance2)
ssh_client_inst2 = self.init_ssh(instance2)
- self.mount_share(first_location, ssh_client_inst2)
+ self.mount_share(locations[0], ssh_client_inst2)
self.addCleanup(self.umount_share,
ssh_client_inst2)
data = self.read_data(ssh_client_inst2)
@@ -246,10 +258,14 @@
cleanup=False)
ssh_client = self.init_ssh(instance1)
- # TODO(vponomaryov): use separate API for getting export location for
- # share when "v2" client is used.
- first_location = self.share['export_locations'][0]
- self.mount_share(first_location, ssh_client)
+ if utils.is_microversion_lt(CONF.share.max_api_microversion, "2.9"):
+ locations = self.share['export_locations']
+ else:
+ exports = self.shares_v2_client.list_share_export_locations(
+ self.share['id'])
+ locations = [x['path'] for x in exports]
+
+ self.mount_share(locations[0], ssh_client)
ssh_client.exec_command("mkdir -p /mnt/f1")
ssh_client.exec_command("mkdir -p /mnt/f2")
@@ -273,20 +289,20 @@
self.umount_share(ssh_client)
share = self.migrate_share(share['id'], dest_pool)
- if utils.is_microversion_supported("2.9"):
- second_location = (
- self.shares_v2_client.list_share_export_locations(
- share['id'])[0]['path'])
+ if utils.is_microversion_lt(CONF.share.max_api_microversion, "2.9"):
+ new_locations = self.share['export_locations']
else:
- # NOTE(vponomaryov): following approach is valid for picking up
- # export location only using microversions lower than '2.9'.
- second_location = share['export_locations'][0]
+ new_exports = self.shares_v2_client.list_share_export_locations(
+ self.share['id'])
+ new_locations = [x['path'] for x in new_exports]
self.assertEqual(dest_pool, share['host'])
- self.assertNotEqual(first_location, second_location)
+ locations.sort()
+ new_locations.sort()
+ self.assertNotEqual(locations, new_locations)
self.assertEqual('migration_success', share['task_state'])
- self.mount_share(second_location, ssh_client)
+ self.mount_share(new_locations[0], ssh_client)
output = ssh_client.exec_command("ls -lRA --ignore=lost+found /mnt")
@@ -303,7 +319,7 @@
protocol = "NFS"
def mount_share(self, location, ssh_client):
- ssh_client.exec_command("sudo mount \"%s\" /mnt" % location)
+ ssh_client.exec_command("sudo mount -vt nfs \"%s\" /mnt" % location)
class TestShareBasicOpsCIFS(ShareBasicOpsBase):
diff --git a/manila_tempest_tests/utils.py b/manila_tempest_tests/utils.py
index 94d8cd3..dea51ab 100644
--- a/manila_tempest_tests/utils.py
+++ b/manila_tempest_tests/utils.py
@@ -81,6 +81,15 @@
return lambda f: f
+def skip_if_microversion_lt(microversion):
+ """Decorator for tests that are microversion-specific."""
+ if is_microversion_lt(CONF.share.max_api_microversion, microversion):
+ reason = ("Skipped. Test requires microversion greater than or "
+ "equal to '%s'." % microversion)
+ return testtools.skip(reason)
+ return lambda f: f
+
+
def rand_ip():
"""This uses the TEST-NET-3 range of reserved IP addresses.