<p>Max <strong>merged</strong> this change.</p><p><a href="https://gerrit.osmocom.org/11952">View Change</a></p><div style="white-space:pre-wrap">Approvals:
  Jenkins Builder: Verified
  Pau Espin Pedrol: Looks good to me, approved

</div><pre style="font-family: monospace,monospace; white-space: pre-wrap;">Move Trap class back to separate files<br><br>After further testing it turns out that Trap() have too many<br>implementation details which makes it cumbersome to be shared. Instead,<br>it's easier to make it into wrapper over shared functions.<br><br>Change-Id: I8a3c62bcdf4286f8127c5b6d8dee6d740aca23b9<br>---<br>M scripts/ctrl2cgi.py<br>M scripts/soap.py<br>2 files changed, 114 insertions(+), 15 deletions(-)<br><br></pre><pre style="font-family: monospace,monospace; white-space: pre-wrap;"><span>diff --git a/scripts/ctrl2cgi.py b/scripts/ctrl2cgi.py</span><br><span>index 18cfdbe..843aeb9 100755</span><br><span>--- a/scripts/ctrl2cgi.py</span><br><span>+++ b/scripts/ctrl2cgi.py</span><br><span>@@ -22,14 +22,14 @@</span><br><span>  */</span><br><span> """</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-__version__ = "0.0.1" # bump this on every non-trivial change</span><br><span style="color: hsl(120, 100%, 40%);">+__version__ = "0.0.2" # bump this on every non-trivial change</span><br><span> </span><br><span> from twisted.internet import defer, reactor</span><br><span> from osmopy.twisted_ipa import CTRL, IPAFactory, __version__ as twisted_ipa_version</span><br><span> from osmopy.osmo_ipa import Ctrl</span><br><span> from treq import post, collect</span><br><span> from functools import partial</span><br><span style="color: hsl(0, 100%, 40%);">-from osmopy.trap_helper import Trap, reloader, debug_init</span><br><span style="color: hsl(120, 100%, 40%);">+from osmopy.trap_helper import reloader, debug_init, get_type, get_r, p_h, make_params</span><br><span> from distutils.version import StrictVersion as V # FIXME: use NormalizedVersion from PEP-386 when available</span><br><span> import argparse, datetime, signal, sys, os, logging, logging.handlers</span><br><span> import hashlib</span><br><span>@@ -75,6 +75,61 @@</span><br><span>     #print('HASH: \nparams="%r"\ninput="%s" \nres="%s"' %(params, input, res))</span><br><span>     return res</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+class Trap(CTRL):</span><br><span style="color: hsl(120, 100%, 40%);">+    """</span><br><span style="color: hsl(120, 100%, 40%);">+    TRAP handler (agnostic to factory's client object)</span><br><span style="color: hsl(120, 100%, 40%);">+    """</span><br><span style="color: hsl(120, 100%, 40%);">+    def ctrl_TRAP(self, data, op_id, v):</span><br><span style="color: hsl(120, 100%, 40%);">+        """</span><br><span style="color: hsl(120, 100%, 40%);">+        Parse CTRL TRAP and dispatch to appropriate handler after normalization</span><br><span style="color: hsl(120, 100%, 40%);">+        """</span><br><span style="color: hsl(120, 100%, 40%);">+        self.factory.log.debug('TRAP %s' % v)</span><br><span style="color: hsl(120, 100%, 40%);">+        t_type = get_type(v)</span><br><span style="color: hsl(120, 100%, 40%);">+        p = p_h(v)</span><br><span style="color: hsl(120, 100%, 40%);">+        method = getattr(self, 'handle_' + t_type.replace('-', ''), lambda *_: "Unhandled %s trap" % t_type)</span><br><span style="color: hsl(120, 100%, 40%);">+        method(p(1), p(3), p(5), p(7), get_r(v))</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def ctrl_SET_REPLY(self, data, _, v):</span><br><span style="color: hsl(120, 100%, 40%);">+        """</span><br><span style="color: hsl(120, 100%, 40%);">+        Debug log for replies to our commands</span><br><span style="color: hsl(120, 100%, 40%);">+        """</span><br><span style="color: hsl(120, 100%, 40%);">+        self.factory.log.debug('SET REPLY %s' % v)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def ctrl_ERROR(self, data, op_id, v):</span><br><span style="color: hsl(120, 100%, 40%);">+        """</span><br><span style="color: hsl(120, 100%, 40%);">+        We want to know if smth went wrong</span><br><span style="color: hsl(120, 100%, 40%);">+        """</span><br><span style="color: hsl(120, 100%, 40%);">+        self.factory.log.debug('CTRL ERROR [%s] %s' % (op_id, v))</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def connectionMade(self):</span><br><span style="color: hsl(120, 100%, 40%);">+        """</span><br><span style="color: hsl(120, 100%, 40%);">+        Logging wrapper, calling super() is necessary not to break reconnection logic</span><br><span style="color: hsl(120, 100%, 40%);">+        """</span><br><span style="color: hsl(120, 100%, 40%);">+        self.factory.log.info("Connected to CTRL@%s:%d" % (self.factory.host, self.factory.port))</span><br><span style="color: hsl(120, 100%, 40%);">+        super(CTRL, self).connectionMade()</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    @defer.inlineCallbacks</span><br><span style="color: hsl(120, 100%, 40%);">+    def handle_locationstate(self, net, bsc, bts, trx, data):</span><br><span style="color: hsl(120, 100%, 40%);">+        """</span><br><span style="color: hsl(120, 100%, 40%);">+        Handle location-state TRAP: parse trap content, build CGI Request and use treq's routines to post it while setting up async handlers</span><br><span style="color: hsl(120, 100%, 40%);">+        """</span><br><span style="color: hsl(120, 100%, 40%);">+        params = make_params(bsc, data)</span><br><span style="color: hsl(120, 100%, 40%);">+        self.factory.log.debug('location-state@%s.%s.%s.%s (%s) => %s' % (net, bsc, bts, trx, params['time_stamp'], data))</span><br><span style="color: hsl(120, 100%, 40%);">+        params['h'] = gen_hash(params, self.factory.secret_key)</span><br><span style="color: hsl(120, 100%, 40%);">+        d = post(self.factory.location, None, params=params)</span><br><span style="color: hsl(120, 100%, 40%);">+        d.addCallback(partial(handle_reply, self.transport.write, self.factory.log)) # treq's collect helper is handy to get all reply content at once using closure on ctx</span><br><span style="color: hsl(120, 100%, 40%);">+        d.addErrback(lambda e, bsc: self.factory.log.critical("HTTP POST error %s while trying to register BSC %s on %s" % (e, bsc, self.factory.location)), bsc) # handle HTTP errors</span><br><span style="color: hsl(120, 100%, 40%);">+        # Ensure that we run only limited number of requests in parallel:</span><br><span style="color: hsl(120, 100%, 40%);">+        yield self.factory.semaphore.acquire()</span><br><span style="color: hsl(120, 100%, 40%);">+        yield d # we end up here only if semaphore is available which means it's ok to fire the request without exceeding the limit</span><br><span style="color: hsl(120, 100%, 40%);">+        self.factory.semaphore.release()</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def handle_notificationrejectionv1(self, net, bsc, bts, trx, data):</span><br><span style="color: hsl(120, 100%, 40%);">+        """</span><br><span style="color: hsl(120, 100%, 40%);">+        Handle notification-rejection-v1 TRAP: just an example to show how more message types can be handled</span><br><span style="color: hsl(120, 100%, 40%);">+        """</span><br><span style="color: hsl(120, 100%, 40%);">+        self.factory.log.debug('notification-rejection-v1@bsc-id %s => %s' % (bsc, data))</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> </span><br><span> class TrapFactory(IPAFactory):</span><br><span>     """</span><br><span>@@ -100,12 +155,6 @@</span><br><span>         self.log.setLevel(level)</span><br><span>         self.log.debug("Using IPA %s, CGI server: %s" % (Ctrl.version, self.location))</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-    def prepare_params(bsc, lon, lat, fix, tstamp, oper, admin, policy):</span><br><span style="color: hsl(0, 100%, 40%);">-        params = {'bsc_id': bsc, 'lon': lon, 'lat': lat, 'position_validity': fix, 'time_stamp': tstamp, 'oper_status': oper, 'admin_status': admin, 'policy_status': policy }</span><br><span style="color: hsl(0, 100%, 40%);">-        params['h'] = gen_hash(params, self.factory.secret_key)</span><br><span style="color: hsl(0, 100%, 40%);">-        d = post(self.factory.location, None, params=params)</span><br><span style="color: hsl(0, 100%, 40%);">-        d.addCallback(partial(handle_reply, self.transport.write, self.factory.log))</span><br><span style="color: hsl(0, 100%, 40%);">-        return d</span><br><span> </span><br><span> if __name__ == '__main__':</span><br><span>     p = argparse.ArgumentParser(description='Proxy between given GCI service and Osmocom CTRL protocol.')</span><br><span>diff --git a/scripts/soap.py b/scripts/soap.py</span><br><span>index 156157c..75acd89 100755</span><br><span>--- a/scripts/soap.py</span><br><span>+++ b/scripts/soap.py</span><br><span>@@ -30,7 +30,7 @@</span><br><span> from treq import post, collect</span><br><span> from suds.client import Client</span><br><span> from functools import partial</span><br><span style="color: hsl(0, 100%, 40%);">-from osmopy.trap_helper import Trap, reloader, debug_init</span><br><span style="color: hsl(120, 100%, 40%);">+from osmopy.trap_helper import reloader, debug_init, get_type, get_r, p_h, make_params</span><br><span> from distutils.version import StrictVersion as V # FIXME: use NormalizedVersion from PEP-386 when available</span><br><span> import argparse, datetime, signal, sys, os, logging, logging.handlers</span><br><span> </span><br><span>@@ -51,6 +51,62 @@</span><br><span>         f(m)</span><br><span> </span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+class Trap(CTRL):</span><br><span style="color: hsl(120, 100%, 40%);">+    """</span><br><span style="color: hsl(120, 100%, 40%);">+    TRAP handler (agnostic to factory's client object)</span><br><span style="color: hsl(120, 100%, 40%);">+    """</span><br><span style="color: hsl(120, 100%, 40%);">+    def ctrl_TRAP(self, data, op_id, v):</span><br><span style="color: hsl(120, 100%, 40%);">+        """</span><br><span style="color: hsl(120, 100%, 40%);">+        Parse CTRL TRAP and dispatch to appropriate handler after normalization</span><br><span style="color: hsl(120, 100%, 40%);">+        """</span><br><span style="color: hsl(120, 100%, 40%);">+        self.factory.log.debug('TRAP %s' % v)</span><br><span style="color: hsl(120, 100%, 40%);">+        t_type = get_type(v)</span><br><span style="color: hsl(120, 100%, 40%);">+        p = p_h(v)</span><br><span style="color: hsl(120, 100%, 40%);">+        method = getattr(self, 'handle_' + t_type.replace('-', ''), lambda *_: "Unhandled %s trap" % t_type)</span><br><span style="color: hsl(120, 100%, 40%);">+        method(p(1), p(3), p(5), p(7), get_r(v))</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def ctrl_SET_REPLY(self, data, _, v):</span><br><span style="color: hsl(120, 100%, 40%);">+        """</span><br><span style="color: hsl(120, 100%, 40%);">+        Debug log for replies to our commands</span><br><span style="color: hsl(120, 100%, 40%);">+        """</span><br><span style="color: hsl(120, 100%, 40%);">+        self.factory.log.debug('SET REPLY %s' % v)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def ctrl_ERROR(self, data, op_id, v):</span><br><span style="color: hsl(120, 100%, 40%);">+        """</span><br><span style="color: hsl(120, 100%, 40%);">+        We want to know if smth went wrong</span><br><span style="color: hsl(120, 100%, 40%);">+        """</span><br><span style="color: hsl(120, 100%, 40%);">+        self.factory.log.debug('CTRL ERROR [%s] %s' % (op_id, v))</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def connectionMade(self):</span><br><span style="color: hsl(120, 100%, 40%);">+        """</span><br><span style="color: hsl(120, 100%, 40%);">+        Logging wrapper, calling super() is necessary not to break reconnection logic</span><br><span style="color: hsl(120, 100%, 40%);">+        """</span><br><span style="color: hsl(120, 100%, 40%);">+        self.factory.log.info("Connected to CTRL@%s:%d" % (self.factory.host, self.factory.port))</span><br><span style="color: hsl(120, 100%, 40%);">+        super(CTRL, self).connectionMade()</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    @defer.inlineCallbacks</span><br><span style="color: hsl(120, 100%, 40%);">+    def handle_locationstate(self, net, bsc, bts, trx, data):</span><br><span style="color: hsl(120, 100%, 40%);">+        """</span><br><span style="color: hsl(120, 100%, 40%);">+        Handle location-state TRAP: parse trap content, build SOAP context and use treq's routines to post it while setting up async handlers</span><br><span style="color: hsl(120, 100%, 40%);">+        """</span><br><span style="color: hsl(120, 100%, 40%);">+        params = make_params(bsc, data)</span><br><span style="color: hsl(120, 100%, 40%);">+        self.factory.log.debug('location-state@%s.%s.%s.%s (%s) => %s' % (net, bsc, bts, trx, params['time_stamp'], data))</span><br><span style="color: hsl(120, 100%, 40%);">+        ctx = self.factory.client.registerSiteLocation(bsc, float(params['lon']), float(params['lat']), params['position_validity'], params['time_stamp'], params['oper_status'], params['admin_status'], params['policy_status'])</span><br><span style="color: hsl(120, 100%, 40%);">+        d = post(self.factory.location, ctx.envelope)</span><br><span style="color: hsl(120, 100%, 40%);">+        d.addCallback(collect, partial(handle_reply, ctx.process_reply, self.transport.write, self.factory.log)) # treq's collect helper is handy to get all reply content at once using closure on ctx</span><br><span style="color: hsl(120, 100%, 40%);">+        d.addErrback(lambda e, bsc: self.factory.log.critical("HTTP POST error %s while trying to register BSC %s on %s" % (e, bsc, self.factory.location)), bsc) # handle HTTP errors</span><br><span style="color: hsl(120, 100%, 40%);">+        # Ensure that we run only limited number of requests in parallel:</span><br><span style="color: hsl(120, 100%, 40%);">+        yield self.factory.semaphore.acquire()</span><br><span style="color: hsl(120, 100%, 40%);">+        yield d # we end up here only if semaphore is available which means it's ok to fire the request without exceeding the limit</span><br><span style="color: hsl(120, 100%, 40%);">+        self.factory.semaphore.release()</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def handle_notificationrejectionv1(self, net, bsc, bts, trx, data):</span><br><span style="color: hsl(120, 100%, 40%);">+        """</span><br><span style="color: hsl(120, 100%, 40%);">+        Handle notification-rejection-v1 TRAP: just an example to show how more message types can be handled</span><br><span style="color: hsl(120, 100%, 40%);">+        """</span><br><span style="color: hsl(120, 100%, 40%);">+        self.factory.log.debug('notification-rejection-v1@bsc-id %s => %s' % (bsc, data))</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> class TrapFactory(IPAFactory):</span><br><span>     """</span><br><span>     Store SOAP client object so TRAP handler can use it for requests</span><br><span>@@ -75,12 +131,6 @@</span><br><span>         self.log.setLevel(level)</span><br><span>         self.log.debug("Using IPA %s, SUDS client: %s" % (Ctrl.version, soap))</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-    def prepare_params(bsc, lon, lat, fix, tstamp, oper, admin, policy):</span><br><span style="color: hsl(0, 100%, 40%);">-        ctx = self.factory.client.registerSiteLocation(bsc, float(lon), float(lat), fix, tstamp, oper, admin, policy)</span><br><span style="color: hsl(0, 100%, 40%);">-        d = post(self.factory.location, ctx.envelope)</span><br><span style="color: hsl(0, 100%, 40%);">-         # treq's collect helper is handy to get all reply content at once using closure on ctx:</span><br><span style="color: hsl(0, 100%, 40%);">-        d.addCallback(collect, partial(handle_reply, ctx.process_reply, self.transport.write, self.factory.log))</span><br><span style="color: hsl(0, 100%, 40%);">-        return d</span><br><span> </span><br><span> if __name__ == '__main__':</span><br><span>     p = argparse.ArgumentParser(description='Proxy between given SOAP service and Osmocom CTRL protocol.')</span><br><span></span><br></pre><p>To view, visit <a href="https://gerrit.osmocom.org/11952">change 11952</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/11952"/><meta itemprop="name" content="View Change"/></div></div>

<div style="display:none"> Gerrit-Project: python/osmo-python-tests </div>
<div style="display:none"> Gerrit-Branch: master </div>
<div style="display:none"> Gerrit-MessageType: merged </div>
<div style="display:none"> Gerrit-Change-Id: I8a3c62bcdf4286f8127c5b6d8dee6d740aca23b9 </div>
<div style="display:none"> Gerrit-Change-Number: 11952 </div>
<div style="display:none"> Gerrit-PatchSet: 2 </div>
<div style="display:none"> Gerrit-Owner: Max <msuraev@sysmocom.de> </div>
<div style="display:none"> Gerrit-Reviewer: Harald Welte <laforge@gnumonks.org> </div>
<div style="display:none"> Gerrit-Reviewer: Jenkins Builder (1000002) </div>
<div style="display:none"> Gerrit-Reviewer: Max <msuraev@sysmocom.de> </div>
<div style="display:none"> Gerrit-Reviewer: Pau Espin Pedrol <pespin@sysmocom.de> </div>
<div style="display:none"> Gerrit-Reviewer: daniel <dwillmann@sysmocom.de> </div>