Beispiel #1
0
    def __init__(self, RouterPBConfig, persistenceTimer=True):
        self.config = RouterPBConfig
        self.persistenceTimer = None
        self.avatar = None

        # Set up a dedicated logger
        self.log = logging.getLogger(LOG_CATEGORY)
        if len(self.log.handlers) != 1:
            self.log.setLevel(self.config.log_level)
            if 'stdout' in self.config.log_file:
                handler = logging.StreamHandler(sys.stdout)
            else:
                handler = TimedRotatingFileHandler(
                    filename=self.config.log_file, when=self.config.log_rotate)
            formatter = logging.Formatter(self.config.log_format,
                                          self.config.log_date_format)
            handler.setFormatter(formatter)
            self.log.addHandler(handler)
            self.log.propagate = False

        # Set pickleProtocol
        self.pickleProtocol = self.config.pickle_protocol

        # Init routing-related objects
        self.mo_routing_table = MORoutingTable()
        self.mt_routing_table = MTRoutingTable()
        self.users = []
        self.groups = []

        # Init interception-related objects
        self.mo_interception_table = MOInterceptionTable()
        self.mt_interception_table = MTInterceptionTable()

        if persistenceTimer:
            # Activate persistenceTimer, used for persisting users and groups whenever critical updates
            # occured
            self.activatePersistenceTimer()

        # Persistence flag, accessed through perspective_is_persisted
        self.persistenceState = {
            'users': True,
            'groups': True,
            'moroutes': True,
            'mtroutes': True
        }

        self.log.info('Router configured and ready.')
Beispiel #2
0
    def __init__(self, RouterPBConfig, persistenceTimer=True):
        self.config = RouterPBConfig
        self.persistenceTimer = None
        self.avatar = None

        # Set up a dedicated logger
        self.log = logging.getLogger(LOG_CATEGORY)
        if len(self.log.handlers) != 1:
            self.log.setLevel(self.config.log_level)
            handler = TimedRotatingFileHandler(filename=self.config.log_file,
                                               when=self.config.log_rotate)
            formatter = logging.Formatter(self.config.log_format, self.config.log_date_format)
            handler.setFormatter(formatter)
            self.log.addHandler(handler)
            self.log.propagate = False

        # Set pickleProtocol
        self.pickleProtocol = self.config.pickle_protocol

        # Init routing-related objects
        self.mo_routing_table = MORoutingTable()
        self.mt_routing_table = MTRoutingTable()
        self.users = []
        self.groups = []

        # Init interception-related objects
        self.mo_interception_table = MOInterceptionTable()
        self.mt_interception_table = MTInterceptionTable()

        if persistenceTimer:
            # Activate persistenceTimer, used for persisting users and groups whenever critical updates
            # occured
            self.activatePersistenceTimer()

        # Persistence flag, accessed through perspective_is_persisted
        self.persistenceState = {'users': True, 'groups': True, 'moroutes': True, 'mtroutes': True}

        self.log.info('Router configured and ready.')
Beispiel #3
0
class RouterPB(pb.Avatar):
    def setConfig(self, RouterPBConfig, persistenceTimer=True):
        self.config = RouterPBConfig
        self.persistenceTimer = None

        # Set up a dedicated logger
        self.log = logging.getLogger(LOG_CATEGORY)
        if len(self.log.handlers) != 1:
            self.log.setLevel(self.config.log_level)
            handler = TimedRotatingFileHandler(filename=self.config.log_file,
                                               when=self.config.log_rotate)
            formatter = logging.Formatter(self.config.log_format,
                                          self.config.log_date_format)
            handler.setFormatter(formatter)
            self.log.addHandler(handler)
            self.log.propagate = False

        # Set pickleProtocol
        self.pickleProtocol = self.config.pickle_protocol

        # Init routing-related objects
        self.mo_routing_table = MORoutingTable()
        self.mt_routing_table = MTRoutingTable()
        self.users = []
        self.groups = []

        # Init interception-related objects
        self.mo_interception_table = MOInterceptionTable()
        self.mt_interception_table = MTInterceptionTable()

        if persistenceTimer:
            # Activate persistenceTimer, used for persisting users and groups whenever critical updates
            # occured
            self.activatePersistenceTimer()

        # Persistence flag, accessed through perspective_is_persisted
        self.persistenceState = {
            'users': True,
            'groups': True,
            'moroutes': True,
            'mtroutes': True
        }

        self.log.info('Router configured and ready.')

    def setAvatar(self, avatar):
        if isinstance(avatar, str):
            self.log.info('Authenticated Avatar: %s', avatar)
        else:
            self.log.info('Anonymous connection')

        self.avatar = avatar

    @defer.inlineCallbacks
    def addAmqpBroker(self, amqpBroker):
        self.amqpBroker = amqpBroker
        self.log.info('Added amqpBroker to RouterPB')

        if not self.amqpBroker.connected:
            self.log.warn(
                'AMQP Broker channel is not yet ready, waiting for it to become ready.'
            )
            yield self.amqpBroker.channelReady
            self.log.info("AMQP Broker channel is ready now, let's go !")

        # Subscribe to deliver.sm.* queues
        yield self.amqpBroker.chan.exchange_declare(exchange='messaging',
                                                    type='topic')
        consumerTag = 'RouterPB-delivers'
        routingKey = 'deliver.sm.*'
        queueName = 'RouterPB_deliver_sm_all'  # A local queue to RouterPB
        yield self.amqpBroker.named_queue_declare(queue=queueName)
        yield self.amqpBroker.chan.queue_bind(queue=queueName,
                                              exchange="messaging",
                                              routing_key=routingKey)
        yield self.amqpBroker.chan.basic_consume(queue=queueName,
                                                 no_ack=False,
                                                 consumer_tag=consumerTag)
        self.deliver_sm_q = yield self.amqpBroker.client.queue(consumerTag)
        self.deliver_sm_q.get().addCallback(
            self.deliver_sm_callback).addErrback(self.deliver_sm_errback)
        self.log.info('RouterPB is consuming from routing key: %s', routingKey)

        # Subscribe to bill_request.submit_sm_resp.* queues
        yield self.amqpBroker.chan.exchange_declare(exchange='billing',
                                                    type='topic')
        consumerTag = 'RouterPB-billrequests'
        routingKey = 'bill_request.submit_sm_resp.*'
        queueName = 'RouterPB_bill_request_submit_sm_resp_all'  # A local queue to RouterPB
        yield self.amqpBroker.named_queue_declare(queue=queueName)
        yield self.amqpBroker.chan.queue_bind(queue=queueName,
                                              exchange="billing",
                                              routing_key=routingKey)
        yield self.amqpBroker.chan.basic_consume(queue=queueName,
                                                 no_ack=False,
                                                 consumer_tag=consumerTag)
        self.bill_request_submit_sm_resp_q = yield self.amqpBroker.client.queue(
            consumerTag)
        self.bill_request_submit_sm_resp_q.get().addCallback(
            self.bill_request_submit_sm_resp_callback).addErrback(
                self.bill_request_submit_sm_resp_errback)
        self.log.info('RouterPB is consuming from routing key: %s', routingKey)

    @defer.inlineCallbacks
    def rejectMessage(self, message):
        yield self.amqpBroker.chan.basic_reject(
            delivery_tag=message.delivery_tag, requeue=0)

    @defer.inlineCallbacks
    def ackMessage(self, message):
        yield self.amqpBroker.chan.basic_ack(message.delivery_tag)

    def activatePersistenceTimer(self):
        if self.persistenceTimer and self.persistenceTimer.active():
            self.log.debug('Reseting persistenceTimer with %ss',
                           self.config.persistence_timer_secs)
            self.persistenceTimer.reset(self.config.persistence_timer_secs)
        else:
            self.log.debug('Activating persistenceTimer with %ss',
                           self.config.persistence_timer_secs)
            self.persistenceTimer = reactor.callLater(
                self.config.persistence_timer_secs,
                self.persistenceTimerExpired)

    def cancelPersistenceTimer(self):
        if self.persistenceTimer and self.persistenceTimer.active():
            self.log.debug('Cancelling persistenceTimer')
            self.persistenceTimer.cancel()
            self.persistenceTimer = None

    def persistenceTimerExpired(self):
        'This is run every self.config.persistence_timer_secs seconds'
        self.log.debug('persistenceTimerExpired called')

        # If at least one user have its quotas updated, then persist
        # groups and users to disk
        for u in self.users:
            if u.mt_credential.quotas_updated:
                self.log.info(
                    'Detected a user quota update, users and groups will be persisted.'
                )
                self.perspective_persist(scope='groups')
                self.perspective_persist(scope='users')
                u.mt_credential.quotas_updated = False
                self.log.debug('Persisted successfully')
                break

        self.activatePersistenceTimer()

    @defer.inlineCallbacks
    def deliver_sm_callback(self, message):
        """This callback is a queue listener
        It will only decide where to send the input message and republish it to the routedConnector
        The consumer will execute the remaining job of final delivery
        c.f. test_router.DeliverSmDeliveryTestCases for use cases
        """
        msgid = message.content.properties['message-id']
        scid = message.content.properties['headers']['connector-id']
        concatenated = message.content.properties['headers']['concatenated']
        will_be_concatenated = message.content.properties['headers'][
            'will_be_concatenated']
        connector = Connector(scid)
        DeliverSmPDU = pickle.loads(message.content.body)
        self.log.debug(
            "Callbacked a deliver_sm with a DeliverSmPDU[%s] (?): %s", msgid,
            DeliverSmPDU)

        # @todo: Implement MO throttling here, same as in
        # jasmin.managers.listeners.SMPPClientSMListener.submit_sm_callback
        self.deliver_sm_q.get().addCallback(
            self.deliver_sm_callback).addErrback(self.deliver_sm_errback)

        # Routing
        routable = RoutableDeliverSm(DeliverSmPDU, connector)
        route = self.getMORoutingTable().getRouteFor(routable)
        if route is None:
            self.log.debug(
                "No route matched this DeliverSmPDU with scid:%s and msgid:%s",
                scid, msgid)
            yield self.rejectMessage(message)
        else:
            # Get connector from selected route
            self.log.debug("RouterPB selected %s for this SubmitSmPDU", route)
            routedConnector = route.getConnector()

            # Smpps will not route any concatenated content, it must instead route
            # multiparted messages
            # Only http connector needs concatenated content
            if concatenated and routedConnector.type != 'http':
                self.log.debug(
                    "DeliverSmPDU [msgid:%s] not routed because its content is concatenated and the routedConnector is not http: %s",
                    msgid, routedConnector.type)
                yield self.rejectMessage(message)

            # Http will not route any multipart messages, it must instead route
            # concatenated messages
            # Only smpps connector needs multipart content
            elif will_be_concatenated and routedConnector.type == 'http':
                self.log.debug(
                    "DeliverSmPDU [msgid:%s] not routed because there will be a one concatenated message for all parts",
                    msgid)
                yield self.rejectMessage(message)

            else:
                self.log.debug(
                    "Connector '%s'(%s) is set to be a route for this DeliverSmPDU",
                    routedConnector.cid, routedConnector.type)
                yield self.ackMessage(message)

                # Enqueue DeliverSm for delivery through publishing it to deliver_sm_thrower.(type)
                content = RoutedDeliverSmContent(DeliverSmPDU, msgid, scid,
                                                 routedConnector)
                self.log.debug(
                    "Publishing RoutedDeliverSmContent [msgid:%s] in deliver_sm_thrower.%s with [dcid:%s]",
                    msgid, routedConnector.type, routedConnector.cid)
                yield self.amqpBroker.publish(
                    exchange='messaging',
                    routing_key='deliver_sm_thrower.%s' % routedConnector.type,
                    content=content)

    def deliver_sm_errback(self, error):
        """It appears that when closing a queue with the close() method it errbacks with
        a txamqp.queue.Closed exception, didnt find a clean way to stop consuming a queue
        without errbacking here so this is a workaround to make it clean, it can be considered
        as a @TODO requiring knowledge of the queue api behaviour
        """
        if error.check(Closed) == None:
            #@todo: implement this errback
            # For info, this errback is called whenever:
            # - an error has occured inside deliver_sm_callback
            self.log.error("Error in deliver_sm_errback: %s", error)

    @defer.inlineCallbacks
    def bill_request_submit_sm_resp_callback(self, message):
        """This callback is a queue listener
        """
        bid = message.content.properties['message-id']
        amount = float(message.content.properties['headers']['amount'])
        uid = message.content.properties['headers']['user-id']
        self.log.debug(
            "Callbacked a bill_request_submit_sm_resp [uid:%s] [amount:%s] [related-bid:%s]",
            uid, amount, bid)

        self.bill_request_submit_sm_resp_q.get().addCallback(
            self.bill_request_submit_sm_resp_callback).addErrback(
                self.bill_request_submit_sm_resp_errback)

        _user = self.getUser(uid)
        if _user is None:
            self.log.error(
                "User [uid:%s] not found, billing request [bid:%s] rejected",
                uid, bid)
            yield self.rejectMessage(message)
        elif _user.mt_credential.getQuota('balance') is not None:
            if _user.mt_credential.getQuota('balance') < amount:
                self.log.error(
                    'User [uid:%s] have no sufficient balance (%s/%s) for this billing [bid:%s] request: rejected',
                    uid, _user.mt_credential.getQuota('balance'), amount, bid)
                yield self.rejectMessage(message)
            else:
                _user.mt_credential.updateQuota('balance', -amount)
                self.log.info('User [uid:%s] charged for amount: %s (bid:%s)',
                              uid, amount, bid)
                yield self.ackMessage(message)

    def bill_request_submit_sm_resp_errback(self, error):
        """It appears that when closing a queue with the close() method it errbacks with
        a txamqp.queue.Closed exception, didnt find a clean way to stop consuming a queue
        without errbacking here so this is a workaround to make it clean, it can be considered
        as a @TODO requiring knowledge of the queue api behaviour
        """
        if error.check(Closed) == None:
            #@todo: implement this errback
            # For info, this errback is called whenever:
            # - an error has occured inside deliver_sm_callback
            self.log.error("Error in bill_request_submit_sm_resp_errback: %s",
                           error)
            self.log.critical("User were not charged !")

    def getMOInterceptionTable(self):
        return self.mo_interception_table

    def getMTInterceptionTable(self):
        return self.mt_interception_table

    def getMORoutingTable(self):
        return self.mo_routing_table

    def getMTRoutingTable(self):
        return self.mt_routing_table

    def authenticateUser(self, username, password, return_pickled=False):
        """Authenticate a user agains username and password and return user object or None
        """
        # Find user having correct username/password
        for _user in self.users:
            if _user.username == username and _user.password == md5(
                    password).digest():
                self.log.debug(
                    'authenticateUser [username:%s] returned a User', username)

                # Check if user's group is enabled
                _group = self.getGroup(_user.group.gid)
                if _group is not None and not _group.enabled:
                    self.log.info(
                        'authenticateUser [username:%s] returned None (group %s is disabled)',
                        username, _user.group)
                    return None

                # Check if user is enabled
                if not _user.enabled:
                    self.log.info(
                        'authenticateUser [username:%s] returned None (user is disabled)',
                        username)
                    return None

                # If user/group are enabled:
                if return_pickled:
                    return pickle.dumps(_user, self.pickleProtocol)
                else:
                    return _user

        self.log.info('authenticateUser [username:%s] returned None', username)
        return None

    def chargeUserForSubmitSms(self,
                               user,
                               bill,
                               submit_sm_count=1,
                               requirements=None):
        """Will charge the user using the bill object after checking requirements
        """
        if requirements is None:
            # Default:
            requirements = []

        # Check if User is already existent in Router ?
        _user = self.getUser(user.uid)
        if _user is None:
            self.log.error("User [uid:%s] not found for charging", user.uid)

        # Verify user-defined requirements
        for requirement in requirements:
            if not requirement['condition']:
                self.log.warn(requirement['error_message'])
                return None

        # Charge _user
        if (bill.getAmount('submit_sm') * submit_sm_count > 0
                and _user.mt_credential.getQuota('balance') is not None):
            if _user.mt_credential.getQuota(
                    'balance') < bill.getAmount('submit_sm') * submit_sm_count:
                self.log.info(
                    'User [uid:%s] have no sufficient balance (%s) for submit_sm charging: %s',
                    user.uid, _user.mt_credential.getQuota('balance'),
                    bill.getAmount('submit_sm') * submit_sm_count)
                return None
            _user.mt_credential.updateQuota(
                'balance', -(bill.getAmount('submit_sm') * submit_sm_count))
            self.log.info('User [uid:%s] charged for submit_sm amount: %s',
                          user.uid,
                          bill.getAmount('submit_sm') * submit_sm_count)
        # Decrement counts
        if (bill.getAction('decrement_submit_sm_count') * submit_sm_count > 0
                and _user.mt_credential.getQuota('submit_sm_count')
                is not None):
            if _user.mt_credential.getQuota(
                    'submit_sm_count'
            ) < bill.getAction('decrement_submit_sm_count') * submit_sm_count:
                self.log.info(
                    'User [uid:%s] have no sufficient submit_sm_count (%s) for submit_sm charging: %s',
                    user.uid, _user.mt_credential.getQuota('submit_sm_count'),
                    bill.getAction('decrement_submit_sm_count') *
                    submit_sm_count)
                return None
            _user.mt_credential.updateQuota(
                'submit_sm_count',
                -(bill.getAction('decrement_submit_sm_count') *
                  submit_sm_count))
            self.log.info(
                'User\'s [uid:%s] submit_sm_count decremented for submit_sm: %s',
                user.uid,
                bill.getAction('decrement_submit_sm_count') * submit_sm_count)

        return True

    def getUser(self, uid):
        for _user in self.users:
            if str(_user.uid) == str(uid):
                self.log.debug('getUser [uid:%s] returned a User', uid)
                return _user

        self.log.debug('getUser [uid:%s] returned None', uid)
        return None

    def getGroup(self, gid):
        for _group in self.groups:
            if str(_group.gid) == str(gid):
                self.log.debug('getGroup [gid:%s] returned a Group', gid)
                return _group

        self.log.debug('getGroup [gid:%s] returned None', gid)
        return None

    def getMOInterceptor(self, order):
        mointerceptors = self.mo_interception_table.getAll()

        for e in mointerceptors:
            if order == e.keys()[0]:
                self.log.debug(
                    'getMOInterceptor [order:%s] returned a MOInterceptor',
                    order)
                return e[order]

        self.log.debug('getMOInterceptor [order:%s] returned None', order)
        return None

    def getMTInterceptor(self, order):
        mtinterceptors = self.mt_interception_table.getAll()

        for e in mtinterceptors:
            if order == e.keys()[0]:
                self.log.debug(
                    'getMTInterceptor [order:%s] returned a MTInterceptor',
                    order)
                return e[order]

        self.log.debug('getMTInterceptor [order:%s] returned None', order)
        return None

    def getMORoute(self, order):
        moroutes = self.mo_routing_table.getAll()

        for e in moroutes:
            if order == e.keys()[0]:
                self.log.debug('getMORoute [order:%s] returned a MORoute',
                               order)
                return e[order]

        self.log.debug('getMORoute [order:%s] returned None', order)
        return None

    def getMTRoute(self, order):
        mtroutes = self.mt_routing_table.getAll()

        for e in mtroutes:
            if order == e.keys()[0]:
                self.log.debug('getMTRoute [order:%s] returned a MTRoute',
                               order)
                return e[order]

        self.log.debug('getMTRoute [order:%s] returned None', order)
        return None

    def perspective_version_release(self):
        return jasmin.get_release()

    def perspective_version(self):
        return jasmin.get_version()

    def perspective_persist(self, profile='jcli-prod', scope='all'):
        try:
            if scope in ['all', 'groups']:
                # Persist groups configuration
                path = '%s/%s.router-groups' % (self.config.store_path,
                                                profile)
                self.log.info(
                    'Persisting current Groups configuration to [%s] profile in %s',
                    profile, path)

                fh = open(path, 'w')
                # Write configuration with datetime stamp
                fh.write('Persisted on %s [Jasmin %s]\n' %
                         (time.strftime("%c"), jasmin.get_release()))
                fh.write(pickle.dumps(self.groups, self.pickleProtocol))
                fh.close()

                # Set persistance state to True
                self.persistenceState['groups'] = True

            if scope in ['all', 'users']:
                # Persist users configuration
                path = '%s/%s.router-users' % (self.config.store_path, profile)
                self.log.info(
                    'Persisting current Users configuration to [%s] profile in %s',
                    profile, path)

                fh = open(path, 'w')
                # Write configuration with datetime stamp
                fh.write('Persisted on %s [Jasmin %s]\n' %
                         (time.strftime("%c"), jasmin.get_release()))
                fh.write(pickle.dumps(self.users, self.pickleProtocol))
                fh.close()

                # Set persistance state to True
                self.persistenceState['users'] = True
                for u in self.users:
                    u.mt_credential.quotas_updated = False

            if scope in ['all', 'moroutes']:
                # Persist moroutes configuration
                path = '%s/%s.router-moroutes' % (self.config.store_path,
                                                  profile)
                self.log.info(
                    'Persisting current MORoutingTable to [%s] profile in %s',
                    profile, path)

                fh = open(path, 'w')
                # Write configuration with datetime stamp
                fh.write('Persisted on %s [Jasmin %s]\n' %
                         (time.strftime("%c"), jasmin.get_release()))
                fh.write(
                    pickle.dumps(self.mo_routing_table, self.pickleProtocol))
                fh.close()

                # Set persistance state to True
                self.persistenceState['moroutes'] = True

            if scope in ['all', 'mtroutes']:
                # Persist mtroutes configuration
                path = '%s/%s.router-mtroutes' % (self.config.store_path,
                                                  profile)
                self.log.info(
                    'Persisting current MTRoutingTable to [%s] profile in %s',
                    profile, path)

                fh = open(path, 'w')
                # Write configuration with datetime stamp
                fh.write('Persisted on %s [Jasmin %s]\n' %
                         (time.strftime("%c"), jasmin.get_release()))
                fh.write(
                    pickle.dumps(self.mt_routing_table, self.pickleProtocol))
                fh.close()

                # Set persistance state to True
                self.persistenceState['mtroutes'] = True

            if scope in ['all', 'mointerceptors']:
                # Persist mointerceptors configuration
                path = '%s/%s.router-mointerceptors' % (self.config.store_path,
                                                        profile)
                self.log.info(
                    'Persisting current MOInterceptionTable to [%s] profile in %s',
                    profile, path)

                fh = open(path, 'w')
                # Write configuration with datetime stamp
                fh.write('Persisted on %s [Jasmin %s]\n' %
                         (time.strftime("%c"), jasmin.get_release()))
                fh.write(
                    pickle.dumps(self.mo_interception_table,
                                 self.pickleProtocol))
                fh.close()

                # Set persistance state to True
                self.persistenceState['mointerceptors'] = True

            if scope in ['all', 'mtinterceptors']:
                # Persist mtinterceptors configuration
                path = '%s/%s.router-mtinterceptors' % (self.config.store_path,
                                                        profile)
                self.log.info(
                    'Persisting current MTInterceptionTable to [%s] profile in %s',
                    profile, path)

                fh = open(path, 'w')
                # Write configuration with datetime stamp
                fh.write('Persisted on %s [Jasmin %s]\n' %
                         (time.strftime("%c"), jasmin.get_release()))
                fh.write(
                    pickle.dumps(self.mt_interception_table,
                                 self.pickleProtocol))
                fh.close()

                # Set persistance state to True
                self.persistenceState['mtinterceptors'] = True

        except IOError:
            self.log.error('Cannot persist to %s', path)
            return False
        except Exception, e:
            self.log.error(
                'Unknown error occurred while persisting configuration: %s', e)
            return False

        return True
Beispiel #4
0
class RouterPB(pb.Avatar):
    def __init__(self, RouterPBConfig, persistenceTimer=True):
        self.config = RouterPBConfig
        self.persistenceTimer = None
        self.avatar = None

        # Set up a dedicated logger
        self.log = logging.getLogger(LOG_CATEGORY)
        if len(self.log.handlers) != 1:
            self.log.setLevel(self.config.log_level)
            handler = TimedRotatingFileHandler(filename=self.config.log_file,
                                               when=self.config.log_rotate)
            formatter = logging.Formatter(self.config.log_format, self.config.log_date_format)
            handler.setFormatter(formatter)
            self.log.addHandler(handler)
            self.log.propagate = False

        # Set pickleProtocol
        self.pickleProtocol = self.config.pickle_protocol

        # Init routing-related objects
        self.mo_routing_table = MORoutingTable()
        self.mt_routing_table = MTRoutingTable()
        self.users = []
        self.groups = []

        # Init interception-related objects
        self.mo_interception_table = MOInterceptionTable()
        self.mt_interception_table = MTInterceptionTable()

        if persistenceTimer:
            # Activate persistenceTimer, used for persisting users and groups whenever critical updates
            # occured
            self.activatePersistenceTimer()

        # Persistence flag, accessed through perspective_is_persisted
        self.persistenceState = {'users': True, 'groups': True, 'moroutes': True, 'mtroutes': True}

        self.log.info('Router configured and ready.')

    def setAvatar(self, avatar):
        if isinstance(avatar, str):
            self.log.info('Authenticated Avatar: %s', avatar)
        else:
            self.log.info('Anonymous connection')

        self.avatar = avatar

    @defer.inlineCallbacks
    def addAmqpBroker(self, amqpBroker):
        self.amqpBroker = amqpBroker
        self.log.info('Added amqpBroker to RouterPB')

        if not self.amqpBroker.connected:
            self.log.warn('AMQP Broker channel is not yet ready, waiting for it to become ready.')
            yield self.amqpBroker.channelReady
            self.log.info("AMQP Broker channel is ready now, let's go !")

        # Subscribe to deliver.sm.* queues
        yield self.amqpBroker.chan.exchange_declare(exchange='messaging', type='topic')
        consumerTag = 'RouterPB-delivers'
        routingKey = 'deliver.sm.*'
        queueName = 'RouterPB_deliver_sm_all'  # A local queue to RouterPB
        yield self.amqpBroker.named_queue_declare(queue=queueName)
        yield self.amqpBroker.chan.queue_bind(queue=queueName, exchange="messaging", routing_key=routingKey)
        yield self.amqpBroker.chan.basic_consume(queue=queueName, no_ack=False, consumer_tag=consumerTag)
        self.deliver_sm_q = yield self.amqpBroker.client.queue(consumerTag)
        self.deliver_sm_q.get().addCallback(self.deliver_sm_callback).addErrback(self.deliver_sm_errback)
        self.log.info('RouterPB is consuming from routing key: %s', routingKey)

        # Subscribe to bill_request.submit_sm_resp.* queues
        yield self.amqpBroker.chan.exchange_declare(exchange='billing', type='topic')
        consumerTag = 'RouterPB-billrequests'
        routingKey = 'bill_request.submit_sm_resp.*'
        queueName = 'RouterPB_bill_request_submit_sm_resp_all'  # A local queue to RouterPB
        yield self.amqpBroker.named_queue_declare(queue=queueName)
        yield self.amqpBroker.chan.queue_bind(queue=queueName, exchange="billing", routing_key=routingKey)
        yield self.amqpBroker.chan.basic_consume(queue=queueName, no_ack=False, consumer_tag=consumerTag)
        self.bill_request_submit_sm_resp_q = yield self.amqpBroker.client.queue(consumerTag)
        self.bill_request_submit_sm_resp_q.get().addCallback(
            self.bill_request_submit_sm_resp_callback).addErrback(
            self.bill_request_submit_sm_resp_errback)
        self.log.info('RouterPB is consuming from routing key: %s', routingKey)

    @defer.inlineCallbacks
    def rejectMessage(self, message):
        yield self.amqpBroker.chan.basic_reject(delivery_tag=message.delivery_tag, requeue=0)

    @defer.inlineCallbacks
    def ackMessage(self, message):
        yield self.amqpBroker.chan.basic_ack(message.delivery_tag)

    def activatePersistenceTimer(self):
        if self.persistenceTimer and self.persistenceTimer.active():
            self.log.debug('Reseting persistenceTimer with %ss', self.config.persistence_timer_secs)
            self.persistenceTimer.reset(self.config.persistence_timer_secs)
        else:
            self.log.debug('Activating persistenceTimer with %ss', self.config.persistence_timer_secs)
            self.persistenceTimer = reactor.callLater(
                self.config.persistence_timer_secs,
                self.persistenceTimerExpired
            )

    def cancelPersistenceTimer(self):
        if self.persistenceTimer and self.persistenceTimer.active():
            self.log.debug('Cancelling persistenceTimer')
            self.persistenceTimer.cancel()
            self.persistenceTimer = None

    def persistenceTimerExpired(self):
        'This is run every self.config.persistence_timer_secs seconds'
        self.log.debug('persistenceTimerExpired called')

        # If at least one user have its quotas updated, then persist
        # groups and users to disk
        for u in self.users:
            if u.mt_credential.quotas_updated:
                self.log.info('Detected a user quota update, users and groups will be persisted.')
                self.perspective_persist(scope='groups')
                self.perspective_persist(scope='users')
                u.mt_credential.quotas_updated = False
                self.log.debug('Persisted successfully')
                break

        self.activatePersistenceTimer()

    @defer.inlineCallbacks
    def deliver_sm_callback(self, message):
        """This callback is a queue listener
        It will only decide where to send the input message and republish it to the routedConnector
        The consumer will execute the remaining job of final delivery
        c.f. test_router.DeliverSmDeliveryTestCases for use cases
        """
        msgid = message.content.properties['message-id']
        scid = message.content.properties['headers']['connector-id']
        concatenated = message.content.properties['headers']['concatenated']
        will_be_concatenated = message.content.properties['headers']['will_be_concatenated']
        routable = pickle.loads(message.content.body)
        self.log.debug("Callbacked a deliver_sm with a DeliverSmPDU[%s] (?): %s", msgid, routable.pdu)

        # @todo: Implement MO throttling here, same as in
        # jasmin.managers.listeners.SMPPClientSMListener.submit_sm_callback
        self.deliver_sm_q.get().addCallback(self.deliver_sm_callback).addErrback(self.deliver_sm_errback)

        # Routing
        route = self.getMORoutingTable().getRouteFor(routable)
        if route is None:
            self.log.info("No route matched this DeliverSmPDU with scid:%s and msgid:%s", scid, msgid)
            yield self.rejectMessage(message)
        else:
            # Get connector from selected route
            self.log.debug("RouterPB selected %s for this SubmitSmPDU", route)
            if repr(route) == 'FailoverMORoute':
                # The failover route will return all connectors, we don't care about
                #   connectors statuses, this will be the thrower responsability
                routedConnectors = route.getConnectors()
                route_type = 'failover'
            else:
                routedConnectors = [route.getConnector()]
                route_type = 'simple'

            # Smpps will not route any concatenated content, it must instead route
            # multiparted messages
            # Only http connector needs concatenated content
            if concatenated and routedConnectors[0].type != 'http':
                self.log.debug(
                    "DeliverSmPDU [msgid:%s] not routed because its content is concatenated and the routedConnector is not http: %s",
                    msgid, routedConnectors[0].type)
                yield self.rejectMessage(message)

            # Http will not route any multipart messages, it must instead route
            # concatenated messages
            # Only smpps connector needs multipart content
            elif will_be_concatenated and routedConnectors[0].type == 'http':
                self.log.debug(
                    "DeliverSmPDU [msgid:%s] not routed because there will be a one concatenated message for all parts",
                    msgid)
                yield self.rejectMessage(message)

            else:
                if len(routedConnectors) == 1:
                    self.log.debug("Connector '%s'(%s) is set to be a route for this DeliverSmPDU",
                                   routedConnectors[0].cid, routedConnectors[0].type)
                else:
                    self.log.debug("%s %s connectors (failover route) are set to be a route for this DeliverSmPDU",
                                   len(routedConnectors), routedConnectors[0].type)
                yield self.ackMessage(message)

                # Enqueue DeliverSm for delivery through publishing it to deliver_sm_thrower.(type)
                content = RoutedDeliverSmContent(routable.pdu, msgid, scid, routedConnectors, route_type)
                self.log.debug("Publishing RoutedDeliverSmContent [msgid:%s] in deliver_sm_thrower.%s",
                               msgid, routedConnectors[0].type)
                yield self.amqpBroker.publish(exchange='messaging', routing_key='deliver_sm_thrower.%s' %
                                                                                routedConnectors[0].type,
                                              content=content)

    def deliver_sm_errback(self, error):
        """It appears that when closing a queue with the close() method it errbacks with
        a txamqp.queue.Closed exception, didnt find a clean way to stop consuming a queue
        without errbacking here so this is a workaround to make it clean, it can be considered
        as a @TODO requiring knowledge of the queue api behaviour
        """
        if error.check(Closed) is None:
            # @todo: implement this errback
            # For info, this errback is called whenever:
            # - an error has occured inside deliver_sm_callback
            self.log.error("Error in deliver_sm_errback: %s", error)

    @defer.inlineCallbacks
    def bill_request_submit_sm_resp_callback(self, message):
        """This callback is a queue listener
        """
        bid = message.content.properties['message-id']
        amount = float(message.content.properties['headers']['amount'])
        uid = message.content.properties['headers']['user-id']
        self.log.debug("Callbacked a bill_request_submit_sm_resp [uid:%s] [amount:%s] [related-bid:%s]",
                       uid, amount, bid)

        self.bill_request_submit_sm_resp_q.get().addCallback(
            self.bill_request_submit_sm_resp_callback).addErrback(
            self.bill_request_submit_sm_resp_errback)

        _user = self.getUser(uid)
        if _user is None:
            self.log.error("User [uid:%s] not found, billing request [bid:%s] rejected", uid, bid)
            yield self.rejectMessage(message)
        elif _user.mt_credential.getQuota('balance') is not None:
            if _user.mt_credential.getQuota('balance') < amount:
                self.log.error(
                    'User [uid:%s] have no sufficient balance (%s/%s) for this billing [bid:%s] request: rejected',
                    uid, _user.mt_credential.getQuota('balance'), amount, bid)
                yield self.rejectMessage(message)
            else:
                _user.mt_credential.updateQuota('balance', -amount)
                self.log.info('User [uid:%s] charged for amount: %s (bid:%s)', uid, amount, bid)
                yield self.ackMessage(message)

    def bill_request_submit_sm_resp_errback(self, error):
        """It appears that when closing a queue with the close() method it errbacks with
        a txamqp.queue.Closed exception, didnt find a clean way to stop consuming a queue
        without errbacking here so this is a workaround to make it clean, it can be considered
        as a @TODO requiring knowledge of the queue api behaviour
        """
        if error.check(Closed) is None:
            # @todo: implement this errback
            # For info, this errback is called whenever:
            # - an error has occured inside deliver_sm_callback
            self.log.error("Error in bill_request_submit_sm_resp_errback: %s", error)
            self.log.critical("User were not charged !")

    def getMOInterceptionTable(self):
        return self.mo_interception_table

    def getMTInterceptionTable(self):
        return self.mt_interception_table

    def getMORoutingTable(self):
        return self.mo_routing_table

    def getMTRoutingTable(self):
        return self.mt_routing_table

    def authenticateUser(self, username, password, return_pickled=False):
        """Authenticate a user agains username and password and return user object or None
        """
        # Find user having correct username/password
        for _user in self.users:
            if _user.username == username and _user.password == md5(password).digest():
                self.log.debug('authenticateUser [username:%s] returned a User', username)

                # Check if user's group is enabled
                _group = self.getGroup(_user.group.gid)
                if _group is not None and not _group.enabled:
                    self.log.info('authenticateUser [username:%s] returned None (group %s is disabled)',
                                  username, _user.group)
                    return None

                # Check if user is enabled
                if not _user.enabled:
                    self.log.info('authenticateUser [username:%s] returned None (user is disabled)',
                                  username)
                    return None

                # If user/group are enabled:
                if return_pickled:
                    return pickle.dumps(_user, self.pickleProtocol)
                else:
                    return _user

        self.log.info('authenticateUser [username:%s] returned None', username)
        return None

    def chargeUserForSubmitSms(self, user, bill, submit_sm_count=1, requirements=None):
        """Will charge the user using the bill object after checking requirements
        """
        if requirements is None:
            # Default:
            requirements = []

        # Check if User is already existent in Router ?
        _user = self.getUser(user.uid)
        if _user is None:
            self.log.error("User [uid:%s] not found for charging", user.uid)

        # Verify user-defined requirements
        for requirement in requirements:
            if not requirement['condition']:
                self.log.warn(requirement['error_message'])
                return None

        # Charge _user
        if (bill.getAmount('submit_sm') * submit_sm_count > 0
            and _user.mt_credential.getQuota('balance') is not None):
            if _user.mt_credential.getQuota('balance') < bill.getAmount('submit_sm') * submit_sm_count:
                self.log.info('User [uid:%s] have no sufficient balance (%s) for submit_sm charging: %s',
                              user.uid, _user.mt_credential.getQuota('balance'),
                              bill.getAmount('submit_sm') * submit_sm_count)
                return None
            _user.mt_credential.updateQuota('balance', -(bill.getAmount('submit_sm') * submit_sm_count))
            self.log.info('User [uid:%s] charged for submit_sm amount: %s',
                          user.uid, bill.getAmount('submit_sm') * submit_sm_count)
        # Decrement counts
        if (bill.getAction('decrement_submit_sm_count') * submit_sm_count > 0
            and _user.mt_credential.getQuota('submit_sm_count') is not None):
            if _user.mt_credential.getQuota('submit_sm_count') < bill.getAction(
                    'decrement_submit_sm_count') * submit_sm_count:
                self.log.info('User [uid:%s] have no sufficient submit_sm_count (%s) for submit_sm charging: %s',
                              user.uid, _user.mt_credential.getQuota('submit_sm_count'),
                              bill.getAction('decrement_submit_sm_count') * submit_sm_count)
                return None
            _user.mt_credential.updateQuota(
                'submit_sm_count',
                -(bill.getAction('decrement_submit_sm_count') * submit_sm_count))
            self.log.info('User\'s [uid:%s] submit_sm_count decremented for submit_sm: %s',
                          user.uid, bill.getAction('decrement_submit_sm_count') * submit_sm_count)

        return True

    def getUser(self, uid):
        for _user in self.users:
            if str(_user.uid) == str(uid):
                self.log.debug('getUser [uid:%s] returned a User', uid)
                return _user

        self.log.debug('getUser [uid:%s] returned None', uid)
        return None

    def getGroup(self, gid):
        for _group in self.groups:
            if str(_group.gid) == str(gid):
                self.log.debug('getGroup [gid:%s] returned a Group', gid)
                return _group

        self.log.debug('getGroup [gid:%s] returned None', gid)
        return None

    def getMOInterceptor(self, order):
        mointerceptors = self.mo_interception_table.getAll()

        for e in mointerceptors:
            if order == e.keys()[0]:
                self.log.debug('getMOInterceptor [order:%s] returned a MOInterceptor', order)
                return e[order]

        self.log.debug('getMOInterceptor [order:%s] returned None', order)
        return None

    def getMTInterceptor(self, order):
        mtinterceptors = self.mt_interception_table.getAll()

        for e in mtinterceptors:
            if order == e.keys()[0]:
                self.log.debug('getMTInterceptor [order:%s] returned a MTInterceptor', order)
                return e[order]

        self.log.debug('getMTInterceptor [order:%s] returned None', order)
        return None

    def getMORoute(self, order):
        moroutes = self.mo_routing_table.getAll()

        for e in moroutes:
            if order == e.keys()[0]:
                self.log.debug('getMORoute [order:%s] returned a MORoute', order)
                return e[order]

        self.log.debug('getMORoute [order:%s] returned None', order)
        return None

    def getMTRoute(self, order):
        mtroutes = self.mt_routing_table.getAll()

        for e in mtroutes:
            if order == e.keys()[0]:
                self.log.debug('getMTRoute [order:%s] returned a MTRoute', order)
                return e[order]

        self.log.debug('getMTRoute [order:%s] returned None', order)
        return None

    def perspective_version_release(self):
        return jasmin.get_release()

    def perspective_version(self):
        return jasmin.get_version()

    def perspective_persist(self, profile='jcli-prod', scope='all'):
        try:
            if scope in ['all', 'groups']:
                # Persist groups configuration
                path = '%s/%s.router-groups' % (self.config.store_path, profile)
                self.log.info('Persisting current Groups configuration to [%s] profile in %s',
                              profile, path)

                fh = open(path, 'w')
                # Write configuration with datetime stamp
                fh.write('Persisted on %s [Jasmin %s]\n' % (time.strftime("%c"), jasmin.get_release()))
                fh.write(pickle.dumps(self.groups, self.pickleProtocol))
                fh.close()

                # Set persistance state to True
                self.persistenceState['groups'] = True

            if scope in ['all', 'users']:
                # Persist users configuration
                path = '%s/%s.router-users' % (self.config.store_path, profile)
                self.log.info('Persisting current Users configuration to [%s] profile in %s',
                              profile, path)

                fh = open(path, 'w')
                # Write configuration with datetime stamp
                fh.write('Persisted on %s [Jasmin %s]\n' % (time.strftime("%c"), jasmin.get_release()))
                fh.write(pickle.dumps(self.users, self.pickleProtocol))
                fh.close()

                # Set persistance state to True
                self.persistenceState['users'] = True
                for u in self.users:
                    u.mt_credential.quotas_updated = False

            if scope in ['all', 'moroutes']:
                # Persist moroutes configuration
                path = '%s/%s.router-moroutes' % (self.config.store_path, profile)
                self.log.info('Persisting current MORoutingTable to [%s] profile in %s', profile, path)

                fh = open(path, 'w')
                # Write configuration with datetime stamp
                fh.write('Persisted on %s [Jasmin %s]\n' % (time.strftime("%c"), jasmin.get_release()))
                fh.write(pickle.dumps(self.mo_routing_table, self.pickleProtocol))
                fh.close()

                # Set persistance state to True
                self.persistenceState['moroutes'] = True

            if scope in ['all', 'mtroutes']:
                # Persist mtroutes configuration
                path = '%s/%s.router-mtroutes' % (self.config.store_path, profile)
                self.log.info('Persisting current MTRoutingTable to [%s] profile in %s', profile, path)

                fh = open(path, 'w')
                # Write configuration with datetime stamp
                fh.write('Persisted on %s [Jasmin %s]\n' % (time.strftime("%c"), jasmin.get_release()))
                fh.write(pickle.dumps(self.mt_routing_table, self.pickleProtocol))
                fh.close()

                # Set persistance state to True
                self.persistenceState['mtroutes'] = True

            if scope in ['all', 'mointerceptors']:
                # Persist mointerceptors configuration
                path = '%s/%s.router-mointerceptors' % (self.config.store_path, profile)
                self.log.info('Persisting current MOInterceptionTable to [%s] profile in %s',
                              profile, path)

                fh = open(path, 'w')
                # Write configuration with datetime stamp
                fh.write('Persisted on %s [Jasmin %s]\n' % (time.strftime("%c"), jasmin.get_release()))
                fh.write(pickle.dumps(self.mo_interception_table, self.pickleProtocol))
                fh.close()

                # Set persistance state to True
                self.persistenceState['mointerceptors'] = True

            if scope in ['all', 'mtinterceptors']:
                # Persist mtinterceptors configuration
                path = '%s/%s.router-mtinterceptors' % (self.config.store_path, profile)
                self.log.info('Persisting current MTInterceptionTable to [%s] profile in %s',
                              profile, path)

                fh = open(path, 'w')
                # Write configuration with datetime stamp
                fh.write('Persisted on %s [Jasmin %s]\n' % (time.strftime("%c"), jasmin.get_release()))
                fh.write(pickle.dumps(self.mt_interception_table, self.pickleProtocol))
                fh.close()

                # Set persistance state to True
                self.persistenceState['mtinterceptors'] = True

        except IOError:
            self.log.error('Cannot persist to %s', path)
            return False
        except Exception as e:
            self.log.error('Unknown error occurred while persisting configuration: %s', e)
            return False

        return True

    def perspective_load(self, profile='jcli-prod', scope='all'):
        try:
            if scope in ['all', 'groups']:
                # Load groups configuration
                path = '%s/%s.router-groups' % (self.config.store_path, profile)
                self.log.info('Loading/Activating [%s] profile Groups configuration from %s',
                              profile, path)

                # Load configuration from file
                fh = open(path, 'r')
                lines = fh.readlines()
                fh.close()

                # Init migrator
                cf = ConfigurationMigrator(context='groups', header=lines[0], data=''.join(lines[1:]))

                # Remove current configuration
                self.log.info('Removing current Groups (%d)', len(self.groups))
                self.perspective_group_remove_all()

                # Adding new groups
                self.groups = cf.getMigratedData()
                self.log.info('Added new Groups (%d)', len(self.groups))

                # Set persistance state to True
                self.persistenceState['groups'] = True

            if scope in ['all', 'users']:
                # Load users configuration
                path = '%s/%s.router-users' % (self.config.store_path, profile)
                self.log.info('Loading/Activating [%s] profile Users configuration from %s',
                              profile, path)

                # Load configuration from file
                fh = open(path, 'r')
                lines = fh.readlines()
                fh.close()

                # Init migrator
                cf = ConfigurationMigrator(context='users', header=lines[0], data=''.join(lines[1:]))

                # Remove current configuration
                self.log.info('Removing current Users (%d)', len(self.users))
                self.perspective_user_remove_all()

                # Adding new users
                self.users = cf.getMigratedData()
                self.log.info('Added new Users (%d)', len(self.users))

                # Set persistance state to True
                self.persistenceState['users'] = True
                for u in self.users:
                    u.mt_credential.quotas_updated = False

            if scope in ['all', 'mointerceptors']:
                # Load mointerceptors configuration
                path = '%s/%s.router-mointerceptors' % (self.config.store_path, profile)
                self.log.info('Loading/Activating [%s] profile MO Interceptors configuration from %s',
                              profile, path)

                # Load configuration from file
                fh = open(path, 'r')
                lines = fh.readlines()
                fh.close()

                # Init migrator
                cf = ConfigurationMigrator(context='mointerceptors',
                                           header=lines[0], data=''.join(lines[1:]))

                # Adding new MO Interceptors
                self.mo_interception_table = cf.getMigratedData()
                self.log.info('Added new MOInterceptionTable with %d routes',
                              len(self.mo_interception_table.getAll()))

                # Set persistance state to True
                self.persistenceState['mointerceptors'] = True

            if scope in ['all', 'mtinterceptors']:
                # Load mtinterceptors configuration
                path = '%s/%s.router-mtinterceptors' % (self.config.store_path, profile)
                self.log.info('Loading/Activating [%s] profile MT Interceptors configuration from %s',
                              profile, path)

                # Load configuration from file
                fh = open(path, 'r')
                lines = fh.readlines()
                fh.close()

                # Init migrator
                cf = ConfigurationMigrator(context='mtinterceptors',
                                           header=lines[0], data=''.join(lines[1:]))

                # Adding new MT Interceptors
                self.mt_interception_table = cf.getMigratedData()
                self.log.info('Added new MTInterceptionTable with %d routes',
                              len(self.mt_interception_table.getAll()))

                # Set persistance state to True
                self.persistenceState['mtinterceptors'] = True

            if scope in ['all', 'moroutes']:
                # Load moroutes configuration
                path = '%s/%s.router-moroutes' % (self.config.store_path, profile)
                self.log.info('Loading/Activating [%s] profile MO Routes configuration from %s',
                              profile, path)

                # Load configuration from file
                fh = open(path, 'r')
                lines = fh.readlines()
                fh.close()

                # Init migrator
                cf = ConfigurationMigrator(context='moroutes',
                                           header=lines[0], data=''.join(lines[1:]))

                # Adding new MO Routes
                self.mo_routing_table = cf.getMigratedData()
                self.log.info('Added new MORoutingTable with %d routes',
                              len(self.mo_routing_table.getAll()))

                # Set persistance state to True
                self.persistenceState['moroutes'] = True

            if scope in ['all', 'mtroutes']:
                # Load mtroutes configuration
                path = '%s/%s.router-mtroutes' % (self.config.store_path, profile)
                self.log.info('Loading/Activating [%s] profile MT Routes configuration from %s',
                              profile, path)

                # Load configuration from file
                fh = open(path, 'r')
                lines = fh.readlines()
                fh.close()

                # Init migrator
                cf = ConfigurationMigrator(context='mtroutes',
                                           header=lines[0], data=''.join(lines[1:]))

                # Adding new MT Routes
                self.mt_routing_table = cf.getMigratedData()
                self.log.info('Added new MTRoutingTable with %d routes',
                              len(self.mt_routing_table.getAll()))

                # Set persistance state to True
                self.persistenceState['mtroutes'] = True

        except IOError as e:
            self.log.error('Cannot load configuration from %s: %s', path, str(e))
            return False
        except Exception as e:
            self.log.error('Unknown error occurred while loading configuration: %s', e)
            return False

        return True

    def perspective_is_persisted(self):
        for _, v in self.persistenceState.iteritems():
            if not v:
                return False

        return True

    def perspective_user_add(self, user):
        user = pickle.loads(user)
        self.log.debug('Adding a User: %s', user)
        self.log.info('Adding a User (id:%s)', user.uid)

        # Check if group exists
        foundGroup = False
        for _group in self.groups:
            if _group.gid == user.group.gid:
                foundGroup = True
        if not foundGroup:
            self.log.error("Group with id:%s not found, cancelling user adding.", user.group.gid)
            return False

        # Replace existant users
        for _user in self.users:
            if user.uid == _user.uid or user.username == _user.username:
                self.log.warn('User (id:%s) already existant, will be replaced !', user.uid)
                self.users.remove(_user)

                # Save old CnxStatus in new user
                user.setCnxStatus(_user.getCnxStatus())
                break

        self.users.append(user)

        # Set persistance state to False (pending for persistance)
        self.persistenceState['users'] = False

        return True

    def perspective_user_authenticate(self, username, password):
        self.log.debug('Authenticating with username:%s and password:%s', username, password)
        self.log.info('Authentication request with username:%s', username)

        return self.authenticateUser(username, password, True)

    def perspective_user_enable(self, uid):
        self.log.info('Enabling a User (id:%s)', uid)

        # Enable user
        for _user in self.users:
            if uid == _user.uid:
                _user.enable()

                # Set persistance state to False (pending for persistance)
                self.persistenceState['users'] = False
                return True

        self.log.error("User with id:%s not found, not enabling it.", uid)
        return False

    def perspective_user_disable(self, uid):
        self.log.info('Disabling a User (id:%s)', uid)

        # Disable user
        for _user in self.users:
            if uid == _user.uid:
                _user.disable()

                # Set persistance state to False (pending for persistance)
                self.persistenceState['users'] = False
                return True

        self.log.error("User with id:%s not found, not disabling it.", uid)
        return False

    def perspective_user_remove(self, uid):
        self.log.info('Removing a User (id:%s)', uid)

        # Remove user
        for _user in self.users:
            if uid == _user.uid:
                self.users.remove(_user)

                # Set persistance state to False (pending for persistance)
                self.persistenceState['users'] = False
                return True

        self.log.error("User with id:%s not found, not removing it.", uid)
        return False

    def perspective_user_remove_all(self):
        self.log.info('Removing all users')

        self.users = []

        # Set persistance state to False (pending for persistance)
        self.persistenceState['users'] = False

        return True

    def perspective_user_get_all(self, gid=None):
        self.log.info('Getting all users')
        self.log.debug('Getting all users: %s', self.users)

        if gid is None:
            return pickle.dumps(self.users)
        else:
            _users = []
            for _user in self.users:
                if _user.group.gid == gid:
                    _users.append(_user)

            return pickle.dumps(_users)

    def perspective_user_set_quota(self, uid, cred, quota, value):
        self.log.info('Setting a User (id:%s) quota: %s/%s %s', uid, cred, quota, value)

        # Find user
        for _user in self.users:
            if uid == _user.uid:
                try:
                    if not hasattr(_user, cred):
                        raise Exception("Invalid cred: %s", cred)
                    else:
                        _cred = getattr(_user, cred)

                    if quota not in _cred.quotas:
                        raise Exception("Unknown quota: %s", quota)

                    # Update the quota
                    _cred.setQuota(quota, value)

                except Exception as e:
                    self.log.error("Error updating user (id:%s): %s", uid, e)
                    return False
                else:
                    # Successful update !
                    # Set persistance state to False (pending for persistance)
                    self.persistenceState['users'] = False
                    return True

        self.log.error("User with id:%s not found, not updating it.", uid)

        return False

    def perspective_user_update_quota(self, uid, cred, quota, value):
        self.log.info('Updating a User (id:%s) quota: %s/%s %s', uid, cred, quota, value)

        # Find user
        for _user in self.users:
            if uid == _user.uid:
                try:
                    if not hasattr(_user, cred):
                        raise Exception("Invalid cred: %s", cred)
                    else:
                        _cred = getattr(_user, cred)

                    if quota not in _cred.quotas:
                        raise Exception("Unknown quota: %s", quota)

                    # Update the quota
                    _cred.updateQuota(quota, value)

                except Exception as e:
                    self.log.error("Error updating user (id:%s): %s", uid, e)
                    return False
                else:
                    # Successful update !
                    # Set persistance state to False (pending for persistance)
                    self.persistenceState['users'] = False
                    return True

        self.log.error("User with id:%s not found, not updating it.", uid)

        return False

    def perspective_group_add(self, group):
        group = pickle.loads(group)
        self.log.info('Adding a Group (id:%s)', group.gid)

        # Replace existant groups
        for _group in self.groups:
            if group.gid == _group.gid:
                self.groups.remove(_group)
                break

        self.groups.append(group)

        # Set persistance state to False (pending for persistance)
        self.persistenceState['groups'] = False

        return True

    def perspective_group_enable(self, gid):
        self.log.info('Enabling a Group (id:%s)', gid)

        # Enable group
        for _group in self.groups:
            if gid == _group.gid:
                _group.enable()

                # Set persistance state to False (pending for persistance)
                self.persistenceState['groups'] = False
                return True

        self.log.error("Group with id:%s not found, not enabling it.", gid)
        return False

    def perspective_group_disable(self, gid):
        self.log.info('Disabling a Group (id:%s)', gid)

        # Disable group
        for _group in self.groups:
            if gid == _group.gid:
                _group.disable()

                # Set persistance state to False (pending for persistance)
                self.persistenceState['groups'] = False
                return True

        self.log.error("Group with id:%s not found, not disabling it.", gid)
        return False

    def perspective_group_remove(self, gid):
        self.log.info('Removing a Group (id:%s)', gid)

        # Remove group
        for _group in self.groups:
            if gid == _group.gid:
                # Remove users from this group
                _users = copy(self.users)
                for _user in _users:
                    if _user.group.gid == _group.gid:
                        self.log.info('Removing a User (id:%s) from the Group (id:%s)', _user.uid, gid)
                        self.users.remove(_user)

                # Safely remove this group
                self.groups.remove(_group)
                return True

        self.log.error("Group with id:%s not found, not removing it.", gid)

        # Set persistance state to False (pending for persistance)
        self.persistenceState['groups'] = False

        return False

    def perspective_group_remove_all(self):
        self.log.info('Removing all groups')

        # Remove group
        for _group in self.groups:
            self.log.debug('Removing a Group: %s', _group)
            self.log.info('Removing a Group (id:%s)', _group.gid)

            # Remove users from this group
            _users = copy(self.users)
            for _user in _users:
                if _user.group.gid == _group.gid:
                    self.log.info('Removing a User (id:%s) from the Group (id:%s)',
                                  _user.uid, _group.gid)
                    self.users.remove(_user)

        self.groups = []

        # Set persistance state to False (pending for persistance)
        self.persistenceState['groups'] = False

        return True

    def perspective_group_get_all(self):
        self.log.info('Getting all groups')
        self.log.debug('Getting all groups: %s', self.groups)

        return pickle.dumps(self.groups)

    def perspective_mtinterceptor_add(self, interceptor, order):
        interceptor = pickle.loads(interceptor)
        self.log.debug('Adding a MT Interceptor, order = %s, interceptor = %s', order, interceptor)
        self.log.info('Adding a MT Interceptor with order %s', order)

        try:
            self.mt_interception_table.add(interceptor, order)
        except InvalidInterceptionTableParameterError as e:
            self.log.error('Cannot add MT Interceptor: %s', str(e))
            return False
        except Exception as e:
            self.log.error('Unknown error occurred while adding MT Interceptor: %s', str(e))
            return False

        # Set persistance state to False (pending for persistance)
        self.persistenceState['mtinterceptors'] = False

        return True

    def perspective_mointerceptor_add(self, interceptor, order):
        interceptor = pickle.loads(interceptor)
        self.log.debug('Adding a MO Interceptor, order = %s, interceptor = %s', order, interceptor)
        self.log.info('Adding a MO Interceptor with order %s', order)

        try:
            self.mo_interception_table.add(interceptor, order)
        except InvalidInterceptionTableParameterError as e:
            self.log.error('Cannot add MO Interceptor: %s', str(e))
            return False
        except Exception as e:
            self.log.error('Unknown error occurred while adding MO Interceptor: %s', str(e))
            return False

        # Set persistance state to False (pending for persistance)
        self.persistenceState['mointerceptors'] = False

        return True

    def perspective_mointerceptor_remove(self, order):
        self.log.info('Removing MO Interceptor [%s]', order)

        # Set persistance state to False (pending for persistance)
        self.persistenceState['mointerceptors'] = False

        return self.mo_interception_table.remove(order)

    def perspective_mtinterceptor_remove(self, order):
        self.log.info('Removing MT Interceptor [%s]', order)

        # Set persistance state to False (pending for persistance)
        self.persistenceState['mtinterceptors'] = False

        return self.mt_interception_table.remove(order)

    def perspective_mtinterceptor_flush(self):
        self.log.info('Flushing MT Interceptor table')

        # Set persistance state to False (pending for persistance)
        self.persistenceState['mtinterceptors'] = False

        return self.mt_interception_table.flush()

    def perspective_mointerceptor_flush(self):
        self.log.info('Flushing MO Interceptor table')

        # Set persistance state to False (pending for persistance)
        self.persistenceState['mointerceptors'] = False

        return self.mo_interception_table.flush()

    def perspective_mtinterceptor_get_all(self):
        self.log.info('Getting MT Interceptor table')

        interceptors = self.mt_interception_table.getAll()
        self.log.debug('Getting MT Interceptor table: %s', interceptors)

        return pickle.dumps(interceptors, self.pickleProtocol)

    def perspective_mointerceptor_get_all(self):
        self.log.info('Getting MO Interceptor table')

        interceptors = self.mo_interception_table.getAll()
        self.log.debug('Getting MO Interceptor table: %s', interceptors)

        return pickle.dumps(interceptors, self.pickleProtocol)

    def perspective_mtroute_add(self, route, order):
        route = pickle.loads(route)
        self.log.debug('Adding a MT Route, order = %s, route = %s', order, route)
        self.log.info('Adding a MT Route with order %s', order)

        try:
            self.mt_routing_table.add(route, order)
        except InvalidRoutingTableParameterError as e:
            self.log.error('Cannot add MT Route: %s', str(e))
            return False
        except Exception as e:
            self.log.error('Unknown error occurred while adding MT Route: %s', str(e))
            return False

        # Set persistance state to False (pending for persistance)
        self.persistenceState['mtroutes'] = False

        return True

    def perspective_moroute_add(self, route, order):
        route = pickle.loads(route)
        self.log.debug('Adding a MO Route, order = %s, route = %s', order, route)
        self.log.info('Adding a MO Route with order %s', order)

        try:
            self.mo_routing_table.add(route, order)
        except InvalidRoutingTableParameterError as e:
            self.log.error('Cannot add MO Route: %s', str(e))
            return False
        except Exception as e:
            self.log.error('Unknown error occurred while adding MO Route: %s', str(e))
            return False

        # Set persistance state to False (pending for persistance)
        self.persistenceState['moroutes'] = False

        return True

    def perspective_moroute_remove(self, order):
        self.log.info('Removing MO Route [%s]', order)

        # Set persistance state to False (pending for persistance)
        self.persistenceState['moroutes'] = False

        return self.mo_routing_table.remove(order)

    def perspective_mtroute_remove(self, order):
        self.log.info('Removing MT Route [%s]', order)

        # Set persistance state to False (pending for persistance)
        self.persistenceState['mtroutes'] = False

        return self.mt_routing_table.remove(order)

    def perspective_mtroute_flush(self):
        self.log.info('Flushing MT Routing table')

        # Set persistance state to False (pending for persistance)
        self.persistenceState['mtroutes'] = False

        return self.mt_routing_table.flush()

    def perspective_moroute_flush(self):
        self.log.info('Flushing MO Routing table')

        # Set persistance state to False (pending for persistance)
        self.persistenceState['moroutes'] = False

        return self.mo_routing_table.flush()

    def perspective_mtroute_get_all(self):
        self.log.info('Getting MT Routing table')

        routes = self.mt_routing_table.getAll()
        self.log.debug('Getting MT Routing table: %s', routes)

        return pickle.dumps(routes, self.pickleProtocol)

    def perspective_moroute_get_all(self):
        self.log.info('Getting MO Routing table')

        routes = self.mo_routing_table.getAll()
        self.log.debug('Getting MO Routing table: %s', routes)

        return pickle.dumps(routes, self.pickleProtocol)
Beispiel #5
0
class RouterPB(pb.Avatar):
    def setConfig(self, RouterPBConfig, persistenceTimer=True):
        self.config = RouterPBConfig
        self.persistenceTimer = None

        # Set up a dedicated logger
        self.log = logging.getLogger(LOG_CATEGORY)
        if len(self.log.handlers) != 1:
            self.log.setLevel(self.config.log_level)
            handler = TimedRotatingFileHandler(filename=self.config.log_file,
                                               when=self.config.log_rotate)
            formatter = logging.Formatter(self.config.log_format, self.config.log_date_format)
            handler.setFormatter(formatter)
            self.log.addHandler(handler)
            self.log.propagate = False

        # Set pickleProtocol
        self.pickleProtocol = self.config.pickle_protocol

        # Init routing-related objects
        self.mo_routing_table = MORoutingTable()
        self.mt_routing_table = MTRoutingTable()
        self.users = []
        self.groups = []

        # Init interception-related objects
        self.mo_interception_table = MOInterceptionTable()
        self.mt_interception_table = MTInterceptionTable()

        if persistenceTimer:
            # Activate persistenceTimer, used for persisting users and groups whenever critical updates
            # occured
            self.activatePersistenceTimer()

        # Persistence flag, accessed through perspective_is_persisted
        self.persistenceState = {'users': True, 'groups': True, 'moroutes': True, 'mtroutes': True}

        self.log.info('Router configured and ready.')

    def setAvatar(self, avatar):
        if isinstance(avatar, str):
            self.log.info('Authenticated Avatar: %s', avatar)
        else:
            self.log.info('Anonymous connection')

        self.avatar = avatar

    @defer.inlineCallbacks
    def addAmqpBroker(self, amqpBroker):
        self.amqpBroker = amqpBroker
        self.log.info('Added amqpBroker to RouterPB')

        if not self.amqpBroker.connected:
            self.log.warn('AMQP Broker channel is not yet ready, waiting for it to become ready.')
            yield self.amqpBroker.channelReady
            self.log.info("AMQP Broker channel is ready now, let's go !")

        # Subscribe to deliver.sm.* queues
        yield self.amqpBroker.chan.exchange_declare(exchange='messaging', type='topic')
        consumerTag = 'RouterPB-delivers'
        routingKey = 'deliver.sm.*'
        queueName = 'RouterPB_deliver_sm_all' # A local queue to RouterPB
        yield self.amqpBroker.named_queue_declare(queue=queueName)
        yield self.amqpBroker.chan.queue_bind(queue=queueName, exchange="messaging", routing_key=routingKey)
        yield self.amqpBroker.chan.basic_consume(queue=queueName, no_ack=False, consumer_tag=consumerTag)
        self.deliver_sm_q = yield self.amqpBroker.client.queue(consumerTag)
        self.deliver_sm_q.get().addCallback(self.deliver_sm_callback).addErrback(self.deliver_sm_errback)
        self.log.info('RouterPB is consuming from routing key: %s', routingKey)

        # Subscribe to bill_request.submit_sm_resp.* queues
        yield self.amqpBroker.chan.exchange_declare(exchange='billing', type='topic')
        consumerTag = 'RouterPB-billrequests'
        routingKey = 'bill_request.submit_sm_resp.*'
        queueName = 'RouterPB_bill_request_submit_sm_resp_all' # A local queue to RouterPB
        yield self.amqpBroker.named_queue_declare(queue=queueName)
        yield self.amqpBroker.chan.queue_bind(queue=queueName, exchange="billing", routing_key=routingKey)
        yield self.amqpBroker.chan.basic_consume(queue=queueName, no_ack=False, consumer_tag=consumerTag)
        self.bill_request_submit_sm_resp_q = yield self.amqpBroker.client.queue(consumerTag)
        self.bill_request_submit_sm_resp_q.get().addCallback(
            self.bill_request_submit_sm_resp_callback).addErrback(
                self.bill_request_submit_sm_resp_errback)
        self.log.info('RouterPB is consuming from routing key: %s', routingKey)

    @defer.inlineCallbacks
    def rejectMessage(self, message):
        yield self.amqpBroker.chan.basic_reject(delivery_tag=message.delivery_tag, requeue=0)
    @defer.inlineCallbacks
    def ackMessage(self, message):
        yield self.amqpBroker.chan.basic_ack(message.delivery_tag)

    def activatePersistenceTimer(self):
        if self.persistenceTimer and self.persistenceTimer.active():
            self.log.debug('Reseting persistenceTimer with %ss', self.config.persistence_timer_secs)
            self.persistenceTimer.reset(self.config.persistence_timer_secs)
        else:
            self.log.debug('Activating persistenceTimer with %ss', self.config.persistence_timer_secs)
            self.persistenceTimer = reactor.callLater(
                self.config.persistence_timer_secs,
                self.persistenceTimerExpired
            )

    def cancelPersistenceTimer(self):
        if self.persistenceTimer and self.persistenceTimer.active():
            self.log.debug('Cancelling persistenceTimer')
            self.persistenceTimer.cancel()
            self.persistenceTimer = None

    def persistenceTimerExpired(self):
        'This is run every self.config.persistence_timer_secs seconds'
        self.log.debug('persistenceTimerExpired called')

        # If at least one user have its quotas updated, then persist
        # groups and users to disk
        for u in self.users:
            if u.mt_credential.quotas_updated:
                self.log.info('Detected a user quota update, users and groups will be persisted.')
                self.perspective_persist(scope='groups')
                self.perspective_persist(scope='users')
                u.mt_credential.quotas_updated = False
                self.log.debug('Persisted successfully')
                break

        self.activatePersistenceTimer()

    @defer.inlineCallbacks
    def deliver_sm_callback(self, message):
        """This callback is a queue listener
        It will only decide where to send the input message and republish it to the routedConnector
        The consumer will execute the remaining job of final delivery
        c.f. test_router.DeliverSmDeliveryTestCases for use cases
        """
        msgid = message.content.properties['message-id']
        scid = message.content.properties['headers']['connector-id']
        concatenated = message.content.properties['headers']['concatenated']
        will_be_concatenated = message.content.properties['headers']['will_be_concatenated']
        connector = Connector(scid)
        DeliverSmPDU = pickle.loads(message.content.body)
        self.log.debug("Callbacked a deliver_sm with a DeliverSmPDU[%s] (?): %s", msgid, DeliverSmPDU)

        # @todo: Implement MO throttling here, same as in
        # jasmin.managers.listeners.SMPPClientSMListener.submit_sm_callback
        self.deliver_sm_q.get().addCallback(self.deliver_sm_callback).addErrback(self.deliver_sm_errback)

        # Routing
        routable = RoutableDeliverSm(DeliverSmPDU, connector)
        route = self.getMORoutingTable().getRouteFor(routable)
        if route is None:
            self.log.debug("No route matched this DeliverSmPDU with scid:%s and msgid:%s", scid, msgid)
            yield self.rejectMessage(message)
        else:
            # Get connector from selected route
            self.log.debug("RouterPB selected %s for this SubmitSmPDU", route)
            routedConnector = route.getConnector()

            # Smpps will not route any concatenated content, it must instead route
            # multiparted messages
            # Only http connector needs concatenated content
            if concatenated and routedConnector.type != 'http':
                self.log.debug("DeliverSmPDU [msgid:%s] not routed because its content is concatenated and the routedConnector is not http: %s",
                               msgid, routedConnector.type)
                yield self.rejectMessage(message)

            # Http will not route any multipart messages, it must instead route
            # concatenated messages
            # Only smpps connector needs multipart content
            elif will_be_concatenated and routedConnector.type == 'http':
                self.log.debug("DeliverSmPDU [msgid:%s] not routed because there will be a one concatenated message for all parts",
                               msgid)
                yield self.rejectMessage(message)

            else:
                self.log.debug("Connector '%s'(%s) is set to be a route for this DeliverSmPDU",
                               routedConnector.cid, routedConnector.type)
                yield self.ackMessage(message)

                # Enqueue DeliverSm for delivery through publishing it to deliver_sm_thrower.(type)
                content = RoutedDeliverSmContent(DeliverSmPDU, msgid, scid, routedConnector)
                self.log.debug("Publishing RoutedDeliverSmContent [msgid:%s] in deliver_sm_thrower.%s with [dcid:%s]",
                               msgid, routedConnector.type, routedConnector.cid)
                yield self.amqpBroker.publish(exchange='messaging', routing_key='deliver_sm_thrower.%s' %
                                              routedConnector.type, content=content)

    def deliver_sm_errback(self, error):
        """It appears that when closing a queue with the close() method it errbacks with
        a txamqp.queue.Closed exception, didnt find a clean way to stop consuming a queue
        without errbacking here so this is a workaround to make it clean, it can be considered
        as a @TODO requiring knowledge of the queue api behaviour
        """
        if error.check(Closed) == None:
            #@todo: implement this errback
            # For info, this errback is called whenever:
            # - an error has occured inside deliver_sm_callback
            self.log.error("Error in deliver_sm_errback: %s", error)

    @defer.inlineCallbacks
    def bill_request_submit_sm_resp_callback(self, message):
        """This callback is a queue listener
        """
        bid = message.content.properties['message-id']
        amount = float(message.content.properties['headers']['amount'])
        uid = message.content.properties['headers']['user-id']
        self.log.debug("Callbacked a bill_request_submit_sm_resp [uid:%s] [amount:%s] [related-bid:%s]",
                       uid, amount, bid)

        self.bill_request_submit_sm_resp_q.get().addCallback(
            self.bill_request_submit_sm_resp_callback).addErrback(
                self.bill_request_submit_sm_resp_errback)

        _user = self.getUser(uid)
        if _user is None:
            self.log.error("User [uid:%s] not found, billing request [bid:%s] rejected", uid, bid)
            yield self.rejectMessage(message)
        elif _user.mt_credential.getQuota('balance') is not None:
            if _user.mt_credential.getQuota('balance') < amount:
                self.log.error('User [uid:%s] have no sufficient balance (%s/%s) for this billing [bid:%s] request: rejected',
                               uid, _user.mt_credential.getQuota('balance'), amount, bid)
                yield self.rejectMessage(message)
            else:
                _user.mt_credential.updateQuota('balance', -amount)
                self.log.info('User [uid:%s] charged for amount: %s (bid:%s)', uid, amount, bid)
                yield self.ackMessage(message)

    def bill_request_submit_sm_resp_errback(self, error):
        """It appears that when closing a queue with the close() method it errbacks with
        a txamqp.queue.Closed exception, didnt find a clean way to stop consuming a queue
        without errbacking here so this is a workaround to make it clean, it can be considered
        as a @TODO requiring knowledge of the queue api behaviour
        """
        if error.check(Closed) == None:
            #@todo: implement this errback
            # For info, this errback is called whenever:
            # - an error has occured inside deliver_sm_callback
            self.log.error("Error in bill_request_submit_sm_resp_errback: %s", error)
            self.log.critical("User were not charged !")

    def getMOInterceptionTable(self):
        return self.mo_interception_table
    def getMTInterceptionTable(self):
        return self.mt_interception_table
    def getMORoutingTable(self):
        return self.mo_routing_table
    def getMTRoutingTable(self):
        return self.mt_routing_table
    def authenticateUser(self, username, password, return_pickled=False):
        """Authenticate a user agains username and password and return user object or None
        """
        for _user in self.users:
            if _user.username == username and _user.password == md5(password).digest():
                self.log.debug('authenticateUser [username:%s] returned a User', username)
                if return_pickled:
                    return pickle.dumps(_user, self.pickleProtocol)
                else:
                    return _user

        self.log.debug('authenticateUser [username:%s] returned None', username)
        return None
    def chargeUserForSubmitSms(self, user, bill, submit_sm_count=1, requirements=None):
        """Will charge the user using the bill object after checking requirements
        """
        if requirements is None:
            # Default:
            requirements = []

        # Check if User is already existent in Router ?
        _user = self.getUser(user.uid)
        if _user is None:
            self.log.error("User [uid:%s] not found for charging", user.uid)

        # Verify user-defined requirements
        for requirement in requirements:
            if not requirement['condition']:
                self.log.warn(requirement['error_message'])
                return None

        # Charge _user
        if (bill.getAmount('submit_sm') * submit_sm_count > 0
                and _user.mt_credential.getQuota('balance') is not None):
            if _user.mt_credential.getQuota('balance') < bill.getAmount('submit_sm') * submit_sm_count:
                self.log.info('User [uid:%s] have no sufficient balance (%s) for submit_sm charging: %s',
                              user.uid, _user.mt_credential.getQuota('balance'),
                              bill.getAmount('submit_sm') * submit_sm_count)
                return None
            _user.mt_credential.updateQuota('balance', -(bill.getAmount('submit_sm')*submit_sm_count))
            self.log.info('User [uid:%s] charged for submit_sm amount: %s',
                          user.uid, bill.getAmount('submit_sm') * submit_sm_count)
        # Decrement counts
        if (bill.getAction('decrement_submit_sm_count') * submit_sm_count > 0
                and _user.mt_credential.getQuota('submit_sm_count') is not None):
            if _user.mt_credential.getQuota('submit_sm_count') < bill.getAction('decrement_submit_sm_count') * submit_sm_count:
                self.log.info('User [uid:%s] have no sufficient submit_sm_count (%s) for submit_sm charging: %s',
                              user.uid, _user.mt_credential.getQuota('submit_sm_count'),
                              bill.getAction('decrement_submit_sm_count') * submit_sm_count)
                return None
            _user.mt_credential.updateQuota(
                'submit_sm_count',
                -(bill.getAction('decrement_submit_sm_count') * submit_sm_count))
            self.log.info('User\'s [uid:%s] submit_sm_count decremented for submit_sm: %s',
                          user.uid, bill.getAction('decrement_submit_sm_count') * submit_sm_count)

        return True

    def getUser(self, uid):
        for _user in self.users:
            if str(_user.uid) == str(uid):
                self.log.debug('getUser [uid:%s] returned a User', uid)
                return _user

        self.log.debug('getUser [uid:%s] returned None', uid)
        return None

    def getGroup(self, gid):
        for _group in self.groups:
            if str(_group.gid) == str(gid):
                self.log.debug('getGroup [gid:%s] returned a Group', gid)
                return _group

        self.log.debug('getGroup [gid:%s] returned None', gid)
        return None

    def getMOInterceptor(self, order):
        mointerceptors = self.mo_interception_table.getAll()

        for e in mointerceptors:
            if order == e.keys()[0]:
                self.log.debug('getMOInterceptor [order:%s] returned a MOInterceptor', order)
                return e[order]

        self.log.debug('getMOInterceptor [order:%s] returned None', order)
        return None

    def getMTInterceptor(self, order):
        mtinterceptors = self.mt_interception_table.getAll()

        for e in mtinterceptors:
            if order == e.keys()[0]:
                self.log.debug('getMTInterceptor [order:%s] returned a MTInterceptor', order)
                return e[order]

        self.log.debug('getMTInterceptor [order:%s] returned None', order)
        return None

    def getMORoute(self, order):
        moroutes = self.mo_routing_table.getAll()

        for e in moroutes:
            if order == e.keys()[0]:
                self.log.debug('getMORoute [order:%s] returned a MORoute', order)
                return e[order]

        self.log.debug('getMORoute [order:%s] returned None', order)
        return None

    def getMTRoute(self, order):
        mtroutes = self.mt_routing_table.getAll()

        for e in mtroutes:
            if order == e.keys()[0]:
                self.log.debug('getMTRoute [order:%s] returned a MTRoute', order)
                return e[order]

        self.log.debug('getMTRoute [order:%s] returned None', order)
        return None

    def perspective_version_release(self):
        return jasmin.get_release()

    def perspective_persist(self, profile='jcli-prod', scope='all'):
        try:
            if scope in ['all', 'groups']:
                # Persist groups configuration
                path = '%s/%s.router-groups' % (self.config.store_path, profile)
                self.log.info('Persisting current Groups configuration to [%s] profile in %s',
                              profile, path)

                fh = open(path, 'w')
                # Write configuration with datetime stamp
                fh.write('Persisted on %s [Jasmin %s]\n' % (time.strftime("%c"), jasmin.get_release()))
                fh.write(pickle.dumps(self.groups, self.pickleProtocol))
                fh.close()

                # Set persistance state to True
                self.persistenceState['groups'] = True

            if scope in ['all', 'users']:
                # Persist users configuration
                path = '%s/%s.router-users' % (self.config.store_path, profile)
                self.log.info('Persisting current Users configuration to [%s] profile in %s',
                              profile, path)

                fh = open(path, 'w')
                # Write configuration with datetime stamp
                fh.write('Persisted on %s [Jasmin %s]\n' % (time.strftime("%c"), jasmin.get_release()))
                fh.write(pickle.dumps(self.users, self.pickleProtocol))
                fh.close()

                # Set persistance state to True
                self.persistenceState['users'] = True
                for u in self.users:
                    u.mt_credential.quotas_updated = False

            if scope in ['all', 'moroutes']:
                # Persist moroutes configuration
                path = '%s/%s.router-moroutes' % (self.config.store_path, profile)
                self.log.info('Persisting current MORoutingTable to [%s] profile in %s', profile, path)

                fh = open(path, 'w')
                # Write configuration with datetime stamp
                fh.write('Persisted on %s [Jasmin %s]\n' % (time.strftime("%c"), jasmin.get_release()))
                fh.write(pickle.dumps(self.mo_routing_table, self.pickleProtocol))
                fh.close()

                # Set persistance state to True
                self.persistenceState['moroutes'] = True

            if scope in ['all', 'mtroutes']:
                # Persist mtroutes configuration
                path = '%s/%s.router-mtroutes' % (self.config.store_path, profile)
                self.log.info('Persisting current MTRoutingTable to [%s] profile in %s', profile, path)

                fh = open(path, 'w')
                # Write configuration with datetime stamp
                fh.write('Persisted on %s [Jasmin %s]\n' % (time.strftime("%c"), jasmin.get_release()))
                fh.write(pickle.dumps(self.mt_routing_table, self.pickleProtocol))
                fh.close()

                # Set persistance state to True
                self.persistenceState['mtroutes'] = True

            if scope in ['all', 'mointerceptors']:
                # Persist mointerceptors configuration
                path = '%s/%s.router-mointerceptors' % (self.config.store_path, profile)
                self.log.info('Persisting current MOInterceptionTable to [%s] profile in %s',
                              profile, path)

                fh = open(path, 'w')
                # Write configuration with datetime stamp
                fh.write('Persisted on %s [Jasmin %s]\n' % (time.strftime("%c"), jasmin.get_release()))
                fh.write(pickle.dumps(self.mo_interception_table, self.pickleProtocol))
                fh.close()

                # Set persistance state to True
                self.persistenceState['mointerceptors'] = True

            if scope in ['all', 'mtinterceptors']:
                # Persist mtinterceptors configuration
                path = '%s/%s.router-mtinterceptors' % (self.config.store_path, profile)
                self.log.info('Persisting current MTInterceptionTable to [%s] profile in %s',
                              profile, path)

                fh = open(path, 'w')
                # Write configuration with datetime stamp
                fh.write('Persisted on %s [Jasmin %s]\n' % (time.strftime("%c"), jasmin.get_release()))
                fh.write(pickle.dumps(self.mt_interception_table, self.pickleProtocol))
                fh.close()

                # Set persistance state to True
                self.persistenceState['mtinterceptors'] = True

        except IOError:
            self.log.error('Cannot persist to %s', path)
            return False
        except Exception, e:
            self.log.error('Unknown error occurred while persisting configuration: %s', e)
            return False

        return True
Beispiel #6
0
class RouterPB(pb.Avatar):
    def __init__(self, RouterPBConfig, persistenceTimer=True):
        self.config = RouterPBConfig
        self.persistenceTimer = None
        self.avatar = None

        # Set up a dedicated logger
        self.log = logging.getLogger(LOG_CATEGORY)
        if len(self.log.handlers) != 1:
            self.log.setLevel(self.config.log_level)
            if 'stdout' in self.config.log_file:
                handler = logging.StreamHandler(sys.stdout)
            else:
                handler = TimedRotatingFileHandler(
                    filename=self.config.log_file, when=self.config.log_rotate)
            formatter = logging.Formatter(self.config.log_format,
                                          self.config.log_date_format)
            handler.setFormatter(formatter)
            self.log.addHandler(handler)
            self.log.propagate = False

        # Set pickleProtocol
        self.pickleProtocol = self.config.pickle_protocol

        # Init routing-related objects
        self.mo_routing_table = MORoutingTable()
        self.mt_routing_table = MTRoutingTable()
        self.users = []
        self.groups = []

        # Init interception-related objects
        self.mo_interception_table = MOInterceptionTable()
        self.mt_interception_table = MTInterceptionTable()

        if persistenceTimer:
            # Activate persistenceTimer, used for persisting users and groups whenever critical updates
            # occured
            self.activatePersistenceTimer()

        # Persistence flag, accessed through perspective_is_persisted
        self.persistenceState = {
            'users': True,
            'groups': True,
            'moroutes': True,
            'mtroutes': True
        }

        self.log.info('Router configured and ready.')

    def setAvatar(self, avatar):
        if isinstance(avatar, str):
            self.log.info('Authenticated Avatar: %s', avatar)
        else:
            self.log.info('Anonymous connection')

        self.avatar = avatar

    @defer.inlineCallbacks
    def addAmqpBroker(self, amqpBroker):
        self.amqpBroker = amqpBroker
        self.log.info('Added amqpBroker to RouterPB')

        if not self.amqpBroker.connected:
            self.log.warn(
                'AMQP Broker channel is not yet ready, waiting for it to become ready.'
            )
            yield self.amqpBroker.channelReady
            self.log.info("AMQP Broker channel is ready now, let's go !")

        # Subscribe to deliver.sm.* queues
        yield self.amqpBroker.chan.exchange_declare(exchange='messaging',
                                                    type='topic')
        consumerTag = 'RouterPB-delivers'
        routingKey = 'deliver.sm.*'
        queueName = 'RouterPB_deliver_sm_all'  # A local queue to RouterPB
        yield self.amqpBroker.named_queue_declare(queue=queueName)
        yield self.amqpBroker.chan.queue_bind(queue=queueName,
                                              exchange="messaging",
                                              routing_key=routingKey)
        yield self.amqpBroker.chan.basic_consume(queue=queueName,
                                                 no_ack=False,
                                                 consumer_tag=consumerTag)
        self.deliver_sm_q = yield self.amqpBroker.client.queue(consumerTag)
        self.deliver_sm_q.get().addCallback(
            self.deliver_sm_callback).addErrback(self.deliver_sm_errback)
        self.log.info('RouterPB is consuming from routing key: %s', routingKey)

        # Subscribe to bill_request.submit_sm_resp.* queues
        yield self.amqpBroker.chan.exchange_declare(exchange='billing',
                                                    type='topic')
        consumerTag = 'RouterPB-billrequests'
        routingKey = 'bill_request.submit_sm_resp.*'
        queueName = 'RouterPB_bill_request_submit_sm_resp_all'  # A local queue to RouterPB
        yield self.amqpBroker.named_queue_declare(queue=queueName)
        yield self.amqpBroker.chan.queue_bind(queue=queueName,
                                              exchange="billing",
                                              routing_key=routingKey)
        yield self.amqpBroker.chan.basic_consume(queue=queueName,
                                                 no_ack=False,
                                                 consumer_tag=consumerTag)
        self.bill_request_submit_sm_resp_q = yield self.amqpBroker.client.queue(
            consumerTag)
        self.bill_request_submit_sm_resp_q.get().addCallback(
            self.bill_request_submit_sm_resp_callback).addErrback(
                self.bill_request_submit_sm_resp_errback)
        self.log.info('RouterPB is consuming from routing key: %s', routingKey)

    @defer.inlineCallbacks
    def rejectMessage(self, message):
        yield self.amqpBroker.chan.basic_reject(
            delivery_tag=message.delivery_tag, requeue=0)

    @defer.inlineCallbacks
    def ackMessage(self, message):
        yield self.amqpBroker.chan.basic_ack(message.delivery_tag)

    def activatePersistenceTimer(self):
        if self.persistenceTimer and self.persistenceTimer.active():
            self.log.debug('Reseting persistenceTimer with %ss',
                           self.config.persistence_timer_secs)
            self.persistenceTimer.reset(self.config.persistence_timer_secs)
        else:
            self.log.debug('Activating persistenceTimer with %ss',
                           self.config.persistence_timer_secs)
            self.persistenceTimer = reactor.callLater(
                self.config.persistence_timer_secs,
                self.persistenceTimerExpired)

    def cancelPersistenceTimer(self):
        if self.persistenceTimer and self.persistenceTimer.active():
            self.log.debug('Cancelling persistenceTimer')
            self.persistenceTimer.cancel()
            self.persistenceTimer = None

    def persistenceTimerExpired(self):
        'This is run every self.config.persistence_timer_secs seconds'
        self.log.debug('persistenceTimerExpired called')

        # If at least one user have its quotas updated, then persist
        # groups and users to disk
        for u in self.users:
            if u.mt_credential.quotas_updated:
                self.log.info(
                    'Detected a user quota update, users and groups will be persisted.'
                )
                self.perspective_persist(scope='groups')
                self.perspective_persist(scope='users')
                u.mt_credential.quotas_updated = False
                self.log.debug('Persisted successfully')
                break

        self.activatePersistenceTimer()

    @defer.inlineCallbacks
    def deliver_sm_callback(self, message):
        """This callback is a queue listener
        It will only decide where to send the input message and republish it to the routedConnector
        The consumer will execute the remaining job of final delivery
        c.f. test_router.DeliverSmDeliveryTestCases for use cases
        """
        msgid = message.content.properties['message-id']
        scid = message.content.properties['headers']['connector-id']
        concatenated = message.content.properties['headers']['concatenated']
        will_be_concatenated = message.content.properties['headers'][
            'will_be_concatenated']
        routable = pickle.loads(message.content.body)
        self.log.debug(
            "Callbacked a deliver_sm with a DeliverSmPDU[%s] (?): %s", msgid,
            routable.pdu)

        # @todo: Implement MO throttling here, same as in
        # jasmin.managers.listeners.SMPPClientSMListener.submit_sm_callback
        self.deliver_sm_q.get().addCallback(
            self.deliver_sm_callback).addErrback(self.deliver_sm_errback)

        # Routing
        route = self.getMORoutingTable().getRouteFor(routable)
        if route is None:
            self.log.info(
                "No route matched this DeliverSmPDU with scid:%s and msgid:%s",
                scid, msgid)
            yield self.rejectMessage(message)
        else:
            # Get connector from selected route
            self.log.debug("RouterPB selected %s for this SubmitSmPDU", route)
            if repr(route) == 'FailoverMORoute':
                # The failover route will return all connectors, we don't care about
                #   connectors statuses, this will be the thrower responsability
                routedConnectors = route.getConnectors()
                route_type = 'failover'
            else:
                routedConnectors = [route.getConnector()]
                route_type = 'simple'

            # Smpps will not route any concatenated content, it must instead route
            # multiparted messages
            # Only http connector needs concatenated content
            if concatenated and routedConnectors[0]._type != 'http':
                self.log.debug(
                    "DeliverSmPDU [msgid:%s] not routed because its content is concatenated and the routedConnector is not http: %s",
                    msgid, routedConnectors[0]._type)
                yield self.rejectMessage(message)

            # Http will not route any multipart messages, it must instead route
            # concatenated messages
            # Only smpps connector needs multipart content
            elif will_be_concatenated and routedConnectors[0]._type == 'http':
                self.log.debug(
                    "DeliverSmPDU [msgid:%s] not routed because there will be a one concatenated message for all parts",
                    msgid)
                yield self.rejectMessage(message)

            else:
                if len(routedConnectors) == 1:
                    self.log.debug(
                        "Connector '%s'(%s) is set to be a route for this DeliverSmPDU",
                        routedConnectors[0].cid, routedConnectors[0]._type)
                else:
                    self.log.debug(
                        "%s %s connectors (failover route) are set to be a route for this DeliverSmPDU",
                        len(routedConnectors), routedConnectors[0]._type)
                yield self.ackMessage(message)

                # Enqueue DeliverSm for delivery through publishing it to deliver_sm_thrower.(type)
                content = RoutedDeliverSmContent(routable.pdu, msgid, scid,
                                                 routedConnectors, route_type)
                self.log.debug(
                    "Publishing RoutedDeliverSmContent [msgid:%s] in deliver_sm_thrower.%s",
                    msgid, routedConnectors[0]._type)
                yield self.amqpBroker.publish(
                    exchange='messaging',
                    routing_key='deliver_sm_thrower.%s' %
                    routedConnectors[0]._type,
                    content=content)

    def deliver_sm_errback(self, error):
        """It appears that when closing a queue with the close() method it errbacks with
        a txamqp.queue.Closed exception, didnt find a clean way to stop consuming a queue
        without errbacking here so this is a workaround to make it clean, it can be considered
        as a @TODO requiring knowledge of the queue api behaviour
        """
        if error.check(Closed) is None:
            # @todo: implement this errback
            # For info, this errback is called whenever:
            # - an error has occured inside deliver_sm_callback
            self.log.error("Error in deliver_sm_errback: %s", error)

    @defer.inlineCallbacks
    def bill_request_submit_sm_resp_callback(self, message):
        """This callback is a queue listener
        """
        bid = message.content.properties['message-id']
        amount = float(message.content.properties['headers']['amount'])
        uid = message.content.properties['headers']['user-id']
        self.log.debug(
            "Callbacked a bill_request_submit_sm_resp [uid:%s] [amount:%s] [related-bid:%s]",
            uid, amount, bid)

        self.bill_request_submit_sm_resp_q.get().addCallback(
            self.bill_request_submit_sm_resp_callback).addErrback(
                self.bill_request_submit_sm_resp_errback)

        _user = self.getUser(uid)
        if _user is None:
            self.log.error(
                "User [uid:%s] not found, billing request [bid:%s] rejected",
                uid, bid)
            yield self.rejectMessage(message)
        elif _user.mt_credential.getQuota('balance') is not None:
            if _user.mt_credential.getQuota('balance') < amount:
                self.log.error(
                    'User [uid:%s] have no sufficient balance (%s/%s) for this billing [bid:%s] request: rejected',
                    uid, _user.mt_credential.getQuota('balance'), amount, bid)
                yield self.rejectMessage(message)
            else:
                _user.mt_credential.updateQuota('balance', -amount)
                self.log.info('User [uid:%s] charged for amount: %s (bid:%s)',
                              uid, amount, bid)
                yield self.ackMessage(message)

    def bill_request_submit_sm_resp_errback(self, error):
        """It appears that when closing a queue with the close() method it errbacks with
        a txamqp.queue.Closed exception, didnt find a clean way to stop consuming a queue
        without errbacking here so this is a workaround to make it clean, it can be considered
        as a @TODO requiring knowledge of the queue api behaviour
        """
        if error.check(Closed) is None:
            # @todo: implement this errback
            # For info, this errback is called whenever:
            # - an error has occured inside deliver_sm_callback
            self.log.error("Error in bill_request_submit_sm_resp_errback: %s",
                           error)
            self.log.critical("User were not charged !")

    def getMOInterceptionTable(self):
        return self.mo_interception_table

    def getMTInterceptionTable(self):
        return self.mt_interception_table

    def getMORoutingTable(self):
        return self.mo_routing_table

    def getMTRoutingTable(self):
        return self.mt_routing_table

    def authenticateUser(self, username, password, return_pickled=False):
        """Authenticate a user agains username and password and return user object or None
        """
        # Find user having correct username/password
        for _user in self.users:
            if _user.username == username and _user.password == md5(
                    password.encode('ascii')).digest():
                self.log.debug(
                    'authenticateUser [username:%s] returned a User', username)

                # Check if user's group is enabled
                _group = self.getGroup(_user.group.gid)
                if _group is not None and not _group.enabled:
                    self.log.info(
                        'authenticateUser [username:%s] returned None (group %s is disabled)',
                        username, _user.group)
                    return None

                # Check if user is enabled
                if not _user.enabled:
                    self.log.info(
                        'authenticateUser [username:%s] returned None (user is disabled)',
                        username)
                    return None

                # If user/group are enabled:
                if return_pickled:
                    return pickle.dumps(_user, self.pickleProtocol)
                else:
                    return _user

        self.log.info('authenticateUser [username:%s] returned None', username)
        return None

    def chargeUserForSubmitSms(self,
                               user,
                               bill,
                               submit_sm_count=1,
                               requirements=None):
        """Will charge the user using the bill object after checking requirements
        """
        if requirements is None:
            # Default:
            requirements = []

        # Check if User is already existent in Router ?
        _user = self.getUser(user.uid)
        if _user is None:
            self.log.error("User [uid:%s] not found for charging", user.uid)

        # Verify user-defined requirements
        for requirement in requirements:
            if not requirement['condition']:
                self.log.warn(requirement['error_message'])
                return None

        # Charge _user
        if (bill.getAmount('submit_sm') * submit_sm_count > 0
                and _user.mt_credential.getQuota('balance') is not None):
            if _user.mt_credential.getQuota(
                    'balance') < bill.getAmount('submit_sm') * submit_sm_count:
                self.log.info(
                    'User [uid:%s] have no sufficient balance (%s) for submit_sm charging: %s',
                    user.uid, _user.mt_credential.getQuota('balance'),
                    bill.getAmount('submit_sm') * submit_sm_count)
                return None
            _user.mt_credential.updateQuota(
                'balance', -(bill.getAmount('submit_sm') * submit_sm_count))
            self.log.info('User [uid:%s] charged for submit_sm amount: %s',
                          user.uid,
                          bill.getAmount('submit_sm') * submit_sm_count)
        # Decrement counts
        if (bill.getAction('decrement_submit_sm_count') * submit_sm_count > 0
                and _user.mt_credential.getQuota('submit_sm_count')
                is not None):
            if _user.mt_credential.getQuota(
                    'submit_sm_count'
            ) < bill.getAction('decrement_submit_sm_count') * submit_sm_count:
                self.log.info(
                    'User [uid:%s] have no sufficient submit_sm_count (%s) for submit_sm charging: %s',
                    user.uid, _user.mt_credential.getQuota('submit_sm_count'),
                    bill.getAction('decrement_submit_sm_count') *
                    submit_sm_count)
                return None
            _user.mt_credential.updateQuota(
                'submit_sm_count',
                -(bill.getAction('decrement_submit_sm_count') *
                  submit_sm_count))
            self.log.info(
                'User\'s [uid:%s] submit_sm_count decremented for submit_sm: %s',
                user.uid,
                bill.getAction('decrement_submit_sm_count') * submit_sm_count)

        return True

    def getUser(self, uid):
        for _user in self.users:
            if str(_user.uid) == str(uid):
                self.log.debug('getUser [uid:%s] returned a User', uid)
                return _user

        self.log.debug('getUser [uid:%s] returned None', uid)
        return None

    def getGroup(self, gid):
        for _group in self.groups:
            if str(_group.gid) == str(gid):
                self.log.debug('getGroup [gid:%s] returned a Group', gid)
                return _group

        self.log.debug('getGroup [gid:%s] returned None', gid)
        return None

    def getMOInterceptor(self, order):
        mointerceptors = self.mo_interception_table.getAll()

        for e in mointerceptors:
            if order == list(e)[0]:
                self.log.debug(
                    'getMOInterceptor [order:%s] returned a MOInterceptor',
                    order)
                return e[order]

        self.log.debug('getMOInterceptor [order:%s] returned None', order)
        return None

    def getMTInterceptor(self, order):
        mtinterceptors = self.mt_interception_table.getAll()

        for e in mtinterceptors:
            if order == list(e)[0]:
                self.log.debug(
                    'getMTInterceptor [order:%s] returned a MTInterceptor',
                    order)
                return e[order]

        self.log.debug('getMTInterceptor [order:%s] returned None', order)
        return None

    def getMORoute(self, order):
        moroutes = self.mo_routing_table.getAll()

        for e in moroutes:
            if order == list(e)[0]:
                self.log.debug('getMORoute [order:%s] returned a MORoute',
                               order)
                return e[order]

        self.log.debug('getMORoute [order:%s] returned None', order)
        return None

    def getMTRoute(self, order):
        mtroutes = self.mt_routing_table.getAll()

        for e in mtroutes:
            if order == list(e)[0]:
                self.log.debug('getMTRoute [order:%s] returned a MTRoute',
                               order)
                return e[order]

        self.log.debug('getMTRoute [order:%s] returned None', order)
        return None

    def perspective_version_release(self):
        return jasmin.get_release()

    def perspective_version(self):
        return jasmin.get_version()

    def perspective_persist(self, profile='jcli-prod', scope='all'):
        try:
            if scope in ['all', 'groups']:
                # Persist groups configuration
                path = '%s/%s.router-groups' % (self.config.store_path,
                                                profile)
                self.log.info(
                    'Persisting current Groups configuration to [%s] profile in %s',
                    profile, path)

                fh = open(path, 'wb')
                # Write configuration with datetime stamp
                fh.write(('Persisted on %s [Jasmin %s]\n' %
                          (time.strftime("%c"),
                           jasmin.get_release())).encode('ascii'))
                fh.write(pickle.dumps(self.groups, self.pickleProtocol))
                fh.close()

                # Set persistance state to True
                self.persistenceState['groups'] = True

            if scope in ['all', 'users']:
                # Persist users configuration
                path = '%s/%s.router-users' % (self.config.store_path, profile)
                self.log.info(
                    'Persisting current Users configuration to [%s] profile in %s',
                    profile, path)

                fh = open(path, 'wb')
                # Write configuration with datetime stamp
                fh.write(('Persisted on %s [Jasmin %s]\n' %
                          (time.strftime("%c"),
                           jasmin.get_release())).encode('ascii'))
                fh.write(pickle.dumps(self.users, self.pickleProtocol))
                fh.close()

                # Set persistance state to True
                self.persistenceState['users'] = True
                for u in self.users:
                    u.mt_credential.quotas_updated = False

            if scope in ['all', 'moroutes']:
                # Persist moroutes configuration
                path = '%s/%s.router-moroutes' % (self.config.store_path,
                                                  profile)
                self.log.info(
                    'Persisting current MORoutingTable to [%s] profile in %s',
                    profile, path)

                fh = open(path, 'wb')
                # Write configuration with datetime stamp
                fh.write(('Persisted on %s [Jasmin %s]\n' %
                          (time.strftime("%c"),
                           jasmin.get_release())).encode('ascii'))
                fh.write(
                    pickle.dumps(self.mo_routing_table, self.pickleProtocol))
                fh.close()

                # Set persistance state to True
                self.persistenceState['moroutes'] = True

            if scope in ['all', 'mtroutes']:
                # Persist mtroutes configuration
                path = '%s/%s.router-mtroutes' % (self.config.store_path,
                                                  profile)
                self.log.info(
                    'Persisting current MTRoutingTable to [%s] profile in %s',
                    profile, path)

                fh = open(path, 'wb')
                # Write configuration with datetime stamp
                fh.write(('Persisted on %s [Jasmin %s]\n' %
                          (time.strftime("%c"),
                           jasmin.get_release())).encode('ascii'))
                fh.write(
                    pickle.dumps(self.mt_routing_table, self.pickleProtocol))
                fh.close()

                # Set persistance state to True
                self.persistenceState['mtroutes'] = True

            if scope in ['all', 'mointerceptors']:
                # Persist mointerceptors configuration
                path = '%s/%s.router-mointerceptors' % (self.config.store_path,
                                                        profile)
                self.log.info(
                    'Persisting current MOInterceptionTable to [%s] profile in %s',
                    profile, path)

                fh = open(path, 'wb')
                # Write configuration with datetime stamp
                fh.write(('Persisted on %s [Jasmin %s]\n' %
                          (time.strftime("%c"),
                           jasmin.get_release())).encode('ascii'))
                fh.write(
                    pickle.dumps(self.mo_interception_table,
                                 self.pickleProtocol))
                fh.close()

                # Set persistance state to True
                self.persistenceState['mointerceptors'] = True

            if scope in ['all', 'mtinterceptors']:
                # Persist mtinterceptors configuration
                path = '%s/%s.router-mtinterceptors' % (self.config.store_path,
                                                        profile)
                self.log.info(
                    'Persisting current MTInterceptionTable to [%s] profile in %s',
                    profile, path)

                fh = open(path, 'wb')
                # Write configuration with datetime stamp
                fh.write(('Persisted on %s [Jasmin %s]\n' %
                          (time.strftime("%c"),
                           jasmin.get_release())).encode('ascii'))
                fh.write(
                    pickle.dumps(self.mt_interception_table,
                                 self.pickleProtocol))
                fh.close()

                # Set persistance state to True
                self.persistenceState['mtinterceptors'] = True

        except IOError:
            self.log.error('Cannot persist to %s', path)
            return False
        except Exception as e:
            self.log.error(
                'Unknown error occurred while persisting configuration: %s', e)
            return False

        return True

    def perspective_load(self, profile='jcli-prod', scope='all'):
        try:
            if scope in ['all', 'groups']:
                # Load groups configuration
                path = '%s/%s.router-groups' % (self.config.store_path,
                                                profile)
                self.log.info(
                    'Loading/Activating [%s] profile Groups configuration from %s',
                    profile, path)

                # Load configuration from file
                fh = open(path, 'rb')
                lines = fh.readlines()
                fh.close()

                # Init migrator
                cf = ConfigurationMigrator(context='groups',
                                           header=lines[0].decode('ascii'),
                                           data=b''.join(lines[1:]))

                # Remove current configuration
                self.log.info('Removing current Groups (%d)', len(self.groups))
                self.perspective_group_remove_all()

                # Adding new groups
                self.groups = cf.getMigratedData()
                self.log.info('Added new Groups (%d)', len(self.groups))

                # Set persistance state to True
                self.persistenceState['groups'] = True

            if scope in ['all', 'users']:
                # Load users configuration
                path = '%s/%s.router-users' % (self.config.store_path, profile)
                self.log.info(
                    'Loading/Activating [%s] profile Users configuration from %s',
                    profile, path)

                # Load configuration from file
                fh = open(path, 'rb')
                lines = fh.readlines()
                fh.close()

                # Init migrator
                cf = ConfigurationMigrator(context='users',
                                           header=lines[0].decode('ascii'),
                                           data=b''.join(lines[1:]))

                # Remove current configuration
                self.log.info('Removing current Users (%d)', len(self.users))
                self.perspective_user_remove_all()

                # Adding new users
                self.users = cf.getMigratedData()
                self.log.info('Added new Users (%d)', len(self.users))

                # Set persistance state to True
                self.persistenceState['users'] = True
                for u in self.users:
                    u.mt_credential.quotas_updated = False

            if scope in ['all', 'mointerceptors']:
                # Load mointerceptors configuration
                path = '%s/%s.router-mointerceptors' % (self.config.store_path,
                                                        profile)
                self.log.info(
                    'Loading/Activating [%s] profile MO Interceptors configuration from %s',
                    profile, path)

                # Load configuration from file
                fh = open(path, 'rb')
                lines = fh.readlines()
                fh.close()

                # Init migrator
                cf = ConfigurationMigrator(context='mointerceptors',
                                           header=lines[0].decode('ascii'),
                                           data=b''.join(lines[1:]))

                # Adding new MO Interceptors
                self.mo_interception_table = cf.getMigratedData()
                self.log.info('Added new MOInterceptionTable with %d routes',
                              len(self.mo_interception_table.getAll()))

                # Set persistance state to True
                self.persistenceState['mointerceptors'] = True

            if scope in ['all', 'mtinterceptors']:
                # Load mtinterceptors configuration
                path = '%s/%s.router-mtinterceptors' % (self.config.store_path,
                                                        profile)
                self.log.info(
                    'Loading/Activating [%s] profile MT Interceptors configuration from %s',
                    profile, path)

                # Load configuration from file
                fh = open(path, 'rb')
                lines = fh.readlines()
                fh.close()

                # Init migrator
                cf = ConfigurationMigrator(context='mtinterceptors',
                                           header=lines[0].decode('ascii'),
                                           data=b''.join(lines[1:]))

                # Adding new MT Interceptors
                self.mt_interception_table = cf.getMigratedData()
                self.log.info('Added new MTInterceptionTable with %d routes',
                              len(self.mt_interception_table.getAll()))

                # Set persistance state to True
                self.persistenceState['mtinterceptors'] = True

            if scope in ['all', 'moroutes']:
                # Load moroutes configuration
                path = '%s/%s.router-moroutes' % (self.config.store_path,
                                                  profile)
                self.log.info(
                    'Loading/Activating [%s] profile MO Routes configuration from %s',
                    profile, path)

                # Load configuration from file
                fh = open(path, 'rb')
                lines = fh.readlines()
                fh.close()

                # Init migrator
                cf = ConfigurationMigrator(context='moroutes',
                                           header=lines[0].decode('ascii'),
                                           data=b''.join(lines[1:]))

                # Adding new MO Routes
                self.mo_routing_table = cf.getMigratedData()
                self.log.info('Added new MORoutingTable with %d routes',
                              len(self.mo_routing_table.getAll()))

                # Set persistance state to True
                self.persistenceState['moroutes'] = True

            if scope in ['all', 'mtroutes']:
                # Load mtroutes configuration
                path = '%s/%s.router-mtroutes' % (self.config.store_path,
                                                  profile)
                self.log.info(
                    'Loading/Activating [%s] profile MT Routes configuration from %s',
                    profile, path)

                # Load configuration from file
                fh = open(path, 'rb')
                lines = fh.readlines()
                fh.close()

                # Init migrator
                cf = ConfigurationMigrator(context='mtroutes',
                                           header=lines[0].decode('ascii'),
                                           data=b''.join(lines[1:]))

                # Adding new MT Routes
                self.mt_routing_table = cf.getMigratedData()
                self.log.info('Added new MTRoutingTable with %d routes',
                              len(self.mt_routing_table.getAll()))

                # Set persistance state to True
                self.persistenceState['mtroutes'] = True

        except IOError as e:
            self.log.error('Cannot load configuration from %s: %s', path,
                           str(e))
            return False
        except Exception as e:
            self.log.error(
                'Unknown error occurred while loading configuration: %s', e)
            return False

        return True

    def perspective_is_persisted(self):
        for _, v in self.persistenceState.items():
            if not v:
                return False

        return True

    def perspective_user_add(self, user):
        user = pickle.loads(user)
        self.log.debug('Adding a User: %s', user)
        self.log.info('Adding a User (id:%s)', user.uid)

        # Check if group exists
        foundGroup = False
        for _group in self.groups:
            if _group.gid == user.group.gid:
                foundGroup = True
        if not foundGroup:
            self.log.error(
                "Group with id:%s not found, cancelling user adding.",
                user.group.gid)
            return False

        # Replace existant users
        for _user in self.users:
            if user.uid == _user.uid or user.username == _user.username:
                self.log.warn(
                    'User (id:%s) already existant, will be replaced !',
                    user.uid)
                self.users.remove(_user)

                # Save old CnxStatus in new user
                user.setCnxStatus(_user.getCnxStatus())
                break

        self.users.append(user)

        # Set persistance state to False (pending for persistance)
        self.persistenceState['users'] = False

        return True

    def perspective_user_authenticate(self, username, password):
        self.log.debug('Authenticating with username:%s and password:%s',
                       username, password)
        self.log.info('Authentication request with username:%s', username)

        return self.authenticateUser(username, password, True)

    def perspective_user_enable(self, uid):
        self.log.info('Enabling a User (id:%s)', uid)

        # Enable user
        for _user in self.users:
            if uid == _user.uid:
                _user.enable()

                # Set persistance state to False (pending for persistance)
                self.persistenceState['users'] = False
                return True

        self.log.error("User with id:%s not found, not enabling it.", uid)
        return False

    def perspective_user_disable(self, uid):
        self.log.info('Disabling a User (id:%s)', uid)

        # Disable user
        for _user in self.users:
            if uid == _user.uid:
                _user.disable()

                # Set persistance state to False (pending for persistance)
                self.persistenceState['users'] = False
                return True

        self.log.error("User with id:%s not found, not disabling it.", uid)
        return False

    def perspective_user_remove(self, uid):
        self.log.info('Removing a User (id:%s)', uid)

        # Remove user
        for _user in self.users:
            if uid == _user.uid:
                self.users.remove(_user)

                # Set persistance state to False (pending for persistance)
                self.persistenceState['users'] = False
                return True

        self.log.error("User with id:%s not found, not removing it.", uid)
        return False

    def perspective_user_remove_all(self):
        self.log.info('Removing all users')

        self.users = []

        # Set persistance state to False (pending for persistance)
        self.persistenceState['users'] = False

        return True

    def perspective_user_get_all(self, gid=None):
        self.log.info('Getting all users')
        self.log.debug('Getting all users: %s', self.users)

        if gid is None:
            return pickle.dumps(self.users)
        else:
            _users = []
            for _user in self.users:
                if _user.group.gid == gid:
                    _users.append(_user)

            return pickle.dumps(_users)

    def perspective_user_set_quota(self, uid, cred, quota, value):
        self.log.info('Setting a User (id:%s) quota: %s/%s %s', uid, cred,
                      quota, value)

        # Find user
        for _user in self.users:
            if uid == _user.uid:
                try:
                    if not hasattr(_user, cred):
                        raise Exception("Invalid cred: %s", cred)
                    else:
                        _cred = getattr(_user, cred)

                    if quota not in _cred.quotas:
                        raise Exception("Unknown quota: %s", quota)

                    # Update the quota
                    _cred.setQuota(quota, value)

                except Exception as e:
                    self.log.error("Error updating user (id:%s): %s", uid, e)
                    return False
                else:
                    # Successful update !
                    # Set persistance state to False (pending for persistance)
                    self.persistenceState['users'] = False
                    return True

        self.log.error("User with id:%s not found, not updating it.", uid)

        return False

    def perspective_user_update_quota(self, uid, cred, quota, value):
        self.log.info('Updating a User (id:%s) quota: %s/%s %s', uid, cred,
                      quota, value)

        # Find user
        for _user in self.users:
            if uid == _user.uid:
                try:
                    if not hasattr(_user, cred):
                        raise Exception("Invalid cred: %s", cred)
                    else:
                        _cred = getattr(_user, cred)

                    if quota not in _cred.quotas:
                        raise Exception("Unknown quota: %s", quota)

                    # Update the quota
                    _cred.updateQuota(quota, value)

                except Exception as e:
                    self.log.error("Error updating user (id:%s): %s", uid, e)
                    return False
                else:
                    # Successful update !
                    # Set persistance state to False (pending for persistance)
                    self.persistenceState['users'] = False
                    return True

        self.log.error("User with id:%s not found, not updating it.", uid)

        return False

    def perspective_group_add(self, group):
        group = pickle.loads(group)
        self.log.info('Adding a Group (id:%s)', group.gid)

        # Replace existant groups
        for _group in self.groups:
            if group.gid == _group.gid:
                self.groups.remove(_group)
                break

        self.groups.append(group)

        # Set persistance state to False (pending for persistance)
        self.persistenceState['groups'] = False

        return True

    def perspective_group_enable(self, gid):
        self.log.info('Enabling a Group (id:%s)', gid)

        # Enable group
        for _group in self.groups:
            if gid == _group.gid:
                _group.enable()

                # Set persistance state to False (pending for persistance)
                self.persistenceState['groups'] = False
                return True

        self.log.error("Group with id:%s not found, not enabling it.", gid)
        return False

    def perspective_group_disable(self, gid):
        self.log.info('Disabling a Group (id:%s)', gid)

        # Disable group
        for _group in self.groups:
            if gid == _group.gid:
                _group.disable()

                # Set persistance state to False (pending for persistance)
                self.persistenceState['groups'] = False
                return True

        self.log.error("Group with id:%s not found, not disabling it.", gid)
        return False

    def perspective_group_remove(self, gid):
        self.log.info('Removing a Group (id:%s)', gid)

        # Remove group
        for _group in self.groups:
            if gid == _group.gid:
                # Remove users from this group
                _users = copy(self.users)
                for _user in _users:
                    if _user.group.gid == _group.gid:
                        self.log.info(
                            'Removing a User (id:%s) from the Group (id:%s)',
                            _user.uid, gid)
                        self.users.remove(_user)

                # Safely remove this group
                self.groups.remove(_group)
                return True

        self.log.error("Group with id:%s not found, not removing it.", gid)

        # Set persistance state to False (pending for persistance)
        self.persistenceState['groups'] = False

        return False

    def perspective_group_remove_all(self):
        self.log.info('Removing all groups')

        # Remove group
        for _group in self.groups:
            self.log.debug('Removing a Group: %s', _group)
            self.log.info('Removing a Group (id:%s)', _group.gid)

            # Remove users from this group
            _users = copy(self.users)
            for _user in _users:
                if _user.group.gid == _group.gid:
                    self.log.info(
                        'Removing a User (id:%s) from the Group (id:%s)',
                        _user.uid, _group.gid)
                    self.users.remove(_user)

        self.groups = []

        # Set persistance state to False (pending for persistance)
        self.persistenceState['groups'] = False

        return True

    def perspective_group_get_all(self):
        self.log.info('Getting all groups')
        self.log.debug('Getting all groups: %s', self.groups)

        return pickle.dumps(self.groups)

    def perspective_mtinterceptor_add(self, interceptor, order):
        interceptor = pickle.loads(interceptor)
        self.log.debug('Adding a MT Interceptor, order = %s, interceptor = %s',
                       order, interceptor)
        self.log.info('Adding a MT Interceptor with order %s', order)

        try:
            self.mt_interception_table.add(interceptor, order)
        except InvalidInterceptionTableParameterError as e:
            self.log.error('Cannot add MT Interceptor: %s', str(e))
            return False
        except Exception as e:
            self.log.error(
                'Unknown error occurred while adding MT Interceptor: %s',
                str(e))
            return False

        # Set persistance state to False (pending for persistance)
        self.persistenceState['mtinterceptors'] = False

        return True

    def perspective_mointerceptor_add(self, interceptor, order):
        interceptor = pickle.loads(interceptor)
        self.log.debug('Adding a MO Interceptor, order = %s, interceptor = %s',
                       order, interceptor)
        self.log.info('Adding a MO Interceptor with order %s', order)

        try:
            self.mo_interception_table.add(interceptor, order)
        except InvalidInterceptionTableParameterError as e:
            self.log.error('Cannot add MO Interceptor: %s', str(e))
            return False
        except Exception as e:
            self.log.error(
                'Unknown error occurred while adding MO Interceptor: %s',
                str(e))
            return False

        # Set persistance state to False (pending for persistance)
        self.persistenceState['mointerceptors'] = False

        return True

    def perspective_mointerceptor_remove(self, order):
        self.log.info('Removing MO Interceptor [%s]', order)

        # Set persistance state to False (pending for persistance)
        self.persistenceState['mointerceptors'] = False

        return self.mo_interception_table.remove(order)

    def perspective_mtinterceptor_remove(self, order):
        self.log.info('Removing MT Interceptor [%s]', order)

        # Set persistance state to False (pending for persistance)
        self.persistenceState['mtinterceptors'] = False

        return self.mt_interception_table.remove(order)

    def perspective_mtinterceptor_flush(self):
        self.log.info('Flushing MT Interceptor table')

        # Set persistance state to False (pending for persistance)
        self.persistenceState['mtinterceptors'] = False

        return self.mt_interception_table.flush()

    def perspective_mointerceptor_flush(self):
        self.log.info('Flushing MO Interceptor table')

        # Set persistance state to False (pending for persistance)
        self.persistenceState['mointerceptors'] = False

        return self.mo_interception_table.flush()

    def perspective_mtinterceptor_get_all(self):
        self.log.info('Getting MT Interceptor table')

        interceptors = self.mt_interception_table.getAll()
        self.log.debug('Getting MT Interceptor table: %s', interceptors)

        return pickle.dumps(interceptors, self.pickleProtocol)

    def perspective_mointerceptor_get_all(self):
        self.log.info('Getting MO Interceptor table')

        interceptors = self.mo_interception_table.getAll()
        self.log.debug('Getting MO Interceptor table: %s', interceptors)

        return pickle.dumps(interceptors, self.pickleProtocol)

    def perspective_mtroute_add(self, route, order):
        route = pickle.loads(route)
        self.log.debug('Adding a MT Route, order = %s, route = %s', order,
                       route)
        self.log.info('Adding a MT Route with order %s', order)

        try:
            self.mt_routing_table.add(route, order)
        except InvalidRoutingTableParameterError as e:
            self.log.error('Cannot add MT Route: %s', str(e))
            return False
        except Exception as e:
            self.log.error('Unknown error occurred while adding MT Route: %s',
                           str(e))
            return False

        # Set persistance state to False (pending for persistance)
        self.persistenceState['mtroutes'] = False

        return True

    def perspective_moroute_add(self, route, order):
        route = pickle.loads(route)
        self.log.debug('Adding a MO Route, order = %s, route = %s', order,
                       route)
        self.log.info('Adding a MO Route with order %s', order)

        try:
            self.mo_routing_table.add(route, order)
        except InvalidRoutingTableParameterError as e:
            self.log.error('Cannot add MO Route: %s', str(e))
            return False
        except Exception as e:
            self.log.error('Unknown error occurred while adding MO Route: %s',
                           str(e))
            return False

        # Set persistance state to False (pending for persistance)
        self.persistenceState['moroutes'] = False

        return True

    def perspective_moroute_remove(self, order):
        self.log.info('Removing MO Route [%s]', order)

        # Set persistance state to False (pending for persistance)
        self.persistenceState['moroutes'] = False

        return self.mo_routing_table.remove(order)

    def perspective_mtroute_remove(self, order):
        self.log.info('Removing MT Route [%s]', order)

        # Set persistance state to False (pending for persistance)
        self.persistenceState['mtroutes'] = False

        return self.mt_routing_table.remove(order)

    def perspective_mtroute_flush(self):
        self.log.info('Flushing MT Routing table')

        # Set persistance state to False (pending for persistance)
        self.persistenceState['mtroutes'] = False

        return self.mt_routing_table.flush()

    def perspective_moroute_flush(self):
        self.log.info('Flushing MO Routing table')

        # Set persistance state to False (pending for persistance)
        self.persistenceState['moroutes'] = False

        return self.mo_routing_table.flush()

    def perspective_mtroute_get_all(self):
        self.log.info('Getting MT Routing table')

        routes = self.mt_routing_table.getAll()
        self.log.debug('Getting MT Routing table: %s', routes)

        return pickle.dumps(routes, self.pickleProtocol)

    def perspective_moroute_get_all(self):
        self.log.info('Getting MO Routing table')

        routes = self.mo_routing_table.getAll()
        self.log.debug('Getting MO Routing table: %s', routes)

        return pickle.dumps(routes, self.pickleProtocol)