Merge "Add CephFS Native scenario tests"
diff --git a/manila_tempest_tests/tests/scenario/manager_share.py b/manila_tempest_tests/tests/scenario/manager_share.py
index 4593e9a..10074b1 100644
--- a/manila_tempest_tests/tests/scenario/manager_share.py
+++ b/manila_tempest_tests/tests/scenario/manager_share.py
@@ -124,6 +124,9 @@
     def mount_share(self, location, remote_client, target_dir=None):
         raise NotImplementedError
 
+    def allow_access(self, **kwargs):
+        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)
@@ -347,6 +350,54 @@
                 share['id'], instance=instance, cleanup=False,
                 snapshot=snapshot, access_level=access_level, client=client)
 
+    def provide_access_to_client_identified_by_cephx(self, share=None,
+                                                     access_level='rw',
+                                                     access_to=None,
+                                                     remote_client=None,
+                                                     locations=None,
+                                                     client=None,
+                                                     oc_size=20971520):
+        share = share or self.share
+        client = client or self.shares_v2_client
+        access_to = access_to or data_utils.rand_name(
+            self.__class__.__name__ + '-cephx-id')
+        # Check if access is already granted to the client
+        access = self.shares_v2_client.list_access_rules(
+            share['id'], metadata={'metadata': {'access_to': access_to}})
+        access = access[0] if access else None
+
+        if not access:
+            access = self._allow_access(
+                share['id'], access_level=access_level, access_to=access_to,
+                access_type="cephx", cleanup=False, client=client)
+            # Set metadata to access rule to be filtered if necessary.
+            # This is necessary to prevent granting access to a client who
+            # already has.
+            self.shares_v2_client.update_access_metadata(
+                metadata={"access_to": "{}".format(access_to)},
+                access_id=access['id'])
+        get_access = self.shares_v2_client.get_access(access['id'])
+        # Set 'access_key' and 'access_to' attributes for being use in mount
+        # operation.
+        setattr(self, 'access_key', get_access['access_key'])
+        setattr(self, 'access_to', access_to)
+
+        remote_client.exec_command(
+            "sudo crudini --set {access_to}.keyring client.{access_to} key "
+            "{access_key}"
+            .format(access_to=access_to, access_key=self.access_key))
+        remote_client.exec_command(
+            "sudo crudini --set ceph.conf client \"client quota\" true")
+        remote_client.exec_command(
+            "sudo crudini --set ceph.conf client \"client oc size\" {}"
+            .format(oc_size))
+        if not isinstance(locations, list):
+            locations = [locations]
+        remote_client.exec_command(
+            "sudo crudini --set ceph.conf client \"mon host\" {}"
+            .format(locations[0].split(':/')[0]))
+        return access
+
     def wait_for_active_instance(self, instance_id):
         waiters.wait_for_server_status(
             self.os_primary.servers_client, instance_id, "ACTIVE")
@@ -598,9 +649,10 @@
             locations = self._get_snapshot_export_locations(snapshot)
 
         self.assertNotEmpty(locations)
-        locations = self._get_export_locations_according_to_ip_version(
-            locations, error_on_invalid_ip_version)
-        self.assertNotEmpty(locations)
+        if self.protocol != 'cephfs':
+            locations = self._get_export_locations_according_to_ip_version(
+                locations, error_on_invalid_ip_version)
+            self.assertNotEmpty(locations)
 
         return locations
 
@@ -630,3 +682,39 @@
             message = ("Protocol %s is not supported" % self.protocol)
             raise self.skipException(message)
         return ip, version
+
+
+class BaseShareCEPHFSTest(ShareScenarioTest):
+
+    def allow_access(self, access_level='rw', **kwargs):
+        return self.provide_access_to_client_identified_by_cephx(
+            remote_client=kwargs['remote_client'],
+            locations=kwargs['locations'], access_level=access_level)
+
+    def _fuse_client(self, mountpoint, remote_client, target_dir, access_to):
+        remote_client.exec_command(
+            "sudo ceph-fuse {target_dir} --id={access_to} --conf=ceph.conf "
+            "--keyring={access_to}.keyring --client-mountpoint={mountpoint}"
+            .format(target_dir=target_dir, access_to=access_to,
+                    mountpoint=mountpoint))
+
+    def mount_share(self, location, remote_client, target_dir=None,
+                    access_to=None):
+        target_dir = target_dir or "/mnt"
+        access_to = access_to or self.access_to
+        mountpoint = location.split(':')[-1]
+        if getattr(self, 'mount_client', None):
+            return self._fuse_client(mountpoint, remote_client, target_dir,
+                                     access_to=access_to)
+        remote_client.exec_command(
+            "sudo mount -t ceph {location} {target_dir} -o name={access_to},"
+            "secret={access_key}"
+            .format(location=location, target_dir=target_dir,
+                    access_to=access_to, access_key=self.access_key))
+
+    def unmount_share(self, remote_client, target_dir=None):
+        target_dir = target_dir or "/mnt"
+        if getattr(self, 'mount_client', None):
+            return remote_client.exec_command(
+                "sudo fusermount -uz %s" % target_dir)
+        super(BaseShareCEPHFSTest, self).unmount_share(remote_client)
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 8a55b2e..06a23bf 100644
--- a/manila_tempest_tests/tests/scenario/test_share_basic_ops.py
+++ b/manila_tempest_tests/tests/scenario/test_share_basic_ops.py
@@ -44,14 +44,6 @@
      * Terminate the instance
     """
 
-    @classmethod
-    def skip_checks(cls):
-        super(ShareBasicOpsBase, cls).skip_checks()
-        if cls.protocol not in CONF.share.enable_ip_rules_for_protocols:
-            message = ("%s tests for access rules other than IP are disabled" %
-                       cls.protocol)
-            raise cls.skipException(message)
-
     def _ping_host_from_export_location(self, export, remote_client):
         ip, version = self.get_ip_and_version_from_export_location(export)
         if version == 6:
@@ -66,7 +58,8 @@
         locations = self.get_user_export_locations(self.share)
         instance = self.wait_for_active_instance(instance["id"])
         remote_client = self.init_remote_client(instance)
-        self.provide_access_to_auxiliary_instance(instance)
+        self.allow_access(instance=instance, remote_client=remote_client,
+                          locations=locations)
 
         for location in locations:
             self.mount_share(location, remote_client)
@@ -85,12 +78,17 @@
         remote_client_inst = self.init_remote_client(instance)
 
         # First, check if write works RW access.
-        acc_rule_id = self.provide_access_to_auxiliary_instance(instance)['id']
+        acc_rule_id = self.allow_access(
+            instance=instance, remote_client=remote_client_inst,
+            locations=location)['id']
+
         self.mount_share(location, remote_client_inst)
         self.write_data_to_mounted_share(test_data, remote_client_inst)
         self.deny_access(self.share['id'], acc_rule_id)
 
-        self.provide_access_to_auxiliary_instance(instance, access_level='ro')
+        self.allow_access(instance=instance, remote_client=remote_client_inst,
+                          locations=location, access_level='ro')
+
         self.addCleanup(self.unmount_share, remote_client_inst)
 
         # Test if write with RO access fails.
@@ -113,7 +111,9 @@
 
         # Write data to first VM
         remote_client_inst1 = self.init_remote_client(instance1)
-        self.provide_access_to_auxiliary_instance(instance1)
+        self.allow_access(instance=instance1,
+                          remote_client=remote_client_inst1,
+                          locations=location)
 
         self.mount_share(location, remote_client_inst1)
         self.addCleanup(self.unmount_share,
@@ -123,7 +123,9 @@
         # Read from second VM
         remote_client_inst2 = self.init_remote_client(instance2)
         if not CONF.share.override_ip_for_nfs_access or self.ipv6_enabled:
-            self.provide_access_to_auxiliary_instance(instance2)
+            self.allow_access(instance=instance2,
+                              remote_client=remote_client_inst2,
+                              locations=location)
 
         self.mount_share(location, remote_client_inst2)
         self.addCleanup(self.unmount_share,
@@ -387,6 +389,18 @@
 class TestShareBasicOpsNFS(ShareBasicOpsBase):
     protocol = "nfs"
 
+    @classmethod
+    def skip_checks(cls):
+        super(TestShareBasicOpsNFS, cls).skip_checks()
+        if cls.protocol not in CONF.share.enable_ip_rules_for_protocols:
+            message = ("%s tests for access rules other than IP are disabled" %
+                       cls.protocol)
+            raise cls.skipException(message)
+
+    def allow_access(self, access_level='rw', **kwargs):
+        return self.provide_access_to_auxiliary_instance(
+            instance=kwargs['instance'], access_level=access_level)
+
     def mount_share(self, location, remote_client, target_dir=None):
 
         self._ping_host_from_export_location(location, remote_client)
@@ -399,6 +413,18 @@
 class TestShareBasicOpsCIFS(ShareBasicOpsBase):
     protocol = "cifs"
 
+    @classmethod
+    def skip_checks(cls):
+        super(TestShareBasicOpsCIFS, cls).skip_checks()
+        if cls.protocol not in CONF.share.enable_ip_rules_for_protocols:
+            message = ("%s tests for access rules other than IP are disabled" %
+                       cls.protocol)
+            raise cls.skipException(message)
+
+    def allow_access(self, access_level='rw', **kwargs):
+        return self.provide_access_to_auxiliary_instance(
+            instance=kwargs['instance'], access_level=access_level)
+
     def mount_share(self, location, remote_client, target_dir=None):
 
         self._ping_host_from_export_location(location, remote_client)
@@ -426,6 +452,25 @@
         raise self.skipException(msg)
 
 
+class TestShareBasicOpsCEPHFS(ShareBasicOpsBase, manager.BaseShareCEPHFSTest):
+    protocol = "cephfs"
+
+    @tc.attr(base.TAG_POSITIVE, base.TAG_BACKEND)
+    def test_mount_share_one_vm_with_ceph_fuse_client(self):
+        self.mount_client = 'fuse'
+        super(TestShareBasicOpsCEPHFS, self).test_mount_share_one_vm()
+
+    @tc.attr(base.TAG_POSITIVE, base.TAG_BACKEND)
+    def test_write_with_ro_access_with_ceph_fuse_client(self):
+        self.mount_client = 'fuse'
+        super(TestShareBasicOpsCEPHFS, self).test_write_with_ro_access()
+
+    @tc.attr(base.TAG_POSITIVE, base.TAG_BACKEND)
+    def test_read_write_two_vms_with_ceph_fuse_client(self):
+        self.mount_client = 'fuse'
+        super(TestShareBasicOpsCEPHFS, self).test_read_write_two_vms()
+
+
 class TestShareBasicOpsNFSIPv6(TestShareBasicOpsNFS):
     ip_version = 6
 
diff --git a/manila_tempest_tests/tests/scenario/test_share_extend.py b/manila_tempest_tests/tests/scenario/test_share_extend.py
index 7827b75..e0a1e4a 100644
--- a/manila_tempest_tests/tests/scenario/test_share_extend.py
+++ b/manila_tempest_tests/tests/scenario/test_share_extend.py
@@ -45,14 +45,6 @@
      * Terminate the instance
     """
 
-    @classmethod
-    def skip_checks(cls):
-        super(ShareExtendBase, cls).skip_checks()
-        if cls.protocol not in CONF.share.enable_ip_rules_for_protocols:
-            message = ("%s tests for access rules other than IP are disabled" %
-                       cls.protocol)
-            raise cls.skipException(message)
-
     @tc.attr(base.TAG_POSITIVE, base.TAG_BACKEND)
     def test_create_extend_and_write(self):
         default_share_size = CONF.share.share_size
@@ -69,10 +61,11 @@
         remote_client = self.init_remote_client(instance)
 
         LOG.debug('Step 4 - grant access')
-        self.provide_access_to_auxiliary_instance(instance, share=share)
+        location = self.get_share_export_location_for_mount(share)
+        self.allow_access(instance=instance, remote_client=remote_client,
+                          locations=location)
 
         LOG.debug('Step 5 - mount')
-        location = self.get_share_export_location_for_mount(share)
         self.mount_share(location, remote_client)
 
         total_blocks = (units.Ki * default_share_size) / 64
@@ -153,6 +146,18 @@
 class TestShareExtendNFS(ShareExtendBase):
     protocol = "nfs"
 
+    @classmethod
+    def skip_checks(cls):
+        super(ShareExtendBase, cls).skip_checks()
+        if cls.protocol not in CONF.share.enable_ip_rules_for_protocols:
+            message = ("%s tests for access rules other than IP are disabled" %
+                       cls.protocol)
+            raise cls.skipException(message)
+
+    def allow_access(self, access_level='rw', **kwargs):
+        return self.provide_access_to_auxiliary_instance(
+            instance=kwargs['instance'], access_level=access_level)
+
     def mount_share(self, location, remote_client, target_dir=None):
         target_dir = target_dir or "/mnt"
         remote_client.exec_command(
@@ -163,6 +168,18 @@
 class TestShareExtendCIFS(ShareExtendBase):
     protocol = "cifs"
 
+    @classmethod
+    def skip_checks(cls):
+        super(ShareExtendBase, cls).skip_checks()
+        if cls.protocol not in CONF.share.enable_ip_rules_for_protocols:
+            message = ("%s tests for access rules other than IP are disabled" %
+                       cls.protocol)
+            raise cls.skipException(message)
+
+    def allow_access(self, access_level='rw', **kwargs):
+        return self.provide_access_to_auxiliary_instance(
+            instance=kwargs['instance'], access_level=access_level)
+
     def mount_share(self, location, remote_client, target_dir=None):
         location = location.replace("\\", "/")
         target_dir = target_dir or "/mnt"
@@ -171,6 +188,15 @@
         )
 
 
+class TestShareExtendCEPHFS(ShareExtendBase, manager.BaseShareCEPHFSTest):
+    protocol = "cephfs"
+
+    @tc.attr(base.TAG_POSITIVE, base.TAG_BACKEND)
+    def test_create_extend_and_write_with_ceph_fuse_client(self):
+        self.mount_client = 'fuse'
+        super(TestShareExtendCEPHFS, self).test_create_extend_and_write()
+
+
 # NOTE(u_glide): this function is required to exclude ShareExtendBase
 # from executed test cases.
 # See: https://docs.python.org/3/library/unittest.html#load-tests-protocol
diff --git a/manila_tempest_tests/tests/scenario/test_share_shrink.py b/manila_tempest_tests/tests/scenario/test_share_shrink.py
index e8ffdcb..785c6ce 100644
--- a/manila_tempest_tests/tests/scenario/test_share_shrink.py
+++ b/manila_tempest_tests/tests/scenario/test_share_shrink.py
@@ -46,14 +46,6 @@
      * Terminate the instance
     """
 
-    @classmethod
-    def skip_checks(cls):
-        super(ShareShrinkBase, cls).skip_checks()
-        if cls.protocol not in CONF.share.enable_ip_rules_for_protocols:
-            message = ("%s tests for access rules other than IP are disabled" %
-                       cls.protocol)
-            raise cls.skipException(message)
-
     @tc.attr(base.TAG_POSITIVE, base.TAG_BACKEND)
     @testtools.skipUnless(
         CONF.share.run_shrink_tests, 'Shrink share tests are disabled.')
@@ -72,10 +64,11 @@
         remote_client = self.init_remote_client(instance)
 
         LOG.debug('Step 4 - grant access')
-        self.provide_access_to_auxiliary_instance(instance)
+        location = self.get_share_export_location_for_mount(share)
+        self.allow_access(instance=instance, remote_client=remote_client,
+                          locations=location)
 
         LOG.debug('Step 5 - mount')
-        location = self.get_share_export_location_for_mount(share)
         self.mount_share(location, remote_client)
 
         total_blocks = (1024 * default_share_size) / 64
@@ -167,6 +160,18 @@
 class TestShareShrinkNFS(ShareShrinkBase):
     protocol = "nfs"
 
+    @classmethod
+    def skip_checks(cls):
+        super(ShareShrinkBase, cls).skip_checks()
+        if cls.protocol not in CONF.share.enable_ip_rules_for_protocols:
+            message = ("%s tests for access rules other than IP are disabled" %
+                       cls.protocol)
+            raise cls.skipException(message)
+
+    def allow_access(self, access_level='rw', **kwargs):
+        return self.provide_access_to_auxiliary_instance(
+            instance=kwargs['instance'], access_level=access_level)
+
     def mount_share(self, location, ssh_client, target_dir=None):
         target_dir = target_dir or "/mnt"
         ssh_client.exec_command(
@@ -177,6 +182,18 @@
 class TestShareShrinkCIFS(ShareShrinkBase):
     protocol = "cifs"
 
+    @classmethod
+    def skip_checks(cls):
+        super(ShareShrinkBase, cls).skip_checks()
+        if cls.protocol not in CONF.share.enable_ip_rules_for_protocols:
+            message = ("%s tests for access rules other than IP are disabled" %
+                       cls.protocol)
+            raise cls.skipException(message)
+
+    def allow_access(self, access_level='rw', **kwargs):
+        return self.provide_access_to_auxiliary_instance(
+            instance=kwargs['instance'], access_level=access_level)
+
     def mount_share(self, location, ssh_client, target_dir=None):
         location = location.replace("\\", "/")
         target_dir = target_dir or "/mnt"
@@ -185,6 +202,15 @@
         )
 
 
+class TestShareShrinkCEPHFS(ShareShrinkBase, manager.BaseShareCEPHFSTest):
+    protocol = "cephfs"
+
+    @tc.attr(base.TAG_POSITIVE, base.TAG_BACKEND)
+    def test_create_shrink_and_write_with_ceph_fuse_client(self):
+        self.mount_client = 'fuse'
+        super(TestShareShrinkCEPHFS, self).test_create_shrink_and_write()
+
+
 # NOTE(u_glide): this function is required to exclude ShareShrinkBase from
 # executed test cases.
 # See: https://docs.python.org/3/library/unittest.html#load-tests-protocol