Source code for pyloggr.cache

# encoding: utf-8

"""
This module defines the Cache class and the `cache` singleton. They are used to store and retrieve
data from Redis. For example, the syslog server process uses Cache to stores information about
currently connected syslog clients, so that the web frontend is able to display that information.

Clients should typically use the `cache` singleton, instead of the `Cache` class.

`Cache` initialization is done by `initialize` class method. The `initialize` method should be called
by launchers, at startup time.

Note
====

In a development environment, if Redis has not been started by the OS, Redis can be started directly by
pyloggr using configuration item ``REDIS['try_spawn_redis'] = True``



.. py:data:: cache

    Cache singleton
"""

__author__ = 'stef'

import logging

from pyloggr.utils.lmdb_wrapper import LmdbWrapper
from pyloggr.config import Config

logger = logging.getLogger(__name__)
security_logger = logging.getLogger('security')

syslog_key = 'pyloggr.syslog.server.'
rescue_key = 'pyloggr.rescue_queue'


[docs]class SyslogCache(object): """ Stores information about the running pyloggr's syslog processes in a Redis cache Parameters ---------- redis_conn: :py:class:`StrictRedis` the Redis connection server_id: int The syslog process server_id """ def __init__(self, server_id): self.server_id = server_id self.hash_name = syslog_key + '.' + str(server_id) @property def status(self): """ Returns syslog process status :returns: Boolean or None (if Redis not available) """ lmdb = LmdbWrapper.get_instance(Config.EXCHANGE_SPACE) return lmdb.hash(self.hash_name)['status'] == "True" @status.setter def status(self, new_status): """ Set one syslog process's status :param new_status: True (running) or False (stopped) :type new_status: bool """ lmdb = LmdbWrapper.get_instance(Config.EXCHANGE_SPACE) if new_status: lmdb.set(syslog_key).add(self.server_id) else: # syslog server has been shut down self.clients = [] lmdb.set(syslog_key).remove(self.server_id) lmdb.hash(self.hash_name)['status'] = str(new_status) @property def clients(self): """ Return the list of clients for this syslog process :return: list of clients or None (Redis not available) """ if not self.status: return [] lmdb = LmdbWrapper.get_instance(Config.EXCHANGE_SPACE) clients = lmdb.hash(self.hash_name)['clients'] clients = clients if clients else [] if clients: clients.sort(key=lambda client: client['id']) return clients return [] @clients.setter def clients(self, new_clients): """ Sets the list of clients for this syslog process :param new_clients: The list of clients :type new_clients: list of :py:class:`pyloggr.main.syslog_server.SyslogClientConnection` """ if self.status: lmdb = LmdbWrapper.get_instance(Config.EXCHANGE_SPACE) if hasattr(new_clients, 'values'): lmdb.hash(self.hash_name)['clients'] = [client.props for client in new_clients.values()] else: lmdb.hash(self.hash_name)['clients'] = [client.props for client in new_clients] @property def ports(self): """ Return the list of ports that the syslog process listens on :returns: list of ports (numeric and socket name) :rtype: list """ if not self.status: return [] lmdb = LmdbWrapper.get_instance(Config.EXCHANGE_SPACE) ports = lmdb.hash(self.hash_name)['ports'] return ports if ports else [] @ports.setter def ports(self, ports): """ Sets the list of ports that the syslog process listens on :param ports: list of ports :type ports: list """ if self.status: lmdb = LmdbWrapper.get_instance(Config.EXCHANGE_SPACE) lmdb.hash(self.hash_name)['ports'] = ports
[docs]class SyslogServerList(object): """ Encapsulates the list of syslog processes """ def __init__(self): self._syslog_servers_dict = dict()
[docs] def __getitem__(self, server_id): """ Return a SyslogCache object based on process id :param server_id: process id :rtype: SyslogCache """ if server_id not in self._syslog_servers_dict: self._syslog_servers_dict[server_id] = SyslogCache(server_id) return self._syslog_servers_dict[server_id]
def __setitem__(self, key, value): raise NotImplementedError
[docs] def __delitem__(self, server_id): """ Deletes a SyslogCache object (used when the syslog server shuts down) :param server_id: process id """ lmdb = LmdbWrapper.get_instance(Config.EXCHANGE_SPACE) hash_name = syslog_key + '_' + str(server_id) lmdb.set(syslog_key).remove(server_id) # self.redis_conn.delete(hash_name)
[docs] def __len__(self): """ Returns the number of syslog processes :returns: how many syslog processes, or None (Redis not available) :rtype: int """ lmdb = LmdbWrapper.get_instance(Config.EXCHANGE_SPACE) return len(lmdb.set(syslog_key)) # noinspection PyDocstring
def keys(self): lmdb = LmdbWrapper.get_instance(Config.EXCHANGE_SPACE) keys = list(int(key) for key in lmdb.set(syslog_key).members()) keys.sort() return keys # noinspection PyDocstring def values(self): return [self[key] for key in self.keys()] def __iter__(self): k = self.keys() return iter(k) if k else iter([])
[docs]class Cache(object): """ `Cache` class abstracts storage and retrieval from Redis Attributes ---------- redis_conn: :py:class:`StrictRedis` underlying `StrictRedis` connection object (class variable) """ syslog_list = None def __init__(self): pass @classmethod
[docs] def initialize(cls): """ Cache initialization. `initialize` tries to connect to redis and sets `redis_conn` class variable. If connection fails and ``REDIS['try_spawn_redis']`` is set, it also tries to spawn the Redis process. :raise CacheError: when redis initialization fails """ cls.syslog_list = SyslogServerList() LmdbWrapper(Config.EXCHANGE_SPACE, size=52428800).open( sync=False, metasync=False, max_dbs=0 )
@classmethod def shutdown(cls): LmdbWrapper.get_instance(Config.EXCHANGE_SPACE).close()