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"