Esempio n. 1
0
class SessionTracker(object):

    def __init__(self):
        self.session_store = SessionStore()
        self.kestrel_connection = KestrelConnection()

    def track_event(self, sid, time, info):
        message = {
            "session_id": sid,
            "time": time,
            "info": info,
        }

        self.kestrel_connection.send_to("session_processor", message)
        self.session_store.set(sid, time, info)
        logger.info("Track session:%s, time:%s, info: %s in Redis." %
                    (sid, time, info.keys()))
Esempio n. 2
0
class SessionsStoreTest(TestCase):

    @classmethod
    def setUpClass(cls):
        super(SessionsStoreTest, cls).setUpClass()
        # TODO(andrei): move this into a custom test runner
        log.setup_logging(level=logging.DEBUG)

    def setUp(self):
        super(SessionsStoreTest, self).setUp()
        settings.REDIS_SERVER = 'localhost'


        self.redis = redis.StrictRedis(host = settings.REDIS_SERVER,
                                       port = settings.REDIS_PORT,
                                       db = settings.REDIS_SESSION_DB)

        self.redis.flushdb()

        self.store = SessionStore()

    def test_all(self):
        self.store.set('ts1', 30, {'p4': '1-3-p4'})

        self.store.set('ts1', 10, {'p1': '1-1-p1'})
        self.store.set('ts1', 20, {'p1': '1-2-p1'})
        self.store.set('ts1', 10, {'p3': '1-1-p3'})

        self.store.set('ts2', 20, {'p1': '2-2-p1'})
        self.store.set('ts2', 20, {'p1': '2-2-p1-b'})

        sessions = self.store.get_all_sessions()

        #test for the number of sessions
        eq_(2, len(sessions), "Expecting two sessions")
        ok_(set(sessions) == set(['ts1', 'ts2']), "Sessions must match")

        #test to see if
        s1props = self.store.get_sessions_measurements(['ts1'], ['p1']).values()[0]
        s1p1 = [x['p1'] for x in s1props if x['p1'] is not None]
        ok_(set(['1-1-p1', '1-2-p1']) == set(s1p1), "Props must match")

        s1times = self.store.get_session_times('ts1')
        eq_(3, len(s1times), "Expecting 3 times")
        eq_([30,20,10], s1times, "Times must match")

        s2props = self.store.get_sessions_measurements(['ts2'], ['time']).values()[0]
        s2times = [int(x['time']) for x in s2props]

        eq_(1, len(s2times), "Expecting 1 times")
        ok_(set([20]) == set(s2times), "Times must match")

    def test_session_store_always_keeps_max_timestamp_for_measurements(self):
        """ Given that measurements may come in async with timestamps a little
        'out of order' because of that, test that no matter what happens, the
        session store will store only the max timestamp for each session. """

        self.store.set('session1', 20, {'a': 'b'})
        self.store.set('session1', 10, {'c': 'd'})

        last_timestamp = int(self.redis.hget('sessions', 'session1'))
        eq_(last_timestamp, 20, "Session processor should always keep the "
            "maximal timestamp for out-of-order measurements")

    def test_stale_sessions_works_correctly(self):
        """ Check that detecting stale sessions works correctly. """
        result = self._create_mixture_of_fresh_and_stale_sessions()
        the_sessions = self.store.stale_sessions()
        eq_(set(the_sessions),
            set(result['stale']),
            "Stale sessions should be detected correctly.")

    def test_remove_session_works_correctly(self):
        """ Check that removing a session works correctly. """
        result = self._create_mixture_of_fresh_and_stale_sessions()
        sid = random.choice(result['stale'])
        self.store.remove_session(sid)

        # Check that the session doesn't appear anymore in the
        # sid -> last_updated_at mapping
        sessions_updated_at = self.store.get_all_sessions_with_last_update()
        ok_(sid not in sessions_updated_at,
            "Removed session should not be present in mapping from sid to "
            "last updated at")

    def test_set_randomly_deletes_stale_sessions(self):
        """ Test that calling set() on the store should clean-up stale
        sessions correctly. """

        result = self._create_mixture_of_fresh_and_stale_sessions()
        stale_sids = result['stale']
        fresh_sid = random.choice(result['fresh'])

        # Small enough probability so that clean-up is triggered
        p = max(SessionStore.CLEANUP_PROBABILITY - 0.01, 0)
        with patch('random.random', return_value=p) as random_mock:
            with patch.object(SessionStore, 'remove_session') as remove_session_mock:
                # Perform a set() which will trigger a cleanup of stale sessions
                self.store.set(fresh_sid, int(time.time() * 1000), {})

                # First, check that remove session has been called with the
                # correct number of arguments.
                eq_(remove_session_mock.call_count,
                    len(stale_sids),
                    "Remove session should have been called the correct "
                    "number of times")

                # Next, let's check that the actual sessions on which
                # removeSession was called were the correct ones.
                removed_sids = []
                for remove_session_args in remove_session_mock.call_args_list:
                    removed_sids.append(remove_session_args[0][0])

                eq_(set(removed_sids), set(stale_sids),
                    "All stale sessions should have been removed")

    def test_keeps_type_for_deeply_nested_property(self):
        self.store.set("session1", 30, {'p': {'x':1, 'y':{1:2}, 'z':[1,2,3]}})
        m = self.store.get_session_measurement("session1", 30, 'p')
        value = m['p']
        eq_(type(value), dict, "Expected dict")
        eq_(set(value.keys()), set(['x','y','z']))
        eq_(1, value['x'])
        eq_([1,2,3], value['z'])

    @patch('random.random', return_value=SessionStore.CLEANUP_PROBABILITY + 0.1)
    # We need to patch random.random() so that SessionStore.set() does
    # not perform any clean-up while creating the initial db contents.
    def _create_mixture_of_fresh_and_stale_sessions(self, random_patch):
        """ Creates a mixture of fresh and stale sessions.

        Returns a dictionary with two keys, one with the fresh session ids,
        and one with the stale session ids.

        """
        old_time = int(time.time() * 1000) - \
                   SessionStore.STALE_SESSION_THRESHOLD_MS

        result = {'fresh': [], 'stale': []}

        # We make sure to create less than MAX_SESSIONS_TO_CLEANUP
        # sessions because this way they will all be cleaned up on a
        # set() whose random factor is "good enough".
        N = SessionStore.MAX_SESSIONS_TO_CLEANUP
        n_fresh = random.randint(int(0.5 * N), N)
        n_stale = random.randint(int(0.5 * N), N)

        for _ in xrange(n_fresh):
            sid = ''.join([random.choice(string.hexdigits)
                           for _ in xrange(16)])
            result['stale'].append(sid)
            self.store.set(sid, old_time - random.randint(5000, 10000), {})

        for _ in xrange(n_stale):
            sid = ''.join([random.choice(string.hexdigits)
                           for _ in xrange(16)])
            result['fresh'].append(sid)
            self.store.set(sid, int(time.time() * 1000), {})

        return result