diff --git a/README.md b/README.md index c3beb84..bae6ce8 100644 --- a/README.md +++ b/README.md @@ -178,7 +178,10 @@ What's new in version 2.0.0 What's new in version 2.0.1 1. Adding **Submit Log** report (DLR report) -**TODO:** bug fixes, adding new feature +What's new in version 2.0.2 +1. Adding FailOverRouter supports to MT / MO Router + +**TODO:** bug fixes, adding new features ## Usage diff --git a/main/core/models/smpp.py b/main/core/models/smpp.py index 27c1350..ff41878 100644 --- a/main/core/models/smpp.py +++ b/main/core/models/smpp.py @@ -8,97 +8,130 @@ from .timestamped import TimeStampedModel filter_types = ( - ('TransparentFilter', 'TransparentFilter',), - ('ConnectorFilter', 'ConnectorFilter',), - ('UserFilter', 'UserFilter',), - ('GroupFilter', 'GroupFilter',), - ('SourceAddrFilter', 'SourceAddrFilter',), - ('DestinationAddrFilter', 'DestinationAddrFilter',), - ('ShortMessageFilter', 'ShortMessageFilter',), - ('DateIntervalFilter', 'DateIntervalFilter',), - ('TimeIntervalFilter', 'TimeIntervalFilter',), - ('TagFilter', 'TagFilter',), - ('EvalPyFilter', 'EvalPyFilter',), + ('TransparentFilter', 'TransparentFilter',), + ('ConnectorFilter', 'ConnectorFilter',), + ('UserFilter', 'UserFilter',), + ('GroupFilter', 'GroupFilter',), + ('SourceAddrFilter', 'SourceAddrFilter',), + ('DestinationAddrFilter', 'DestinationAddrFilter',), + ('ShortMessageFilter', 'ShortMessageFilter',), + ('DateIntervalFilter', 'DateIntervalFilter',), + ('TimeIntervalFilter', 'TimeIntervalFilter',), + ('TagFilter', 'TagFilter',), + ('EvalPyFilter', 'EvalPyFilter',), ) + class FiltersModel(TimeStampedModel): - class Meta: - db_table = "tbl_filters" - verbose_name='Filters' - verbose_name_plural = verbose_name - type = models.CharField(u'Type', choices=filter_types, max_length=24) - fid = models.CharField(u'Filter ID', max_length=24, unique=True) - parameters = models.TextField(u'Parameters') - def __str__(self): - return self.fid + class Meta: + db_table = "tbl_filters" + verbose_name = 'Filters' + verbose_name_plural = verbose_name + + type = models.CharField('Type', choices=filter_types, max_length=24) + fid = models.CharField('Filter ID', max_length=24, unique=True) + parameters = models.TextField('Parameters') + + def __str__(self): + return self.fid + class GroupsModel(TimeStampedModel): - class Meta: - db_table = "tbl_groups" - verbose_name='Groups' - verbose_name_plural = verbose_name - gid = models.CharField(u'Group ID', max_length=24, unique=True) - status = models.BooleanField(u'Status', default=True) - def __str__(self): - return self.gid + class Meta: + db_table = "tbl_groups" + verbose_name = 'Groups' + verbose_name_plural = verbose_name + + gid = models.CharField('Group ID', max_length=24, unique=True) + status = models.BooleanField('Status', default=True) + + def __str__(self): + return self.gid + class UsersModel(TimeStampedModel): - class Meta: - db_table = "tbl_users" - verbose_name='Users' - verbose_name_plural=verbose_name - uid = models.CharField(u'User ID', max_length=24, unique=True) - gid = models.ForeignKey(GroupsModel, on_delete=models.CASCADE, verbose_name=u'Group ID') - username = models.CharField(u'Username', max_length=24) - password = models.CharField(u'Password', max_length=24) - parameters = models.TextField(u'Parameters') - user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name=u"Related User") - def __str__(self): - return self.uid + class Meta: + db_table = "tbl_users" + verbose_name = 'Users' + verbose_name_plural = verbose_name + + uid = models.CharField('User ID', max_length=24, unique=True) + gid = models.ForeignKey(GroupsModel, on_delete=models.CASCADE, verbose_name=u'Group ID') + username = models.CharField('Username', max_length=24) + password = models.CharField('Password', max_length=24) + parameters = models.TextField('Parameters') + user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name=u"Related User") + + def __str__(self): + return self.uid + class HTTPccmModel(TimeStampedModel): - class Meta: - db_table = "tbl_httpccm" - verbose_name='HTTP Client Connector' - verbose_name_plural=verbose_name - cid = models.CharField(u'Connector ID', max_length=24, unique=True, help_text='Connector identifier') - url = models.CharField(u'URL', max_length=128, help_text='URL to be called with message parameters') - method = models.CharField(u'Method', max_length=16, choices=(("GET", "GET",), ("POST", "POST",),)) - def __str__(self): - return self.cid + class Meta: + db_table = "tbl_httpccm" + verbose_name = 'HTTP Client Connector' + verbose_name_plural = verbose_name + + cid = models.CharField('Connector ID', max_length=24, unique=True, help_text='Connector identifier') + url = models.CharField('URL', max_length=128, help_text='URL to be called with message parameters') + method = models.CharField('Method', max_length=16, choices=(("GET", "GET",), ("POST", "POST",),)) + + def __str__(self): + return self.cid + class SMPPccmModel(TimeStampedModel): - class Meta: - db_table = "tbl_smppccm" - verbose_name = "SMPP Client Connector" - verbose_name_plural=verbose_name - cid = models.CharField(u'Connector ID', max_length=24, unique=True, help_text='Connector identifier') - parameters = models.TextField(u'Parameters') - action = models.BooleanField(u"Action", default=True, help_text='Start/Stop SMPP Connector') - def __str__(self): - return self.cid - -morouter_types = (("DefaultRoute", "DefaultRoute",), ("StaticMORoute", "StaticMORoute",), ("RandomRoundrobinMORoute", "RandomRoundrobinMORoute",),) + class Meta: + db_table = "tbl_smppccm" + verbose_name = "SMPP Client Connector" + verbose_name_plural = verbose_name + + cid = models.CharField('Connector ID', max_length=24, unique=True, help_text='Connector identifier') + parameters = models.TextField('Parameters') + action = models.BooleanField(u"Action", default=True, help_text='Start/Stop SMPP Connector') + + def __str__(self): + return self.cid + + +morouter_types = ( + ("DefaultRoute", "DefaultRoute",), + ("StaticMORoute", "StaticMORoute",), + ("RandomRoundrobinMORoute", "RandomRoundrobinMORoute",), + ("FailoverMORoute", "FailoverMORoute",), +) + + class MORoutersModel(TimeStampedModel): - class Meta: - db_table = "tbl_morouters" - verbose_name='MO Router' - verbose_name_plural=verbose_name - type = models.CharField(u'Type', choices=morouter_types, max_length=24) - order = models.CharField(u'Order', max_length=24, help_text='Router order, also used to identify router') - smppconnectors = models.ForeignKey(SMPPccmModel, on_delete=models.CASCADE, verbose_name=u'SMPP Connectors') - httpconnectors = models.ForeignKey(HTTPccmModel, on_delete=models.CASCADE, verbose_name=u'SMPP Connectors') - filters = models.ForeignKey(FiltersModel, on_delete=models.CASCADE, verbose_name=u'Filters') - -mtrouter_types = (("DefaultRoute", "DefaultRoute",), ("StaticMTRoute", "StaticMTRoute",), ("RandomRoundrobinMTRoute", "RandomRoundrobinMTRoute",),) + class Meta: + db_table = "tbl_morouters" + verbose_name = 'MO Router' + verbose_name_plural = verbose_name + + type = models.CharField('Type', choices=morouter_types, max_length=24) + order = models.CharField('Order', max_length=24, help_text='Router order, also used to identify router') + smppconnectors = models.ForeignKey(SMPPccmModel, on_delete=models.CASCADE, verbose_name=u'SMPP Connectors') + httpconnectors = models.ForeignKey(HTTPccmModel, on_delete=models.CASCADE, verbose_name=u'SMPP Connectors') + filters = models.ForeignKey(FiltersModel, on_delete=models.CASCADE, verbose_name=u'Filters') + + +mtrouter_types = ( + ("DefaultRoute", "DefaultRoute",), + ("StaticMTRoute", "StaticMTRoute",), + ("RandomRoundrobinMTRoute", "RandomRoundrobinMTRoute",), + ("FailoverMTRoute", "FailoverMTRoute"), +) + + class MTRoutersModel(TimeStampedModel): - class Meta: - db_table = "tbl_mtrouters" - verbose_name='MT Router' - verbose_name_plural=verbose_name - type = models.CharField(u'Type', choices=mtrouter_types, max_length=24) - order = models.CharField(u'Order', max_length=24, help_text='Router order, also used to identify router') - rate = models.FloatField(u'Rate', default=0.1) - smppconnectors = models.ForeignKey(SMPPccmModel, on_delete=models.CASCADE, verbose_name=u'SMPP Connectors') - httpconnectors = models.ForeignKey(HTTPccmModel, on_delete=models.CASCADE, verbose_name=u'SMPP Connectors') - filters = models.ForeignKey(FiltersModel, on_delete=models.CASCADE, verbose_name=u'Filters') \ No newline at end of file + class Meta: + db_table = "tbl_mtrouters" + verbose_name = 'MT Router' + verbose_name_plural = verbose_name + + type = models.CharField('Type', choices=mtrouter_types, max_length=24) + order = models.CharField('Order', max_length=24, help_text='Router order, also used to identify router') + rate = models.FloatField('Rate', default=0.1) + smppconnectors = models.ForeignKey(SMPPccmModel, on_delete=models.CASCADE, verbose_name=u'SMPP Connectors') + httpconnectors = models.ForeignKey(HTTPccmModel, on_delete=models.CASCADE, verbose_name=u'SMPP Connectors') + filters = models.ForeignKey(FiltersModel, on_delete=models.CASCADE, verbose_name=u'Filters') diff --git a/main/core/smpp/morouter.py b/main/core/smpp/morouter.py index 76d2403..c4d0993 100644 --- a/main/core/smpp/morouter.py +++ b/main/core/smpp/morouter.py @@ -6,8 +6,8 @@ from main.core.utils import is_int from main.core.tools import set_ikeys, split_cols from main.core.exceptions import (JasminSyntaxError, JasminError, - UnknownError, MissingKeyError, - MutipleValuesRequiredKeyError, ObjectNotFoundError) + UnknownError, MissingKeyError, + MutipleValuesRequiredKeyError, ObjectNotFoundError) import logging, random @@ -16,159 +16,167 @@ logger = logging.getLogger(__name__) + class MORouter(object): - "MORouter for managing MO Routes" - lookup_field = 'order' - def __init__(self, telnet): - self.telnet = telnet - def _list(self): - "List MO router as python dict" - self.telnet.sendline('morouter -l') - self.telnet.expect([r'(.+)\n' + STANDARD_PROMPT]) - result = str(self.telnet.match.group(0)).strip().replace("\\r", '').split("\\n") - if len(result) < 3: - return {'morouters': []} - results = [l.replace(', ', ',').replace('(!)', '') - for l in result[2:-2] if l] - routers = split_cols(results) - #print(routers) - return { - 'morouters': - [ - { - 'order': r[0].strip().lstrip('#'), - 'type': r[1], - 'connectors': [c.strip() for c in r[2].split(',')], - 'filters': [c.strip() for c in ' '.join(r[3:]).split(',') - ] if len(r) > 3 else [] - } for r in routers - ] - } - - def list(self): - "List MO routers. No parameters" - return self._list() - - def get_router(self, order): - "Return data for one morouter as Python dict" - morouters = self._list()['morouters'] - try: - return {'morouter': - next((m for m in morouters if m['order'] == order), None) - } - except StopIteration: - raise ObjectNotFoundError('No MoROuter with order: %s' % order) - - def retrieve(self, order): - "Details for one MORouter by order (integer)" - return self.get_router(order) - - - # methods=['delete'] - def flush(self): - "Flush entire routing table" - self.telnet.sendline('morouter -f') - self.telnet.expect([r'(.+)\n' + STANDARD_PROMPT]) - self.telnet.sendline('persist') - self.telnet.expect(r'.*' + STANDARD_PROMPT) - return {'morouters': []} - - def create(self, data): - """Create MORouter. - Required parameters: type, order, smppconnectors, httpconnectors - More than one connector is allowed only for RandomRoundrobinMORoute - --- - # YAML - omit_serializer: true - parameters: - - name: type - description: One of DefaultRoute, StaticMORoute, RandomRoundrobinMORoute - required: true - type: string - paramType: form - - name: order - description: Router order, also used to identify router - required: true - type: string - paramType: form - - name: smppconnectors - description: List of SMPP connector ids. - required: false - type: array - paramType: form - - name: httpconnectors - description: List of HTTP connector ids. - required: false - type: array - paramType: form - - name: filters - description: List of filters, required except for DefaultRoute - required: false - type: array - paramType: form - """ - try: - rtype, order = data['type'], data['order'] - except IndexError: - raise MissingKeyError( - 'Missing parameter: type or order required') - rtype = rtype.lower() - self.telnet.sendline('morouter -a') - self.telnet.expect(r'Adding a new MO Route(.+)\n' + INTERACTIVE_PROMPT) - ikeys = OrderedDict({'type': rtype}) - if rtype != 'defaultroute': - try: - filters = data['filters'] or "" - filters = filters.split(',') - except MultiValueDictKeyError: - raise MissingKeyError('%s router requires filters' % rtype) - ikeys['filters'] = ';'.join(filters) - ikeys['order'] = order if is_int(order) else str(random.randrange(1, 99)) - smppconnectors = data.get('smppconnectors') or "" - httpconnectors = data.get('httpconnectors') or "" - connectors = ['smpps(%s)' % c.strip() - for c in smppconnectors.split(',') if c.strip() - ] + ['http(%s)' % c for c in httpconnectors.split(',') if c.strip()] - if rtype == 'randomroundrobinmoroute': - if len(connectors) < 2: - raise MutipleValuesRequiredKeyError( - 'Round Robin route requires at least two connectors') - ikeys['connectors'] = ';'.join(connectors) - else: - if len(connectors) != 1: - raise MissingKeyError('One and only one connector required') - ikeys['connector'] = connectors[0] - set_ikeys(self.telnet, ikeys) - self.telnet.sendline('persist') - self.telnet.expect(r'.*' + STANDARD_PROMPT) - return {'morouter': self.get_router(order)} - - def simple_morouter_action(self, action, order, return_moroute=True): - self.telnet.sendline('morouter -%s %s' % (action, order)) - matched_index = self.telnet.expect([ - r'.+Successfully(.+)' + STANDARD_PROMPT, - r'.+Unknown MO Route: (.+)' + STANDARD_PROMPT, - r'.+(.*)' + STANDARD_PROMPT, - ]) - if matched_index == 0: - self.telnet.sendline('persist') - if return_moroute: - self.telnet.expect(r'.*' + STANDARD_PROMPT) - return {'morouter': self.get_router(fid)} - else: - return {'order': order} - elif matched_index == 1: - raise UnknownError(detail='No router:' + order) - else: - raise JasminError(self.telnet.match.group(1)) - - def destroy(self, order): - """Delete a morouter. One parameter required, the router identifier (a string) - - HTTP codes indicate result as follows - - - 200: successful deletion - - 404: nonexistent router - - 400: other error - """ - return self.simple_morouter_action('r', order, return_moroute=False) + "MORouter for managing MO Routes" + lookup_field = 'order' + + def __init__(self, telnet): + self.telnet = telnet + + def _list(self): + "List MO router as python dict" + self.telnet.sendline('morouter -l') + self.telnet.expect([r'(.+)\n' + STANDARD_PROMPT]) + result = str(self.telnet.match.group(0)).strip().replace("\\r", '').split("\\n") + if len(result) < 3: + return {'morouters': []} + results = [l.replace(', ', ',').replace('(!)', '') + for l in result[2:-2] if l] + routers = split_cols(results) + # print(routers) + return { + 'morouters': + [ + { + 'order': r[0].strip().lstrip('#'), + 'type': r[1], + 'connectors': [c.strip() for c in r[2].split(',')], + 'filters': [c.strip() for c in ' '.join(r[3:]).split(',') + ] if len(r) > 3 else [] + } for r in routers + ] + } + + def list(self): + "List MO routers. No parameters" + return self._list() + + def get_router(self, order): + "Return data for one morouter as Python dict" + morouters = self._list()['morouters'] + try: + return {'morouter': + next(m for m in morouters if m['order'] == order) # , None + } + except StopIteration: + raise ObjectNotFoundError('No MoROuter with order: %s' % order) + + def retrieve(self, order): + "Details for one MORouter by order (integer)" + return self.get_router(order) + + # methods=['delete'] + def flush(self): + "Flush entire routing table" + self.telnet.sendline('morouter -f') + self.telnet.expect([r'(.+)\n' + STANDARD_PROMPT]) + self.telnet.sendline('persist') + self.telnet.expect(r'.*' + STANDARD_PROMPT) + return {'morouters': []} + + def create(self, data): + """Create MORouter. + Required parameters: type, order, smppconnectors, httpconnectors + More than one connector is allowed only for RandomRoundrobinMORoute and FailoverMORoute + --- + # YAML + omit_serializer: true + parameters: + - name: type + description: One of DefaultRoute, StaticMORoute, RandomRoundrobinMORoute, FailoverMORoute + required: true + type: string + paramType: form + - name: order + description: Router order, also used to identify router + required: true + type: string + paramType: form + - name: smppconnectors + description: List of SMPP connector ids. + required: false + type: array + paramType: form + - name: httpconnectors + description: List of HTTP connector ids. + required: false + type: array + paramType: form + - name: filters + description: List of filters, required except for DefaultRoute + required: false + type: array + paramType: form + """ + try: + rtype, order = data.get('type'), data.get('order') + self.retrieve(order) + except Exception: # noqa + pass + else: + raise MissingKeyError('MO route already exists') + # raise MissingKeyError('Missing parameter: type or order required') + rtype = rtype.lower() + self.telnet.sendline('morouter -a') + self.telnet.expect(r'Adding a new MO Route(.+)\n' + INTERACTIVE_PROMPT) + ikeys = OrderedDict({'type': rtype}) + if rtype != 'defaultroute': + try: + filters = data['filters'] or "" + filters = filters.split(',') + except MultiValueDictKeyError: + raise MissingKeyError('%s router requires filters' % rtype) + ikeys['filters'] = ';'.join(filters) + ikeys['order'] = order if is_int(order) else str(random.randrange(1, 99)) + smppconnectors = data.get('smppconnectors') or "" + httpconnectors = data.get('httpconnectors') or "" + connectors = ['smpps(%s)' % c.strip() + for c in smppconnectors.split(',') if c.strip() + ] + ['http(%s)' % c for c in httpconnectors.split(',') if c.strip()] + if rtype == 'randomroundrobinmoroute': + if len(connectors) < 2: + raise MutipleValuesRequiredKeyError('Round Robin route requires at least two connectors') + ikeys['connectors'] = ';'.join(connectors) + elif rtype == 'failovermoroute': + if len(connectors) < 2: + raise MutipleValuesRequiredKeyError('FailOver route requires at least two connectors') + ikeys['connectors'] = ';'.join(connectors) + else: + if len(connectors) != 1: + raise MissingKeyError('One and only one connector required') + ikeys['connector'] = connectors[0] + set_ikeys(self.telnet, ikeys) + self.telnet.sendline('persist') + self.telnet.expect(r'.*' + STANDARD_PROMPT) + return {'morouter': self.get_router(order)} + + def simple_morouter_action(self, action, order, return_moroute=True): + self.telnet.sendline('morouter -%s %s' % (action, order)) + matched_index = self.telnet.expect([ + r'.+Successfully(.+)' + STANDARD_PROMPT, + r'.+Unknown MO Route: (.+)' + STANDARD_PROMPT, + r'.+(.*)' + STANDARD_PROMPT, + ]) + if matched_index == 0: + self.telnet.sendline('persist') + if return_moroute: + self.telnet.expect(r'.*' + STANDARD_PROMPT) + return {'morouter': self.get_router(fid)} + else: + return {'order': order} + elif matched_index == 1: + raise UnknownError(detail='No router:' + order) + else: + raise JasminError(self.telnet.match.group(1)) + + def destroy(self, order): + """Delete a morouter. One parameter required, the router identifier (a string) + + HTTP codes indicate result as follows + + - 200: successful deletion + - 404: nonexistent router + - 400: other error + """ + return self.simple_morouter_action('r', order, return_moroute=False) diff --git a/main/core/smpp/mtrouter.py b/main/core/smpp/mtrouter.py index c1ab86c..26fe99f 100644 --- a/main/core/smpp/mtrouter.py +++ b/main/core/smpp/mtrouter.py @@ -6,8 +6,8 @@ from main.core.utils import is_float, is_int from main.core.tools import set_ikeys, split_cols from main.core.exceptions import (JasminSyntaxError, JasminError, - UnknownError, MissingKeyError, - MutipleValuesRequiredKeyError, ObjectNotFoundError) + UnknownError, MissingKeyError, + MutipleValuesRequiredKeyError, ObjectNotFoundError) import logging, random @@ -16,167 +16,174 @@ logger = logging.getLogger(__name__) + class MTRouter(object): - "MTRouter for managing MT Routes" - lookup_field = 'order' - def __init__(self, telnet): - self.telnet = telnet - - def _list(self): - "List MT router as python dict" - self.telnet.sendline('mtrouter -l') - self.telnet.expect([r'(.+)\n' + STANDARD_PROMPT]) - result = str(self.telnet.match.group(0)).strip().replace("\\r", '').split("\\n") - if len(result) < 3: - return {'mtrouters': []} - results = [l.replace(', ', ',').replace('(!)', '') - for l in result[2:-2] if l] - routers = split_cols(results) - return { - 'mtrouters': - [ - { - 'order': r[0].strip().lstrip('#'), - 'type': r[1], - 'rate': r[2], - 'connectors': [c.strip() for c in r[3].split(',')], - 'filters': [c.strip() for c in ' '.join(r[4:]).split(',') - ] if len(r) > 3 else [] - } for r in routers - ] - } - - def list(self): - "List MT Routers. No parameters" - return self._list() - - def get_router(self, order): - "Return data for one mtrouter as Python dict" - routers = self._list()['mtrouters'] - try: - return {'mtrouter': - next((m for m in routers if m['order'] == order), None) - } - except StopIteration: - raise ObjectNotFoundError('No MTRouter with order: %s' % order) - - def retrieve(self, order): - "Details for one MTRouter by order (integer)" - return self.get_router(order) - - - # methods=['delete'] - def flush(self): - "Flush entire routing table" - self.telnet.sendline('mtrouter -f') - self.telnet.expect([r'(.+)\n' + STANDARD_PROMPT]) - self.telnet.sendline('persist') - self.telnet.expect(r'.*' + STANDARD_PROMPT) - return {'mtrouters': []} - - def create(self, data): - """Create MTRouter. - Required parameters: type, order, smppconnectors, httpconnectors - More than one connector is allowed only for RandomRoundrobinMTRoute - --- - # YAML - omit_serializer: true - parameters: - - name: type - description: One of DefaultRoute, StaticMTRoute, RandomRoundrobinMTRoute - required: true - type: string - paramType: form - - name: order - description: Router order, also used to identify router - required: true - type: string - paramType: form - - name: rate - description: Router rate, may be zero for free - required: true - type: float - paramType: form - - name: smppconnectors - description: List of SMPP connector ids. - required: false - type: array - paramType: form - - name: httpconnectors - description: List of HTTP connector ids. - required: false - type: array - paramType: form - - name: filters - description: List of filters, required except for DefaultRoute - required: false - type: array - paramType: form - """ - try: - rtype, order, rate = data['type'], data['order'], data['rate'] - except IndexError: - raise MissingKeyError( - 'Missing parameter: type or order required') - rtype = rtype.lower() - self.telnet.sendline('mtrouter -a') - self.telnet.expect(r'Adding a new MT Route(.+)\n' + INTERACTIVE_PROMPT) - ikeys = OrderedDict({'type': rtype}) - if rtype != 'defaultroute': - try: - filters = data['filters'] or "" - filters = filters.split(',') - except MultiValueDictKeyError: - raise MissingKeyError('%s router requires filters' % rtype) - ikeys['filters'] = ';'.join(filters) - ikeys['order'] = order if is_int(order) else str(random.randrange(1, 99)) - smppconnectors = data.get('smppconnectors') or "" - """ MT Router only support SMPP connectors, HTTP not allowed """ - #httpconnectors = data.get('httpconnectors') or "" - connectors = ['smppc(%s)' % c.strip() - for c in smppconnectors.split(',') if c.strip() - ] # + ['http(%s)' % c for c in httpconnectors.split(',') if c.strip()] - if rtype == 'randomroundrobinmtroute': - if len(connectors) < 2: - raise MutipleValuesRequiredKeyError( - 'Round Robin route requires at least two connectors') - ikeys['connectors'] = ';'.join(connectors) - else: - if len(connectors) != 1: - raise MissingKeyError('One and only one connector required') - ikeys['connector'] = connectors[0] - ikeys['rate'] = str(float(rate)) if is_float(rate) else "0.0" - set_ikeys(self.telnet, ikeys) - self.telnet.sendline('persist') - self.telnet.expect(r'.*' + STANDARD_PROMPT) - return {'mtrouter': self.get_router(order)} - - def simple_mtrouter_action(self, action, order, return_mtroute=True): - self.telnet.sendline('mtrouter -%s %s' % (action, order)) - matched_index = self.telnet.expect([ - r'.+Successfully(.+)' + STANDARD_PROMPT, - r'.+Unknown MT Route: (.+)' + STANDARD_PROMPT, - r'.+(.*)' + STANDARD_PROMPT, - ]) - if matched_index == 0: - self.telnet.sendline('persist') - if return_mtroute: - self.telnet.expect(r'.*' + STANDARD_PROMPT) - return {'mtrouter': self.get_router(fid)} - else: - return {'order': order} - elif matched_index == 1: - raise UnknownError(detail='No router:' + order) - else: - raise JasminError(self.telnet.match.group(1)) - - def destroy(self, order): - """Delete a mtrouter. One parameter required, the router identifier (a string) - - HTTP codes indicate result as follows - - - 200: successful deletion - - 404: nonexistent router - - 400: other error - """ - return self.simple_mtrouter_action('r', order, return_mtroute=False) \ No newline at end of file + "MTRouter for managing MT Routes" + lookup_field = 'order' + + def __init__(self, telnet): + self.telnet = telnet + + def _list(self): + "List MT router as python dict" + self.telnet.sendline('mtrouter -l') + self.telnet.expect([r'(.+)\n' + STANDARD_PROMPT]) + result = str(self.telnet.match.group(0)).strip().replace("\\r", '').split("\\n") + if len(result) < 3: + return {'mtrouters': []} + results = [l.replace(', ', ',').replace('(!)', '') + for l in result[2:-2] if l] + routers = split_cols(results) + return { + 'mtrouters': + [ + { + 'order': r[0].strip().lstrip('#'), + 'type': r[1], + 'rate': r[2], + 'connectors': [c.strip() for c in r[3].split(',')], + 'filters': [c.strip() for c in ' '.join(r[4:]).split(',') + ] if len(r) > 3 else [] + } for r in routers + ] + } + + def list(self): + "List MT Routers. No parameters" + return self._list() + + def get_router(self, order): + "Return data for one mtrouter as Python dict" + routers = self._list()['mtrouters'] + try: + return {'mtrouter': + next(m for m in routers if m['order'] == order) # , None + } + except StopIteration: + raise ObjectNotFoundError('No MTRouter with order: %s' % order) + + def retrieve(self, order): + "Details for one MTRouter by order (integer)" + return self.get_router(order) + + # methods=['delete'] + def flush(self): + "Flush entire routing table" + self.telnet.sendline('mtrouter -f') + self.telnet.expect([r'(.+)\n' + STANDARD_PROMPT]) + self.telnet.sendline('persist') + self.telnet.expect(r'.*' + STANDARD_PROMPT) + return {'mtrouters': []} + + def create(self, data): + """Create MTRouter. + Required parameters: type, order, smppconnectors, httpconnectors + More than one connector is allowed only for RandomRoundrobinMTRoute and FailoverMTRoute + --- + # YAML + omit_serializer: true + parameters: + - name: type + description: One of DefaultRoute, StaticMTRoute, RandomRoundrobinMTRoute, FailoverMTRoute + required: true + type: string + paramType: form + - name: order + description: Router order, also used to identify router + required: true + type: string + paramType: form + - name: rate + description: Router rate, may be zero for free + required: true + type: float + paramType: form + - name: smppconnectors + description: List of SMPP connector ids. + required: false + type: array + paramType: form + - name: httpconnectors + description: List of HTTP connector ids. + required: false + type: array + paramType: form + - name: filters + description: List of filters, required except for DefaultRoute + required: false + type: array + paramType: form + """ + try: + rtype, order, rate = data.get("type"), data.get("order"), data.get("rate") + self.retrieve(order) + except Exception: # noqa + pass + else: + raise MissingKeyError('MT route already exists') + # raise MissingKeyError('Missing parameter: type or order required') + rtype = rtype.lower() + self.telnet.sendline('mtrouter -a') + self.telnet.expect(r'Adding a new MT Route(.+)\n' + INTERACTIVE_PROMPT) + ikeys = OrderedDict({'type': rtype}) + if rtype != 'defaultroute': + try: + filters = data['filters'] or "" + filters = filters.split(',') + except MultiValueDictKeyError: + raise MissingKeyError('%s router requires filters' % rtype) + ikeys['filters'] = ';'.join(filters) + ikeys['order'] = order if is_int(order) else str(random.randrange(1, 99)) + smppconnectors = data.get('smppconnectors') or "" + """ MT Router only support SMPP connectors, HTTP not allowed """ + # httpconnectors = data.get('httpconnectors') or "" + connectors = ['smppc(%s)' % c.strip() + for c in smppconnectors.split(',') if c.strip() + ] # + ['http(%s)' % c for c in httpconnectors.split(',') if c.strip()] + if rtype == 'randomroundrobinmtroute': + if len(connectors) < 2: + raise MutipleValuesRequiredKeyError('Round Robin route requires at least two connectors') + ikeys['connectors'] = ';'.join(connectors) + elif rtype == 'failovermtroute': + if len(connectors) < 2: + raise MutipleValuesRequiredKeyError('FailOver route requires at least two connectors') + ikeys['connectors'] = ';'.join(connectors) + else: + if len(connectors) != 1: + raise MissingKeyError('One and only one connector required') + ikeys['connector'] = connectors[0] + ikeys['rate'] = str(float(rate)) if is_float(rate) else "0.0" + set_ikeys(self.telnet, ikeys) + self.telnet.sendline('persist') + self.telnet.expect(r'.*' + STANDARD_PROMPT) + return {'mtrouter': self.get_router(order)} + + def simple_mtrouter_action(self, action, order, return_mtroute=True): + self.telnet.sendline('mtrouter -%s %s' % (action, order)) + matched_index = self.telnet.expect([ + r'.+Successfully(.+)' + STANDARD_PROMPT, + r'.+Unknown MT Route: (.+)' + STANDARD_PROMPT, + r'.+(.*)' + STANDARD_PROMPT, + ]) + if matched_index == 0: + self.telnet.sendline('persist') + if return_mtroute: + self.telnet.expect(r'.*' + STANDARD_PROMPT) + return {'mtrouter': self.get_router(fid)} + else: + return {'order': order} + elif matched_index == 1: + raise UnknownError(detail='No router:' + order) + else: + raise JasminError(self.telnet.match.group(1)) + + def destroy(self, order): + """Delete a mtrouter. One parameter required, the router identifier (a string) + + HTTP codes indicate result as follows + + - 200: successful deletion + - 404: nonexistent router + - 400: other error + """ + return self.simple_mtrouter_action('r', order, return_mtroute=False) diff --git a/main/web/templates/web/content/morouter.html b/main/web/templates/web/content/morouter.html index 1de5359..868f7e9 100644 --- a/main/web/templates/web/content/morouter.html +++ b/main/web/templates/web/content/morouter.html @@ -4,6 +4,7 @@ {% block extracss %}{% endblock extracss %} {% block content %}

{% trans "MO Router" %}

+

{% trans "MO is referred to Mobile Originated, a SMS-MO is an SMS sent from mobile" %}

@@ -42,13 +43,19 @@