Scenario test: Create/manage share and write data
Implements test #6 from:
https://specs.openstack.org/openstack/manila-specs/specs/release_independent/scenario-tests.html
Change-Id: I4245cd202543cfefb0aa4e9c56ab949e2a9821f6
diff --git a/manila_tempest_tests/tests/scenario/manager_share.py b/manila_tempest_tests/tests/scenario/manager_share.py
index 17f4f0e..9542515 100644
--- a/manila_tempest_tests/tests/scenario/manager_share.py
+++ b/manila_tempest_tests/tests/scenario/manager_share.py
@@ -102,27 +102,30 @@
ssh_user=self.ssh_user))
self.security_group = self._create_security_group()
- self.share_network = self.create_share_network()
-
- def mount_share(self, location, remote_client, target_dir=None):
- raise NotImplementedError
-
- def umount_share(self, remote_client, target_dir=None):
- target_dir = target_dir or "/mnt"
- remote_client.exec_command("sudo umount %s" % target_dir)
-
- def create_share_network(self):
- self.net = self._create_network(namestart="manila-share")
+ self.network = self._create_network(namestart="manila-share")
self.subnet = self._create_subnet(
- network=self.net,
+ network=self.network,
namestart="manila-share-sub",
ip_version=self.ip_version,
use_default_subnetpool=self.ipv6_enabled)
router = self._get_router()
self._create_router_interface(subnet_id=self.subnet['id'],
router_id=router['id'])
+
+ if CONF.share.multitenancy_enabled:
+ # Skip if DHSS=False
+ self.share_network = self.create_share_network()
+
+ def mount_share(self, location, remote_client, target_dir=None):
+ raise NotImplementedError
+
+ def unmount_share(self, remote_client, target_dir=None):
+ target_dir = target_dir or "/mnt"
+ remote_client.exec_command("sudo umount %s" % target_dir)
+
+ def create_share_network(self):
share_network = self._create_share_network(
- neutron_net_id=self.net['id'],
+ neutron_net_id=self.network['id'],
neutron_subnet_id=self.subnet['id'],
name=data_utils.rand_name("sn-name"))
return share_network
@@ -134,7 +137,7 @@
'key_name': self.keypair['name'],
'security_groups': security_groups,
'wait_until': wait_until,
- 'networks': [{'uuid': self.net['id']}, ],
+ 'networks': [{'uuid': self.network['id']}, ],
}
instance = self.create_server(
image_id=self.image_id, flavor=self.flavor_ref, **create_kwargs)
@@ -177,6 +180,30 @@
.format(escaped_string=escaped_string,
mount_point=mount_point))
+ def write_data_to_mounted_share_using_dd(self, remote_client,
+ output_file,
+ block_size,
+ block_count,
+ input_file='/dev/zero'):
+ """Writes data to mounted share using dd command
+
+ Example Usage for writing 512Mb to a file on /mnt/
+ (remote_client, block_size=1024, block_count=512000,
+ output_file='/mnt/512mb_of_zeros', input_file='/dev/zero')
+
+ For more information, refer to the dd man page.
+
+ :param remote_client: An SSH client connection to the Nova instance
+ :param block_size: The size of an individual block in bytes
+ :param block_count: The number of blocks to write
+ :param output_file: Path to the file to be written
+ :param input_file: Path to the file to read from
+ """
+
+ remote_client.exec_command(
+ "sudo sh -c \"dd bs={} count={} if={} of={} conv=fsync\""
+ .format(block_size, block_count, input_file, output_file))
+
def read_data_from_mounted_share(self,
remote_client,
mount_point='/mnt/t1'):
@@ -199,7 +226,7 @@
'share_protocol': self.protocol,
})
if not ('share_type_id' in kwargs or 'snapshot_id' in kwargs):
- default_share_type_id = self._get_share_type()['id']
+ default_share_type_id = self.get_share_type()['id']
kwargs.update({'share_type_id': default_share_type_id})
if CONF.share.multitenancy_enabled:
kwargs.update({'share_network_id': self.share_network['id']})
@@ -240,7 +267,9 @@
return linux_client
def allow_access_ip(self, share_id, ip=None, instance=None,
- access_level="rw", cleanup=True, snapshot=None):
+ access_level="rw", cleanup=True, snapshot=None,
+ client=None):
+ client = client or self.shares_v2_client
if instance and not ip:
try:
net_addresses = instance['addresses']
@@ -256,12 +285,13 @@
if snapshot:
self._allow_access_snapshot(snapshot['id'], access_type='ip',
- access_to=ip, cleanup=cleanup)
+ access_to=ip, cleanup=cleanup,
+ client=client)
else:
return self._allow_access(share_id, access_type='ip',
access_level=access_level, access_to=ip,
cleanup=cleanup,
- client=self.shares_v2_client)
+ client=client)
def deny_access(self, share_id, access_rule_id, client=None):
"""Deny share access
@@ -275,12 +305,14 @@
share_id, "active", status_attr='access_rules_status')
def provide_access_to_auxiliary_instance(self, instance, share=None,
- snapshot=None, access_level='rw'):
+ snapshot=None, access_level='rw',
+ client=None):
share = share or self.share
+ client = client or self.shares_v2_client
if self.protocol.lower() == 'cifs':
self.allow_access_ip(
share['id'], instance=instance, cleanup=False,
- snapshot=snapshot, access_level=access_level)
+ snapshot=snapshot, access_level=access_level, client=client)
elif not CONF.share.multitenancy_enabled:
if self.ipv6_enabled:
server_ip = self._get_ipv6_server_ip(instance)
@@ -291,12 +323,12 @@
return self.allow_access_ip(
share['id'], ip=server_ip,
instance=instance, cleanup=False, snapshot=snapshot,
- access_level=access_level)
+ access_level=access_level, client=client)
elif (CONF.share.multitenancy_enabled and
self.protocol.lower() == 'nfs'):
return self.allow_access_ip(
share['id'], instance=instance, cleanup=False,
- snapshot=snapshot, access_level=access_level)
+ snapshot=snapshot, access_level=access_level, client=client)
def wait_for_active_instance(self, instance_id):
waiters.wait_for_server_status(
@@ -304,7 +336,7 @@
return self.os_primary.servers_client.show_server(
instance_id)["server"]
- def _get_share_type(self):
+ def get_share_type(self):
if CONF.share.default_share_type_name:
return self.shares_client.get_share_type(
CONF.share.default_share_type_name)['share_type']
@@ -324,7 +356,7 @@
def _create_share(self, share_protocol=None, size=None, name=None,
snapshot_id=None, description=None, metadata=None,
share_network_id=None, share_type_id=None,
- client=None, cleanup_in_class=True):
+ client=None, cleanup=True):
"""Create a share
:param share_protocol: NFS or CIFS
@@ -336,7 +368,7 @@
:param share_network_id: id of network to be used
:param share_type_id: type of the share to be created
:param client: client object
- :param cleanup_in_class: default: True
+ :param cleanup: default: True
:returns: a created share
"""
client = client or self.shares_client
@@ -360,10 +392,11 @@
}
share = self.shares_client.create_share(**kwargs)
- self.addCleanup(client.wait_for_resource_deletion,
- share_id=share['id'])
- self.addCleanup(client.delete_share,
- share['id'])
+ if cleanup:
+ self.addCleanup(client.wait_for_resource_deletion,
+ share_id=share['id'])
+ self.addCleanup(client.delete_share,
+ share['id'])
client.wait_for_share_status(share['id'], 'available')
return share
@@ -420,13 +453,11 @@
:param access_to
:returns: access object
"""
- client = client or self.shares_client
+ client = client or self.shares_v2_client
access = client.create_access_rule(share_id, access_type, access_to,
access_level)
- # NOTE(u_glide): Ignore provided client, because we always need v2
- # client to make this call
- self.shares_v2_client.wait_for_share_status(
+ client.wait_for_share_status(
share_id, "active", status_attr='access_rules_status')
if cleanup:
@@ -434,22 +465,25 @@
return access
def _allow_access_snapshot(self, snapshot_id, access_type="ip",
- access_to="0.0.0.0/0", cleanup=True):
+ access_to="0.0.0.0/0", cleanup=True,
+ client=None):
"""Allow snapshot access
:param snapshot_id: id of the snapshot
:param access_type: "ip", "user" or "cert"
:param access_to
+ :param client: shares client, normal/admin
:returns: access object
"""
- access = self.shares_v2_client.create_snapshot_access_rule(
+ client = client or self.shares_v2_client
+ access = client.create_snapshot_access_rule(
snapshot_id, access_type, access_to)
if cleanup:
- self.addCleanup(self.shares_v2_client.delete_snapshot_access_rule,
+ self.addCleanup(client.delete_snapshot_access_rule,
snapshot_id, access['id'])
- self.shares_v2_client.wait_for_snapshot_access_rule_status(
+ client.wait_for_snapshot_access_rule_status(
snapshot_id, access['id'])
return access
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 32fd735..4daa2f3 100644
--- a/manila_tempest_tests/tests/scenario/test_share_basic_ops.py
+++ b/manila_tempest_tests/tests/scenario/test_share_basic_ops.py
@@ -122,7 +122,7 @@
for location in locations:
self.mount_share(location, remote_client)
- self.umount_share(remote_client)
+ self.unmount_share(remote_client)
@tc.attr(base.TAG_NEGATIVE, base.TAG_BACKEND)
def test_write_with_ro_access(self):
@@ -143,7 +143,7 @@
self.deny_access(self.share['id'], acc_rule_id)
self.provide_access_to_auxiliary_instance(instance, access_level='ro')
- self.addCleanup(self.umount_share, remote_client_inst)
+ self.addCleanup(self.unmount_share, remote_client_inst)
# Test if write with RO access fails.
self.assertRaises(exceptions.SSHExecCommandFailed,
@@ -168,7 +168,7 @@
self.provide_access_to_auxiliary_instance(instance1)
self.mount_share(location, remote_client_inst1)
- self.addCleanup(self.umount_share,
+ self.addCleanup(self.unmount_share,
remote_client_inst1)
self.write_data_to_mounted_share(test_data, remote_client_inst1)
@@ -178,7 +178,7 @@
self.provide_access_to_auxiliary_instance(instance2)
self.mount_share(location, remote_client_inst2)
- self.addCleanup(self.umount_share,
+ self.addCleanup(self.unmount_share,
remote_client_inst2)
data = self.read_data_from_mounted_share(remote_client_inst2)
self.assertEqual(test_data, data)
@@ -268,7 +268,7 @@
remote_client.exec_command,
"dd if=/dev/zero of=/mnt/f1/1m6.bin bs=1M count=1")
- self.umount_share(remote_client)
+ self.unmount_share(remote_client)
self.share = self.migration_complete(self.share['id'], dest_pool)
@@ -283,7 +283,7 @@
output = remote_client.exec_command("ls -lRA --ignore=lost+found /mnt")
- self.umount_share(remote_client)
+ self.unmount_share(remote_client)
self.assertIn('1m1.bin', output)
self.assertIn('1m2.bin', output)
@@ -319,7 +319,7 @@
remote_client.exec_command("sudo mkdir -p %s" % parent_share_dir)
self.mount_share(user_export_location, remote_client, parent_share_dir)
- self.addCleanup(self.umount_share, remote_client, parent_share_dir)
+ self.addCleanup(self.unmount_share, remote_client, parent_share_dir)
# 6 - Create "file1", ok, created
remote_client.exec_command("sudo touch %s/file1" % parent_share_dir)
@@ -351,7 +351,7 @@
# 12 - Try mount S2, ok, mounted
self.mount_share(user_export_location, remote_client, child_share_dir)
- self.addCleanup(self.umount_share, remote_client, child_share_dir)
+ self.addCleanup(self.unmount_share, remote_client, child_share_dir)
# 13 - List files on S2, only "file1" exists
output = remote_client.exec_command(
@@ -409,7 +409,7 @@
remote_client.exec_command("sudo mkdir -p %s" % snapshot_dir)
self.mount_share(user_export_location, remote_client, parent_share_dir)
- self.addCleanup(self.umount_share, remote_client, parent_share_dir)
+ self.addCleanup(self.unmount_share, remote_client, parent_share_dir)
# 6 - Create "file1", ok, created
remote_client.exec_command("sudo touch %s/file1" % parent_share_dir)
@@ -428,7 +428,7 @@
user_export_location = self._get_user_export_locations(
snapshot=snapshot)[0]
self.mount_share(user_export_location, remote_client, snapshot_dir)
- self.addCleanup(self.umount_share, remote_client, snapshot_dir)
+ self.addCleanup(self.unmount_share, remote_client, snapshot_dir)
# 11 - List files on SS1, only "file1" exists
# NOTE(lseki): using ls without recursion to avoid permission denied
diff --git a/manila_tempest_tests/tests/scenario/test_share_manage_unmanage.py b/manila_tempest_tests/tests/scenario/test_share_manage_unmanage.py
new file mode 100644
index 0000000..81cb499
--- /dev/null
+++ b/manila_tempest_tests/tests/scenario/test_share_manage_unmanage.py
@@ -0,0 +1,194 @@
+# 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 ddt
+
+from oslo_log import log as logging
+from tempest import config
+from tempest.lib import exceptions
+import testtools
+from testtools import testcase as tc
+
+from manila_tempest_tests.tests.api import base
+from manila_tempest_tests.tests.scenario import manager_share as manager
+from manila_tempest_tests import utils
+
+CONF = config.CONF
+LOG = logging.getLogger(__name__)
+
+
+@ddt.ddt
+class ShareManageUnmanageBase(manager.ShareScenarioTest):
+
+ """This test case uses the following flow:
+
+ * Launch an instance
+ * Create share (1GB)
+ * Configure RW access to the share
+ * Perform ssh to instance
+ * Mount share
+ * Write data in share
+ * Unmount share
+ * Unmanage share
+ * Attempt to access share (fail expected)
+ * Manage share
+ * Configure RW access to the share
+ * Mount share
+ * Read data from share
+ * Unmount share
+ * Delete share
+ * Attempt to manage share (fail expected)
+ * Terminate the instance
+ """
+
+ @tc.attr(base.TAG_POSITIVE, base.TAG_BACKEND)
+ @testtools.skipUnless(
+ CONF.share.run_manage_unmanage_tests,
+ "Manage/unmanage tests are disabled.")
+ @testtools.skipIf(
+ CONF.share.multitenancy_enabled,
+ "Manage/unmanage tests are skipped when DHSS is enabled")
+ def test_create_manage_and_write(self):
+ share_size = CONF.share.share_size
+
+ LOG.debug('Step 1 - create instance')
+ instance = self.boot_instance(wait_until="BUILD")
+
+ LOG.debug('Step 2 - create share of size {} Gb'.format(share_size))
+ share = self.create_share(size=share_size, cleanup=False)
+ instance = self.wait_for_active_instance(instance["id"])
+
+ LOG.debug('Step 3 - SSH to UVM')
+ remote_client = self.init_remote_client(instance)
+
+ LOG.debug('Step 4 - provide access to instance')
+ self.provide_access_to_auxiliary_instance(instance, share=share)
+
+ if utils.is_microversion_lt(CONF.share.max_api_microversion, "2.9"):
+ locations = share['export_locations']
+ else:
+ exports = self.shares_v2_client.list_share_export_locations(
+ share['id'])
+ locations = [x['path'] for x in exports]
+
+ LOG.debug('Step 5 - mount')
+ self.mount_share(locations[0], remote_client)
+
+ # Update share info, needed later
+ share = self.shares_admin_v2_client.get_share(share['id'])
+
+ LOG.debug('Step 6a - create file')
+ remote_client.exec_command("sudo touch /mnt/t1")
+
+ LOG.debug('Step 6b - write data')
+ LOG.debug('Step 6b - writing 640mb')
+ self.write_data_to_mounted_share_using_dd(remote_client,
+ '/mnt/t1', 1024,
+ 2048, '/dev/zero')
+ ls_result = remote_client.exec_command("sudo ls -lA /mnt/")
+ LOG.debug(ls_result)
+
+ LOG.debug('Step 7 - unmount share')
+ self.unmount_share(remote_client)
+
+ LOG.debug('Step 8a - unmanage share')
+ self.shares_admin_v2_client.unmanage_share(share['id'])
+
+ LOG.debug('Step 8b - wait for status change')
+ self.shares_admin_v2_client.wait_for_resource_deletion(
+ share_id=share['id'])
+
+ LOG.debug('Step 9 - get share, should fail')
+ self.assertRaises(
+ exceptions.NotFound,
+ self.shares_admin_v2_client.get_share,
+ self.share['id'])
+
+ LOG.debug('Step 10 - manage share')
+ share_type = self.get_share_type()
+ managed_share = self.shares_admin_v2_client.manage_share(
+ share['host'],
+ share['share_proto'],
+ locations[0],
+ share_type['id'])
+ self.shares_admin_v2_client.wait_for_share_status(
+ managed_share['id'], 'available')
+
+ LOG.debug('Step 11 - grant access again')
+ self.provide_access_to_auxiliary_instance(
+ instance,
+ share=managed_share,
+ client=self.shares_admin_v2_client)
+
+ exports = self.shares_admin_v2_client.list_share_export_locations(
+ managed_share['id'])
+ locations = [x['path'] for x in exports]
+
+ LOG.debug('Step 12 - mount')
+ self.mount_share(locations[0], remote_client)
+
+ LOG.debug('Step 12 - verify data')
+ ls_result = remote_client.exec_command("sudo ls -lA /mnt/")
+ LOG.debug(ls_result)
+
+ LOG.debug('Step 13 - unmount share')
+ self.unmount_share(remote_client)
+
+ LOG.debug('Step 14 - delete share')
+ self.shares_admin_v2_client.delete_share(managed_share['id'])
+ self.shares_admin_v2_client.wait_for_resource_deletion(
+ share_id=managed_share['id'])
+
+ LOG.debug('Step 15 - manage share, should fail')
+ remanaged_share = self.shares_admin_v2_client.manage_share(
+ share['host'],
+ share['share_proto'],
+ locations[0],
+ share_type['id'])
+ self.shares_admin_v2_client.wait_for_share_status(
+ remanaged_share['id'], 'manage_error')
+
+ self.shares_admin_v2_client.reset_state(remanaged_share['id'])
+
+
+class ShareManageUnmanageNFS(ShareManageUnmanageBase):
+ protocol = "nfs"
+
+ def mount_share(self, location, remote_client, target_dir=None):
+ target_dir = target_dir or "/mnt"
+ remote_client.exec_command(
+ "sudo mount -vt nfs \"%s\" %s" % (location, target_dir)
+ )
+
+
+class ShareManageUnmanageCIFS(ShareManageUnmanageBase):
+ protocol = "cifs"
+
+ def mount_share(self, location, remote_client, target_dir=None):
+ location = location.replace("\\", "/")
+ target_dir = target_dir or "/mnt"
+ remote_client.exec_command(
+ "sudo mount.cifs \"%s\" %s -o guest" % (location, target_dir)
+ )
+
+
+# NOTE(u_glide): this function is required to exclude ShareManageUnmanageBase
+# from executed test cases.
+# See: https://docs.python.org/2/library/unittest.html#load-tests-protocol
+# for details.
+def load_tests(loader, tests, _):
+ result = []
+ for test_case in tests:
+ if type(test_case._tests[0]) is ShareManageUnmanageBase:
+ continue
+ result.append(test_case)
+ return loader.suiteClass(result)