Add Share Migration tempest functional tests

This patch adds functional tests for Share Migration,
running on generic driver DHSS = true mode.

Implements: blueprint share-migration
Change-Id: I64b0a3ee77b27278cc294f72702408a27888e0e9
diff --git a/manila_tempest_tests/config.py b/manila_tempest_tests/config.py
index ea1f678..caa4589 100644
--- a/manila_tempest_tests/config.py
+++ b/manila_tempest_tests/config.py
@@ -151,4 +151,11 @@
     cfg.StrOpt("client_vm_flavor_ref",
                default="100",
                help="Flavor used for client vm in scenario tests."),
+    cfg.IntOpt("migration_timeout",
+               default=1200,
+               help="Time to wait for share migration before "
+                    "timing out (seconds)."),
+    cfg.BoolOpt("migration_enabled",
+                default=True,
+                help="Enable or disable migration tests."),
 ]
diff --git a/manila_tempest_tests/services/share/json/shares_client.py b/manila_tempest_tests/services/share/json/shares_client.py
index fcfd5ea..5ce561e 100644
--- a/manila_tempest_tests/services/share/json/shares_client.py
+++ b/manila_tempest_tests/services/share/json/shares_client.py
@@ -47,10 +47,24 @@
         self.build_interval = CONF.share.build_interval
         self.build_timeout = CONF.share.build_timeout
         self.API_MICROVERSIONS_HEADER = 'x-openstack-manila-api-version'
+        self.API_MICROVERSIONS_EXPERIMENTAL_HEADER = (
+            'x-openstack-manila-api-experimental')
 
     def _get_version_dict(self, version):
         return {self.API_MICROVERSIONS_HEADER: version}
 
+    def migrate_share(self, share_id, host):
+        post_body = {
+            'os-migrate_share': {
+                'host': host,
+            }
+        }
+        body = json.dumps(post_body)
+        headers = self._get_version_dict('1.6')
+        headers[self.API_MICROVERSIONS_EXPERIMENTAL_HEADER] = 'true'
+        return self.post('shares/%s/action' % share_id, body,
+                         headers=headers, extra_headers=True)
+
     def send_microversion_request(self, version=None):
         """Prepare and send the HTTP GET Request to the base URL.
 
@@ -263,6 +277,30 @@
         self.expected_success(202, resp.status)
         return body
 
+    def wait_for_migration_completed(self, share_id, dest_host):
+        """Waits for a share to migrate to a certain host."""
+        share = self.get_share(share_id)
+        migration_timeout = CONF.share.migration_timeout
+        start = int(time.time())
+        while share['task_state'] != 'migration_success':
+            time.sleep(self.build_interval)
+            share = self.get_share(share_id)
+            if share['task_state'] == 'migration_success':
+                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.' % {
+                               'src': share['host'],
+                               'dest': dest_host,
+                               'share_id': share['id'],
+                               'timeout': self.build_timeout
+                           })
+                raise exceptions.TimeoutException(message)
+
     def wait_for_share_status(self, share_id, status):
         """Waits for a share to reach a given status."""
         body = self.get_share(share_id)
diff --git a/manila_tempest_tests/share_exceptions.py b/manila_tempest_tests/share_exceptions.py
index 0f3de85..33478cd 100644
--- a/manila_tempest_tests/share_exceptions.py
+++ b/manila_tempest_tests/share_exceptions.py
@@ -48,5 +48,10 @@
     message = "Provided invalid resource: %(message)s"
 
 
+class ShareMigrationException(exceptions.TempestException):
+    message = ("Share %(share_id)s failed to migrate from "
+               "host %(src)s to host %(dest)s.")
+
+
 class ResourceReleaseFailed(exceptions.TempestException):
     message = "Failed to release resource '%(res_type)s' with id '%(res_id)s'."
diff --git a/manila_tempest_tests/tests/api/admin/test_migration.py b/manila_tempest_tests/tests/api/admin/test_migration.py
new file mode 100644
index 0000000..4c6bacf
--- /dev/null
+++ b/manila_tempest_tests/tests/api/admin/test_migration.py
@@ -0,0 +1,67 @@
+# 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 import test  # noqa
+
+from manila_tempest_tests.tests.api import base
+
+CONF = config.CONF
+
+
+class MigrationTest(base.BaseSharesAdminTest):
+    """Tests Share Migration.
+
+    Tests migration in multi-backend environment.
+    """
+
+    protocol = "nfs"
+
+    @classmethod
+    def resource_setup(cls):
+        super(MigrationTest, cls).resource_setup()
+        if cls.protocol not in CONF.share.enable_protocols:
+            message = "%s tests are disabled" % cls.protocol
+            raise cls.skipException(message)
+
+    @test.attr(type=["gate", "smoke", ])
+    def test_migration_empty(self):
+
+        if not CONF.share.migration_enabled:
+            raise self.skipException("Migration tests disabled. Skipping.")
+
+        pools = self.shares_client.list_pools()['pools']
+
+        if len(pools) < 2:
+            raise self.skipException("At least two different running "
+                                     "manila-share services are needed to "
+                                     "run migration tests. Skipping.")
+        share = self.create_share(self.protocol)
+        share = self.shares_client.get_share(share['id'])
+
+        dest_pool = next((x for x in pools if x['name'] != share['host']),
+                         None)
+
+        self.assertIsNotNone(dest_pool)
+
+        dest_pool = dest_pool['name']
+
+        old_export_location = share['export_locations'][0]
+
+        share = self.migrate_share(share['id'], dest_pool)
+
+        self.assertEqual(dest_pool, share['host'])
+        self.assertNotEqual(old_export_location, share['export_locations'][0])
+        self.assertEqual('migration_success', share['task_state'])
diff --git a/manila_tempest_tests/tests/api/base.py b/manila_tempest_tests/tests/api/base.py
index 554ba35..6444ccc 100644
--- a/manila_tempest_tests/tests/api/base.py
+++ b/manila_tempest_tests/tests/api/base.py
@@ -304,6 +304,13 @@
         return share
 
     @classmethod
+    def migrate_share(cls, share_id, dest_host, client=None):
+        client = client or cls.shares_client
+        client.migrate_share(share_id, dest_host)
+        share = client.wait_for_migration_completed(share_id, dest_host)
+        return share
+
+    @classmethod
     def create_share(cls, *args, **kwargs):
         """Create one share and wait for available state. Retry if allowed."""
         result = cls.create_shares([{"args": args, "kwargs": kwargs}])
diff --git a/manila_tempest_tests/tests/scenario/manager_share.py b/manila_tempest_tests/tests/scenario/manager_share.py
index a8133f9..00a2209 100644
--- a/manila_tempest_tests/tests/scenario/manager_share.py
+++ b/manila_tempest_tests/tests/scenario/manager_share.py
@@ -116,7 +116,7 @@
         return sn
 
     def _allow_access(self, share_id, client=None,
-                      access_type="ip", access_to="0.0.0.0"):
+                      access_type="ip", access_to="0.0.0.0", cleanup=True):
         """Allow share access
 
         :param share_id: id of the share
@@ -128,8 +128,8 @@
         client = client or self.shares_client
         access = client.create_access_rule(share_id, access_type, access_to)
         client.wait_for_access_rule_status(share_id, access['id'], "active")
-        self.addCleanup(client.delete_access_rule,
-                        share_id, access['id'])
+        if cleanup:
+            self.addCleanup(client.delete_access_rule, share_id, access['id'])
         return access
 
     def _create_router_interface(self, subnet_id, client=None,
@@ -182,3 +182,9 @@
             raise
 
         return linux_client
+
+    def _migrate_share(self, share_id, dest_host, client=None):
+        client = client or self.shares_client
+        client.migrate_share(share_id, dest_host)
+        share = client.wait_for_migration_completed(share_id, dest_host)
+        return share
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 2864b00..be07639 100644
--- a/manila_tempest_tests/tests/scenario/test_share_basic_ops.py
+++ b/manila_tempest_tests/tests/scenario/test_share_basic_ops.py
@@ -116,6 +116,11 @@
         data = ssh_client.exec_command("sudo cat /mnt/t1")
         return data.rstrip()
 
+    def migrate_share(self, share_id, dest_host):
+        share = self._migrate_share(share_id, dest_host,
+                                    self.shares_admin_client)
+        return share
+
     def create_share_network(self):
         self.net = self._create_network(namestart="manila-share")
         self.subnet = self._create_subnet(network=self.net,
@@ -132,7 +137,7 @@
         self.share = self._create_share(share_protocol=self.protocol,
                                         share_network_id=share_net_id)
 
-    def allow_access_ip(self, share_id, ip=None, instance=None):
+    def allow_access_ip(self, share_id, ip=None, instance=None, cleanup=True):
         if instance and not ip:
             try:
                 net_addresses = instance['addresses']
@@ -144,7 +149,8 @@
                               "Falling back to default")
         if not ip:
             ip = '0.0.0.0/0'
-        self._allow_access(share_id, access_type='ip', access_to=ip)
+        self._allow_access(share_id, access_type='ip', access_to=ip,
+                           cleanup=cleanup)
 
     @test.services('compute', 'network')
     def test_mount_share_one_vm(self):
@@ -187,6 +193,84 @@
         data = self.read_data(ssh_client_inst2)
         self.assertEqual(test_data, data)
 
+    @test.services('compute', 'network')
+    def test_migration_files(self):
+
+        if self.protocol == "CIFS":
+            raise self.skipException("Test for CIFS protocol not supported "
+                                     "at this moment. Skipping.")
+
+        if not CONF.share.migration_enabled:
+            raise self.skipException("Migration tests disabled. Skipping.")
+
+        pools = self.shares_admin_client.list_pools()['pools']
+
+        if len(pools) < 2:
+            raise self.skipException("At least two different running "
+                                     "manila-share services are needed to "
+                                     "run migration tests. Skipping.")
+
+        self.security_group = self._create_security_group()
+        self.create_share_network()
+        self.create_share(self.share_net['id'])
+        share = self.shares_client.get_share(self.share['id'])
+
+        dest_pool = next((x for x in pools if x['name'] != share['host']),
+                         None)
+
+        self.assertIsNotNone(dest_pool)
+
+        dest_pool = dest_pool['name']
+
+        old_export_location = share['export_locations'][0]
+
+        instance1 = self.boot_instance(self.net)
+        self.allow_access_ip(self.share['id'], instance=instance1,
+                             cleanup=False)
+        ssh_client = self.init_ssh(instance1)
+        first_location = self.share['export_locations'][0]
+        self.mount_share(first_location, ssh_client)
+
+        ssh_client.exec_command("mkdir -p /mnt/f1")
+        ssh_client.exec_command("mkdir -p /mnt/f2")
+        ssh_client.exec_command("mkdir -p /mnt/f3")
+        ssh_client.exec_command("mkdir -p /mnt/f4")
+        ssh_client.exec_command("mkdir -p /mnt/f1/ff1")
+        ssh_client.exec_command("sleep 1")
+        ssh_client.exec_command("dd if=/dev/zero of=/mnt/f1/1m1.bin bs=1M"
+                                " count=1")
+        ssh_client.exec_command("dd if=/dev/zero of=/mnt/f2/1m2.bin bs=1M"
+                                " count=1")
+        ssh_client.exec_command("dd if=/dev/zero of=/mnt/f3/1m3.bin bs=1M"
+                                " count=1")
+        ssh_client.exec_command("dd if=/dev/zero of=/mnt/f4/1m4.bin bs=1M"
+                                " count=1")
+        ssh_client.exec_command("dd if=/dev/zero of=/mnt/f1/ff1/1m5.bin bs=1M"
+                                " count=1")
+        ssh_client.exec_command("chmod -R 555 /mnt/f3")
+        ssh_client.exec_command("chmod -R 777 /mnt/f4")
+
+        self.umount_share(ssh_client)
+
+        share = self.migrate_share(share['id'], dest_pool)
+
+        self.assertEqual(dest_pool, share['host'])
+        self.assertNotEqual(old_export_location, share['export_locations'][0])
+        self.assertEqual('migration_success', share['task_state'])
+
+        second_location = share['export_locations'][0]
+        self.mount_share(second_location, ssh_client)
+
+        output = ssh_client.exec_command("ls -lRA --ignore=lost+found /mnt")
+
+        self.umount_share(ssh_client)
+
+        self.assertTrue('1m1.bin' in output)
+        self.assertTrue('1m2.bin' in output)
+        self.assertTrue('1m3.bin' in output)
+        self.assertTrue('1m4.bin' in output)
+        self.assertTrue('1m5.bin' in output)
+
 
 class TestShareBasicOpsNFS(ShareBasicOpsBase):
     protocol = "NFS"