diff --git a/devstack/plugin.sh b/devstack/plugin.sh index 10d3dd5a23..93dfa873b3 100755 --- a/devstack/plugin.sh +++ b/devstack/plugin.sh @@ -542,6 +542,16 @@ function update_tempest { fi } +function install_libraries { + if [ "$MANILA_MULTI_BACKEND" = "True" ]; then + if is_ubuntu; then + install_package nfs-common + else + install_package nfs-utils + fi + fi +} + # Main dispatcher if [[ "$1" == "stack" && "$2" == "install" ]]; then echo_summary "Installing Manila" @@ -552,6 +562,8 @@ elif [[ "$1" == "stack" && "$2" == "post-config" ]]; then configure_manila echo_summary "Initializing Manila" init_manila + echo_summary "Installing extra libraries" + install_libraries elif [[ "$1" == "stack" && "$2" == "extra" ]]; then echo_summary "Creating Manila entities for auth service" create_manila_accounts diff --git a/manila_tempest_tests/config.py b/manila_tempest_tests/config.py index ea1f678ce8..caa4589822 100644 --- a/manila_tempest_tests/config.py +++ b/manila_tempest_tests/config.py @@ -151,4 +151,11 @@ ShareGroup = [ 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 fcfd5eaf55..5ce561e8e1 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 @@ class SharesClient(rest_client.RestClient): 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 @@ class SharesClient(rest_client.RestClient): 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 0f3de8592f..33478cd159 100644 --- a/manila_tempest_tests/share_exceptions.py +++ b/manila_tempest_tests/share_exceptions.py @@ -48,5 +48,10 @@ class InvalidResource(exceptions.TempestException): 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 0000000000..4c6bacfdf3 --- /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 554ba35c00..6444ccc37a 100644 --- a/manila_tempest_tests/tests/api/base.py +++ b/manila_tempest_tests/tests/api/base.py @@ -303,6 +303,13 @@ class BaseSharesTest(test.BaseTestCase): cleanup_list.insert(0, resource) 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.""" diff --git a/manila_tempest_tests/tests/scenario/manager_share.py b/manila_tempest_tests/tests/scenario/manager_share.py index a8133f994d..00a2209352 100644 --- a/manila_tempest_tests/tests/scenario/manager_share.py +++ b/manila_tempest_tests/tests/scenario/manager_share.py @@ -116,7 +116,7 @@ class ShareScenarioTest(manager.NetworkScenarioTest): 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 @@ class ShareScenarioTest(manager.NetworkScenarioTest): 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 @@ class ShareScenarioTest(manager.NetworkScenarioTest): 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 2864b00d4b..be07639ec2 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 @@ class ShareBasicOpsBase(manager.ShareScenarioTest): 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 @@ class ShareBasicOpsBase(manager.ShareScenarioTest): 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 @@ class ShareBasicOpsBase(manager.ShareScenarioTest): "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 @@ class ShareBasicOpsBase(manager.ShareScenarioTest): 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"