Add unittests for multisession LVHDoISCSI attach

From: Mark Syms <mark.syms@citrix.com>

Signed-off-by: Mark Syms <mark.syms@citrix.com>
---
 drivers/iscsilib.py        |   28 ++++---
 tests/test_LVHDoISCSISR.py |  175 ++++++++++++++++++++++++++++++++++++++++----
 2 files changed, 178 insertions(+), 25 deletions(-)

diff --git a/drivers/iscsilib.py b/drivers/iscsilib.py
index ca9c02f..10e178d 100644
--- a/drivers/iscsilib.py
+++ b/drivers/iscsilib.py
@@ -456,13 +456,25 @@ def match_targetIQN(tgtIQN, s):
         return True
     # Extract IQN from iscsiadm -m session
     # Ex: tcp: [17] 10.220.98.9:3260,1 iqn.2009-01.xenrt.test:iscsi4181a93e
-    siqn = s.split(",")[1].split()[1]
+    siqn = s.split(",")[1].split()[1].strip()
     return (siqn == tgtIQN)
 
 def match_session(s):
     regex = re.compile("^tcp:")
     return regex.search(s, 0)
 
+
+def _compare_sessions_to_tgt(session_output, tgtIQN, tgt=''):
+    for line in session_output.split('\n'):
+        if match_targetIQN(tgtIQN, line) and match_session(line):
+            if len(tgt):
+                if match_target(tgt, line):
+                    return True
+            else:
+                return True
+    return False
+
+
 def _checkTGT(tgtIQN, tgt=''):
     if not is_iscsi_daemon_running():
         return False
@@ -472,18 +484,12 @@ def _checkTGT(tgtIQN, tgt=''):
         (stdout,stderr) = exn_on_failure(cmd, failuremessage)
     # Recent versions of iscsiadm return error it this list is empty.
     # Quick and dirty handling
-    except Exception, e:
+    except Exception as e:
         util.SMlog("%s failed with %s" %(cmd, e.args))
         stdout = ""
-    for line in stdout.split('\n'):
-        if match_targetIQN(tgtIQN, line) and match_session(line):
-            if len(tgt):
-                if match_target(tgt, line):
-                    return True
-            else:
-                return True
-    return False
-    
+    return _compare_sessions_to_tgt(stdout, tgtIQN, tgt)
+
+
 def get_rootdisk_IQNs():
     """Return the list of IQNs for targets required by root filesystem"""
     if not os.path.isdir('/sys/firmware/ibft/'):
diff --git a/tests/test_LVHDoISCSISR.py b/tests/test_LVHDoISCSISR.py
index bae25d7..535090f 100644
--- a/tests/test_LVHDoISCSISR.py
+++ b/tests/test_LVHDoISCSISR.py
@@ -1,10 +1,14 @@
 import mock
+import os
 import unittest
 
+import traceback
+
 from uuid import uuid4
 
 import SR
 import LVHDoISCSISR
+import iscsilib
 from BaseISCSI import BaseISCSISR
 import SRCommand
 import util
@@ -126,13 +130,29 @@ class TestLVHDoISCSISR_load(unittest.TestCase):
 class TestLVHDoISCSISR(unittest.TestCase):
 
     def setUp(self):
-        util_patcher = mock.patch('LVHDoISCSISR.util')
+        util_patcher = mock.patch('LVHDoISCSISR.util', autospec=True)
         self.mock_util = util_patcher.start()
+        # self.mock_util.SMlog.side_effect = print
+        self.mock_util.isVDICommand = util.isVDICommand
         self.mock_util.sessions_less_than_targets = util.sessions_less_than_targets
-        baseiscsi_patcher = mock.patch('LVHDoISCSISR.BaseISCSI.BaseISCSISR')
+
+        self.base_srs = set()
+        baseiscsi_patcher = mock.patch('LVHDoISCSISR.BaseISCSI.BaseISCSISR',
+                                       autospec=True)
         patched_baseiscsi = baseiscsi_patcher.start()
-        self.mock_baseiscsi = mock.create_autospec(BaseISCSISR)
-        patched_baseiscsi.return_value = self.mock_baseiscsi
+        patched_baseiscsi.side_effect = self.baseiscsi
+        lvhdsr_patcher = mock.patch ('LVHDoISCSISR.LVHDSR')
+
+        iscsilib_patcher = mock.patch('LVHDoISCSISR.iscsilib',
+                                      autospec=True)
+        self.mock_iscsilib = iscsilib_patcher.start()
+        self.mock_iscsilib.discovery.side_effect = self.discovery
+        self.mock_iscsilib._checkTGT.side_effect = self._checkTGT
+        self.mock_iscsilib.login.side_effect = self.iscsi_login
+        self.discovery_data = {}
+        self.sessions = []
+
+        self.mock_lvhdsr = lvhdsr_patcher.start()
         self.mock_session = mock.MagicMock()
         xenapi_patcher = mock.patch('SR.XenAPI')
         mock_xenapi = xenapi_patcher.start()
@@ -151,28 +171,81 @@ class TestLVHDoISCSISR(unittest.TestCase):
 
         self.addCleanup(mock.patch.stopall)
 
-        dummy_cmd = mock.create_autospec(SRCommand)
-        dummy_cmd.dconf = {
+    def _checkTGT(self, tgtIQN, tgt=''):
+        all_sessions = '\n'.join(self.sessions)
+        matched = iscsilib._compare_sessions_to_tgt(all_sessions, tgtIQN, tgt)
+        return matched
+
+    def discovery(self, target, port, chapuser, chappassword,
+                  targetIQN="any", interfaceArray=["default"]):
+        return self.discovery_data.get(target, [])
+
+    def iscsi_login(self, target, target_iqn, chauser, chappassword,
+                    incoming_user, incoming_password, mpath):
+        print("Logging in %s - %s" % (target, target_iqn))
+        session_count = len(self.sessions)
+        self.sessions.append('tcp: [%s] %s:3260,1 %s' % (session_count, target, target_iqn))
+
+    @property
+    def mock_baseiscsi(self):
+        assert len(self.base_srs) == 1
+        single_sr = None
+        for sr in self.base_srs:
+            single_sr = sr
+
+        return single_sr
+
+    def baseiscsi(self, srcmd, sr_uuid):
+        new_baseiscsi = mock.create_autospec(BaseISCSISR)
+        local_iqn = srcmd.dconf['localIQN']
+        target_iqn = srcmd.dconf['targetIQN']
+        target = srcmd.dconf['target']
+        new_baseiscsi.localIQN = local_iqn
+        new_baseiscsi.targetIQN = target_iqn
+        new_baseiscsi.target = target
+        new_baseiscsi.path = os.path.join('/dev/iscsi', target_iqn, target)
+        new_baseiscsi.port = 3260
+        new_baseiscsi.chapuser = srcmd.dconf.get('chapuser')
+        new_baseiscsi.chappassword = srcmd.dconf.get('chappassword')
+        new_baseiscsi.incoming_chapuser = srcmd.dconf.get('incoming_chapuser')
+        new_baseiscsi.incoming_chappassword = srcmd.dconf.get('incoming_chappassword')
+        self.base_srs.add(new_baseiscsi)
+
+        return new_baseiscsi
+
+    def create_test_sr(self, sr_cmd):
+        self.sr_uuid = str(uuid4())
+        self.subject = LVHDoISCSISR.LVHDoISCSISR(
+            sr_cmd, self.sr_uuid)
+
+    def create_sr_command(
+            self, additional_dconf=None, cmd=None,
+            target_iqn='iqn.2009-01.example.test:iscsi085e938a'):
+
+        sr_cmd = mock.create_autospec(SRCommand)
+        sr_cmd.dconf = {
             'SCSIid': '3600a098038313577792450384a4a6275',
             'multihomelist': 'tgt1:3260,tgt2:3260',
             'target': "10.70.89.34",
-            'targetIQN': 'iqn.2009-01.example.test:iscsi085e938a'
+            'targetIQN': target_iqn,
+            'localIQN': 'iqn.2018-05.com.example:0d312804'
         }
-        dummy_cmd.params = {
+        if additional_dconf:
+            sr_cmd.dconf.update(additional_dconf)
+
+        sr_cmd.params = {
             'command': 'nop',
             'session_ref': 'test_session',
             'host_ref': 'test_host',
             'sr_ref': 'sr_ref'
         }
-        dummy_cmd.cmd = None
-
-        self.sr_uuid = str(uuid4())
-        self.subject = LVHDoISCSISR.LVHDoISCSISR(
-            dummy_cmd, self.sr_uuid)
+        sr_cmd.cmd = cmd
+        return sr_cmd
 
     def test_check_sr_pbd_not_found(self):
         # Arrange
         self.mock_util.find_my_pbd.return_value = None
+        self.create_test_sr(self.create_sr_command())
 
         # Act
         self.subject.check_sr(TEST_SR_UUID)
@@ -187,6 +260,7 @@ class TestLVHDoISCSISR(unittest.TestCase):
         self.mock_session.xenapi.PBD.get_other_config.return_value = {
             'iscsi_sessions': 2
         }
+        self.create_test_sr(self.create_sr_command())
 
         # Act
         self.subject.check_sr(TEST_SR_UUID)
@@ -200,7 +274,7 @@ class TestLVHDoISCSISR(unittest.TestCase):
         self.mock_session.xenapi.PBD.get_other_config.return_value = {
             'iscsi_sessions': 1
         }
-
+        self.create_test_sr(self.create_sr_command())
 
         # Act
         self.subject.check_sr(TEST_SR_UUID)
@@ -209,3 +283,76 @@ class TestLVHDoISCSISR(unittest.TestCase):
         self.mock_baseiscsi.attach.assert_called_with(
             TEST_SR_UUID
         )
+
+    def test_sr_attach_multi_session(self):
+        # Arrange
+        self.mock_util.find_my_pbd.return_value = 'my_pbd'
+        additional_dconf = {
+            'multiSession': '10.207.6.60,3260,iqn.2009-11.com.infinidat:storage:infinibox-sn-3393|'
+                            '10.207.3.65,3260,iqn.2009-11.com.infinidat:storage:infinibox-sn-3394|'
+                            '10.207.3.61,3260,iqn.2009-11.com.infinidat:storage:infinibox-sn-3393|'
+                            '10.207.6.61,3260,iqn.2009-11.com.infinidat:storage:infinibox-sn-3393|'
+                            '10.207.3.63,3260,iqn.2009-11.com.infinidat:storage:infinibox-sn-3394|'
+                            '10.207.6.62,3260,iqn.2009-11.com.infinidat:storage:infinibox-sn-3393|'
+                            '10.207.3.62,3260,iqn.2009-11.com.infinidat:storage:infinibox-sn-3393|'
+                            '10.207.3.60,3260,iqn.2009-11.com.infinidat:storage:infinibox-sn-3393|'
+                            '10.207.6.64,3260,iqn.2009-11.com.infinidat:storage:infinibox-sn-3394|'
+                            '10.207.6.65,3260,iqn.2009-11.com.infinidat:storage:infinibox-sn-3394|'
+                            '10.207.3.64,3260,iqn.2009-11.com.infinidat:storage:infinibox-sn-3394|'
+                            '10.207.6.63,3260,iqn.2009-11.com.infinidat:storage:infinibox-sn-3394|'
+        }
+
+        tpg_data = [
+            [
+                ('10.207.3.60:3260', 1, 'iqn.2009-11.com.infinidat:storage:infinibox-sn-3393'),
+                ('10.207.3.61:3260', 1, 'iqn.2009-11.com.infinidat:storage:infinibox-sn-3393'),
+                ('10.207.3.62:3260', 1, 'iqn.2009-11.com.infinidat:storage:infinibox-sn-3393')],
+            [
+                ('10.207.3.63:3260', 1, 'iqn.2009-11.com.infinidat:storage:infinibox-sn-3394'),
+                ('10.207.3.64:3260', 1, 'iqn.2009-11.com.infinidat:storage:infinibox-sn-3394'),
+                ('10.207.3.65:3260', 1, 'iqn.2009-11.com.infinidat:storage:infinibox-sn-3394')],
+            [
+                ('10.207.6.60:3260', 2, 'iqn.2009-11.com.infinidat:storage:infinibox-sn-3393'),
+                ('10.207.6.61:3260', 2, 'iqn.2009-11.com.infinidat:storage:infinibox-sn-3393'),
+                ('10.207.6.62:3260', 2, 'iqn.2009-11.com.infinidat:storage:infinibox-sn-3393')
+            ],
+            [
+                ('10.207.6.63:3260', 2, 'iqn.2009-11.com.infinidat:storage:infinibox-sn-3394'),
+                ('10.207.6.64:3260', 2, 'iqn.2009-11.com.infinidat:storage:infinibox-sn-3394'),
+                ('10.207.6.65:3260', 2, 'iqn.2009-11.com.infinidat:storage:infinibox-sn-3394')
+            ]
+        ]
+
+        self.discovery_data = {
+            '10.207.3.60': tpg_data[0],
+            '10.207.3.61': tpg_data[0],
+            '10.207.3.62': tpg_data[0],
+            '10.207.3.63': tpg_data[1],
+            '10.207.3.64': tpg_data[1],
+            '10.207.3.65': tpg_data[1],
+            '10.207.6.60': tpg_data[2],
+            '10.207.6.61': tpg_data[2],
+            '10.207.6.62': tpg_data[2],
+            '10.207.6.63': tpg_data[3],
+            '10.207.6.64': tpg_data[3],
+            '10.207.6.65': tpg_data[3]
+        }
+
+        # Create SR
+        self.create_test_sr(self.create_sr_command(
+            additional_dconf=additional_dconf,
+            cmd='sr_attach',
+            target_iqn='*'))
+
+        # Act
+        self.subject.attach(TEST_SR_UUID)
+
+        # Assert
+        # print(f"iscsilib calls {self.mock_iscsilib.mock_calls}")
+        attach_count = 0
+        for sr in self.base_srs:
+           attach_count += sr.attach.call_count
+
+        self.assertEqual(12, attach_count)
+        self.assertEqual(12, self.mock_iscsilib.discovery.call_count)
+        self.assertEqual(12, self.mock_iscsilib.login.call_count)
