From 9901248f7982481c5434caaa93175e9b1f549fff Mon Sep 17 00:00:00 2001 From: Ionut Balutoiu Date: Mon, 1 Mar 2021 12:20:06 +0200 Subject: [PATCH 1/2] Refactor Manila Ganesha test The current Manila Ganesha `test_manila_share` had a lot of functionality that can be re-used to any configured Manila backend. It's a good idea to have this functionality generalized into `zaza.manila.tests.ManilaBaseTest`, which can be reused for any tested Manila backend. --- zaza/openstack/charm_tests/manila/tests.py | 264 ++++++++++++++++++ .../charm_tests/manila_ganesha/setup.py | 5 +- .../charm_tests/manila_ganesha/tests.py | 118 +------- 3 files changed, 276 insertions(+), 111 deletions(-) diff --git a/zaza/openstack/charm_tests/manila/tests.py b/zaza/openstack/charm_tests/manila/tests.py index c00b993..e7cba8f 100644 --- a/zaza/openstack/charm_tests/manila/tests.py +++ b/zaza/openstack/charm_tests/manila/tests.py @@ -16,12 +16,47 @@ """Encapsulate Manila testing.""" +import logging import tenacity from manilaclient import client as manilaclient import zaza.model +import zaza.openstack.configure.guest as guest +import zaza.openstack.utilities.openstack as openstack_utils import zaza.openstack.charm_tests.test_utils as test_utils +import zaza.openstack.charm_tests.nova.utils as nova_utils +import zaza.openstack.charm_tests.neutron.tests as neutron_tests + + +def verify_status(stdin, stdout, stderr): + """Callable to verify the command output. + + It checks if the command successfully executed. + + This is meant to be given as parameter 'verify' to the helper function + 'openstack_utils.ssh_command'. + """ + status = stdout.channel.recv_exit_status() + if status: + logging.info("{}".format(stderr.readlines()[0].strip())) + assert status == 0 + + +def verify_manila_testing_file(stdin, stdout, stderr): + """Callable to verify the command output. + + It checks if the command successfully executed, and it validates the + testing file written on the Manila share. + + This is meant to be given as parameter 'verify' to the helper function + 'openstack_utils.ssh_command'. + """ + verify_status(stdin, stdout, stderr) + out = "" + for line in iter(stdout.readline, ""): + out += line + assert out == "test\n" class ManilaTests(test_utils.OpenStackBaseTest): @@ -49,3 +84,232 @@ class ManilaTests(test_utils.OpenStackBaseTest): wait=tenacity.wait_exponential(multiplier=3, min=2, max=10)) def _list_shares(self): return self.manila_client.shares.list() + + +class ManilaBaseTest(test_utils.OpenStackBaseTest): + """Encapsulate a Manila basic functionality test.""" + + RESOURCE_PREFIX = 'zaza-manilatests' + INSTANCE_KEY = 'bionic' + INSTANCE_USERDATA = """#cloud-config +packages: +- nfs-common +""" + + @classmethod + def setUpClass(cls): + """Run class setup for running tests.""" + super(ManilaBaseTest, cls).setUpClass() + cls.nova_client = openstack_utils.get_nova_session_client( + session=cls.keystone_session) + cls.manila_client = manilaclient.Client( + session=cls.keystone_session, client_version='2') + cls.share_name = 'test-manila-share' + cls.share_type_name = 'default_share_type' + cls.share_protocol = 'nfs' + cls.mount_dir = '/mnt/manila_share' + cls.share_network = None + + @classmethod + def tearDownClass(cls): + """Run class teardown after tests finished.""" + # Cleanup Nova servers + logging.info('Cleaning up test Nova servers') + fips_reservations = [] + for vm in cls.nova_client.servers.list(): + fips_reservations += neutron_tests.floating_ips_from_instance(vm) + vm.delete() + openstack_utils.resource_removed( + cls.nova_client.servers, + vm.id, + msg="Waiting for the Nova VM {} to be deleted".format(vm.name)) + + # Delete FiPs reservations + logging.info('Cleaning up test FiPs reservations') + neutron = openstack_utils.get_neutron_session_client( + session=cls.keystone_session) + for fip in neutron.list_floatingips()['floatingips']: + if fip['floating_ip_address'] in fips_reservations: + neutron.delete_floatingip(fip['id']) + + # Cleanup Manila shares + logging.info('Cleaning up test shares') + for share in cls.manila_client.shares.list(): + share.delete() + openstack_utils.resource_removed( + cls.manila_client.shares, + share.id, + msg="Waiting for the Manila share {} to be deleted".format( + share.name)) + + # Cleanup test Manila share servers (spawned by the driver when DHSS + # is enabled). + logging.info('Cleaning up test shares servers (if found)') + for server in cls.manila_client.share_servers.list(): + server.delete() + openstack_utils.resource_removed( + cls.manila_client.share_servers, + server.id, + msg="Waiting for the share server {} to be deleted".format( + server.id)) + + def _get_mount_options(self): + """Get the appropriate mount options used to mount the Manila share. + + :returns: The proper mount options flags for the share protocol. + :rtype: string + """ + if self.share_protocol == 'nfs': + return 'nfsvers=4.1,proto=tcp' + else: + raise NotImplementedError( + 'Share protocol not supported yet: {}'.format( + self.share_protocol)) + + def _mount_share_on_instance(self, instance_ip, ssh_user_name, + ssh_private_key, share_path): + """Mount a share into a Nova instance. + + The mount command is executed via SSH. + + :param instance_ip: IP of the Nova instance. + :type instance_ip: string + :param ssh_user_name: SSH user name. + :type ssh_user_name: string + :param ssh_private_key: SSH private key. + :type ssh_private_key: string + :param share_path: Share network path. + :type share_path: string + """ + ssh_cmd = ( + 'sudo mkdir -p {0} && ' + 'sudo mount -t {1} -o {2} {3} {0}'.format( + self.mount_dir, + self.share_protocol, + self._get_mount_options(), + share_path)) + + for attempt in tenacity.Retrying( + stop=tenacity.stop_after_attempt(5), + wait=tenacity.wait_exponential(multiplier=3, min=2, max=10)): + with attempt: + openstack_utils.ssh_command( + vm_name="instance-{}".format(instance_ip), + ip=instance_ip, + username=ssh_user_name, + privkey=ssh_private_key, + command=ssh_cmd, + verify=verify_status) + + @tenacity.retry( + stop=tenacity.stop_after_attempt(5), + wait=tenacity.wait_exponential(multiplier=3, min=2, max=10)) + def _write_testing_file_on_instance(self, instance_ip, ssh_user_name, + ssh_private_key): + """Write a file on a Manila share mounted into a Nova instance. + + Mount the Manila share in the given Nova instance, and write a testing + file on it (which is meant to be validated from another instance). + These commands are executed via SSH. + + :param instance_ip: IP of the Nova instance. + :type instance_ip: string + :param ssh_user_name: SSH user name. + :type ssh_user_name: string + :param ssh_private_key: SSH private key. + :type ssh_private_key: string + """ + openstack_utils.ssh_command( + vm_name="instance-{}".format(instance_ip), + ip=instance_ip, + username=ssh_user_name, + privkey=ssh_private_key, + command='echo "test" | sudo tee {}/test'.format( + self.mount_dir), + verify=verify_status) + + @tenacity.retry( + stop=tenacity.stop_after_attempt(5), + wait=tenacity.wait_exponential(multiplier=3, min=2, max=10)) + def _validate_testing_file_from_instance(self, instance_ip, ssh_user_name, + ssh_private_key): + """Validate a file from the Manila share mounted into a Nova instance. + + This is meant to run after the testing file was already written into + another Nova instance. It validates the written file. The commands are + executed via SSH. + + :param instance_ip: IP of the Nova instance. + :type instance_ip: string + :param ssh_user_name: SSH user name. + :type ssh_user_name: string + :param ssh_private_key: SSH private key. + :type ssh_private_key: string + """ + openstack_utils.ssh_command( + vm_name="instance-{}".format(instance_ip), + ip=instance_ip, + username=ssh_user_name, + privkey=ssh_private_key, + command='sudo cat {}/test'.format(self.mount_dir), + verify=verify_manila_testing_file) + + def test_manila_share(self): + """Test that a Manila share can be accessed on two instances. + + 1. Create a share + 2. Spawn two servers + 3. Mount it on both + 4. Write a file on one + 5. Read it on the other + 6. Profit + """ + # Create a share + share = self.manila_client.shares.create( + share_type=self.share_type_name, + name=self.share_name, + share_proto=self.share_protocol, + share_network=self.share_network, + size=1) + + # Spawn Servers + instance_1 = self.launch_guest( + guest_name='ins-1', + userdata=self.INSTANCE_USERDATA, + instance_key=self.INSTANCE_KEY) + instance_2 = self.launch_guest( + guest_name='ins-2', + userdata=self.INSTANCE_USERDATA, + instance_key=self.INSTANCE_KEY) + + fip_1 = neutron_tests.floating_ips_from_instance(instance_1)[0] + fip_2 = neutron_tests.floating_ips_from_instance(instance_2)[0] + + # Wait for the created share to become available before it gets used. + openstack_utils.resource_reaches_status( + self.manila_client.shares, + share.id, + wait_iteration_max_time=120, + stop_after_attempt=2, + expected_status="available", + msg="Waiting for a share to become available") + + # Grant access to the Manila share for both Nova instances. + share.allow(access_type='ip', access=fip_1, access_level='rw') + share.allow(access_type='ip', access=fip_2, access_level='rw') + + ssh_user_name = guest.boot_tests[self.INSTANCE_KEY]['username'] + privkey = openstack_utils.get_private_key(nova_utils.KEYPAIR_NAME) + share_path = share.export_locations[0] + + # Write a testing file on instance #1 + self._mount_share_on_instance( + fip_1, ssh_user_name, privkey, share_path) + self._write_testing_file_on_instance( + fip_1, ssh_user_name, privkey) + + # Validate the testing file from instance #2 + self._mount_share_on_instance( + fip_2, ssh_user_name, privkey, share_path) + self._validate_testing_file_from_instance( + fip_2, ssh_user_name, privkey) diff --git a/zaza/openstack/charm_tests/manila_ganesha/setup.py b/zaza/openstack/charm_tests/manila_ganesha/setup.py index d2b694e..a804958 100644 --- a/zaza/openstack/charm_tests/manila_ganesha/setup.py +++ b/zaza/openstack/charm_tests/manila_ganesha/setup.py @@ -23,6 +23,9 @@ import zaza.openstack.utilities.openstack as openstack_utils from manilaclient import client as manilaclient +MANILA_GANESHA_TYPE_NAME = "cephfsnfstype" + + def setup_ganesha_share_type(manila_client=None): """Create a share type for manila with Ganesha. @@ -35,7 +38,7 @@ def setup_ganesha_share_type(manila_client=None): session=keystone_session, client_version='2') manila_client.share_types.create( - name="cephfsnfstype", spec_driver_handles_share_servers=False, + name=MANILA_GANESHA_TYPE_NAME, spec_driver_handles_share_servers=False, extra_specs={ 'vendor_name': 'Ceph', 'storage_protocol': 'NFS', diff --git a/zaza/openstack/charm_tests/manila_ganesha/tests.py b/zaza/openstack/charm_tests/manila_ganesha/tests.py index 02ea825..f5d7b63 100644 --- a/zaza/openstack/charm_tests/manila_ganesha/tests.py +++ b/zaza/openstack/charm_tests/manila_ganesha/tests.py @@ -16,122 +16,20 @@ """Encapsulate Manila Ganesha testing.""" -import logging +from zaza.openstack.charm_tests.manila_ganesha.setup import ( + MANILA_GANESHA_TYPE_NAME, +) -from tenacity import Retrying, stop_after_attempt, wait_exponential - -from manilaclient import client as manilaclient - -import zaza.openstack.charm_tests.neutron.tests as neutron_tests -import zaza.openstack.charm_tests.nova.utils as nova_utils -import zaza.openstack.charm_tests.test_utils as test_utils -import zaza.openstack.configure.guest as guest -import zaza.openstack.utilities.openstack as openstack_utils +import zaza.openstack.charm_tests.manila.tests as manila_tests -class ManilaGaneshaTests(test_utils.OpenStackBaseTest): +class ManilaGaneshaTests(manila_tests.ManilaBaseTest): """Encapsulate Manila Ganesha tests.""" - RESOURCE_PREFIX = 'zaza-manilatests' - INSTANCE_USERDATA = """#cloud-config -packages: -- nfs-common -""" - @classmethod def setUpClass(cls): """Run class setup for running tests.""" super(ManilaGaneshaTests, cls).setUpClass() - cls.nova_client = ( - openstack_utils.get_nova_session_client(cls.keystone_session)) - cls.manila_client = manilaclient.Client( - session=cls.keystone_session, client_version='2') - - def test_manila_share(self): - """Test that Manila + Ganesha shares can be accessed on two instances. - - 1. create a share - 2. Spawn two servers - 3. mount it on both - 4. write a file on one - 5. read it on the other - 6. profit - """ - # Create a share - share = self.manila_client.shares.create( - share_type='cephfsnfstype', name='cephnfsshare1', - share_proto="nfs", size=1) - - # Spawn Servers - instance_1, instance_2 = self.launch_guests( - userdata=self.INSTANCE_USERDATA) - - fip_1 = neutron_tests.floating_ips_from_instance(instance_1)[0] - fip_2 = neutron_tests.floating_ips_from_instance(instance_2)[0] - - # Wait for the created share to become available before it gets used. - openstack_utils.resource_reaches_status( - self.manila_client.shares, - share.id, - wait_iteration_max_time=120, - stop_after_attempt=2, - expected_status="available", - msg="Waiting for a share to become available") - - share.allow(access_type='ip', access=fip_1, access_level='rw') - share.allow(access_type='ip', access=fip_2, access_level='rw') - - # Mount Share - username = guest.boot_tests['bionic']['username'] - password = guest.boot_tests['bionic'].get('password') - privkey = openstack_utils.get_private_key(nova_utils.KEYPAIR_NAME) - - # Write a file on instance_1 - def verify_setup(stdin, stdout, stderr): - status = stdout.channel.recv_exit_status() - if status: - logging.info("{}".format(stderr.readlines()[0].strip())) - self.assertEqual(status, 0) - - mount_path = share.export_locations[0] - - for attempt in Retrying( - stop=stop_after_attempt(3), - wait=wait_exponential(multiplier=1, min=2, max=10)): - with attempt: - openstack_utils.ssh_command( - username, fip_1, 'instance-1', - 'sudo mkdir -p /mnt/ceph && ' - 'sudo mount -t nfs -o nfsvers=4.1,proto=tcp ' - '{} /mnt/ceph && ' - 'echo "test" | sudo tee /mnt/ceph/test'.format( - mount_path), - password=password, privkey=privkey, verify=verify_setup) - - for attempt in Retrying( - stop=stop_after_attempt(3), - wait=wait_exponential(multiplier=1, min=2, max=10)): - with attempt: - # Setup that file on instance_2 - openstack_utils.ssh_command( - username, fip_2, 'instance-2', - 'sudo mkdir -p /mnt/ceph && ' - 'sudo /bin/mount -t nfs -o nfsvers=4.1,proto=tcp ' - '{} /mnt/ceph' - .format(mount_path), - password=password, privkey=privkey, verify=verify_setup) - - def verify(stdin, stdout, stderr): - status = stdout.channel.recv_exit_status() - if status: - logging.info("{}".format(stderr.readlines()[0].strip())) - self.assertEqual(status, 0) - out = "" - for line in iter(stdout.readline, ""): - out += line - self.assertEqual(out, "test\n") - - openstack_utils.ssh_command( - username, fip_2, 'instance-2', - 'sudo cat /mnt/ceph/test', - password=password, privkey=privkey, verify=verify) + cls.share_name = 'cephnfsshare1' + cls.share_type_name = MANILA_GANESHA_TYPE_NAME + cls.share_protocol = 'nfs' From 74c5c9d69c979a51b48bf8bd0eedd34dd0eb8883 Mon Sep 17 00:00:00 2001 From: Ionut Balutoiu Date: Fri, 12 Mar 2021 14:53:21 +0200 Subject: [PATCH 2/2] Fix _write_testing_file_on_instance docstring --- zaza/openstack/charm_tests/manila/tests.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/zaza/openstack/charm_tests/manila/tests.py b/zaza/openstack/charm_tests/manila/tests.py index e7cba8f..1b58f65 100644 --- a/zaza/openstack/charm_tests/manila/tests.py +++ b/zaza/openstack/charm_tests/manila/tests.py @@ -208,9 +208,9 @@ packages: ssh_private_key): """Write a file on a Manila share mounted into a Nova instance. - Mount the Manila share in the given Nova instance, and write a testing - file on it (which is meant to be validated from another instance). - These commands are executed via SSH. + Write a testing file into the already mounted Manila share from the + given Nova instance (which is meant to be validated from another + instance). These commands are executed via SSH. :param instance_ip: IP of the Nova instance. :type instance_ip: string