diff --git a/zaza/openstack/charm_tests/ceph/iscsi/__init__.py b/zaza/openstack/charm_tests/ceph/iscsi/__init__.py new file mode 100644 index 0000000..55b06b5 --- /dev/null +++ b/zaza/openstack/charm_tests/ceph/iscsi/__init__.py @@ -0,0 +1,15 @@ +# Copyright 2020 Canonical Ltd. +# +# 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. + +"""Collection of code for setting up and testing ``ceph-iscsi``.""" diff --git a/zaza/openstack/charm_tests/ceph/iscsi/setup.py b/zaza/openstack/charm_tests/ceph/iscsi/setup.py new file mode 100644 index 0000000..8662f8b --- /dev/null +++ b/zaza/openstack/charm_tests/ceph/iscsi/setup.py @@ -0,0 +1,30 @@ +# Copyright 2020 Canonical Ltd. +# +# 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. + +"""Setup for ceph iscsi gateway deployments.""" + +import zaza.model + + +def basic_guest_setup(): + """Run basic setup for iscsi guest.""" + unit = zaza.model.get_units('ubuntu')[0] + setup_cmds = [ + "apt install --yes open-iscsi multipath-tools", + "systemctl start iscsi", + "systemctl start iscsid"] + for cmd in setup_cmds: + zaza.model.run_on_unit( + unit.entity_id, + cmd) diff --git a/zaza/openstack/charm_tests/ceph/iscsi/tests.py b/zaza/openstack/charm_tests/ceph/iscsi/tests.py new file mode 100644 index 0000000..3c904a6 --- /dev/null +++ b/zaza/openstack/charm_tests/ceph/iscsi/tests.py @@ -0,0 +1,156 @@ +# Copyright 2020 Canonical Ltd. +# +# 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. + +"""Encapsulating `ceph-iscsi` testing.""" + +import logging +import tempfile + +import zaza +import zaza.openstack.charm_tests.test_utils as test_utils +import zaza.openstack.utilities.generic as generic_utils + + +class CephISCSIGatewayTest(test_utils.BaseCharmTest): + """Class for `ceph-iscsi` tests.""" + + GW_IQN = "iqn.2003-03.com.canonical.iscsi-gw:iscsi-igw" + DATA_POOL_NAME = 'superssd' + + def get_client_initiatorname(self, unit): + """Return the initiatorname for the given unit.""" + generic_utils.assertRemoteRunOK(zaza.model.run_on_unit( + unit, + ('cp /etc/iscsi/initiatorname.iscsi /tmp; ' + 'chmod 644 /tmp/initiatorname.iscsi'))) + with tempfile.TemporaryDirectory() as tmpdirname: + tmp_file = '{}/{}'.format(tmpdirname, 'initiatorname.iscsi') + zaza.model.scp_from_unit( + unit, + '/tmp/initiatorname.iscsi', + tmp_file) + with open(tmp_file, 'r') as stream: + contents = stream.readlines() + initiatorname = None + for line in contents: + if line.startswith('InitiatorName'): + initiatorname = line.split('=')[1].rstrip() + return initiatorname + + def get_ctxt(self): + """Generate a context for running gwcli commands to create a target.""" + gw_units = zaza.model.get_units('ceph-iscsi') + client_units = zaza.model.get_units('ubuntu') + client = client_units[0] + self.get_client_initiatorname(client.entity_id) + primary_gw = gw_units[0] + secondary_gw = gw_units[1] + host_names = generic_utils.get_unit_hostnames(gw_units, fqdn=True) + ctxt = { + 'pool_name': self.DATA_POOL_NAME, + 'client_entity_id': client.entity_id, + 'gw_iqn': self.GW_IQN, + 'gw1_ip': primary_gw.public_address, + 'gw1_hostname': host_names[primary_gw.entity_id], + 'gw1_entity_id': primary_gw.entity_id, + 'gw2_ip': secondary_gw.public_address, + 'gw2_hostname': host_names[secondary_gw.entity_id], + 'gw2_entity_id': secondary_gw.entity_id, + 'img_size': '1G', + 'img_name': 'disk_1', + 'chap_username': 'myiscsiusername', + 'chap_password': 'myiscsipassword', + 'chap_creds': 'username={chap_username} password={chap_password}', + 'client_initiatorname': self.get_client_initiatorname( + client.entity_id), + 'gwcli_gw_dir': '/iscsi-targets/{gw_iqn}/gateways', + 'gwcli_hosts_dir': '/iscsi-targets/{gw_iqn}/hosts', + 'gwcli_disk_dir': '/disks', + 'gwcli_client_dir': '{gwcli_hosts_dir}/{client_initiatorname}', + } + return ctxt + + def run_commands(self, unit_name, commands, ctxt): + """Run commands on unit. + + Apply context to commands until all variables have been replaced, then + run the command on the given unit. + """ + for _cmd in commands: + cmd = _cmd.format(**ctxt) + generic_utils.assertRemoteRunOK(zaza.model.run_on_unit( + unit_name, + cmd)) + + def create_iscsi_target(self, ctxt): + """Create target on gateway.""" + generic_utils.assertActionRanOK(zaza.model.run_action_on_leader( + 'ceph-iscsi', + 'create-target', + action_params={ + 'gateway-units': '{} {}'.format( + ctxt['gw1_entity_id'], + ctxt['gw2_entity_id']), + 'iqn': self.GW_IQN, + 'pool-name': self.DATA_POOL_NAME, + 'image-size': ctxt['img_size'], + 'image-name': ctxt['img_name'], + 'client-initiatorname': ctxt['client_initiatorname'], + 'client-username': ctxt['chap_username'], + 'client-password': ctxt['chap_password'] + })) + + def mount_iscsi_target(self, ctxt): + """Mount iscsi target on client.""" + base_op_cmd = ('iscsiadm --mode node --targetname {gw_iqn} ' + '--op=update ').format(**ctxt) + setup_cmds = [ + 'iscsiadm -m discovery -t st -p {gw1_ip}', + base_op_cmd + '-n node.session.auth.authmethod -v CHAP', + base_op_cmd + '-n node.session.auth.username -v {chap_username}', + base_op_cmd + '-n node.session.auth.password -v {chap_password}', + 'iscsiadm --mode node --targetname {gw_iqn} --login'] + self.run_commands(ctxt['client_entity_id'], setup_cmds, ctxt) + + def check_client_device(self, ctxt): + """Wait for multipath device to appear on client.""" + async def check_device_present(): + run = await zaza.model.async_run_on_unit( + ctxt['client_entity_id'], + 'ls -l /dev/dm-0') + return '/dev/dm-0' in run['Stdout'] + zaza.model.block_until(check_device_present) + + def create_data_pool(self): + """Create data pool to back iscsi targets.""" + generic_utils.assertActionRanOK(zaza.model.run_action_on_leader( + 'ceph-mon', + 'create-pool', + action_params={ + 'name': self.DATA_POOL_NAME})) + + def test_create_and_mount_volume(self): + """Test creating a target and mounting it on a client.""" + self.create_data_pool() + ctxt = self.get_ctxt() + self.create_iscsi_target(ctxt) + self.mount_iscsi_target(ctxt) + self.check_client_device(ctxt) + + def test_pause_resume(self): + """Test pausing and resuming a unit.""" + with self.pause_resume( + ['rbd-target-api', 'rbd-target-gw'], + pgrep_full=True): + logging.info("Testing pause resume")