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)