Skip to content

Commit

Permalink
Merge pull request #96 from jookies/v0.6-beta
Browse files Browse the repository at this point in the history
V0.6 beta
  • Loading branch information
farirat committed Feb 24, 2015
2 parents d5e55ad + d408767 commit bd0e1ca
Show file tree
Hide file tree
Showing 91 changed files with 8,473 additions and 2,272 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ python:
# Command to install dependencies
install:
- python setup.py sdist
- sudo pip install dist/jasmin-0.5.4-beta.tar.gz
- sudo pip install dist/jasmin-0.6b0.tar.gz
# Commands to run tests:
script:
# Add jasmind to system autostartup:
Expand Down
8 changes: 5 additions & 3 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,11 @@ Change Log
0.6 (*under development*)
=========================

* SMPP Server API
* Better User credentials: expiry and QoS
* Easier installation procedure through Linux packages
* SMPP Server API #49
* Basic statistics in user-level #77
* Delivery retrial on specific/configurable errors #60
* Better User credentials: expiry and QoS #50, #51
* Easier installation procedure through Linux packages #78

0.5
===
Expand Down
1 change: 0 additions & 1 deletion install-requirements
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
enum>=0.4.4
pyasn1>=0.1.7
txAMQP>=0.6.2
txredisapi>=1.0
Expand Down
9 changes: 5 additions & 4 deletions jasmin/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,16 @@
"""Jasmin SMS Gateway by Fourat ZOUARI <fourat@gmail.com>"""

MAJOR = 0
MINOR = 5
PATCH = 4
META = 'beta'
MINOR = 6
PATCH = 0
META = 'b'

def get_version():
return '%s.%s' % (MAJOR, MINOR)

def get_release():
return '%s.%s.%s-%s' % (MAJOR, MINOR, PATCH, META)
"PEP 440 format"
return '%s.%s%s%s' % (MAJOR, MINOR, META, PATCH)

__version__ = get_version()
__release__ = get_release()
84 changes: 68 additions & 16 deletions jasmin/bin/jasmind.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,15 @@
from jasmin.managers.configs import SMPPClientPBConfig
from jasmin.queues.configs import AmqpConfig
from jasmin.queues.factory import AmqpFactory
from jasmin.protocols.smpp.configs import SMPPServerConfig
from jasmin.protocols.smpp.factory import SMPPServerFactory
from jasmin.protocols.http.configs import HTTPApiConfig
from jasmin.protocols.http.server import HTTPApi
from jasmin.tools.cred.portal import SmppsRealm
from jasmin.tools.cred.checkers import RouterAuthChecker
from jasmin.routing.router import RouterPB
from jasmin.routing.configs import RouterPBConfig, deliverSmHttpThrowerConfig, DLRThrowerConfig
from jasmin.routing.throwers import deliverSmHttpThrower, DLRThrower
from jasmin.routing.configs import RouterPBConfig, deliverSmThrowerConfig, DLRThrowerConfig
from jasmin.routing.throwers import deliverSmThrower, DLRThrower
from jasmin.redis.configs import RedisForJasminConfig
from jasmin.redis.client import ConnectionWithConfiguration
from jasmin.protocols.cli.factory import JCliFactory
Expand All @@ -32,6 +36,8 @@ class Options(usage.Options):
'jCli username used to load configuration profile on startup'],
['password', 'p', None,
'jCli password used to load configuration profile on startup'],
['enable-smpp-server', None, True,
'Start SMPP Server service'],
['enable-dlr-thrower', None, True,
'Start DLR Thrower service'],
['enable-deliver-thrower', None, True,
Expand Down Expand Up @@ -138,18 +144,51 @@ def stopSMPPClientManagerPBService(self):
"Stop SMPP Client Manager PB server"
return self.components['smppcm-pb-server'].stopListening()

def startDeliverSmHttpThrowerService(self):
"Start deliverSmHttpThrower"
def startSMPPServerService(self):
"Start SMPP Server"

SMPPServerConfigInstance = SMPPServerConfig(self.options['config'])

# Set authentication portal
p = portal.Portal(
SmppsRealm(
SMPPServerConfigInstance.id,
self.components['router-pb-factory'],
)
)
p.registerChecker(RouterAuthChecker(self.components['router-pb-factory']))

# SMPPServerFactory init
self.components['smpp-server-factory'] = SMPPServerFactory(
SMPPServerConfigInstance,
auth_portal = p,
RouterPB = self.components['router-pb-factory'],
SMPPClientManagerPB = self.components['smppcm-pb-factory'],
)

# Start server
self.components['smpp-server'] = reactor.listenTCP(SMPPServerConfigInstance.port,
self.components['smpp-server-factory'],
interface = SMPPServerConfigInstance.bind
)

def stopSMPPServerService(self):
"Stop SMPP Server"
return self.components['smpp-server'].stopListening()

def startdeliverSmThrowerService(self):
"Start deliverSmThrower"

deliverThrowerConfigInstance = deliverSmHttpThrowerConfig(self.options['config'])
self.components['deliversm-thrower'] = deliverSmHttpThrower()
deliverThrowerConfigInstance = deliverSmThrowerConfig(self.options['config'])
self.components['deliversm-thrower'] = deliverSmThrower()
self.components['deliversm-thrower'].setConfig(deliverThrowerConfigInstance)
self.components['deliversm-thrower'].addSmpps(self.components['smpp-server-factory'])

# AMQP Broker is used to listen to deliver_sm queue
return self.components['deliversm-thrower'].addAmqpBroker(self.components['amqp-broker-factory'])

def stopDeliverSmHttpThrowerService(self):
"Stop deliverSmHttpThrower"
def stopdeliverSmThrowerService(self):
"Stop deliverSmThrower"
return self.components['deliversm-thrower'].stopService()

def startDLRThrowerService(self):
Expand All @@ -158,6 +197,7 @@ def startDLRThrowerService(self):
DLRThrowerConfigInstance = DLRThrowerConfig(self.options['config'])
self.components['dlr-thrower'] = DLRThrower()
self.components['dlr-thrower'].setConfig(DLRThrowerConfigInstance)
self.components['dlr-thrower'].addSmpps(self.components['smpp-server-factory'])

# AMQP Broker is used to listen to DLRThrower queue
return self.components['dlr-thrower'].addAmqpBroker(self.components['amqp-broker-factory'])
Expand All @@ -174,9 +214,9 @@ def startHTTPApiService(self):

self.components['http-api-server'] = reactor.listenTCP(httpApiConfigInstance.port,
server.Site(httpApi_f,
logPath=httpApiConfigInstance.access_log
logPath = httpApiConfigInstance.access_log
),
interface=httpApiConfigInstance.bind
interface = httpApiConfigInstance.bind
)

def stopHTTPApiService(self):
Expand Down Expand Up @@ -221,12 +261,20 @@ def start(self):
syslog.syslog(syslog.LOG_LOCAL0, " RouterPB Started.")

########################################################
# [optional] Start deliverSmHttpThrower
# [optional] Start SMPP Server
if (self.options['enable-smpp-server'] == True or
(type(self.options['enable-smpp-server']) == str and
self.options['enable-smpp-server'].lower() == 'true')):
self.startSMPPServerService()
syslog.syslog(syslog.LOG_LOCAL0, " SMPPServer Started.")

########################################################
# [optional] Start deliverSmThrower
if (self.options['enable-deliver-thrower'] == True or
(type(self.options['enable-deliver-thrower']) == str and
self.options['enable-deliver-thrower'].lower() == 'true')):
yield self.startDeliverSmHttpThrowerService()
syslog.syslog(syslog.LOG_LOCAL0, " DeliverSmHttpThrower Started.")
yield self.startdeliverSmThrowerService()
syslog.syslog(syslog.LOG_LOCAL0, " deliverSmThrower Started.")

########################################################
# [optional] Start DLRThrower
Expand Down Expand Up @@ -269,9 +317,13 @@ def stop(self):
syslog.syslog(syslog.LOG_LOCAL0, " DLRThrower stopped.")

if 'deliversm-thrower' in self.components:
yield self.stopDeliverSmHttpThrowerService()
syslog.syslog(syslog.LOG_LOCAL0, " DeliverSmHttpThrower stopped.")
yield self.stopdeliverSmThrowerService()
syslog.syslog(syslog.LOG_LOCAL0, " deliverSmThrower stopped.")

if 'smpp-server' in self.components:
yield self.stopSMPPServerService()
syslog.syslog(syslog.LOG_LOCAL0, " SMPPServer stopped.")

if 'router-pb-server' in self.components:
yield self.stopRouterPBService()
syslog.syslog(syslog.LOG_LOCAL0, " RouterPB stopped.")
Expand All @@ -291,7 +343,7 @@ def stop(self):
reactor.stop()

def sighandler_stop(self, signum, frame):
syslog.syslog(syslog.LOG_LOCAL0, "Received signal to stop Jasmin Daemin")
syslog.syslog(syslog.LOG_LOCAL0, "Received signal to stop Jasmin Daemon")

return self.stop()

Expand Down
41 changes: 36 additions & 5 deletions jasmin/managers/clients.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@
import pickle
import uuid
import time
import datetime
from twisted.spread import pb
from twisted.internet import defer
from jasmin.protocols.smpp.services import SMPPClientService
from jasmin.managers.listeners import SMPPClientSMListener
from jasmin.managers.configs import SMPPClientSMListenerConfig
from jasmin.managers.content import SubmitSmContent
from jasmin.vendor.smpp.twisted.protocol import SMPPSessionStates
from jasmin.protocols.smpp.protocol import SMPPServerProtocol
from jasmin.vendor.smpp.pdu.pdu_types import RegisteredDeliveryReceipt

LOG_CATEGORY = "jasmin-pb-client-mgmt"

Expand Down Expand Up @@ -50,6 +53,7 @@ def setConfig(self, SMPPClientPBConfig):
self.config.log_date_format)
handler.setFormatter(formatter)
self.log.addHandler(handler)
self.log.propagate = False

# Set pickleProtocol
self.pickleProtocol = self.config.pickle_protocol
Expand Down Expand Up @@ -509,7 +513,8 @@ def setKeyExpiry(self, callbackArg, key, expiry):

@defer.inlineCallbacks
def perspective_submit_sm(self, cid, SubmitSmPDU, priority = 1, validity_period = None, pickled = True,
dlr_url = None, dlr_level = 1, dlr_method = 'POST', submit_sm_resp_bill = None):
dlr_url = None, dlr_level = 1, dlr_method = 'POST', submit_sm_resp_bill = None,
source_connector = 'httpapi'):
"""This will enqueue a submit_sm to a connector
"""

Expand Down Expand Up @@ -541,16 +546,19 @@ def perspective_submit_sm(self, cid, SubmitSmPDU, priority = 1, validity_period

# Pickle SubmitSmPDU if it's not pickled
if not pickled:
SubmitSmPDU = pickle.dumps(SubmitSmPDU, self.pickleProtocol)
PickledSubmitSmPDU = pickle.dumps(SubmitSmPDU, self.pickleProtocol)
submit_sm_resp_bill = pickle.dumps(submit_sm_resp_bill, self.pickleProtocol)
else:
PickledSubmitSmPDU = SubmitSmPDU
SubmitSmPDU = pickle.loads(PickledSubmitSmPDU)

# Publishing a pickled PDU
self.log.info('Publishing SubmitSmPDU with routing_key=%s, priority=%s' % (pubQueueName, priority))
c = SubmitSmContent(SubmitSmPDU, responseQueueName, priority, validity_period, submit_sm_resp_bill = submit_sm_resp_bill)
c = SubmitSmContent(PickledSubmitSmPDU, responseQueueName, priority, validity_period, submit_sm_resp_bill = submit_sm_resp_bill)
yield self.amqpBroker.publish(exchange='messaging', routing_key=pubQueueName, content=c)

# Enqueue DLR request
if dlr_url is not None:
if source_connector == 'httpapi' and dlr_url is not None:
# Enqueue DLR request in redis 'dlr' key if it is a httpapi request
if self.redisClient is None or str(self.redisClient) == '<Redis Connection: Not connected>':
self.log.warn("DLR is not enqueued for SubmitSmPDU [msgid:%s], RC is not connected." %
c.properties['message-id'])
Expand All @@ -568,5 +576,28 @@ def perspective_submit_sm(self, cid, SubmitSmPDU, priority = 1, validity_period
'expiry':connector['config'].dlr_expiry}
self.redisClient.set(hashKey, pickle.dumps(hashValues, self.pickleProtocol)).addCallback(
self.setKeyExpiry, hashKey, connector['config'].dlr_expiry)
elif (isinstance(source_connector, SMPPServerProtocol)
and SubmitSmPDU.params['registered_delivery'].receipt != RegisteredDeliveryReceipt.NO_SMSC_DELIVERY_RECEIPT_REQUESTED):
# If submit_sm is successfully sent from a SMPPServerProtocol connector and DLR is
# requested, then map message-id to the source_connector to permit related deliver_sm
# messages holding further receipts to be sent back to the right connector
if self.redisClient is None or str(self.redisClient) == '<Redis Connection: Not connected>':
self.log.warn("SMPPs mapping is not done for SubmitSmPDU [msgid:%s], RC is not connected." %
c.properties['message-id'])
else:
self.log.debug('Setting SMPPs connector (%s) mapping for message id:%s, registered_dlr: %s, expiring in %s' %
(source_connector.system_id,
c.properties['message-id'],
SubmitSmPDU.params['registered_delivery'],
source_connector.factory.config.dlr_expiry))
# Set values and callback expiration setting
hashKey = "smppsmap:%s" % (c.properties['message-id'])
hashValues = {'system_id': source_connector.system_id,
'source_addr': SubmitSmPDU.params['source_addr'],
'destination_addr': SubmitSmPDU.params['destination_addr'],
'registered_delivery': SubmitSmPDU.params['registered_delivery'],
'expiry': source_connector.factory.config.dlr_expiry}
self.redisClient.set(hashKey, pickle.dumps(hashValues, self.pickleProtocol)).addCallback(
self.setKeyExpiry, hashKey, source_connector.factory.config.dlr_expiry)

defer.returnValue(c.properties['message-id'])
51 changes: 43 additions & 8 deletions jasmin/managers/content.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,9 @@ def __init__(self, body = "", children = None, properties = None, pickleProtocol

Content.__init__(self, body, children, properties)

class DLRContent(Content):
"A DLR Content holding information about the origin SubmitSm and receipt acknowledgment details"
class DLRContentForHttpapi(Content):
"""A DLR Content holding information about the origin SubmitSm sent from httpapi and
receipt acknowledgment details"""

def __init__(self, message_status, msgid, dlr_url, dlr_level, id_smsc = '', sub = '',
dlvrd = '', subdate = '', donedate = '', err = '', text = '', method = 'POST', trycount = 0):
Expand All @@ -39,7 +40,7 @@ def __init__(self, message_status, msgid, dlr_url, dlr_level, id_smsc = '', sub
# ESME_* statuses are returned from SubmitSmResp
# Others are returned from DeliverSm, values must be the same as Table B-2
if message_status[:5] != 'ESME_' and message_status not in ['DELIVRD', 'EXPIRED', 'DELETED',
'UNDELIV', 'ACCEPTED', 'UNKNOWN', 'REJECTD']:
'UNDELIV', 'ACCEPTD', 'UNKNOWN', 'REJECTD']:
raise InvalidParameterError("Invalid message_status: %s", message_status)
if dlr_level not in [1, 2, 3]:
raise InvalidParameterError("Invalid dlr_level: %s", dlr_level)
Expand All @@ -62,6 +63,28 @@ def __init__(self, message_status, msgid, dlr_url, dlr_level, id_smsc = '', sub

Content.__init__(self, msgid, properties = properties)

class DLRContentForSmpps(Content):
"""A DLR Content holding information about the origin SubmitSm sent from smpps and
receipt acknowledgment details"""

def __init__(self, message_status, msgid, system_id, source_addr, destination_addr):
properties = {}

# ESME_* statuses are returned from SubmitSmResp
# Others are returned from DeliverSm, values must be the same as Table B-2
if message_status[:5] != 'ESME_' and message_status not in ['DELIVRD', 'EXPIRED', 'DELETED',
'UNDELIV', 'ACCEPTD', 'UNKNOWN', 'REJECTD']:
raise InvalidParameterError("Invalid message_status: %s", message_status)

properties['message-id'] = msgid
properties['headers'] = {'try-count': 0,
'message_status': message_status,
'system_id': system_id,
'source_addr': source_addr,
'destination_addr': destination_addr}

Content.__init__(self, msgid, properties = properties)

class SubmitSmContent(PDU):
"A SMPP SubmitSm Content"

Expand Down Expand Up @@ -96,22 +119,34 @@ def __init__(self, body, msgid, pickleProtocol = 2, prePickle = True):
props = {}

props['message-id'] = msgid
PDU.__init__(self, body, properties = props, pickleProtocol = pickleProtocol, prePickle = prePickle)
PDU.__init__(self,
body,
properties = props,
pickleProtocol = pickleProtocol,
prePickle = prePickle)

class DeliverSmContent(PDU):
"A SMPP DeliverSm Content"

def __init__(self, body, sourceCid, pickleProtocol = 2, prePickle = True):
def __init__(self, body, sourceCid, pickleProtocol = 2, prePickle = True,
concatenated = False, will_be_concatenated = False):
props = {}

props['message-id'] = randomUniqueId()

# For routing purpose, connector-id indicates the source connector of the PDU
# the connector-id is used to instanciate RoutableDeliverSm when checking for
# routes
props['headers'] = {'connector-id': sourceCid}

PDU.__init__(self, body, properties = props, pickleProtocol = pickleProtocol, prePickle = prePickle)
props['headers'] = {'try-count': 0,
'connector-id': sourceCid,
'concatenated': concatenated,
'will_be_concatenated': will_be_concatenated}

PDU.__init__(self,
body,
properties = props,
pickleProtocol = pickleProtocol,
prePickle = prePickle)

class SubmitSmRespBillContent(Content):
"A Bill Content holding amount to be charged to user (uid)"
Expand Down

0 comments on commit bd0e1ca

Please sign in to comment.