Move Trap class back to separate files

After further testing it turns out that Trap() have too many
implementation details which makes it cumbersome to be shared. Instead,
it's easier to make it into wrapper over shared functions.

Change-Id: I8a3c62bcdf4286f8127c5b6d8dee6d740aca23b9
This commit is contained in:
Max 2018-11-27 17:42:07 +01:00
parent e18d0661b5
commit a07e55335d
2 changed files with 114 additions and 15 deletions

View File

@ -22,14 +22,14 @@
*/
"""
__version__ = "0.0.1" # bump this on every non-trivial change
__version__ = "0.0.2" # bump this on every non-trivial change
from twisted.internet import defer, reactor
from osmopy.twisted_ipa import CTRL, IPAFactory, __version__ as twisted_ipa_version
from osmopy.osmo_ipa import Ctrl
from treq import post, collect
from functools import partial
from osmopy.trap_helper import Trap, reloader, debug_init
from osmopy.trap_helper import reloader, debug_init, get_type, get_r, p_h, make_params
from distutils.version import StrictVersion as V # FIXME: use NormalizedVersion from PEP-386 when available
import argparse, datetime, signal, sys, os, logging, logging.handlers
import hashlib
@ -75,6 +75,61 @@ def gen_hash(params, skey):
#print('HASH: \nparams="%r"\ninput="%s" \nres="%s"' %(params, input, res))
return res
class Trap(CTRL):
"""
TRAP handler (agnostic to factory's client object)
"""
def ctrl_TRAP(self, data, op_id, v):
"""
Parse CTRL TRAP and dispatch to appropriate handler after normalization
"""
self.factory.log.debug('TRAP %s' % v)
t_type = get_type(v)
p = p_h(v)
method = getattr(self, 'handle_' + t_type.replace('-', ''), lambda *_: "Unhandled %s trap" % t_type)
method(p(1), p(3), p(5), p(7), get_r(v))
def ctrl_SET_REPLY(self, data, _, v):
"""
Debug log for replies to our commands
"""
self.factory.log.debug('SET REPLY %s' % v)
def ctrl_ERROR(self, data, op_id, v):
"""
We want to know if smth went wrong
"""
self.factory.log.debug('CTRL ERROR [%s] %s' % (op_id, v))
def connectionMade(self):
"""
Logging wrapper, calling super() is necessary not to break reconnection logic
"""
self.factory.log.info("Connected to CTRL@%s:%d" % (self.factory.host, self.factory.port))
super(CTRL, self).connectionMade()
@defer.inlineCallbacks
def handle_locationstate(self, net, bsc, bts, trx, data):
"""
Handle location-state TRAP: parse trap content, build CGI Request and use treq's routines to post it while setting up async handlers
"""
params = make_params(bsc, data)
self.factory.log.debug('location-state@%s.%s.%s.%s (%s) => %s' % (net, bsc, bts, trx, params['time_stamp'], data))
params['h'] = gen_hash(params, self.factory.secret_key)
d = post(self.factory.location, None, params=params)
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
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
# Ensure that we run only limited number of requests in parallel:
yield self.factory.semaphore.acquire()
yield d # we end up here only if semaphore is available which means it's ok to fire the request without exceeding the limit
self.factory.semaphore.release()
def handle_notificationrejectionv1(self, net, bsc, bts, trx, data):
"""
Handle notification-rejection-v1 TRAP: just an example to show how more message types can be handled
"""
self.factory.log.debug('notification-rejection-v1@bsc-id %s => %s' % (bsc, data))
class TrapFactory(IPAFactory):
"""
@ -100,12 +155,6 @@ class TrapFactory(IPAFactory):
self.log.setLevel(level)
self.log.debug("Using IPA %s, CGI server: %s" % (Ctrl.version, self.location))
def prepare_params(bsc, lon, lat, fix, tstamp, oper, admin, policy):
params = {'bsc_id': bsc, 'lon': lon, 'lat': lat, 'position_validity': fix, 'time_stamp': tstamp, 'oper_status': oper, 'admin_status': admin, 'policy_status': policy }
params['h'] = gen_hash(params, self.factory.secret_key)
d = post(self.factory.location, None, params=params)
d.addCallback(partial(handle_reply, self.transport.write, self.factory.log))
return d
if __name__ == '__main__':
p = argparse.ArgumentParser(description='Proxy between given GCI service and Osmocom CTRL protocol.')

View File

@ -30,7 +30,7 @@ from osmopy.osmo_ipa import Ctrl
from treq import post, collect
from suds.client import Client
from functools import partial
from osmopy.trap_helper import Trap, reloader, debug_init
from osmopy.trap_helper import reloader, debug_init, get_type, get_r, p_h, make_params
from distutils.version import StrictVersion as V # FIXME: use NormalizedVersion from PEP-386 when available
import argparse, datetime, signal, sys, os, logging, logging.handlers
@ -51,6 +51,62 @@ def handle_reply(p, f, log, r):
f(m)
class Trap(CTRL):
"""
TRAP handler (agnostic to factory's client object)
"""
def ctrl_TRAP(self, data, op_id, v):
"""
Parse CTRL TRAP and dispatch to appropriate handler after normalization
"""
self.factory.log.debug('TRAP %s' % v)
t_type = get_type(v)
p = p_h(v)
method = getattr(self, 'handle_' + t_type.replace('-', ''), lambda *_: "Unhandled %s trap" % t_type)
method(p(1), p(3), p(5), p(7), get_r(v))
def ctrl_SET_REPLY(self, data, _, v):
"""
Debug log for replies to our commands
"""
self.factory.log.debug('SET REPLY %s' % v)
def ctrl_ERROR(self, data, op_id, v):
"""
We want to know if smth went wrong
"""
self.factory.log.debug('CTRL ERROR [%s] %s' % (op_id, v))
def connectionMade(self):
"""
Logging wrapper, calling super() is necessary not to break reconnection logic
"""
self.factory.log.info("Connected to CTRL@%s:%d" % (self.factory.host, self.factory.port))
super(CTRL, self).connectionMade()
@defer.inlineCallbacks
def handle_locationstate(self, net, bsc, bts, trx, data):
"""
Handle location-state TRAP: parse trap content, build SOAP context and use treq's routines to post it while setting up async handlers
"""
params = make_params(bsc, data)
self.factory.log.debug('location-state@%s.%s.%s.%s (%s) => %s' % (net, bsc, bts, trx, params['time_stamp'], data))
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'])
d = post(self.factory.location, ctx.envelope)
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
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
# Ensure that we run only limited number of requests in parallel:
yield self.factory.semaphore.acquire()
yield d # we end up here only if semaphore is available which means it's ok to fire the request without exceeding the limit
self.factory.semaphore.release()
def handle_notificationrejectionv1(self, net, bsc, bts, trx, data):
"""
Handle notification-rejection-v1 TRAP: just an example to show how more message types can be handled
"""
self.factory.log.debug('notification-rejection-v1@bsc-id %s => %s' % (bsc, data))
class TrapFactory(IPAFactory):
"""
Store SOAP client object so TRAP handler can use it for requests
@ -75,12 +131,6 @@ class TrapFactory(IPAFactory):
self.log.setLevel(level)
self.log.debug("Using IPA %s, SUDS client: %s" % (Ctrl.version, soap))
def prepare_params(bsc, lon, lat, fix, tstamp, oper, admin, policy):
ctx = self.factory.client.registerSiteLocation(bsc, float(lon), float(lat), fix, tstamp, oper, admin, policy)
d = post(self.factory.location, ctx.envelope)
# treq's collect helper is handy to get all reply content at once using closure on ctx:
d.addCallback(collect, partial(handle_reply, ctx.process_reply, self.transport.write, self.factory.log))
return d
if __name__ == '__main__':
p = argparse.ArgumentParser(description='Proxy between given SOAP service and Osmocom CTRL protocol.')