Developer FAQ

How to ‘log’ messages in a third party database ?

Jasmin runs without a database, everything is in-memory and messages are exchanged through AMQP broker (RabbitMQ), if you need to get these messages you have to consume from the right queues as described in Messaging flows.

Here’s an example:

Thanks to Pedro‘s contribution:

Here is the PySQLPool mod to @farirat ´s gist
https://gist.github.com/pguillem/5750e8db352f001138f2

Here is the code to launch the consumer as a system Daemon in Debian/Ubuntu
https://gist.github.com/pguillem/19693defb3feb0c02fe7

1) create jasmind_consumer file in /etc/init.d/
2) chmod a+x
3) Modify the path and script name of your consumer in jasmind_consumer
4) Remember to exec "update-rc.d jasmind_consumer defaults" in order to start at boot

Cheers
Pedro

More on this:

# Gist from https://gist.github.com/farirat/5701d71bf6e404d17cb4
import cPickle as pickle
from twisted.internet.defer import inlineCallbacks
from twisted.internet import reactor
from twisted.internet.protocol import ClientCreator
from twisted.python import log

from txamqp.protocol import AMQClient
from txamqp.client import TwistedDelegate

import txamqp.spec

@inlineCallbacks
def gotConnection(conn, username, password):
    print "Connected to broker."
    yield conn.authenticate(username, password)

    print "Authenticated. Ready to receive messages"
    chan = yield conn.channel(1)
    yield chan.channel_open()

    yield chan.queue_declare(queue="someQueueName")

    # Bind to submit.sm.* and submit.sm.resp.* routes
    yield chan.queue_bind(queue="someQueueName", exchange="messaging", routing_key='submit.sm.*')
    yield chan.queue_bind(queue="someQueueName", exchange="messaging", routing_key='submit.sm.resp.*')

    yield chan.basic_consume(queue='someQueueName', no_ack=True, consumer_tag="someTag")
    queue = yield conn.queue("someTag")

    # Wait for messages
    # This can be done through a callback ...
    while True:
        msg = yield queue.get()
        props = msg.content.properties
        pdu = pickle.loads(msg.content.body)

    	if msg.routing_key[:15] == 'submit.sm.resp.':
    		print 'SubmitSMResp: status: %s, msgid: %s' % (pdu.status,
    			props['message-id'])
        elif msg.routing_key[:10] == 'submit.sm.':
        	print 'SubmitSM: from %s to %s, content: %s, msgid: %s' % (pdu.params['source_addr'],
        		pdu.params['destination_addr'],
        		pdu.params['short_message'],
        		props['message-id'])
    	else:
    		print 'unknown route'

    # A clean way to tear down and stop
    yield chan.basic_cancel("someTag")
    yield chan.channel_close()
    chan0 = yield conn.channel(0)
    yield chan0.connection_close()

    reactor.stop()


if __name__ == "__main__":
    """
    This example will connect to RabbitMQ broker and consume from two route keys:
      - submit.sm.*: All messages sent through SMPP Connectors
      - submit.sm.resp.*: More relevant than SubmitSM because it contains the sending status

    Note:
      - Messages consumed from submit.sm.resp.* are not verbose enough, they contain only message-id and status
      - Message content can be obtained from submit.sm.*, the message-id will be the same when consuming from submit.sm.resp.*,
        it is used for mapping.
      - Billing information is contained in messages consumed from submit.sm.*
      - This is a proof of concept, saying anyone can consume from any topic in Jasmin's exchange hack a
        third party business, more information here: http://docs.jasminsms.com/en/latest/messaging/index.html
    """

    host = '127.0.0.1'
    port = 5672
    vhost = '/'
    username = 'guest'
    password = 'guest'
    spec_file = '/etc/jasmin/resource/amqp0-9-1.xml'

    spec = txamqp.spec.load(spec_file)

    # Connect and authenticate
    d = ClientCreator(reactor,
    	AMQClient,
    	delegate=TwistedDelegate(),
    	vhost=vhost,
        spec=spec).connectTCP(host, port)
    d.addCallback(gotConnection, username, password)

    def whoops(err):
        if reactor.running:
            log.err(err)
            reactor.stop()

    d.addErrback(whoops)

    reactor.run()

How to directly access the Perspective Broker API ?

Management tasks can be done directly when accessing PerspectiveBroker API, it will be possible to:

  • Manage SMPP Client connectors,
  • Check status of all connectors,
  • Send SMS,
  • Manage Users & Groups,
  • Manage Routes (MO / MT),
  • Access statistics,
  • ...

Here’s an example:

# Gist from https://gist.github.com/farirat/922e1cb2c4782660c257
"""
An example of scenario with the following actions:
 1. Add and start a SMPP Client connector
 2. Provision a DefaultRoute to that connector
 3. Provision a User

This is a demonstration of using PB (PerspectiveBroker) API to gain control Jasmin.

The jasmin SMS gateway shall be already running and having
a pb listening on 8989.
"""

import cPickle as pickle
from twisted.internet import reactor, defer
from jasmin.managers.proxies import SMPPClientManagerPBProxy
from jasmin.routing.proxies import RouterPBProxy
from jasmin.routing.Routes import DefaultRoute
from jasmin.routing.jasminApi import User, Group
from jasmin.protocols.smpp.configs import SMPPClientConfig
from jasmin.protocols.cli.smppccm import JCliSMPPClientConfig as SmppClientConnector
from twisted.web.client import getPage

@defer.inlineCallbacks
def runScenario():
    try:
        ## First part, SMPP Client connector management
        ###############################################
        # Connect to SMPP Client management PB proxy
        proxy_smpp = SMPPClientManagerPBProxy()
        yield proxy_smpp.connect('127.0.0.1', 8989, 'cmadmin', 'cmpwd')

        # Provision SMPPClientManagerPBProxy with a connector and start it
        connector1 = {'id':'abc', 'username':'smppclient1', 'reconnectOnConnectionFailure':True}
        config1 = SMPPClientConfig(**connector1)
        yield proxy_smpp.add(config1)
        yield proxy_smpp.start('abc')

        ## Second part, User and Routing management
        ###########################################
        # Connect to Router PB proxy
        proxy_router = RouterPBProxy()
        yield proxy_router.connect('127.0.0.1', 8988, 'radmin', 'rpwd')

        # Provision RouterPBProxy with MT routes
        yield proxy_router.mtroute_add(DefaultRoute(SmppClientConnector('abc')), 0)
        routes = yield proxy_router.mtroute_get_all()
        print "Configured routes: \n\t%s" % pickle.loads(routes)

        # Provisiong router with users
        g1 = Group(1)
        u1 = User(uid = 1, group = g1, username = 'foo', password = 'bar')
        yield proxy_router.group_add(g1)
        yield proxy_router.user_add(u1)
        users = yield proxy_router.user_get_all()
        print "Users: \n\t%s" % pickle.loads(users)

        ## Last, tear down
        ##################
        # Stop connector
        yield proxy_smpp.stop('abc')
    except Exception, e:
        print "ERROR RUNNING SCENARIO: %s" % str(e)
    finally:
        reactor.stop()

runScenario()
reactor.run()

Can you provide an example of how to use EvalPyFilter ?

Let’s say you need your filter to pass only messages from username foo:

if routable.user.username == 'foo':
    result = False
else:
    result = True

Note

Although UserFilter is already there to provide this feature, this is just a simple example of using EvalPyFilter.

So your python script will have a routable global variable, it is an instance of RoutableDeliverSm if you’re playing with a MO Route and it will be an instance of RoutableSubmitSm if you’re considering it with a MT Route.

In order to implement your specific filter, you have to know all the attributes these objects are providing,

Now let’s make an advanced example, the below filter will:

  • Connect to a database
  • Check if the message destination_address is in blacklisted_numbers table
  • Pass only if the destination_address is not blacklisted
"""This is an example of using EvalPyFilter with a database interrogation, it is written
for demonstration purpose only.
"""
import MySQLdb as mdb

destination_addr = routable.pdu.params['destination_addr']

try:
	con = mdb.connect('localhost', 'jasmin', 'somepassword', 'jasmin_faq');

	cur = con.cursor()
	cur.execute("SELECT COUNT(msisdn) FROM blacklisted_numbers WHERE msisdn = %s" % destination_addr)
	count = cur.fetchone()
	
	if count[0] == 0:
		# It is not blacklisted, filter will pass
		result = True
except mdb.Error, e:
	# A DB error, filter will block
	# Error can be logged as well ...
	result = False
finally:
	# Filter will block for any other exception / reason
	result = False

How to log events inside an EvalPyFilter ?

It is a usual method to get the filter logging directly to the Router’s log file (default is router.log), here’s a very simple example of doing it:

import logging

log = logging.getLogger("jasmin-router")

log.debug('Inside evalpy-test.py')
if routable.user.username == 'Evalpyusr2':
    log.info("Routable's username is Evalpyusr2 !")
    result = False
else:
    log.info("Routable's username is not Evalpyusr2: %s" % routable.user.username)
    result = True

Note

More on python logging: here.

How to set an EvalPyFilter for a MT Route ?

I have written my EvalPyFilter, how can i use it to filter MT messages ?

Using jCli:

First, create your filter:

jcli : filter -a
Adding a new Filter: (ok: save, ko: exit)
> type evalpyfilter
> pyCode /some/path/advanced_evalpyfilter.py
> fid blacklist_check
> ok
Successfully added Filter [EvalPyFilter] with fid:blacklist_check

Second, create a MT Route:

jcli : mtrouter -a
Adding a new MT Route: (ok: save, ko: exit)
> type StaticMTRoute
jasmin.routing.Routes.StaticMTRoute arguments:
filters, connector, rate
> filters blacklist_check
> connector smppc(SOME-SMSC)
> rate 0.0
> order 10
> ok
Successfully added MTRoute [StaticMTRoute] with order:10

And you’re done ! test your filter by sending a SMS through Jasmin’s APIs.

PDU params keep resetting to connector defaults even after interception ?

When sending MT messages through httpapi, some pdu parameters will be reset to connector defaults even if they were manually updated inside an interceptor script, how can Jasmin avoid updatingmy pdu params ?

After updating a pdu parameter, it must be locked so Jasmin will not re-update it again, here’s an example:

# Set pdu param:
routable.pdu.params['sm_default_msg_id'] = 10
# Lock it:
routable.lockPduParam('sm_default_msg_id')

Note

Locking pdu parameters is only needed when message is pushed from httpapi.