<p>neels <strong>submitted</strong> this change.</p><p><a href="https://gerrit.osmocom.org/c/osmo-gsm-tester/+/21564">View Change</a></p><div style="white-space:pre-wrap"></div><pre style="font-family: monospace,monospace; white-space: pre-wrap;">OsmoCtrl cleanup: get_var(), set_var(), get_trap()<br><br>CTRL interface interaction was mostly inherited from the first legacy<br>implementation of osmo-gsm-tester, and it was a pain to look at from the<br>start. Now, while I'm close to the topic, I want this to improve:<br><br>Properly match a GET_REPLY/SET_REPLY to a sent GET/SET by the message<br>ID.<br><br>Completely drop the do_get() and do_set(), which were not useful for<br>correct handling of the CTRL request and response messaging. The API to<br>use by callers is set_var(), get_var()/get_int_var() and get_trap().<br>These call the internal _sendrecv() (or for TRAP only _recv())<br>functions. Make it so that tese work both on an already connected<br>OsmoCtrl, as well as one that needs to establish a (short) connection,<br>so that both are trivially possible:<br><br>    # one CTRL connection stays open<br>    with OsmoCtrl(...) as ctrl:<br>    ctrl.get_var('var1')<br>          ctrl.get_var('var2')<br>          ctrl.get_var('var3')<br><br>and<br><br>  # get_var() opens a connection, does the GET and closes again<br>  OsmoCtrl(...).get_var('var1')<br><br>Do away with doubling the instances OsmoCtrl and e.g. OsmoBscCtrl.<br>Rather make OsmoBscCtrl a child class of OsmoCtrl, which means that we<br>no longer have bsc.ctrl().ctrl(), just bsc.ctrl().<br><br>Have VERB_* constants instead of dup'd strings.<br><br>Apply to / simplify all callers of OsmoCtrl.<br><br>Some of these changes are similar to recently added OsmoVty.<br><br>Change-Id: Id561e5a55d8057a997a8ec9e7fa6f94840194df1<br>---<br>M src/osmo_gsm_tester/obj/bsc_osmo.py<br>M src/osmo_gsm_tester/obj/msc_osmo.py<br>M src/osmo_gsm_tester/obj/nitb_osmo.py<br>M src/osmo_gsm_tester/obj/osmo_ctrl.py<br>4 files changed, 186 insertions(+), 127 deletions(-)<br><br></pre><pre style="font-family: monospace,monospace; white-space: pre-wrap;"><span>diff --git a/src/osmo_gsm_tester/obj/bsc_osmo.py b/src/osmo_gsm_tester/obj/bsc_osmo.py</span><br><span>index 158bf93..510063a 100644</span><br><span>--- a/src/osmo_gsm_tester/obj/bsc_osmo.py</span><br><span>+++ b/src/osmo_gsm_tester/obj/bsc_osmo.py</span><br><span>@@ -149,8 +149,10 @@</span><br><span>         # over this list, we have a 1:1 match in indexes.</span><br><span>         return self.bts.index(bts)</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-    def bts_is_connected(self, bts):</span><br><span style="color: hsl(0, 100%, 40%);">-        return OsmoBscCtrl(self).bts_is_connected(self.bts_num(bts))</span><br><span style="color: hsl(120, 100%, 40%);">+    def bts_is_connected(self, bts, use_ctrl=None):</span><br><span style="color: hsl(120, 100%, 40%);">+        if use_ctrl is None:</span><br><span style="color: hsl(120, 100%, 40%);">+            use_ctrl = self.ctrl()</span><br><span style="color: hsl(120, 100%, 40%);">+        return use_ctrl.bts_is_connected(self.bts_num(bts))</span><br><span> </span><br><span>     def running(self):</span><br><span>         return not self.process.terminated()</span><br><span>@@ -160,32 +162,16 @@</span><br><span>             self.vty.disconnect()</span><br><span>             self.vty = None</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-class OsmoBscCtrl(log.Origin):</span><br><span style="color: hsl(0, 100%, 40%);">-    PORT = 4249</span><br><span style="color: hsl(0, 100%, 40%);">-    BTS_OML_STATE_VAR = "bts.%d.oml-connection-state"</span><br><span style="color: hsl(0, 100%, 40%);">-    BTS_OML_STATE_RE = re.compile("GET_REPLY (\d+) bts.\d+.oml-connection-state (?P<oml_state>\w+)")</span><br><span style="color: hsl(0, 100%, 40%);">-</span><br><span style="color: hsl(0, 100%, 40%);">-    def __init__(self, bsc):</span><br><span style="color: hsl(0, 100%, 40%);">-        self.bsc = bsc</span><br><span style="color: hsl(0, 100%, 40%);">-        super().__init__(log.C_BUS, 'CTRL(%s:%d)' % (self.bsc.addr(), OsmoBscCtrl.PORT))</span><br><span style="color: hsl(0, 100%, 40%);">-</span><br><span>     def ctrl(self):</span><br><span style="color: hsl(0, 100%, 40%);">-        return osmo_ctrl.OsmoCtrl(self.bsc.addr(), OsmoBscCtrl.PORT)</span><br><span style="color: hsl(120, 100%, 40%);">+        return OsmoBscCtrl(self)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+class OsmoBscCtrl(osmo_ctrl.OsmoCtrl):</span><br><span style="color: hsl(120, 100%, 40%);">+    def __init__(self, bsc, port=4249):</span><br><span style="color: hsl(120, 100%, 40%);">+        self.bsc = bsc</span><br><span style="color: hsl(120, 100%, 40%);">+        super().__init__(bsc.addr(), port)</span><br><span> </span><br><span>     def bts_is_connected(self, bts_num):</span><br><span style="color: hsl(0, 100%, 40%);">-        with self.ctrl() as ctrl:</span><br><span style="color: hsl(0, 100%, 40%);">-            ctrl.do_get(OsmoBscCtrl.BTS_OML_STATE_VAR % bts_num)</span><br><span style="color: hsl(0, 100%, 40%);">-            data = ctrl.receive()</span><br><span style="color: hsl(0, 100%, 40%);">-            while (len(data) > 0):</span><br><span style="color: hsl(0, 100%, 40%);">-                (answer, data) = ctrl.remove_ipa_ctrl_header(data)</span><br><span style="color: hsl(0, 100%, 40%);">-                answer_str = answer.decode('utf-8')</span><br><span style="color: hsl(0, 100%, 40%);">-                answer_str = answer_str.replace('\n', ' ')</span><br><span style="color: hsl(0, 100%, 40%);">-                res = OsmoBscCtrl.BTS_OML_STATE_RE.match(answer_str)</span><br><span style="color: hsl(0, 100%, 40%);">-                if res:</span><br><span style="color: hsl(0, 100%, 40%);">-                    oml_state = str(res.group('oml_state'))</span><br><span style="color: hsl(0, 100%, 40%);">-                    if oml_state == 'connected':</span><br><span style="color: hsl(0, 100%, 40%);">-                        return True</span><br><span style="color: hsl(0, 100%, 40%);">-        return False</span><br><span style="color: hsl(120, 100%, 40%);">+        return self.get_var('bts.%d.oml-connection-state' % bts_num) == 'connected'</span><br><span> </span><br><span> class OsmoBscVty(osmo_vty.OsmoVty):</span><br><span>     def __init__(self, bsc, port=4242):</span><br><span>diff --git a/src/osmo_gsm_tester/obj/msc_osmo.py b/src/osmo_gsm_tester/obj/msc_osmo.py</span><br><span>index 67e1d31..5a7c0ba 100644</span><br><span>--- a/src/osmo_gsm_tester/obj/msc_osmo.py</span><br><span>+++ b/src/osmo_gsm_tester/obj/msc_osmo.py</span><br><span>@@ -157,29 +157,12 @@</span><br><span>         return not self.process.terminated()</span><br><span> </span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-class OsmoMscCtrl(log.Origin):</span><br><span style="color: hsl(0, 100%, 40%);">-    PORT = 4255</span><br><span style="color: hsl(0, 100%, 40%);">-    SUBSCR_LIST_ACTIVE_VAR = 'subscriber-list-active-v1'</span><br><span style="color: hsl(0, 100%, 40%);">-</span><br><span style="color: hsl(0, 100%, 40%);">-    def __init__(self, msc):</span><br><span style="color: hsl(120, 100%, 40%);">+class OsmoMscCtrl(osmo_ctrl.OsmoCtrl):</span><br><span style="color: hsl(120, 100%, 40%);">+    def __init__(self, msc, port=4255):</span><br><span>         self.msc = msc</span><br><span style="color: hsl(0, 100%, 40%);">-        super().__init__(log.C_BUS, 'CTRL(%s:%d)' % (self.msc.addr(), self.PORT))</span><br><span style="color: hsl(0, 100%, 40%);">-</span><br><span style="color: hsl(0, 100%, 40%);">-    def ctrl(self):</span><br><span style="color: hsl(0, 100%, 40%);">-        return osmo_ctrl.OsmoCtrl(self.msc.addr(), self.PORT)</span><br><span style="color: hsl(120, 100%, 40%);">+        super().__init__(self.msc.addr(), port)</span><br><span> </span><br><span>     def subscriber_list_active(self):</span><br><span style="color: hsl(0, 100%, 40%);">-        aslist_str = ""</span><br><span style="color: hsl(0, 100%, 40%);">-        with self.ctrl() as ctrl:</span><br><span style="color: hsl(0, 100%, 40%);">-            ctrl.do_get(self.SUBSCR_LIST_ACTIVE_VAR)</span><br><span style="color: hsl(0, 100%, 40%);">-            # This is legacy code from the old osmo-gsm-tester.</span><br><span style="color: hsl(0, 100%, 40%);">-            # looks like this doesn't work for long data.</span><br><span style="color: hsl(0, 100%, 40%);">-            data = ctrl.receive()</span><br><span style="color: hsl(0, 100%, 40%);">-            while (len(data) > 0):</span><br><span style="color: hsl(0, 100%, 40%);">-                (answer, data) = ctrl.remove_ipa_ctrl_header(data)</span><br><span style="color: hsl(0, 100%, 40%);">-                answer_str = answer.decode('utf-8')</span><br><span style="color: hsl(0, 100%, 40%);">-                answer_str = answer_str.replace('\n', ' ')</span><br><span style="color: hsl(0, 100%, 40%);">-                aslist_str = answer_str</span><br><span style="color: hsl(0, 100%, 40%);">-            return aslist_str</span><br><span style="color: hsl(120, 100%, 40%);">+        return self.get_var('subscriber-list-active-v1').replace('\n', ' ')</span><br><span> </span><br><span> # vim: expandtab tabstop=4 shiftwidth=4</span><br><span>diff --git a/src/osmo_gsm_tester/obj/nitb_osmo.py b/src/osmo_gsm_tester/obj/nitb_osmo.py</span><br><span>index a424927..ea00a75 100644</span><br><span>--- a/src/osmo_gsm_tester/obj/nitb_osmo.py</span><br><span>+++ b/src/osmo_gsm_tester/obj/nitb_osmo.py</span><br><span>@@ -158,22 +158,10 @@</span><br><span>         return not self.process.terminated()</span><br><span> </span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-class OsmoNitbCtrl(log.Origin):</span><br><span style="color: hsl(0, 100%, 40%);">-    PORT = 4249</span><br><span style="color: hsl(0, 100%, 40%);">-    SUBSCR_MODIFY_VAR = 'subscriber-modify-v1'</span><br><span style="color: hsl(0, 100%, 40%);">-    SUBSCR_MODIFY_REPLY_RE = re.compile("SET_REPLY (\d+) %s OK" % SUBSCR_MODIFY_VAR)</span><br><span style="color: hsl(0, 100%, 40%);">-    SUBSCR_DELETE_VAR = 'subscriber-delete-v1'</span><br><span style="color: hsl(0, 100%, 40%);">-    SUBSCR_DELETE_REPLY_RE = re.compile("SET_REPLY (\d+) %s Removed" % SUBSCR_DELETE_VAR)</span><br><span style="color: hsl(0, 100%, 40%);">-    SUBSCR_LIST_ACTIVE_VAR = 'subscriber-list-active-v1'</span><br><span style="color: hsl(0, 100%, 40%);">-    BTS_OML_STATE_VAR = "bts.%d.oml-connection-state"</span><br><span style="color: hsl(0, 100%, 40%);">-    BTS_OML_STATE_RE = re.compile("GET_REPLY (\d+) bts.\d+.oml-connection-state (?P<oml_state>\w+)")</span><br><span style="color: hsl(0, 100%, 40%);">-</span><br><span style="color: hsl(0, 100%, 40%);">-    def __init__(self, nitb):</span><br><span style="color: hsl(120, 100%, 40%);">+class OsmoNitbCtrl(osmo_ctrl.OsmoCtrl):</span><br><span style="color: hsl(120, 100%, 40%);">+    def __init__(self, nitb, port=4249):</span><br><span>         self.nitb = nitb</span><br><span style="color: hsl(0, 100%, 40%);">-        super().__init__(log.C_BUS, 'CTRL(%s:%d)' % (self.nitb.addr(), OsmoNitbCtrl.PORT))</span><br><span style="color: hsl(0, 100%, 40%);">-</span><br><span style="color: hsl(0, 100%, 40%);">-    def ctrl(self):</span><br><span style="color: hsl(0, 100%, 40%);">-        return osmo_ctrl.OsmoCtrl(self.nitb.addr(), OsmoNitbCtrl.PORT)</span><br><span style="color: hsl(120, 100%, 40%);">+        super().__init__(nitb.addr(), port)</span><br><span> </span><br><span>     def subscriber_add(self, imsi, msisdn, ki=None, algo=None):</span><br><span>         if algo:</span><br><span>@@ -181,54 +169,17 @@</span><br><span>         else:</span><br><span>             value = '%s,%s' % (imsi, msisdn)</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-        with self.ctrl() as ctrl:</span><br><span style="color: hsl(0, 100%, 40%);">-            ctrl.do_set(OsmoNitbCtrl.SUBSCR_MODIFY_VAR, value)</span><br><span style="color: hsl(0, 100%, 40%);">-            data = ctrl.receive()</span><br><span style="color: hsl(0, 100%, 40%);">-            (answer, data) = ctrl.remove_ipa_ctrl_header(data)</span><br><span style="color: hsl(0, 100%, 40%);">-            answer_str = answer.decode('utf-8')</span><br><span style="color: hsl(0, 100%, 40%);">-            res = OsmoNitbCtrl.SUBSCR_MODIFY_REPLY_RE.match(answer_str)</span><br><span style="color: hsl(0, 100%, 40%);">-            if not res:</span><br><span style="color: hsl(0, 100%, 40%);">-                raise RuntimeError('Cannot create subscriber %r (answer=%r)' % (imsi, answer_str))</span><br><span style="color: hsl(0, 100%, 40%);">-            self.dbg('Created subscriber', imsi=imsi, msisdn=msisdn)</span><br><span style="color: hsl(120, 100%, 40%);">+        assert self.set_var('subscriber-modify-v1', value) == 'OK'</span><br><span style="color: hsl(120, 100%, 40%);">+        self.dbg('Created subscriber', imsi=imsi, msisdn=msisdn)</span><br><span> </span><br><span>     def subscriber_delete(self, imsi):</span><br><span style="color: hsl(0, 100%, 40%);">-        with self.ctrl() as ctrl:</span><br><span style="color: hsl(0, 100%, 40%);">-            ctrl.do_set(OsmoNitbCtrl.SUBSCR_DELETE_VAR, imsi)</span><br><span style="color: hsl(0, 100%, 40%);">-            data = ctrl.receive()</span><br><span style="color: hsl(0, 100%, 40%);">-            (answer, data) = ctrl.remove_ipa_ctrl_header(data)</span><br><span style="color: hsl(0, 100%, 40%);">-            answer_str = answer.decode('utf-8')</span><br><span style="color: hsl(0, 100%, 40%);">-            res = OsmoNitbCtrl.SUBSCR_DELETE_REPLY_RE.match(answer_str)</span><br><span style="color: hsl(0, 100%, 40%);">-            if not res:</span><br><span style="color: hsl(0, 100%, 40%);">-                raise RuntimeError('Cannot delete subscriber %r (answer=%r)' % (imsi, answer_str))</span><br><span style="color: hsl(0, 100%, 40%);">-            self.dbg('Deleted subscriber', imsi=imsi)</span><br><span style="color: hsl(120, 100%, 40%);">+        assert self.set_var('subscriber-delete-v1', imsi) == 'Removed'</span><br><span style="color: hsl(120, 100%, 40%);">+        self.dbg('Deleted subscriber', imsi=imsi)</span><br><span> </span><br><span>     def subscriber_list_active(self):</span><br><span style="color: hsl(0, 100%, 40%);">-        aslist_str = ""</span><br><span style="color: hsl(0, 100%, 40%);">-        with self.ctrl() as ctrl:</span><br><span style="color: hsl(0, 100%, 40%);">-            ctrl.do_get(OsmoNitbCtrl.SUBSCR_LIST_ACTIVE_VAR)</span><br><span style="color: hsl(0, 100%, 40%);">-            # This is legacy code from the old osmo-gsm-tester.</span><br><span style="color: hsl(0, 100%, 40%);">-            # looks like this doesn't work for long data.</span><br><span style="color: hsl(0, 100%, 40%);">-            data = ctrl.receive()</span><br><span style="color: hsl(0, 100%, 40%);">-            while (len(data) > 0):</span><br><span style="color: hsl(0, 100%, 40%);">-                (answer, data) = ctrl.remove_ipa_ctrl_header(data)</span><br><span style="color: hsl(0, 100%, 40%);">-                answer_str = answer.decode('utf-8')</span><br><span style="color: hsl(0, 100%, 40%);">-                answer_str = answer_str.replace('\n', ' ')</span><br><span style="color: hsl(0, 100%, 40%);">-                aslist_str = answer_str</span><br><span style="color: hsl(0, 100%, 40%);">-            return aslist_str</span><br><span style="color: hsl(120, 100%, 40%);">+        return self.get_var('subscriber-list-active-v1').replace('\n', ' ')</span><br><span> </span><br><span>     def bts_is_connected(self, bts_num):</span><br><span style="color: hsl(0, 100%, 40%);">-        with self.ctrl() as ctrl:</span><br><span style="color: hsl(0, 100%, 40%);">-            ctrl.do_get(OsmoNitbCtrl.BTS_OML_STATE_VAR % bts_num)</span><br><span style="color: hsl(0, 100%, 40%);">-            data = ctrl.receive()</span><br><span style="color: hsl(0, 100%, 40%);">-            while (len(data) > 0):</span><br><span style="color: hsl(0, 100%, 40%);">-                (answer, data) = ctrl.remove_ipa_ctrl_header(data)</span><br><span style="color: hsl(0, 100%, 40%);">-                answer_str = answer.decode('utf-8')</span><br><span style="color: hsl(0, 100%, 40%);">-                answer_str = answer_str.replace('\n', ' ')</span><br><span style="color: hsl(0, 100%, 40%);">-                res = OsmoNitbCtrl.BTS_OML_STATE_RE.match(answer_str)</span><br><span style="color: hsl(0, 100%, 40%);">-                if res:</span><br><span style="color: hsl(0, 100%, 40%);">-                    oml_state = str(res.group('oml_state'))</span><br><span style="color: hsl(0, 100%, 40%);">-                    if oml_state == 'connected':</span><br><span style="color: hsl(0, 100%, 40%);">-                        return True</span><br><span style="color: hsl(0, 100%, 40%);">-        return False</span><br><span style="color: hsl(120, 100%, 40%);">+        return self.get_var('bts.%d.oml-connection-state' % bts_num) == 'connected'</span><br><span> </span><br><span> # vim: expandtab tabstop=4 shiftwidth=4</span><br><span>diff --git a/src/osmo_gsm_tester/obj/osmo_ctrl.py b/src/osmo_gsm_tester/obj/osmo_ctrl.py</span><br><span>index c2dd7e3..644025f 100644</span><br><span>--- a/src/osmo_gsm_tester/obj/osmo_ctrl.py</span><br><span>+++ b/src/osmo_gsm_tester/obj/osmo_ctrl.py</span><br><span>@@ -20,8 +20,20 @@</span><br><span> </span><br><span> import socket</span><br><span> import struct</span><br><span style="color: hsl(120, 100%, 40%);">+import re</span><br><span> </span><br><span> from ..core import log</span><br><span style="color: hsl(120, 100%, 40%);">+from ..core.event_loop import MainLoop</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+VERB_SET = 'SET'</span><br><span style="color: hsl(120, 100%, 40%);">+VERB_GET = 'GET'</span><br><span style="color: hsl(120, 100%, 40%);">+VERB_SET_REPLY = 'SET_REPLY'</span><br><span style="color: hsl(120, 100%, 40%);">+VERB_GET_REPLY = 'GET_REPLY'</span><br><span style="color: hsl(120, 100%, 40%);">+VERB_TRAP = 'TRAP'</span><br><span style="color: hsl(120, 100%, 40%);">+VERB_ERROR = 'ERROR'</span><br><span style="color: hsl(120, 100%, 40%);">+RECV_VERBS = (VERB_GET_REPLY, VERB_SET_REPLY, VERB_TRAP, VERB_ERROR)</span><br><span style="color: hsl(120, 100%, 40%);">+recv_re = re.compile('(%s) ([0-9]+) (.*)' % ('|'.join(RECV_VERBS)),</span><br><span style="color: hsl(120, 100%, 40%);">+                     re.MULTILINE + re.DOTALL)</span><br><span> </span><br><span> class CtrlInterfaceExn(Exception):</span><br><span>     pass</span><br><span>@@ -56,41 +68,168 @@</span><br><span>             raise CtrlInterfaceExn("Wrong protocol in answer!")</span><br><span>         return data[4:plen+3], data[plen+3:]</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-    def connect(self):</span><br><span style="color: hsl(0, 100%, 40%);">-        self.dbg('Connecting')</span><br><span style="color: hsl(0, 100%, 40%);">-        self.sck = socket.socket(socket.AF_INET, socket.SOCK_STREAM)</span><br><span style="color: hsl(0, 100%, 40%);">-        self.sck.connect((self.host, self.port))</span><br><span style="color: hsl(120, 100%, 40%);">+    def try_connect(self):</span><br><span style="color: hsl(120, 100%, 40%);">+        '''Do a connection attempt, return True when successful, False otherwise.</span><br><span style="color: hsl(120, 100%, 40%);">+           Does not raise exceptions, but logs them to the debug log.'''</span><br><span style="color: hsl(120, 100%, 40%);">+        assert self.sck is None</span><br><span style="color: hsl(120, 100%, 40%);">+        try:</span><br><span style="color: hsl(120, 100%, 40%);">+            self.dbg('Connecting')</span><br><span style="color: hsl(120, 100%, 40%);">+            sck = socket.socket(socket.AF_INET, socket.SOCK_STREAM)</span><br><span style="color: hsl(120, 100%, 40%);">+            try:</span><br><span style="color: hsl(120, 100%, 40%);">+                sck.connect((self.host, self.port))</span><br><span style="color: hsl(120, 100%, 40%);">+            except:</span><br><span style="color: hsl(120, 100%, 40%);">+                sck.close()</span><br><span style="color: hsl(120, 100%, 40%);">+                raise</span><br><span style="color: hsl(120, 100%, 40%);">+            # set self.sck only after the connect was successful</span><br><span style="color: hsl(120, 100%, 40%);">+            self.sck = sck</span><br><span style="color: hsl(120, 100%, 40%);">+            return True</span><br><span style="color: hsl(120, 100%, 40%);">+        except:</span><br><span style="color: hsl(120, 100%, 40%);">+            self.dbg('Failed to connect', sys.exc_info()[0])</span><br><span style="color: hsl(120, 100%, 40%);">+            return False</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def connect(self, timeout=30):</span><br><span style="color: hsl(120, 100%, 40%);">+        '''Connect to the CTRL self.host and self.port, retry for 'timeout' seconds.'''</span><br><span style="color: hsl(120, 100%, 40%);">+        MainLoop.wait(self.try_connect, timestep=3, timeout=timeout)</span><br><span>         self.sck.setblocking(1)</span><br><span>         self.sck.settimeout(10)</span><br><span> </span><br><span>     def disconnect(self):</span><br><span style="color: hsl(120, 100%, 40%);">+        if self.sck is None:</span><br><span style="color: hsl(120, 100%, 40%);">+            return</span><br><span>         self.dbg('Disconnecting')</span><br><span style="color: hsl(0, 100%, 40%);">-        if self.sck is not None:</span><br><span style="color: hsl(0, 100%, 40%);">-            self.sck.close()</span><br><span style="color: hsl(120, 100%, 40%);">+        self.sck.close()</span><br><span style="color: hsl(120, 100%, 40%);">+        self.sck = None</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-    def _send(self, data):</span><br><span style="color: hsl(120, 100%, 40%);">+    def _recv(self, verbs, match_args=None, match_id=None, attempts=10, length=1024):</span><br><span style="color: hsl(120, 100%, 40%);">+        '''Receive until a response matching the verbs / args / msg-id is obtained from CTRL.</span><br><span style="color: hsl(120, 100%, 40%);">+           The general socket timeout applies for each attempt made, see connect().</span><br><span style="color: hsl(120, 100%, 40%);">+           Multiple attempts may be necessary if, for example, intermediate</span><br><span style="color: hsl(120, 100%, 40%);">+           messages are received that do not relate to what is expected, like</span><br><span style="color: hsl(120, 100%, 40%);">+           TRAPs that are not interesting.</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+           To receive a GET_REPLY / SET_REPLY:</span><br><span style="color: hsl(120, 100%, 40%);">+             verb, rx_id, val = _recv(('GET_REPLY', 'ERROR'), match_id=used_id)</span><br><span style="color: hsl(120, 100%, 40%);">+             if verb == 'ERROR':</span><br><span style="color: hsl(120, 100%, 40%);">+                 raise CtrlInterfaceExn()</span><br><span style="color: hsl(120, 100%, 40%);">+             print(val)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+           To receive a TRAP:</span><br><span style="color: hsl(120, 100%, 40%);">+             verb, rx_id, val = _recv('TRAP', 'bts_connection_status connected')</span><br><span style="color: hsl(120, 100%, 40%);">+             # val == 'bts_connection_status connected'</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+           If the CTRL is not connected yet, open and close a connection for</span><br><span style="color: hsl(120, 100%, 40%);">+           this operation only.</span><br><span style="color: hsl(120, 100%, 40%);">+        '''</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        # allow calling for both already connected VTY as well as establishing</span><br><span style="color: hsl(120, 100%, 40%);">+        # a connection just for this command.</span><br><span style="color: hsl(120, 100%, 40%);">+        if self.sck is None:</span><br><span style="color: hsl(120, 100%, 40%);">+            with self:</span><br><span style="color: hsl(120, 100%, 40%);">+                return self._recv(verbs, match_args=match_args,</span><br><span style="color: hsl(120, 100%, 40%);">+                        match_id=match_id, attempts=attempts, length=length)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        if isinstance(verbs, str):</span><br><span style="color: hsl(120, 100%, 40%);">+            verbs = (verbs, )</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        for i in range(attempts):</span><br><span style="color: hsl(120, 100%, 40%);">+            data = self.sck.recv(length)</span><br><span style="color: hsl(120, 100%, 40%);">+            self.dbg('Receiving', data=data)</span><br><span style="color: hsl(120, 100%, 40%);">+            while len(data) > 0:</span><br><span style="color: hsl(120, 100%, 40%);">+                msg, data = self.remove_ipa_ctrl_header(data)</span><br><span style="color: hsl(120, 100%, 40%);">+                msg_str = msg.decode('utf-8')</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+                m = recv_re.fullmatch(msg_str)</span><br><span style="color: hsl(120, 100%, 40%);">+                if m is None:</span><br><span style="color: hsl(120, 100%, 40%);">+                    raise CtrlInterfaceExn('Received garbage: %r' % data)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+                rx_verb, rx_id, rx_args = m.groups()</span><br><span style="color: hsl(120, 100%, 40%);">+                rx_id = int(rx_id)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+                if match_id is not None and match_id != rx_id:</span><br><span style="color: hsl(120, 100%, 40%);">+                    continue</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+                if verbs and rx_verb not in verbs:</span><br><span style="color: hsl(120, 100%, 40%);">+                    continue</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+                if match_args and not rx_args.startswith(match_args):</span><br><span style="color: hsl(120, 100%, 40%);">+                    continue</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+                return rx_verb, rx_id, rx_args</span><br><span style="color: hsl(120, 100%, 40%);">+        raise CtrlInterfaceExn('No answer found: ' + reply_header)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def _sendrecv(self, verb, send_args, *recv_args, use_id=None, **recv_kwargs):</span><br><span style="color: hsl(120, 100%, 40%);">+        '''Send a request and receive a matching response.</span><br><span style="color: hsl(120, 100%, 40%);">+           If the CTRL is not connected yet, open and close a connection for</span><br><span style="color: hsl(120, 100%, 40%);">+           this operation only.</span><br><span style="color: hsl(120, 100%, 40%);">+        '''</span><br><span style="color: hsl(120, 100%, 40%);">+        if self.sck is None:</span><br><span style="color: hsl(120, 100%, 40%);">+            with self:</span><br><span style="color: hsl(120, 100%, 40%);">+                return self._sendrecv(verb, send_args, *recv_args, use_id=use_id, **recv_kwargs)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        if use_id is None:</span><br><span style="color: hsl(120, 100%, 40%);">+            use_id = self.next_id()</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        # send</span><br><span style="color: hsl(120, 100%, 40%);">+        data = '{verb} {use_id} {send_args}'.format(**locals())</span><br><span>         self.dbg('Sending', data=data)</span><br><span>         data = self.prefix_ipa_ctrl_header(data)</span><br><span>         self.sck.send(data)</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-    def receive(self, length = 1024):</span><br><span style="color: hsl(0, 100%, 40%);">-        data = self.sck.recv(length)</span><br><span style="color: hsl(0, 100%, 40%);">-        self.dbg('Receiving', data=data)</span><br><span style="color: hsl(0, 100%, 40%);">-        return data</span><br><span style="color: hsl(120, 100%, 40%);">+        # receive reply</span><br><span style="color: hsl(120, 100%, 40%);">+        recv_kwargs['match_id'] = use_id</span><br><span style="color: hsl(120, 100%, 40%);">+        return self._recv(*recv_args, **recv_kwargs)</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-    def do_set(self, var, value, use_id=None):</span><br><span style="color: hsl(0, 100%, 40%);">-        if use_id is None:</span><br><span style="color: hsl(0, 100%, 40%);">-            use_id = self.next_id()</span><br><span style="color: hsl(0, 100%, 40%);">-        setmsg = "SET %s %s %s" %(use_id, var, value)</span><br><span style="color: hsl(0, 100%, 40%);">-        self._send(setmsg)</span><br><span style="color: hsl(0, 100%, 40%);">-        return use_id</span><br><span style="color: hsl(120, 100%, 40%);">+    def set_var(self, var, value):</span><br><span style="color: hsl(120, 100%, 40%);">+        '''Set the value of a specific variable on a CTRL interface, and return the response, e.g.:</span><br><span style="color: hsl(120, 100%, 40%);">+              assert set_var('subscriber-modify-v1', '901701234567,2342') == 'OK'</span><br><span style="color: hsl(120, 100%, 40%);">+           If the CTRL is not connected yet, open and close a connection for</span><br><span style="color: hsl(120, 100%, 40%);">+           this operation only.</span><br><span style="color: hsl(120, 100%, 40%);">+        '''</span><br><span style="color: hsl(120, 100%, 40%);">+        verb, rx_id, args = self._sendrecv(VERB_SET, '%s %s' % (var, value), (VERB_SET_REPLY, VERB_ERROR))</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-    def do_get(self, var, use_id=None):</span><br><span style="color: hsl(0, 100%, 40%);">-        if use_id is None:</span><br><span style="color: hsl(0, 100%, 40%);">-            use_id = self.next_id()</span><br><span style="color: hsl(0, 100%, 40%);">-        getmsg = "GET %s %s" %(use_id, var)</span><br><span style="color: hsl(0, 100%, 40%);">-        self._send(getmsg)</span><br><span style="color: hsl(0, 100%, 40%);">-        return use_id</span><br><span style="color: hsl(120, 100%, 40%);">+        if verb == VERB_ERROR:</span><br><span style="color: hsl(120, 100%, 40%);">+            raise CtrlInterfaceExn('SET %s = %s returned %r' % (var, value, ' '.join((verb, str(rx_id), args))))</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        var_and_space = var + ' '</span><br><span style="color: hsl(120, 100%, 40%);">+        if not args.startswith(var_and_space):</span><br><span style="color: hsl(120, 100%, 40%);">+            raise CtrlInterfaceExn('SET %s = %s returned SET_REPLY for different var: %r'</span><br><span style="color: hsl(120, 100%, 40%);">+                                   % (var, value, ' '.join((verb, str(rx_id), args))))</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        return args[len(var_and_space):]</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def get_var(self, var):</span><br><span style="color: hsl(120, 100%, 40%);">+        '''Get the value of a specific variable from a CTRL interface:</span><br><span style="color: hsl(120, 100%, 40%);">+              assert get_var('bts.0.oml-connection-state') == 'connected'</span><br><span style="color: hsl(120, 100%, 40%);">+           If the CTRL is not connected yet, open and close a connection for</span><br><span style="color: hsl(120, 100%, 40%);">+           this operation only.</span><br><span style="color: hsl(120, 100%, 40%);">+        '''</span><br><span style="color: hsl(120, 100%, 40%);">+        verb, rx_id, args = self._sendrecv(VERB_GET, var, (VERB_GET_REPLY, VERB_ERROR))</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        if verb == VERB_ERROR:</span><br><span style="color: hsl(120, 100%, 40%);">+            raise CtrlInterfaceExn('GET %s returned %r' % (var, ' '.join((verb, str(rx_id), args))))</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        var_and_space = var + ' '</span><br><span style="color: hsl(120, 100%, 40%);">+        if not args.startswith(var_and_space):</span><br><span style="color: hsl(120, 100%, 40%);">+            raise CtrlInterfaceExn('GET %s returned GET_REPLY for different var: %r'</span><br><span style="color: hsl(120, 100%, 40%);">+                                   % (var, value, ' '.join((verb, str(rx_id), args))))</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        return args[len(var_and_space):]</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def get_int_var(self, var):</span><br><span style="color: hsl(120, 100%, 40%);">+        '''Same as get_var() but return an int'''</span><br><span style="color: hsl(120, 100%, 40%);">+        return int(self.get_var(var))</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def get_trap(self, name):</span><br><span style="color: hsl(120, 100%, 40%);">+        '''Read from CTRL until a TRAP of this name is received.</span><br><span style="color: hsl(120, 100%, 40%);">+           If name is None, any TRAP is returned.</span><br><span style="color: hsl(120, 100%, 40%);">+           If the CTRL is not connected yet, open and close a connection for</span><br><span style="color: hsl(120, 100%, 40%);">+           this operation only.</span><br><span style="color: hsl(120, 100%, 40%);">+        '''</span><br><span style="color: hsl(120, 100%, 40%);">+        verb, rx_id, args = self._recv(VERB_TRAP, name)</span><br><span style="color: hsl(120, 100%, 40%);">+        name_and_space = var + ' '</span><br><span style="color: hsl(120, 100%, 40%);">+        # _recv() should ensure this:</span><br><span style="color: hsl(120, 100%, 40%);">+        assert args.startswith(name_and_space)</span><br><span style="color: hsl(120, 100%, 40%);">+        return args[len(name_and_space):]</span><br><span> </span><br><span>     def __enter__(self):</span><br><span>         self.connect()</span><br><span></span><br></pre><p>To view, visit <a href="https://gerrit.osmocom.org/c/osmo-gsm-tester/+/21564">change 21564</a>. To unsubscribe, or for help writing mail filters, visit <a href="https://gerrit.osmocom.org/settings">settings</a>.</p><div itemscope itemtype="http://schema.org/EmailMessage"><div itemscope itemprop="action" itemtype="http://schema.org/ViewAction"><link itemprop="url" href="https://gerrit.osmocom.org/c/osmo-gsm-tester/+/21564"/><meta itemprop="name" content="View Change"/></div></div>

<div style="display:none"> Gerrit-Project: osmo-gsm-tester </div>
<div style="display:none"> Gerrit-Branch: master </div>
<div style="display:none"> Gerrit-Change-Id: Id561e5a55d8057a997a8ec9e7fa6f94840194df1 </div>
<div style="display:none"> Gerrit-Change-Number: 21564 </div>
<div style="display:none"> Gerrit-PatchSet: 2 </div>
<div style="display:none"> Gerrit-Owner: neels <nhofmeyr@sysmocom.de> </div>
<div style="display:none"> Gerrit-Reviewer: Jenkins Builder </div>
<div style="display:none"> Gerrit-Reviewer: pespin <pespin@sysmocom.de> </div>
<div style="display:none"> Gerrit-MessageType: merged </div>