Exemplo n.º 1
0
 def setUp(self):
     self.conn = WithRedis.StrictRedis()
     self.conn.flushdb()
     # override with your own UA to verify test results in GA
     self.my_ua = os.getenv('UA_ID', 'UA-116198943-3')
     self.er = EventReporter(UA=self.my_ua, conn=self.conn)
Exemplo n.º 2
0
class EventReporterTest(unittest.TestCase):
    def setUp(self):
        self.conn = WithRedis.StrictRedis()
        self.conn.flushdb()
        # override with your own UA to verify test results in GA
        self.my_ua = os.getenv('UA_ID', 'UA-116198943-3')
        self.er = EventReporter(UA=self.my_ua, conn=self.conn)

    def test_base(self):
        """
        Checks to see that the base EventReporter class loads
        """
        pass

    @unittest.skip('will fail if env var overrides UA_ID')
    def test_args(self):
        """
        Checks to see that EventReporter stores UA
        """
        self.assertTrue(self.er.UA == 'UA-116198943-3')

    def test_store_fetch_dispatch(self):
        """
        Checks to see that the EventReporter stores expected data
        """
        ar = self.er.store('ga',
                           'event',
                           '20538abc-a8af-46e0-b292-0999d94468e9',
                           category='user',
                           action='action_name',
                           aip='1',
                           uip='1.2.3.4',
                           ds='web',
                           ua='my-useragent-test')

        self.assertTrue(ar == None)

        expected = {
            'handler': 'ga',
            'etype': 'event',
            'clientid': '20538abc-a8af-46e0-b292-0999d94468e9',
            'ts': 1548546584914,
            'args': {
                'category': 'user',
                'action': 'action_name',
                'aip': '1',
                'uip': '1.2.3.4',
                'ds': 'web',
                'ua': 'my-useragent-test'
            }
        }

        r = self.er.fetch()

        self.assertTrue(isinstance(r['ts'], int))

        # ts varies
        del expected['ts']

        self.assertDictContainsSubset(expected, r)

        # NOTE: live test.
        self.assertTrue(self.er.dispatch(r))

        # print(f'Data has been sent to {self.my_ua}. Please check real-time stats to confirm correctness.')

    def test_store_fetch_oldest_double(self):
        """
        Checks to see that the EventReporter fetch_oldest gets expected data
        """
        ar = self.er.store('ga',
                           'event',
                           '20538abc-a8af-46e0-b292-0999d94468e9',
                           category='user',
                           action='action_name',
                           aip='1',
                           uip='1.2.3.4',
                           ds='web')
        ar2 = self.er.store('ga',
                            'event',
                            '20538abc-a8af-46e0-b292-0999d94468e9',
                            category='user',
                            action='action_name_2',
                            aip='1',
                            uip='1.2.3.4',
                            ds='web')

        self.assertTrue(ar == None)

        expected = {
            'handler': 'ga',
            'etype': 'event',
            'clientid': '20538abc-a8af-46e0-b292-0999d94468e9',
            'ts': 1548546584914,
            'args': {
                'category': 'user',
                'action': 'action_name',
                'aip': '1',
                'uip': '1.2.3.4',
                'ds': 'web'
            }
        }

        r = self.er.fetch_oldest()

        self.assertTrue(isinstance(r['ts'], int))

        # ts varies
        del expected['ts']

        self.assertDictContainsSubset(expected, r)

    def test_store_fetch_oldest_single(self):
        """
        Checks to see that the EventReporter fetch_oldest gets expected data
        """
        ar = self.er.store('ga',
                           'event',
                           '20538abc-a8af-46e0-b292-0999d94468e9',
                           category='user',
                           action='action_name',
                           aip='1',
                           uip='1.2.3.4',
                           ds='web')

        self.assertTrue(ar == None)

        expected = {
            'handler': 'ga',
            'etype': 'event',
            'clientid': '20538abc-a8af-46e0-b292-0999d94468e9',
            'ts': 1548546584914,
            'args': {
                'category': 'user',
                'action': 'action_name',
                'aip': '1',
                'uip': '1.2.3.4',
                'ds': 'web'
            }
        }

        r = self.er.fetch_oldest()

        self.assertTrue(isinstance(r['ts'], int))

        # ts varies
        del expected['ts']

        self.assertDictContainsSubset(expected, r)

    def test_unsafe_store(self):
        '''
        Verify that e.g. a redis or argument failure throws an exception.
        '''
        with self.assertRaises(TypeError):
            self.er.store(category='user',
                          action='action_name',
                          aip='1',
                          uip='1.2.3.4',
                          ds='web')

    def test_safe_store_fail(self):
        '''
        Verify that e.g. a redis or argument failure does not throw an exception.
        '''
        r = self.er.safe_store(None,
                               None,
                               None,
                               action='action_name',
                               aip='1',
                               uip='1.2.3.4',
                               ds='web')
        self.assertTrue(r == False)

    def test_safe_store_success(self):
        '''
        Verify that e.g. a redis or argument failure does not throw an exception.
        '''
        r = self.er.safe_store('ga',
                               'event',
                               '20538abc-a8af-46e0-b292-0999d94468e9',
                               category='user',
                               action='action_name',
                               aip='1',
                               uip='1.2.3.4',
                               ds='web')
        self.assertTrue(r == None)

    def test_store_fetch_dispatch_referrer(self):
        """
        Checks to see that the EventReporter stores expected data with referrer.
        Looks like it's unnecessary to urlencode prior to handing it off.
        """
        ar = self.er.store('ga',
                           'event',
                           '20538abc-a8af-46e0-b292-0999d94468e9',
                           category='user',
                           action='action_name',
                           aip='1',
                           uip='1.2.3.4',
                           ds='web',
                           dr='http://www.test.com')

        self.assertTrue(ar == None)

        expected = {
            'handler': 'ga',
            'etype': 'event',
            'clientid': '20538abc-a8af-46e0-b292-0999d94468e9',
            'ts': 1548546584914,
            'args': {
                'category': 'user',
                'action': 'action_name',
                'aip': '1',
                'uip': '1.2.3.4',
                'ds': 'web',
                'dr': 'http://www.test.com'
            }
        }

        r = self.er.fetch()

        self.assertTrue(isinstance(r['ts'], int))

        # ts varies
        del expected['ts']

        self.assertDictContainsSubset(expected, r)

        # NOTE: live test.
        self.assertTrue(self.er.dispatch(r))
Exemplo n.º 3
0
    def setup_reporter(self, cfg=None):

        if self.__broker is None:
            self.setup_broker(cfg)

        self.__reporter = EventReporter(self.__broker)
Exemplo n.º 4
0
    def setup_reporter(self, cfg):

        if self.__broker is None:
            self.setup_broker(cfg)

        self.__reporter = EventReporter(self.__broker)
Exemplo n.º 5
0
class ScaleClient:
    """This class parses a configuration file and subsequently creates, configures, and manages each component of the
    overall Scale Client software.
    """

    def __init__(self, config_filename):
        self.__broker = None
        self.__reporter = None
        self.__sensors = []
        self.__applications = None
        try:
            log.info("Reading config file: %s" % config_filename)
            with open(config_filename) as cfile:
                cfg = yaml.load(cfile)
                # lower-case all top-level headings to tolerate different capitalizations
                cfg = {k.lower(): v for k, v in cfg.items()}

                # verify required entries present
                # required_entries = ('main',)
                # for ent in required_entries:
                #     if ent not in cfg:
                #         raise Exception("Required entry %s not present in config file." % ent)

                # call appropriate handlers for each section in the appropriate order

                if "main" in cfg:
                    self.setup_broker(cfg["main"])
                    self.setup_reporter(cfg["main"])
                else:
                    self.setup_broker({})
                    self.setup_reporter({})
                if "eventsinks" in cfg:
                    self.setup_event_sinks(cfg["eventsinks"])
                if "sensors" in cfg:
                    self.setup_sensors(cfg["sensors"])
                if "networks" in cfg:
                    self.setup_network(cfg["networks"])
                if "applications" in cfg:
                    self.setup_applications(cfg["applications"])

        except IOError as e:
            log.error("Error reading config file: %s" % e)
            exit(1)

    def setup_reporter(self, cfg):

        if self.__broker is None:
            self.setup_broker(cfg)

        self.__reporter = EventReporter(self.__broker)

    def setup_broker(self, cfg):
        """
        Currently only creates a dummy Broker object for registering Applications to.

        :param cfg:
        :return:
        """
        self.__broker = Broker()

    def setup_event_sinks(self, sink_configurations):
        for sink_config in (sink.values()[0] for sink in sink_configurations):
            # need to get class definition to call constructor
            if "class" not in sink_config:
                log.warn("Skipping EventSink with no class definition: %s" % sink_config)
                continue
            try:
                # this line lets us tolerate just e.g. mqtt_event_sink.MQTTEventSink as a relative pathname
                cls_name = (
                    sink_config["class"]
                    if "scale_client.event_sinks" in sink_config["class"]
                    else "scale_client.event_sinks." + sink_config["class"]
                )
                cls = self._get_class_by_name(cls_name)

                # make a copy of config so we can tweak it to expose only correct kwargs
                new_sink_config = sink_config.copy()
                new_sink_config.pop("class")

                sink = cls(self.__broker, **new_sink_config)

                self.__reporter.add_sink(sink)
                log.info("EventSink created from config: %s" % sink_config)
            except Exception as e:
                log.error("Unexpected error while creating EventSink: %s" % e)

    def setup_applications(self, application_configurations):
        n_applications = 0
        for application_config in (c.values()[0] for c in application_configurations):
            # need to get class definition to call constructor
            if "class" not in application_config:
                log.warn("Skipping virtual application with no class definition: %s" % application_config)
                continue

            try:
                cls_name = (
                    application_config["class"]
                    if "scale_client.applications" in application_config["class"]
                    else "scale_client.applications." + application_config["class"]
                )
                cls = self._get_class_by_name(cls_name)

                # copy config s so we can tweak it as necessary to expose only correct kwargs
                new_application_config = application_config.copy()
                new_application_config.pop("class")

                cls(self.__broker, **new_application_config)
                n_applications += 1
                log.info("Application created from config: %s" % application_config)

            except Exception as e:
                log.error("Unexpected error (%s) while creating application: %s" % (e, application_config))
                continue

    def setup_sensors(self, sensor_configurations):
        n_sensors = 0
        for sensor_config in (c.values()[0] for c in sensor_configurations):
            # need to get class definition to call constructor
            if "class" not in sensor_config:
                log.warn("Skipping virtual sensor with no class definition: %s" % sensor_config)
                continue

            try:
                cls_name = (
                    sensor_config["class"]
                    if "scale_client.sensors" in sensor_config["class"]
                    else "scale_client.sensors." + sensor_config["class"]
                )
                cls = self._get_class_by_name(cls_name)

                # copy config so we can tweak it as necessary to expose only correct kwargs
                new_sensor_config = sensor_config.copy()
                new_sensor_config.pop("class")
                dev_name = new_sensor_config.get("dev_name", "vs%i" % n_sensors)
                new_sensor_config.pop("dev_name", dev_name)

                cls(self.__broker, device=DeviceDescriptor(dev_name), **new_sensor_config)
                n_sensors += 1
                log.info("Virtual sensor created from config: %s" % sensor_config)

            except Exception as e:
                log.error("Unexpected error (%s) while creating virtual sensor: %s" % (e, sensor_config))
                continue

    def setup_network(self, network_configurations):
        for network_config in (c.values()[0] for c in network_configurations):
            # need to get class definition to call constructor
            if "class" not in network_config:
                log.warn("Skipping network config with no class definition: %s" % network_config)
                continue

            try:
                cls_name = (
                    network_config["class"]
                    if "scale_client.network" in network_config["class"]
                    else "scale_client.network." + network_config["class"]
                )
                cls = self._get_class_by_name(cls_name)

                # copy config s so we can tweak it as necessary to expose only correct kwargs
                new_network_config = network_config.copy()
                new_network_config.pop("class")

                cls(self.__broker, **new_network_config)
                log.info("Virtual sensor created from config: %s" % network_config)

            except Exception as e:
                log.error("Unexpected error (%s) while setting network class: %s" % (e, network_config))

    def run(self):
        """Currently just loop forever to allow the other threads to do their work."""
        # TODO: handle this with less overhead?

        self.__broker.run()

    def _get_class_by_name(self, kls):
        """Imports and returns a class reference for the full module name specified in regular Python import format"""
        # TODO: download definition from some repository if necessary

        # The following code taken from http://stackoverflow.com/questions/452969/does-python-have-an-equivalent-to-java-class-forname
        parts = kls.split(".")
        module = ".".join(parts[:-1])
        m = __import__(module)
        for comp in parts[1:]:
            m = getattr(m, comp)
        return m
Exemplo n.º 6
0
import time
import json
import logging
import os
from redis import StrictRedis
from event_reporter import EventReporter

logging.basicConfig(level=os.environ.get("LOGLEVEL", "INFO"))
LOG = logging.getLogger('evrlistener')

LOCALCONN = StrictRedis()
ER = EventReporter(conn=LOCALCONN)

def process_item(item):
    '''called for each new item'''
    return ER.dispatch(item)

def fetch_func():
    item = ER.fetch_oldest(blocking=False)
    if item:
        try:
            process_item(item)
        except Exception as e:
            LOG.error("process_item failed:{}, item: {}".format(
                e, item))
            time.sleep(10)  # sleep for 10 seconds
    time.sleep(0.001)  # be nice to the system :)


def main():
    while True: