diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-21 11:54:28 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-21 11:54:28 +0000 |
commit | e6918187568dbd01842d8d1d2c808ce16a894239 (patch) | |
tree | 64f88b554b444a49f656b6c656111a145cbbaa28 /src/pybind/mgr/dashboard/tests/test_iscsi.py | |
parent | Initial commit. (diff) | |
download | ceph-e6918187568dbd01842d8d1d2c808ce16a894239.tar.xz ceph-e6918187568dbd01842d8d1d2c808ce16a894239.zip |
Adding upstream version 18.2.2.upstream/18.2.2
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/pybind/mgr/dashboard/tests/test_iscsi.py')
-rw-r--r-- | src/pybind/mgr/dashboard/tests/test_iscsi.py | 1276 |
1 files changed, 1276 insertions, 0 deletions
diff --git a/src/pybind/mgr/dashboard/tests/test_iscsi.py b/src/pybind/mgr/dashboard/tests/test_iscsi.py new file mode 100644 index 000000000..f3f786c29 --- /dev/null +++ b/src/pybind/mgr/dashboard/tests/test_iscsi.py @@ -0,0 +1,1276 @@ +# pylint: disable=too-many-public-methods, too-many-lines + +import copy +import errno +import json +import unittest + +try: + import mock +except ImportError: + import unittest.mock as mock + +from mgr_module import ERROR_MSG_NO_INPUT_FILE + +from .. import mgr +from ..controllers.iscsi import Iscsi, IscsiTarget, IscsiUi +from ..exceptions import DashboardException +from ..rest_client import RequestException +from ..services.exception import handle_request_error +from ..services.iscsi_client import IscsiClient +from ..services.orchestrator import OrchClient +from ..tests import CLICommandTestMixin, CmdException, ControllerTestCase, KVStoreMockMixin +from ..tools import NotificationQueue, TaskManager + + +class IscsiTestCli(unittest.TestCase, CLICommandTestMixin): + + def setUp(self): + self.mock_kv_store() + # pylint: disable=protected-access + IscsiClientMock._instance = IscsiClientMock() + IscsiClient.instance = IscsiClientMock.instance + + def test_cli_add_gateway_invalid_url(self): + with self.assertRaises(CmdException) as ctx: + self.exec_cmd('iscsi-gateway-add', name='node1', + inbuf='http:/hello.com') + + self.assertEqual(ctx.exception.retcode, -errno.EINVAL) + self.assertEqual(str(ctx.exception), + "Invalid service URL 'http:/hello.com'. Valid format: " + "'<scheme>://<username>:<password>@<host>[:port]'.") + + def test_cli_add_gateway_empty_url(self): + with self.assertRaises(CmdException) as ctx: + self.exec_cmd('iscsi-gateway-add', name='node1', + inbuf='') + + self.assertEqual(ctx.exception.retcode, -errno.EINVAL) + self.assertIn(ERROR_MSG_NO_INPUT_FILE, str(ctx.exception)) + + def test_cli_add_gateway(self): + self.exec_cmd('iscsi-gateway-add', name='node1', + inbuf='https://admin:admin@10.17.5.1:5001') + self.exec_cmd('iscsi-gateway-add', name='node2', + inbuf='https://admin:admin@10.17.5.2:5001') + iscsi_config = json.loads(self.get_key("_iscsi_config")) + self.assertEqual(iscsi_config['gateways'], { + 'node1': { + 'service_url': 'https://admin:admin@10.17.5.1:5001' + }, + 'node2': { + 'service_url': 'https://admin:admin@10.17.5.2:5001' + } + }) + + def test_cli_remove_gateway(self): + self.test_cli_add_gateway() + self.exec_cmd('iscsi-gateway-rm', name='node1') + iscsi_config = json.loads(self.get_key("_iscsi_config")) + self.assertEqual(iscsi_config['gateways'], { + 'node2': { + 'service_url': 'https://admin:admin@10.17.5.2:5001' + } + }) + + +class IscsiTestController(ControllerTestCase, KVStoreMockMixin): + + @classmethod + def setup_server(cls): + NotificationQueue.start_queue() + TaskManager.init() + OrchClient.instance().available = lambda: False + mgr.rados.side_effect = None + cls.setup_controllers([Iscsi, IscsiTarget]) + + @classmethod + def tearDownClass(cls): + NotificationQueue.stop() + + def setUp(self): + self.mock_kv_store() + self.CONFIG_KEY_DICT['_iscsi_config'] = ''' + { + "gateways": { + "node1": { + "service_url": "https://admin:admin@10.17.5.1:5001" + }, + "node2": { + "service_url": "https://admin:admin@10.17.5.2:5001" + } + } + } + ''' + # pylint: disable=protected-access + IscsiClientMock._instance = IscsiClientMock() + IscsiClient.instance = IscsiClientMock.instance + + def test_enable_discoveryauth(self): + discoveryauth = { + 'user': 'myiscsiusername', + 'password': 'myiscsipassword', + 'mutual_user': 'myiscsiusername2', + 'mutual_password': 'myiscsipassword2' + } + self._put('/api/iscsi/discoveryauth', discoveryauth) + self.assertStatus(200) + self.assertJsonBody(discoveryauth) + self._get('/api/iscsi/discoveryauth') + self.assertStatus(200) + self.assertJsonBody(discoveryauth) + + def test_bad_discoveryauth(self): + discoveryauth = { + 'user': 'myiscsiusername', + 'password': 'myiscsipasswordmyiscsipasswordmyiscsipassword', + 'mutual_user': '', + 'mutual_password': '' + } + put_response = { + 'detail': 'Bad authentication', + 'code': 'target_bad_auth', + 'component': 'iscsi' + } + get_response = { + 'user': '', + 'password': '', + 'mutual_user': '', + 'mutual_password': '' + } + self._put('/api/iscsi/discoveryauth', discoveryauth) + self.assertStatus(400) + self.assertJsonBody(put_response) + self._get('/api/iscsi/discoveryauth') + self.assertStatus(200) + self.assertJsonBody(get_response) + + def test_disable_discoveryauth(self): + discoveryauth = { + 'user': '', + 'password': '', + 'mutual_user': '', + 'mutual_password': '' + } + self._put('/api/iscsi/discoveryauth', discoveryauth) + self.assertStatus(200) + self.assertJsonBody(discoveryauth) + self._get('/api/iscsi/discoveryauth') + self.assertStatus(200) + self.assertJsonBody(discoveryauth) + + def test_list_empty(self): + self._get('/api/iscsi/target') + self.assertStatus(200) + self.assertJsonBody([]) + + @mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image') + def test_list(self, _validate_image_mock): + target_iqn = "iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw1" + request = copy.deepcopy(iscsi_target_request) + request['target_iqn'] = target_iqn + self._task_post('/api/iscsi/target', request) + self.assertStatus(201) + self._get('/api/iscsi/target') + self.assertStatus(200) + response = copy.deepcopy(iscsi_target_response) + response['target_iqn'] = target_iqn + self.assertJsonBody([response]) + + @mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image') + def test_create(self, _validate_image_mock): + target_iqn = "iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw2" + request = copy.deepcopy(iscsi_target_request) + request['target_iqn'] = target_iqn + self._task_post('/api/iscsi/target', request) + self.assertStatus(201) + self._get('/api/iscsi/target/{}'.format(request['target_iqn'])) + self.assertStatus(200) + response = copy.deepcopy(iscsi_target_response) + response['target_iqn'] = target_iqn + self.assertJsonBody(response) + + @mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image') + def test_create_acl_enabled(self, _validate_image_mock): + target_iqn = "iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw2" + request = copy.deepcopy(iscsi_target_request) + request['target_iqn'] = target_iqn + request['acl_enabled'] = False + self._task_post('/api/iscsi/target', request) + self.assertStatus(201) + self._get('/api/iscsi/target/{}'.format(request['target_iqn'])) + self.assertStatus(200) + response = copy.deepcopy(iscsi_target_response) + response['target_iqn'] = target_iqn + response['acl_enabled'] = False + self.assertJsonBody(response) + + @mock.patch('dashboard.controllers.iscsi.IscsiTarget._create') + def test_create_error(self, _create_mock): + # pylint: disable=protected-access + target_iqn = "iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw2" + request = copy.deepcopy(iscsi_target_request) + request['target_iqn'] = target_iqn + request['config'] = "" + request['settings'] = "" + request['task_progress_begin'] = 0 + request['task_progress_end'] = 100 + _create_mock.side_effect = RequestException("message error") + with self.assertRaises(DashboardException): + with handle_request_error('iscsi'): + IscsiTarget._create(**request) + + def test_validate_error_iqn(self): + # pylint: disable=protected-access + with self.assertRaises(DashboardException) as ctx: + IscsiTarget._validate(None, None, None, None, None, None) + self.assertEquals(ctx.exception.__str__(), + "Target IQN is required") + + def test_validate_error_portals(self): + # pylint: disable=protected-access + target_iqn = iscsi_target_request['target_iqn'] + target_controls = iscsi_target_request['target_controls'] + portals = {} + disks = iscsi_target_request['disks'] + groups = iscsi_target_request['groups'] + settings = {'config': {'minimum_gateways': 1}} + with self.assertRaises(DashboardException) as ctx: + IscsiTarget._validate(target_iqn, target_controls, portals, disks, groups, settings) + self.assertEquals(ctx.exception.__str__(), + "At least one portal is required") + settings = {'config': {'minimum_gateways': 2}} + with self.assertRaises(DashboardException) as ctx: + IscsiTarget._validate(target_iqn, target_controls, portals, disks, groups, settings) + self.assertEquals(ctx.exception.__str__(), + "At least 2 portals are required") + + def test_validate_error_target_control(self): + # pylint: disable=protected-access + target_iqn = iscsi_target_request['target_iqn'] + target_controls = { + 'target_name': 0 + } + portals = iscsi_target_request['portals'] + disks = iscsi_target_request['disks'] + groups = iscsi_target_request['groups'] + settings = { + 'config': {'minimum_gateways': 1}, + 'target_controls_limits': { + 'target_name': { + 'min': 1, + 'max': 2, + } + } + } + with self.assertRaises(DashboardException) as ctx: + IscsiTarget._validate(target_iqn, target_controls, portals, disks, groups, settings) + self.assertEquals(ctx.exception.__str__(), + "Target control target_name must be >= 1") + target_controls = { + 'target_name': 3 + } + with self.assertRaises(DashboardException) as ctx: + IscsiTarget._validate(target_iqn, target_controls, portals, disks, groups, settings) + self.assertEquals(ctx.exception.__str__(), + "Target control target_name must be <= 2") + + @mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image') + def test_validate_error_disk_control(self, _validate_image_mock): + # pylint: disable=protected-access + target_iqn = iscsi_target_request['target_iqn'] + target_controls = {} + portals = iscsi_target_request['portals'] + disks = iscsi_target_request['disks'] + groups = iscsi_target_request['groups'] + settings = { + 'config': {'minimum_gateways': 1}, + 'required_rbd_features': { + 'user:rbd': 0 + }, + 'unsupported_rbd_features': { + 'user:rbd': 0 + }, + 'disk_controls_limits': { + 'user:rbd': {'max_data_area_mb': { + 'min': 129, + 'max': 127, + }} + } + } + with self.assertRaises(DashboardException) as ctx: + IscsiTarget._validate(target_iqn, target_controls, portals, disks, groups, settings) + self.assertEquals(ctx.exception.__str__(), + "Disk control max_data_area_mb must be >= 129") + settings['disk_controls_limits']['user:rbd']['max_data_area_mb']['min'] = 1 + with self.assertRaises(DashboardException) as ctx: + IscsiTarget._validate(target_iqn, target_controls, portals, disks, groups, settings) + self.assertEquals(ctx.exception.__str__(), + "Disk control max_data_area_mb must be <= 127") + + @mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image') + def test_delete(self, _validate_image_mock): + target_iqn = "iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw3" + request = copy.deepcopy(iscsi_target_request) + request['target_iqn'] = target_iqn + self._task_post('/api/iscsi/target', request) + self.assertStatus(201) + self._task_delete('/api/iscsi/target/{}'.format(request['target_iqn'])) + self.assertStatus(204) + self._get('/api/iscsi/target') + self.assertStatus(200) + self.assertJsonBody([]) + + @mock.patch('dashboard.tools.TaskManager.current_task') + def test_delete_raises_exception(self, _validate_image_mock): + target_iqn = "iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw3" + request = copy.deepcopy(iscsi_target_request) + request['target_iqn'] = target_iqn + configs = {'targets': {target_iqn: {'portals': {}}}} + with self.assertRaises(DashboardException): + # pylint: disable=protected-access + IscsiTarget._delete(target_iqn, configs, 0, 100) + + @mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image') + def test_add_client(self, _validate_image_mock): + target_iqn = "iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw4" + create_request = copy.deepcopy(iscsi_target_request) + create_request['target_iqn'] = target_iqn + update_request = copy.deepcopy(create_request) + update_request['new_target_iqn'] = target_iqn + update_request['clients'].append( + { + "luns": [{"image": "lun1", "pool": "rbd"}], + "client_iqn": "iqn.1994-05.com.redhat:rh7-client3", + "auth": { + "password": "myiscsipassword5", + "user": "myiscsiusername5", + "mutual_password": "myiscsipassword6", + "mutual_user": "myiscsiusername6"} + }) + response = copy.deepcopy(iscsi_target_response) + response['target_iqn'] = target_iqn + response['clients'].append( + { + "luns": [{"image": "lun1", "pool": "rbd"}], + "client_iqn": "iqn.1994-05.com.redhat:rh7-client3", + "auth": { + "password": "myiscsipassword5", + "user": "myiscsiusername5", + "mutual_password": "myiscsipassword6", + "mutual_user": "myiscsiusername6"}, + "info": { + "alias": "", + "ip_address": [], + "state": {} + } + }) + self._update_iscsi_target(create_request, update_request, 200, None, response) + + @mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image') + def test_add_bad_client(self, _validate_image_mock): + target_iqn = "iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw4" + create_request = copy.deepcopy(iscsi_target_request) + create_request['target_iqn'] = target_iqn + update_request = copy.deepcopy(create_request) + update_request['new_target_iqn'] = target_iqn + update_request['clients'].append( + { + "luns": [{"image": "lun1", "pool": "rbd"}], + "client_iqn": "iqn.1994-05.com.redhat:rh7-client4", + "auth": { + "password": "myiscsipassword7myiscsipassword7myiscsipasswo", + "user": "myiscsiusername7", + "mutual_password": "myiscsipassword8", + "mutual_user": "myiscsiusername8"} + }) + response = copy.deepcopy(iscsi_target_response) + response['target_iqn'] = target_iqn + + self._task_post('/api/iscsi/target', create_request) + self.assertStatus(201) + self._task_put('/api/iscsi/target/{}'.format(create_request['target_iqn']), update_request) + self.assertStatus(400) + self._get('/api/iscsi/target/{}'.format(update_request['new_target_iqn'])) + self.assertStatus(200) + self.assertJsonBody(response) + + @mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image') + def test_change_client_password(self, _validate_image_mock): + target_iqn = "iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw5" + create_request = copy.deepcopy(iscsi_target_request) + create_request['target_iqn'] = target_iqn + update_request = copy.deepcopy(create_request) + update_request['new_target_iqn'] = target_iqn + update_request['clients'][0]['auth']['password'] = 'MyNewPassword' + response = copy.deepcopy(iscsi_target_response) + response['target_iqn'] = target_iqn + response['clients'][0]['auth']['password'] = 'MyNewPassword' + self._update_iscsi_target(create_request, update_request, 200, None, response) + + @mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image') + def test_rename_client(self, _validate_image_mock): + target_iqn = "iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw6" + create_request = copy.deepcopy(iscsi_target_request) + create_request['target_iqn'] = target_iqn + update_request = copy.deepcopy(create_request) + update_request['new_target_iqn'] = target_iqn + update_request['clients'][0]['client_iqn'] = 'iqn.1994-05.com.redhat:rh7-client0' + response = copy.deepcopy(iscsi_target_response) + response['target_iqn'] = target_iqn + response['clients'][0]['client_iqn'] = 'iqn.1994-05.com.redhat:rh7-client0' + self._update_iscsi_target(create_request, update_request, 200, None, response) + + @mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image') + def test_add_disk(self, _validate_image_mock): + target_iqn = "iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw7" + create_request = copy.deepcopy(iscsi_target_request) + create_request['target_iqn'] = target_iqn + update_request = copy.deepcopy(create_request) + update_request['new_target_iqn'] = target_iqn + update_request['disks'].append( + { + "image": "lun3", + "pool": "rbd", + "controls": {}, + "backstore": "user:rbd" + }) + update_request['clients'][0]['luns'].append({"image": "lun3", "pool": "rbd"}) + response = copy.deepcopy(iscsi_target_response) + response['target_iqn'] = target_iqn + response['disks'].append( + { + "image": "lun3", + "pool": "rbd", + "controls": {}, + "backstore": "user:rbd", + "wwn": "64af6678-9694-4367-bacc-f8eb0baa2", + "lun": 2 + + }) + response['clients'][0]['luns'].append({"image": "lun3", "pool": "rbd"}) + self._update_iscsi_target(create_request, update_request, 200, None, response) + + @mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image') + def test_change_disk_image(self, _validate_image_mock): + target_iqn = "iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw8" + create_request = copy.deepcopy(iscsi_target_request) + create_request['target_iqn'] = target_iqn + update_request = copy.deepcopy(create_request) + update_request['new_target_iqn'] = target_iqn + update_request['disks'][0]['image'] = 'lun0' + update_request['clients'][0]['luns'][0]['image'] = 'lun0' + response = copy.deepcopy(iscsi_target_response) + response['target_iqn'] = target_iqn + response['disks'][0]['image'] = 'lun0' + response['clients'][0]['luns'][0]['image'] = 'lun0' + self._update_iscsi_target(create_request, update_request, 200, None, response) + + @mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image') + def test_change_disk_controls(self, _validate_image_mock): + target_iqn = "iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw9" + create_request = copy.deepcopy(iscsi_target_request) + create_request['target_iqn'] = target_iqn + update_request = copy.deepcopy(create_request) + update_request['new_target_iqn'] = target_iqn + update_request['disks'][0]['controls'] = {"qfull_timeout": 15} + response = copy.deepcopy(iscsi_target_response) + response['target_iqn'] = target_iqn + response['disks'][0]['controls'] = {"qfull_timeout": 15} + self._update_iscsi_target(create_request, update_request, 200, None, response) + + @mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image') + def test_rename_target(self, _validate_image_mock): + target_iqn = "iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw10" + new_target_iqn = "iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw11" + create_request = copy.deepcopy(iscsi_target_request) + create_request['target_iqn'] = target_iqn + update_request = copy.deepcopy(create_request) + update_request['new_target_iqn'] = new_target_iqn + response = copy.deepcopy(iscsi_target_response) + response['target_iqn'] = new_target_iqn + self._update_iscsi_target(create_request, update_request, 200, None, response) + + @mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image') + def test_rename_group(self, _validate_image_mock): + target_iqn = "iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw12" + create_request = copy.deepcopy(iscsi_target_request) + create_request['target_iqn'] = target_iqn + update_request = copy.deepcopy(create_request) + update_request['new_target_iqn'] = target_iqn + update_request['groups'][0]['group_id'] = 'mygroup0' + response = copy.deepcopy(iscsi_target_response) + response['target_iqn'] = target_iqn + response['groups'][0]['group_id'] = 'mygroup0' + self._update_iscsi_target(create_request, update_request, 200, None, response) + + @mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image') + def test_add_client_to_group(self, _validate_image_mock): + target_iqn = "iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw13" + create_request = copy.deepcopy(iscsi_target_request) + create_request['target_iqn'] = target_iqn + update_request = copy.deepcopy(create_request) + update_request['new_target_iqn'] = target_iqn + update_request['clients'].append( + { + "luns": [], + "client_iqn": "iqn.1994-05.com.redhat:rh7-client3", + "auth": { + "password": None, + "user": None, + "mutual_password": None, + "mutual_user": None} + }) + update_request['groups'][0]['members'].append('iqn.1994-05.com.redhat:rh7-client3') + response = copy.deepcopy(iscsi_target_response) + response['target_iqn'] = target_iqn + response['clients'].append( + { + "luns": [], + "client_iqn": "iqn.1994-05.com.redhat:rh7-client3", + "auth": { + "password": None, + "user": None, + "mutual_password": None, + "mutual_user": None}, + "info": { + "alias": "", + "ip_address": [], + "state": {} + } + }) + response['groups'][0]['members'].append('iqn.1994-05.com.redhat:rh7-client3') + self._update_iscsi_target(create_request, update_request, 200, None, response) + + @mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image') + def test_remove_client_from_group(self, _validate_image_mock): + target_iqn = "iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw14" + create_request = copy.deepcopy(iscsi_target_request) + create_request['target_iqn'] = target_iqn + update_request = copy.deepcopy(create_request) + update_request['new_target_iqn'] = target_iqn + update_request['groups'][0]['members'].remove('iqn.1994-05.com.redhat:rh7-client2') + response = copy.deepcopy(iscsi_target_response) + response['target_iqn'] = target_iqn + response['groups'][0]['members'].remove('iqn.1994-05.com.redhat:rh7-client2') + self._update_iscsi_target(create_request, update_request, 200, None, response) + + @mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image') + def test_remove_groups(self, _validate_image_mock): + target_iqn = "iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw15" + create_request = copy.deepcopy(iscsi_target_request) + create_request['target_iqn'] = target_iqn + update_request = copy.deepcopy(create_request) + update_request['new_target_iqn'] = target_iqn + update_request['groups'] = [] + response = copy.deepcopy(iscsi_target_response) + response['target_iqn'] = target_iqn + response['groups'] = [] + self._update_iscsi_target(create_request, update_request, 200, None, response) + + @mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image') + def test_add_client_to_multiple_groups(self, _validate_image_mock): + target_iqn = "iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw16" + create_request = copy.deepcopy(iscsi_target_request) + create_request['target_iqn'] = target_iqn + create_request['groups'].append(copy.deepcopy(create_request['groups'][0])) + create_request['groups'][1]['group_id'] = 'mygroup2' + self._task_post('/api/iscsi/target', create_request) + self.assertStatus(400) + self.assertJsonBody({ + 'detail': 'Each initiator can only be part of 1 group at a time', + 'code': 'initiator_in_multiple_groups', + 'component': 'iscsi' + }) + + @mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image') + def test_remove_client_lun(self, _validate_image_mock): + target_iqn = "iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw17" + create_request = copy.deepcopy(iscsi_target_request) + create_request['target_iqn'] = target_iqn + create_request['clients'][0]['luns'] = [ + {"image": "lun1", "pool": "rbd"}, + {"image": "lun2", "pool": "rbd"}, + {"image": "lun3", "pool": "rbd"} + ] + update_request = copy.deepcopy(create_request) + update_request['new_target_iqn'] = target_iqn + update_request['clients'][0]['luns'] = [ + {"image": "lun1", "pool": "rbd"}, + {"image": "lun3", "pool": "rbd"} + ] + response = copy.deepcopy(iscsi_target_response) + response['target_iqn'] = target_iqn + response['clients'][0]['luns'] = [ + {"image": "lun1", "pool": "rbd"}, + {"image": "lun3", "pool": "rbd"} + ] + self._update_iscsi_target(create_request, update_request, 200, None, response) + + @mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image') + def test_change_client_auth(self, _validate_image_mock): + target_iqn = "iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw18" + create_request = copy.deepcopy(iscsi_target_request) + create_request['target_iqn'] = target_iqn + update_request = copy.deepcopy(create_request) + update_request['new_target_iqn'] = target_iqn + update_request['clients'][0]['auth']['password'] = 'myiscsipasswordX' + response = copy.deepcopy(iscsi_target_response) + response['target_iqn'] = target_iqn + response['clients'][0]['auth']['password'] = 'myiscsipasswordX' + self._update_iscsi_target(create_request, update_request, 200, None, response) + + @mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image') + def test_remove_client_logged_in(self, _validate_image_mock): + client_info = { + 'alias': '', + 'ip_address': [], + 'state': {'LOGGED_IN': ['node1']} + } + # pylint: disable=protected-access + IscsiClientMock._instance.clientinfo = client_info + target_iqn = "iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw19" + create_request = copy.deepcopy(iscsi_target_request) + create_request['target_iqn'] = target_iqn + update_request = copy.deepcopy(create_request) + update_request['new_target_iqn'] = target_iqn + update_request['clients'].pop(0) + response = copy.deepcopy(iscsi_target_response) + response['target_iqn'] = target_iqn + for client in response['clients']: + client['info'] = client_info + update_response = { + 'detail': "Client 'iqn.1994-05.com.redhat:rh7-client' cannot be deleted until it's " + "logged out", + 'code': 'client_logged_in', + 'component': 'iscsi' + } + self._update_iscsi_target(create_request, update_request, 400, update_response, response) + + @mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image') + def test_remove_client(self, _validate_image_mock): + target_iqn = "iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw20" + create_request = copy.deepcopy(iscsi_target_request) + create_request['target_iqn'] = target_iqn + update_request = copy.deepcopy(create_request) + update_request['new_target_iqn'] = target_iqn + update_request['clients'].pop(0) + response = copy.deepcopy(iscsi_target_response) + response['target_iqn'] = target_iqn + response['clients'].pop(0) + self._update_iscsi_target(create_request, update_request, 200, None, response) + + @mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image') + def test_add_image_to_group_with_client_logged_in(self, _validate_image_mock): + client_info = { + 'alias': '', + 'ip_address': [], + 'state': {'LOGGED_IN': ['node1']} + } + new_disk = {"pool": "rbd", "image": "lun1"} + # pylint: disable=protected-access + IscsiClientMock._instance.clientinfo = client_info + target_iqn = "iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw21" + create_request = copy.deepcopy(iscsi_target_request) + create_request['target_iqn'] = target_iqn + update_request = copy.deepcopy(create_request) + update_request['new_target_iqn'] = target_iqn + update_request['groups'][0]['disks'].append(new_disk) + response = copy.deepcopy(iscsi_target_response) + response['target_iqn'] = target_iqn + response['groups'][0]['disks'].insert(0, new_disk) + for client in response['clients']: + client['info'] = client_info + self._update_iscsi_target(create_request, update_request, 200, None, response) + + @mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image') + def test_add_image_to_initiator_with_client_logged_in(self, _validate_image_mock): + client_info = { + 'alias': '', + 'ip_address': [], + 'state': {'LOGGED_IN': ['node1']} + } + new_disk = {"pool": "rbd", "image": "lun2"} + # pylint: disable=protected-access + IscsiClientMock._instance.clientinfo = client_info + target_iqn = "iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw22" + create_request = copy.deepcopy(iscsi_target_request) + create_request['target_iqn'] = target_iqn + update_request = copy.deepcopy(create_request) + update_request['new_target_iqn'] = target_iqn + update_request['clients'][0]['luns'].append(new_disk) + response = copy.deepcopy(iscsi_target_response) + response['target_iqn'] = target_iqn + response['clients'][0]['luns'].append(new_disk) + for client in response['clients']: + client['info'] = client_info + self._update_iscsi_target(create_request, update_request, 200, None, response) + + @mock.patch('dashboard.controllers.iscsi.IscsiTarget._validate_image') + def test_remove_image_from_group_with_client_logged_in(self, _validate_image_mock): + client_info = { + 'alias': '', + 'ip_address': [], + 'state': {'LOGGED_IN': ['node1']} + } + # pylint: disable=protected-access + IscsiClientMock._instance.clientinfo = client_info + target_iqn = "iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw23" + create_request = copy.deepcopy(iscsi_target_request) + create_request['target_iqn'] = target_iqn + update_request = copy.deepcopy(create_request) + update_request['new_target_iqn'] = target_iqn + update_request['groups'][0]['disks'] = [] + response = copy.deepcopy(iscsi_target_response) + response['target_iqn'] = target_iqn + response['groups'][0]['disks'] = [] + for client in response['clients']: + client['info'] = client_info + self._update_iscsi_target(create_request, update_request, 200, None, response) + + def _update_iscsi_target(self, create_request, update_request, update_response_code, + update_response, response): + self._task_post('/api/iscsi/target', create_request) + self.assertStatus(201) + self._task_put( + '/api/iscsi/target/{}'.format(create_request['target_iqn']), update_request) + self.assertStatus(update_response_code) + self.assertJsonBody(update_response) + self._get( + '/api/iscsi/target/{}'.format(update_request['new_target_iqn'])) + self.assertStatus(200) + self.assertJsonBody(response) + + +class TestIscsiUi(ControllerTestCase): + @classmethod + def setup_server(cls): + cls.setup_controllers([IscsiUi]) + + @mock.patch('dashboard.services.tcmu_service.TcmuService.get_image_info') + @mock.patch('dashboard.services.tcmu_service.TcmuService.get_iscsi_info') + def test_overview(self, get_iscsi_info_mock, get_image_info_mock): + get_iscsi_info_mock.return_value = None + get_image_info_mock.return_value = None + response = copy.deepcopy(iscsiui_response) + response['images'] = [] + self._get('/ui-api/iscsi/overview') + self.assertStatus(200) + self.assertJsonBody(response) + + @mock.patch('dashboard.services.iscsi_config.IscsiGatewaysConfig.get_gateways_config') + @mock.patch('dashboard.services.tcmu_service.TcmuService.get_image_info') + @mock.patch('dashboard.services.tcmu_service.TcmuService.get_iscsi_info') + def test_overview_config(self, get_iscsi_info_mock, get_image_info_mock, + get_gateways_config_mock): + get_iscsi_info_mock.return_value = None + get_image_info_mock.return_value = None + response = copy.deepcopy(iscsiui_response) + response['images'] = [] + get_gateways_config_mock.return_value = iscsiui_gateways_config_mock + self._get('/ui-api/iscsi/overview') + self.assertStatus(200) + self.assertJsonBody(response) + + def raise_ex(self): + raise RequestException('error') + config_method = IscsiClientMock.get_config + IscsiClientMock.get_config = raise_ex + response['gateways'][0]['num_sessions'] = 'n/a' + response['gateways'][1]['num_sessions'] = 'n/a' + response['gateways'][0]['num_targets'] = 'n/a' + response['gateways'][1]['num_targets'] = 'n/a' + self._get('/ui-api/iscsi/overview') + self.assertStatus(200) + self.assertJsonBody(response) + IscsiClientMock.get_config = config_method + + @mock.patch('dashboard.services.iscsi_config.IscsiGatewaysConfig.get_gateways_config') + @mock.patch('dashboard.services.tcmu_service.TcmuService.get_image_info') + @mock.patch('dashboard.services.tcmu_service.TcmuService.get_iscsi_info') + def test_overview_ping(self, get_iscsi_info_mock, get_image_info_mock, + get_gateways_config_mock): + get_iscsi_info_mock.return_value = None + get_image_info_mock.return_value = None + get_gateways_config_mock.return_value = iscsiui_gateways_config_mock + response = copy.deepcopy(iscsiui_response) + response['gateways'][0]['num_sessions'] = 0 + response['gateways'][1]['num_sessions'] = 0 + response['gateways'][0]['num_targets'] = 0 + response['gateways'][1]['num_targets'] = 0 + self._get('/ui-api/iscsi/overview') + self.assertStatus(200) + self.assertJsonBody(response) + + def raise_ex(self): + raise RequestException('error') + ping_method = IscsiClientMock.ping + IscsiClientMock.ping = raise_ex + response['gateways'][0]['num_sessions'] = 'n/a' + response['gateways'][1]['num_sessions'] = 'n/a' + response['gateways'][0]['state'] = 'down' + response['gateways'][1]['state'] = 'down' + self._get('/ui-api/iscsi/overview') + self.assertStatus(200) + self.assertJsonBody(response) + IscsiClientMock.ping = ping_method + + @mock.patch( + 'dashboard.services.iscsi_config.IscsiGatewaysConfig.get_gateways_config') + @mock.patch('dashboard.services.tcmu_service.TcmuService.get_image_info') + @mock.patch('dashboard.services.tcmu_service.TcmuService.get_iscsi_info') + def test_overview_images_info(self, get_iscsi_info_mock, get_image_info_mock, + get_gateways_config_mock): + get_iscsi_info_mock.return_value = None + image_info = {"optimized_since": "1616735075", "stats": {}, "stats_history": {}} + # pylint: disable=protected-access + IscsiClientMock._instance.config['disks'] = { + 1: {"image": "lun1", "pool": "rbd", "backstore": "user:rbd", + "optimized_since": "1616735075", "stats": {}, "stats_history": {}}, + 2: {"image": "lun2", "pool": "rbd", "backstore": "user:rbd", + "optimized_since": "1616735075", "stats": {}, "stats_history": {}}, + } + response = copy.deepcopy(iscsiui_response) + response['images'][0]['optimized_since'] = '1616735075' + response['images'][1]['optimized_since'] = '1616735075' + response['images'][0]['stats'] = {} + response['images'][1]['stats'] = {} + response['images'][0]['stats_history'] = {} + response['images'][1]['stats_history'] = {} + get_gateways_config_mock.return_value = iscsiui_gateways_config_mock + get_image_info_mock.return_value = image_info + self._get('/ui-api/iscsi/overview') + self.assertStatus(200) + self.assertJsonBody(response) + + +iscsiui_gateways_config_mock = { + 'gateways': { + 'node1': None, + 'node2': None, + }, + 'disks': { + 1: {"image": "lun1", "pool": "rbd", "backstore": "user:rbd", + "controls": {"max_data_area_mb": 128}}, + 2: {"image": "lun2", "pool": "rbd", "backstore": "user:rbd", + "controls": {"max_data_area_mb": 128}} + } +} +iscsiui_response = { + "gateways": [ + {"name": "node1", "state": "up", "num_targets": 0, "num_sessions": 0}, + {"name": "node2", "state": "up", "num_targets": 0, "num_sessions": 0} + ], + "images": [ + { + 'pool': 'rbd', + 'image': 'lun1', + 'backstore': 'user:rbd', + 'optimized_since': None, + 'stats': None, + 'stats_history': None + }, + { + 'pool': 'rbd', + 'image': 'lun2', + 'backstore': 'user:rbd', + 'optimized_since': None, + 'stats': None, + 'stats_history': None + } + ] +} +iscsi_target_request = { + "target_iqn": "iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw", + "portals": [ + {"ip": "192.168.100.202", "host": "node2"}, + {"ip": "10.0.2.15", "host": "node2"}, + {"ip": "192.168.100.203", "host": "node3"} + ], + "disks": [ + {"image": "lun1", "pool": "rbd", "backstore": "user:rbd", + "controls": {"max_data_area_mb": 128}}, + {"image": "lun2", "pool": "rbd", "backstore": "user:rbd", + "controls": {"max_data_area_mb": 128}} + ], + "clients": [ + { + "luns": [{"image": "lun1", "pool": "rbd"}], + "client_iqn": "iqn.1994-05.com.redhat:rh7-client", + "auth": { + "password": "myiscsipassword1", + "user": "myiscsiusername1", + "mutual_password": "myiscsipassword2", + "mutual_user": "myiscsiusername2"} + }, + { + "luns": [], + "client_iqn": "iqn.1994-05.com.redhat:rh7-client2", + "auth": { + "password": "myiscsipassword3", + "user": "myiscsiusername3", + "mutual_password": "myiscsipassword4", + "mutual_user": "myiscsiusername4" + } + } + ], + "acl_enabled": True, + "auth": { + "password": "", + "user": "", + "mutual_password": "", + "mutual_user": ""}, + "target_controls": {}, + "groups": [ + { + "group_id": "mygroup", + "disks": [{"pool": "rbd", "image": "lun2"}], + "members": ["iqn.1994-05.com.redhat:rh7-client2"] + } + ] +} + +iscsi_target_response = { + 'target_iqn': 'iqn.2003-01.com.redhat.iscsi-gw:iscsi-igw', + 'portals': [ + {'host': 'node2', 'ip': '10.0.2.15'}, + {'host': 'node2', 'ip': '192.168.100.202'}, + {'host': 'node3', 'ip': '192.168.100.203'} + ], + 'disks': [ + {'pool': 'rbd', 'image': 'lun1', 'backstore': 'user:rbd', + 'wwn': '64af6678-9694-4367-bacc-f8eb0baa0', 'lun': 0, + 'controls': {'max_data_area_mb': 128}}, + {'pool': 'rbd', 'image': 'lun2', 'backstore': 'user:rbd', + 'wwn': '64af6678-9694-4367-bacc-f8eb0baa1', 'lun': 1, + 'controls': {'max_data_area_mb': 128}} + ], + 'clients': [ + { + 'client_iqn': 'iqn.1994-05.com.redhat:rh7-client', + 'luns': [{'pool': 'rbd', 'image': 'lun1'}], + 'auth': { + 'user': 'myiscsiusername1', + 'password': 'myiscsipassword1', + 'mutual_password': 'myiscsipassword2', + 'mutual_user': 'myiscsiusername2' + }, + 'info': { + 'alias': '', + 'ip_address': [], + 'state': {} + } + }, + { + 'client_iqn': 'iqn.1994-05.com.redhat:rh7-client2', + 'luns': [], + 'auth': { + 'user': 'myiscsiusername3', + 'password': 'myiscsipassword3', + 'mutual_password': 'myiscsipassword4', + 'mutual_user': 'myiscsiusername4' + }, + 'info': { + 'alias': '', + 'ip_address': [], + 'state': {} + } + } + ], + "acl_enabled": True, + "auth": { + "password": "", + "user": "", + "mutual_password": "", + "mutual_user": ""}, + 'groups': [ + { + 'group_id': 'mygroup', + 'disks': [{'pool': 'rbd', 'image': 'lun2'}], + 'members': ['iqn.1994-05.com.redhat:rh7-client2'] + } + ], + 'target_controls': {}, + 'info': { + 'num_sessions': 0 + } +} + + +class IscsiClientMock(object): + + _instance = None + + def __init__(self): + self.gateway_name = None + self.service_url = None + self.config = { + "created": "2019/01/17 08:57:16", + "discovery_auth": { + "username": "", + "password": "", + "password_encryption_enabled": False, + "mutual_username": "", + "mutual_password": "", + "mutual_password_encryption_enabled": False + }, + "disks": {}, + "epoch": 0, + "gateways": {}, + "targets": {}, + "updated": "", + "version": 11 + } + self.clientinfo = { + 'alias': '', + 'ip_address': [], + 'state': {} + } + + @classmethod + def instance(cls, gateway_name=None, service_url=None): + cls._instance.gateway_name = gateway_name + cls._instance.service_url = service_url + # pylint: disable=unused-argument + return cls._instance + + def ping(self): + return { + "message": "pong" + } + + def get_settings(self): + return { + "api_version": 2, + "backstores": [ + "user:rbd" + ], + "config": { + "minimum_gateways": 2 + }, + "default_backstore": "user:rbd", + "required_rbd_features": { + "rbd": 0, + "user:rbd": 4, + }, + "unsupported_rbd_features": { + "rbd": 88, + "user:rbd": 0, + }, + "disk_default_controls": { + "user:rbd": { + "hw_max_sectors": 1024, + "max_data_area_mb": 8, + "osd_op_timeout": 30, + "qfull_timeout": 5 + } + }, + "target_default_controls": { + "cmdsn_depth": 128, + "dataout_timeout": 20, + "first_burst_length": 262144, + "immediate_data": "Yes", + "initial_r2t": "Yes", + "max_burst_length": 524288, + "max_outstanding_r2t": 1, + "max_recv_data_segment_length": 262144, + "max_xmit_data_segment_length": 262144, + "nopin_response_timeout": 5, + "nopin_timeout": 5 + } + } + + def get_config(self): + return copy.deepcopy(self.config) + + def create_target(self, target_iqn, target_controls): + self.config['targets'][target_iqn] = { + "clients": {}, + "acl_enabled": True, + "auth": { + "username": "", + "password": "", + "password_encryption_enabled": False, + "mutual_username": "", + "mutual_password": "", + "mutual_password_encryption_enabled": False + }, + "controls": target_controls, + "created": "2019/01/17 09:22:34", + "disks": {}, + "groups": {}, + "portals": {} + } + + def create_gateway(self, target_iqn, gateway_name, ip_addresses): + target_config = self.config['targets'][target_iqn] + if 'ip_list' not in target_config: + target_config['ip_list'] = [] + target_config['ip_list'] += ip_addresses + target_config['portals'][gateway_name] = { + "portal_ip_addresses": ip_addresses + } + + def delete_gateway(self, target_iqn, gateway_name): + target_config = self.config['targets'][target_iqn] + portal_config = target_config['portals'][gateway_name] + for ip in portal_config['portal_ip_addresses']: + target_config['ip_list'].remove(ip) + target_config['portals'].pop(gateway_name) + + def create_disk(self, pool, image, backstore, wwn): + if wwn is None: + wwn = '64af6678-9694-4367-bacc-f8eb0baa' + str(len(self.config['disks'])) + image_id = '{}/{}'.format(pool, image) + self.config['disks'][image_id] = { + "pool": pool, + "image": image, + "backstore": backstore, + "controls": {}, + "wwn": wwn + } + + def create_target_lun(self, target_iqn, image_id, lun): + target_config = self.config['targets'][target_iqn] + if lun is None: + lun = len(target_config['disks']) + target_config['disks'][image_id] = { + "lun_id": lun + } + self.config['disks'][image_id]['owner'] = list(target_config['portals'].keys())[0] + + def reconfigure_disk(self, pool, image, controls): + image_id = '{}/{}'.format(pool, image) + settings = self.get_settings() + backstore = self.config['disks'][image_id]['backstore'] + disk_default_controls = settings['disk_default_controls'][backstore] + new_controls = {} + for control_k, control_v in controls.items(): + if control_v != disk_default_controls[control_k]: + new_controls[control_k] = control_v + self.config['disks'][image_id]['controls'] = new_controls + + def create_client(self, target_iqn, client_iqn): + target_config = self.config['targets'][target_iqn] + target_config['clients'][client_iqn] = { + "auth": { + "username": "", + "password": "", + "password_encryption_enabled": False, + "mutual_username": "", + "mutual_password": "", + "mutual_password_encryption_enabled": False + }, + "group_name": "", + "luns": {} + } + + def create_client_lun(self, target_iqn, client_iqn, image_id): + target_config = self.config['targets'][target_iqn] + target_config['clients'][client_iqn]['luns'][image_id] = {} + + def delete_client_lun(self, target_iqn, client_iqn, image_id): + target_config = self.config['targets'][target_iqn] + del target_config['clients'][client_iqn]['luns'][image_id] + + def create_client_auth(self, target_iqn, client_iqn, user, password, m_user, m_password): + target_config = self.config['targets'][target_iqn] + target_config['clients'][client_iqn]['auth']['username'] = user + target_config['clients'][client_iqn]['auth']['password'] = password + target_config['clients'][client_iqn]['auth']['mutual_username'] = m_user + target_config['clients'][client_iqn]['auth']['mutual_password'] = m_password + + def create_group(self, target_iqn, group_name, members, image_ids): + target_config = self.config['targets'][target_iqn] + target_config['groups'][group_name] = { + "disks": {}, + "members": [] + } + for image_id in image_ids: + target_config['groups'][group_name]['disks'][image_id] = {} + target_config['groups'][group_name]['members'] = members + + def update_group(self, target_iqn, group_name, members, image_ids): + target_config = self.config['targets'][target_iqn] + group = target_config['groups'][group_name] + old_members = group['members'] + disks = group['disks'] + target_config['groups'][group_name] = { + "disks": {}, + "members": [] + } + + for image_id in disks.keys(): + if image_id not in image_ids: + target_config['groups'][group_name]['disks'][image_id] = {} + + new_members = [] + for member_iqn in old_members: + if member_iqn not in members: + new_members.append(member_iqn) + target_config['groups'][group_name]['members'] = new_members + + def delete_group(self, target_iqn, group_name): + target_config = self.config['targets'][target_iqn] + del target_config['groups'][group_name] + + def delete_client(self, target_iqn, client_iqn): + target_config = self.config['targets'][target_iqn] + del target_config['clients'][client_iqn] + + def delete_target_lun(self, target_iqn, image_id): + target_config = self.config['targets'][target_iqn] + target_config['disks'].pop(image_id) + del self.config['disks'][image_id]['owner'] + + def delete_disk(self, pool, image): + image_id = '{}/{}'.format(pool, image) + del self.config['disks'][image_id] + + def delete_target(self, target_iqn): + del self.config['targets'][target_iqn] + + def get_ip_addresses(self): + ips = { + 'node1': ['192.168.100.201'], + 'node2': ['192.168.100.202', '10.0.2.15'], + 'node3': ['192.168.100.203'] + } + return {'data': ips[self.gateway_name]} + + def get_hostname(self): + hostnames = { + 'https://admin:admin@10.17.5.1:5001': 'node1', + 'https://admin:admin@10.17.5.2:5001': 'node2', + 'https://admin:admin@10.17.5.3:5001': 'node3' + } + if self.service_url not in hostnames: + raise RequestException('No route to host') + return {'data': hostnames[self.service_url]} + + def update_discoveryauth(self, user, password, mutual_user, mutual_password): + self.config['discovery_auth']['username'] = user + self.config['discovery_auth']['password'] = password + self.config['discovery_auth']['mutual_username'] = mutual_user + self.config['discovery_auth']['mutual_password'] = mutual_password + + def update_targetacl(self, target_iqn, action): + self.config['targets'][target_iqn]['acl_enabled'] = (action == 'enable_acl') + + def update_targetauth(self, target_iqn, user, password, mutual_user, mutual_password): + target_config = self.config['targets'][target_iqn] + target_config['auth']['username'] = user + target_config['auth']['password'] = password + target_config['auth']['mutual_username'] = mutual_user + target_config['auth']['mutual_password'] = mutual_password + + def get_targetinfo(self, target_iqn): + # pylint: disable=unused-argument + return { + 'num_sessions': 0 + } + + def get_clientinfo(self, target_iqn, client_iqn): + # pylint: disable=unused-argument + return self.clientinfo |