Skip to content

Commit

Permalink
Merge pull request #155 from zoufou/v0.6-beta
Browse files Browse the repository at this point in the history
v0.6b15
  • Loading branch information
farirat committed May 20, 2015
2 parents 0bf6b92 + fe69458 commit 45b9509
Show file tree
Hide file tree
Showing 21 changed files with 713 additions and 43 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.6b14.tar.gz
- sudo pip install dist/jasmin-0.6b15.tar.gz
# Commands to run tests:
script:
# Add jasmind to system autostartup:
Expand Down
1 change: 1 addition & 0 deletions install-requirements
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ twisted>=13.0.0
pyparsing>=2.0.0
python-dateutil>=2.0
service_identity>=14.0.0
tabulate>=0.7.5
2 changes: 1 addition & 1 deletion jasmin/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

MAJOR = 0
MINOR = 6
PATCH = 14
PATCH = 15
META = 'b'

def get_version():
Expand Down
6 changes: 3 additions & 3 deletions jasmin/managers/clients.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
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 .listeners import SMPPClientSMListener
from .configs import SMPPClientSMListenerConfig
from .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
Expand Down
8 changes: 4 additions & 4 deletions jasmin/protocols/cli/jcli.py
Original file line number Diff line number Diff line change
Expand Up @@ -345,19 +345,19 @@ def do_load(self, arg, opts):
self.sendData()

@options([make_option(None, '--user', type="string", metavar="UID",
help = "Show user stats"),
help = "Show user stats using it's UID"),
make_option(None, '--users', action="store_true",
help = "Show all users stats"),
make_option(None, '--smppc', type="string", metavar="CID",
help = "Show smpp connector stats"),
help = "Show smpp connector stats using it's CID"),
make_option(None, '--smppcs', action="store_true",
help = "Show all smpp connectors stats"),
make_option(None, '--moroute', type="string", metavar="ORDER",
help = "Show MO Route stats"),
help = "Show MO Route stats using it's ORDER"),
make_option(None, '--moroutes', action="store_true",
help = "Show all MO Routes stats"),
make_option(None, '--mtroute', type="string", metavar="ORDER",
help = "Show MT Route stats"),
help = "Show MT Route stats using it's ORDER"),
make_option(None, '--mtroutes', action="store_true",
help = "Show all MT Routes stats"),
make_option(None, '--httpapi', action="store_true",
Expand Down
100 changes: 96 additions & 4 deletions jasmin/protocols/cli/statsm.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,111 @@
import pickle
from jasmin.protocols.cli.managers import Manager
from jasmin.protocols.smpp.stats import SMPPClientStatsCollector
from .usersm import UserExist
from .smppccm import ConnectorExist
from tabulate import tabulate

def formatDateTime(dt):
if dt == 0:
return 'ND'
else:
return dt.strftime("%Y-%m-%d %H:%M:%S")

class StatsManager(Manager):
managerName = 'stats'

@UserExist(uid_key='user')
def user(self, arg, opts):
return self.protocol.sendData('Not implemented yet.')
headers = ["#Item", "Type", "Value"]

table = []

user = self.pb['router'].getUser(opts.user)
# SMPP Server stats
for k,v in user.CnxStatus.smpps.iteritems():
row = []
row.append('#%s' % k)
row.append('SMPP Server')
if k[-3:] == '_at':
row.append(formatDateTime(v))
else:
row.append(v)

table.append(row)

# HTTP API stats
for k,v in user.CnxStatus.httpapi.iteritems():
row = []
row.append('#%s' % k)
row.append('HTTP Api')
if k[-3:] == '_at':
row.append(formatDateTime(v))
else:
row.append(v)

table.append(row)

self.protocol.sendData(tabulate(table, headers, tablefmt = "plain", numalign = "left").encode('ascii'))

def users(self, arg, opts):
return self.protocol.sendData('Not implemented yet.')
headers = ["#User id", "SMPP Bound connections", "SMPP L.A.", "HTTP requests counter", "HTTP L.A."]

table = []
users = pickle.loads(self.pb['router'].perspective_user_get_all(None))
for user in users:
row = []
row.append('#%s' % user.uid)
row.append(user.CnxStatus.smpps['bound_connections_count']['bind_receiver'] +
user.CnxStatus.smpps['bound_connections_count']['bind_transmitter'] +
user.CnxStatus.smpps['bound_connections_count']['bind_transceiver']
)
row.append(formatDateTime(user.CnxStatus.smpps['last_activity_at']))
row.append(user.CnxStatus.httpapi['connects_count'])
row.append(formatDateTime(user.CnxStatus.httpapi['last_activity_at']))

table.append(row)

self.protocol.sendData(tabulate(table, headers, tablefmt = "plain", numalign = "left").encode('ascii'), prompt = False)
self.protocol.sendData('Total users: %s' % (len(table)))

@ConnectorExist(cid_key='smppc')
def smppc(self, arg, opts):
return self.protocol.sendData('Not implemented yet.')
sc = SMPPClientStatsCollector()
headers = ["#Item", "Value"]

table = []
for k, v in sc.get(opts.smppc)._stats.iteritems():
row = []
row.append('#%s' % k)
if k[-3:] == '_at':
row.append(formatDateTime(v))
else:
row.append(v)

table.append(row)

self.protocol.sendData(tabulate(table, headers, tablefmt = "plain", numalign = "left").encode('ascii'))

def smppcs(self, arg, opts):
return self.protocol.sendData('Not implemented yet.')
sc = SMPPClientStatsCollector()
headers = ["#Connector id", "Bound count", "Connected at", "Bound at", "Disconnected at", "Sent elink at", "Received elink at"]

table = []
connectors = self.pb['smppcm'].perspective_connector_list()
for connector in connectors:
row = []
row.append('#%s' % connector['id'])
row.append(sc.get(connector['id']).get('bound_count'))
row.append(formatDateTime(sc.get(connector['id']).get('connected_at')))
row.append(formatDateTime(sc.get(connector['id']).get('bound_at')))
row.append(formatDateTime(sc.get(connector['id']).get('disconnected_at')))
row.append(formatDateTime(sc.get(connector['id']).get('last_sent_elink_at')))
row.append(formatDateTime(sc.get(connector['id']).get('last_received_elink_at')))

table.append(row)

self.protocol.sendData(tabulate(table, headers, tablefmt = "plain", numalign = "left").encode('ascii'), prompt = False)
self.protocol.sendData('Total connectors: %s' % (len(table)))

def moroute(self, arg, opts):
return self.protocol.sendData('Not implemented yet.')
Expand Down
92 changes: 82 additions & 10 deletions jasmin/protocols/cli/test/test_statsm.py
Original file line number Diff line number Diff line change
@@ -1,30 +1,33 @@
import unittest
from twisted.internet import defer
from test_jcli import jCliWithoutAuthTestCases
from .test_userm import UserTestCases
from .test_smppccm import SmppccmTestCases

class BasicTestCases(jCliWithoutAuthTestCases):

@unittest.skip("Work in progress #123")

def test_user(self):
uid = 'foo'

commands = [{'command': 'stats --user=%s' % uid, 'expect': r'????'}]
commands = [{'command': 'stats --user=%s' % uid, 'expect': r'Unknown User: %s' % uid}]
return self._test(r'jcli : ', commands)

@unittest.skip("Work in progress #123")
def test_users(self):
commands = [{'command': 'stats --users', 'expect': r'????'}]
expectedList = ['#User id\s+SMPP Bound connections\s+SMPP L.A.\s+HTTP requests counter\s+HTTP L.A.',
'Total users: 0']
commands = [{'command': 'stats --users', 'expect': expectedList}]
return self._test(r'jcli : ', commands)

@unittest.skip("Work in progress #123")
def test_smppc(self):
cid = 'foo'

commands = [{'command': 'stats --smppc=%s' % cid, 'expect': r'????'}]
commands = [{'command': 'stats --smppc=%s' % cid, 'expect': r'Unknown connector: %s' % cid}]
return self._test(r'jcli : ', commands)

@unittest.skip("Work in progress #123")
def test_smppcs(self):
commands = [{'command': 'stats --smppcs', 'expect': r'????'}]
expectedList = ['#Connector id\s+Bound count\s+Connected at\s+Bound at\s+Disconnected at\s+Sent elink at\s+Received elink at',
'Total connectors: 0']
commands = [{'command': 'stats --smppcs', 'expect': expectedList}]
return self._test(r'jcli : ', commands)

@unittest.skip("Work in progress #123")
Expand Down Expand Up @@ -59,4 +62,73 @@ def test_httpapi(self):
@unittest.skip("Work in progress #123")
def test_smppsapi(self):
commands = [{'command': 'stats --smppsapi', 'expect': r'????'}]
return self._test(r'jcli : ', commands)
return self._test(r'jcli : ', commands)

class UserStatsTestCases(UserTestCases):
def test_users(self):
extraCommands = [{'command': 'uid test_users'}]
self.add_user('jcli : ', extraCommands, GID = 'AnyGroup', Username = 'AnyUsername')

expectedList = ['#User id\s+SMPP Bound connections\s+SMPP L.A.\s+HTTP requests counter\s+HTTP L.A.',
'#test_users\s+0\s+ND\s+0\s+ND',
'Total users: 1']
commands = [{'command': 'stats --users', 'expect': expectedList}]
return self._test(r'jcli : ', commands)

def test_user(self):
extraCommands = [{'command': 'uid test_user'}]
self.add_user('jcli : ', extraCommands, GID = 'AnyGroup', Username = 'AnyUsername')

expectedList = ['#Item Type Value',
'#last_activity_at SMPP Server ND',
'#bind_count SMPP Server 0',
"#bound_connections_count SMPP Server {'bind_transmitter': 0, 'bind_receiver': 0, 'bind_transceiver': 0}",
'#submit_sm_request_count SMPP Server 0',
'#qos_last_submit_sm_at SMPP Server ND',
'#unbind_count SMPP Server 0',
'#qos_last_submit_sm_at HTTP Api ND',
'#connects_count HTTP Api 0',
'#last_activity_at HTTP Api ND',
'#submit_sm_request_count HTTP Api 0']
commands = [{'command': 'stats --user=test_user', 'expect': expectedList}]
return self._test(r'jcli : ', commands)

class SmppcStatsTestCases(SmppccmTestCases):
@defer.inlineCallbacks
def test_smppcs(self):
extraCommands = [{'command': 'cid test_smppcs'}]
yield self.add_connector(r'jcli : ', extraCommands)

expectedList = ['#Connector id\s+Bound count\s+Connected at\s+Bound at\s+Disconnected at\s+Sent elink at\s+Received elink at',
'#test_smppcs\s+0\s+ND\s+ND\s+ND\s+ND\s+ND',
'Total connectors: 1']
commands = [{'command': 'stats --smppcs', 'expect': expectedList}]
yield self._test(r'jcli : ', commands)

@defer.inlineCallbacks
def test_smppc(self):
extraCommands = [{'command': 'cid test_smppc'}]
yield self.add_connector(r'jcli : ', extraCommands)

expectedList = ['#Connector id\s+Bound count\s+Connected at\s+Bound at\s+Disconnected at\s+Sent elink at\s+Received elink at',
'#test_smppc\s+0\s+ND\s+ND\s+ND\s+ND\s+ND',
'Total connectors: 1']
commands = [{'command': 'stats --smppcs', 'expect': expectedList}]
yield self._test(r'jcli : ', commands)

expectedList = ['#Item Value',
'#disconnected_count 0',
'#last_received_pdu_at ND',
'#last_received_elink_at ND',
'#connected_count 0',
'#connected_at ND',
'#last_seqNum',
'#disconnected_at ND',
'#bound_at ND',
'#created_at \d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}',
'#last_sent_elink_at ND',
'#bound_count 0',
'#last_seqNum_at ND',
'#last_sent_pdu_at ND']
commands = [{'command': 'stats --smppc=test_smppc', 'expect': expectedList}]
yield self._test(r'jcli : ', commands)
6 changes: 3 additions & 3 deletions jasmin/protocols/http/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,10 +176,10 @@ def render(self, request):
dlr_method = None

# QoS throttling
if user.mt_credential.getQuota('http_throughput') >= 0 and user.CnxStatus.httpapi['qos_last_submit_sm'] != 0:
if user.mt_credential.getQuota('http_throughput') >= 0 and user.CnxStatus.httpapi['qos_last_submit_sm_at'] != 0:
qos_throughput_second = 1 / float(user.mt_credential.getQuota('http_throughput'))
qos_throughput_ysecond_td = timedelta( microseconds = qos_throughput_second * 1000000)
qos_delay = datetime.now() - user.CnxStatus.httpapi['qos_last_submit_sm']
qos_delay = datetime.now() - user.CnxStatus.httpapi['qos_last_submit_sm_at']
if qos_delay < qos_throughput_ysecond_td:
self.log.error("QoS: submit_sm_event is faster (%s) than fixed throughput (%s) for user (%s), rejecting message." % (
qos_delay,
Expand All @@ -188,7 +188,7 @@ def render(self, request):
))

raise ThroughputExceededError("User throughput exceeded")
user.CnxStatus.httpapi['qos_last_submit_sm'] = datetime.now()
user.CnxStatus.httpapi['qos_last_submit_sm_at'] = datetime.now()

# Get number of PDUs to be sent (for billing purpose)
_pdu = SubmitSmPDU
Expand Down
22 changes: 15 additions & 7 deletions jasmin/protocols/smpp/factory.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
#pylint: disable-msg=W0401,W0611
import logging
from datetime import datetime
from datetime import datetime, timedelta
from OpenSSL import SSL
from twisted.internet.protocol import ClientFactory
from twisted.internet import defer, reactor, ssl
from jasmin.protocols.smpp.protocol import SMPPClientProtocol, SMPPServerProtocol
from jasmin.protocols.smpp.error import *
from jasmin.protocols.smpp.validation import SmppsCredentialValidator
from .stats import SMPPClientStatsCollector
from .protocol import SMPPClientProtocol, SMPPServerProtocol
from .error import *
from .validation import SmppsCredentialValidator
from jasmin.vendor.smpp.twisted.server import SMPPServerFactory as _SMPPServerFactory
from jasmin.vendor.smpp.twisted.server import SMPPBindManager as _SMPPBindManager
from jasmin.vendor.smpp.pdu import pdu_types, constants
Expand All @@ -30,6 +32,10 @@ def __init__(self, config, msgHandler = None):
self.smpp = None
self.connectionRetry = True
self.config = config

# Setup statistics collector
self.stats = SMPPClientStatsCollector().get(cid = self.config.id)
self.stats.set('created_at', datetime.now())

# Set up a dedicated logger
self.log = logging.getLogger(LOG_CATEGORY_CLIENT_BASE+".%s" % config.id)
Expand All @@ -47,9 +53,11 @@ def __init__(self, config, msgHandler = None):
self.msgHandler = msgHandler

def buildProtocol(self, addr):
"""Provision protocol with the dedicated logger
"""Provision protocol
"""
proto = ClientFactory.buildProtocol(self, addr)

# Setup logger
proto.log = self.log

return proto
Expand Down Expand Up @@ -266,10 +274,10 @@ def submit_sm_event(self, system_id, *args):
routedConnector = route.getConnector()

# QoS throttling
if user.mt_credential.getQuota('smpps_throughput') >= 0 and user.CnxStatus.smpps['qos_last_submit_sm'] != 0:
if user.mt_credential.getQuota('smpps_throughput') >= 0 and user.CnxStatus.smpps['qos_last_submit_sm_at'] != 0:
qos_throughput_second = 1 / float(user.mt_credential.getQuota('smpps_throughput'))
qos_throughput_ysecond_td = timedelta( microseconds = qos_throughput_second * 1000000)
qos_delay = datetime.now() - user.CnxStatus.smpps['qos_last_submit_sm']
qos_delay = datetime.now() - user.CnxStatus.smpps['qos_last_submit_sm_at']
if qos_delay < qos_throughput_ysecond_td:
self.log.error("QoS: submit_sm_event is faster (%s) than fixed throughput (%s) for user (%s), rejecting message." % (
qos_delay,
Expand All @@ -278,7 +286,7 @@ def submit_sm_event(self, system_id, *args):
))

raise SubmitSmThroughputExceededError()
user.CnxStatus.smpps['qos_last_submit_sm'] = datetime.now()
user.CnxStatus.smpps['qos_last_submit_sm_at'] = datetime.now()

# Pre-sending submit_sm: Billing processing
bill = route.getBillFor(user)
Expand Down
2 changes: 1 addition & 1 deletion jasmin/protocols/smpp/operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ def isDeliveryReceipt(self, DeliverSM):
# Example of DLR content
# id:IIIIIIIIII sub:SSS dlvrd:DDD submit date:YYMMDDhhmm done
# date:YYMMDDhhmm stat:DDDDDDD err:E text: . . . . . . . . .
pattern = r"^id:(?P<id>[\dA-F]+) sub:(?P<sub>\d{3}) dlvrd:(?P<dlvrd>\d{3}) submit date:(?P<sdate>\d+) done date:(?P<ddate>\d+) stat:(?P<stat>\w{7}) err:(?P<err>\w{3}) text:(?P<text>.*)"
pattern = r"^id:(?P<id>[\dA-Fa-f-]+) sub:(?P<sub>\d{3}) dlvrd:(?P<dlvrd>\d{3}) submit date:(?P<sdate>\d+) done date:(?P<ddate>\d+) stat:(?P<stat>\w{7}) err:(?P<err>\w{3}) text:(?P<text>.*)"
m = re.search(pattern, DeliverSM.params['short_message'], flags=re.IGNORECASE)
if m is not None:
ret = m.groupdict()
Expand Down

0 comments on commit 45b9509

Please sign in to comment.