class Context(object): """ Un contexte de corrélation pouvant recevoir des attributs arbitraires. Un certain nombre d'attributs sont prédéfinis et utilisés par le corrélateur pour alimenter la base de données. Les attributs prédéfinis sont : - raw_event_id : identifiant de l'événement brut (C{int}). - successors_aggregates : liste des identifiants des agrégats qui doivent être fusionnés avec celui de l'événement courant (C{list} of C{int}). - predecessors_aggregates : liste des agrégats auxquels doit être rattaché l'événement (C{list} of C{int}). - previous_state : état précédent du service (C{int}). - statename : nom de l'état courant du service (C{str}). - servicename : nom du service associé au contexte (C{str}). - hostname : nom de l'hôte qui héberge le service (C{str}). - impacted_hls : liste des identifiants des services de haut niveau impactés par l'événement (C{list} of C{int}). - occurrences_count : nombre d'occurrences de l'alerte (C{int}). - priority : priorité de l'événement corrélé (C{int}). - no_alert : empêche la génération d'une alerte corrélée (C{bool}). - payload : message brut (XML sérialisé) de l'événement reçu (C{str}). - idsupitem : identifiant de l'élément supervisé impacté (C{int}). """ def __init__(self, msgid, transaction=True, timeout=None): """ Initialisation d'un contexte de corrélation (au moyen de MemcacheD). @param msgid: Identifiant de l'alerte brute reçue par le corrélateur. @type msgid: C{basestring}. """ self._connection = MemcachedConnection() self._transaction = transaction self._id = str(msgid) if timeout is None: timeout = settings['correlator'].as_float('context_timeout') self._timeout = timeout def get(self, prop): """ Récupération de la valeur d'un des attributs du contexte. @param prop: Nom de l'attribut, tout identifiant Python valide est autorisé, sauf ceux dont le nom commence par '_'. @type prop: C{str} @return: Valeur de l'attribut demandé. @rtype: C{mixed} """ # Les attributs pour lesquels il y a un getter # sont retournés en utilisant le getter. if prop in ('topology', 'last_topology_update'): return object.__getattribute__(self, prop) key = 'vigilo:%s:%s' % (prop, self._id) return self._connection.get(key, self._transaction) def set(self, prop, value, timeout=NoTimeoutOverride): """ Modification dynamique d'un des attributs du contexte. @param prop: Nom de l'attribut, tout identifiant Python valide est autorisé, sauf ceux dont le nom commence par '_'. @type prop: C{str} @param value: Valeur à donner à l'attribut. La valeur doit être sérialisable à l'aide du module C{pickle} de Python. @type value: C{mixed} @param timeout: Durée de rétention (en secondes) de la donnée. Si omis, la durée de rétention globale associée au contexte est utilisée. @type timeout: C{float} """ key = 'vigilo:%s:%s' % (prop, self._id) if timeout is NoTimeoutOverride: timeout = self._timeout return self._connection.set( key, value, self._transaction, time=timeout) def delete(self, prop): """ Suppression dynamique d'un attribut du contexte. @param prop: Nom de l'attribut, tout identifiant Python valide est autorisé, sauf ceux dont le nom commence par '_'. @type prop: C{str} """ key = 'vigilo:%s:%s' % (prop, self._id) return self._connection.delete(key, self._transaction) def getShared(self, prop): """ Récupération de la valeur d'un des attributs partagés du contexte. @param prop: Nom de l'attribut partagé, tout identifiant Python valide est autorisé, sauf ceux dont le nom commence par '_'. @type prop: C{str} @return: Valeur de l'attribut partagé demandé. @rtype: C{mixed} """ key = 'shared:%s' % prop return self._connection.get(key, self._transaction) def setShared(self, prop, value, timeout=NoTimeoutOverride): """ Modification dynamique d'un des attributs partagés du contexte. @param prop: Nom de l'attribut partagé, tout identifiant Python valide est autorisé, sauf ceux dont le nom commence par '_'. @type prop: C{str} @param value: Valeur à donner à l'attribut partagé. La valeur doit être sérialisable à l'aide du module C{pickle} de Python. @type value: C{mixed} @param timeout: Durée de rétention (en secondes) de la donnée. Si omis, la durée de rétention globale associée au contexte est utilisée. @type timeout: C{float} """ key = 'shared:%s' % prop if timeout is NoTimeoutOverride: timeout = self._timeout return self._connection.set( key, value, self._transaction, time=timeout) def deleteShared(self, prop): """ Suppression dynamique d'un attribut partagé du contexte. @param prop: Nom de l'attribut partagé, tout identifiant Python valide est autorisé, sauf ceux dont le nom commence par '_'. @type prop: C{str} """ key = 'shared:%s' % prop return self._connection.delete(key, self._transaction)
class TestMemcachedConnection(unittest.TestCase): """ Test des méthodes de la classe 'MemcachedConnection' Le setUp et le tearDown sont décorés par @deferred() pour que la création de la base soit réalisée dans le même threads que les accès dans les tests. """ @deferred(timeout=60) def setUp(self): super(TestMemcachedConnection, self).setUp() helpers.setup_db() helpers.setup_mc() self.cache = MemcachedConnection() return defer.succeed(None) @deferred(timeout=60) def tearDown(self): """Arrêt du serveur Memcached à la fin de chaque test.""" super(TestMemcachedConnection, self).tearDown() self.cache = None helpers.teardown_mc() helpers.teardown_db() return defer.succeed(None) @deferred(timeout=10) def test_singleton(self): """Unicité de la connexion au serveur MemcacheD.""" # On instancie une 2ème fois la classe MemcachedConnection. conn = MemcachedConnection() # On s'assure que les deux instances # représentent en fait le même objet. self.assertEqual(conn, self.cache) return defer.succeed(None) def _connect(self): host = helpers.settings['correlator']['memcached_host'] port = helpers.settings['correlator'].as_int('memcached_port') d_connection = protocol.ClientCreator( reactor, MemCacheProtocol ).connectTCP(host, port) return d_connection @deferred(timeout=60) @defer.inlineCallbacks def test_set(self): """Association d'une valeur à une clé""" # On initialise le nom de la clé et de la valeur associée key = "vigilo_test_set" value = "test_set" set_value = yield self.cache.set(key, value) LOGGER.info("'%s' set to '%s'", key, set_value) # On vérifie que la clé a bien été ajoutée # et qu'elle est bien associée à la valeur 'value'. connection = yield self._connect() LOGGER.info("Connected using %r", connection) received = yield connection.get(key) LOGGER.info("Received: %r", received) self.assertEqual(pickle.loads(received[-1]), value) @deferred(timeout=60) @defer.inlineCallbacks def test_get(self): """Récupération de la valeur associée à une clé""" # On initialise le nom de la clé et de la valeur associée key = "vigilo_test_get" value = "test_get" # On associe la valeur 'value' à la clé 'key'. connection = yield self._connect() LOGGER.info("Connected using %r", connection) res = yield connection.set(key, pickle.dumps(value)) LOGGER.info("Success? %r", res) # On tente à nouveau de récupérer la valeur associée à la clé 'key' result = yield self.cache.get(key) # On vérifie que la méthode get retourne bien 'value'. self.assertEqual(result, value) @deferred(timeout=60) @defer.inlineCallbacks def test_delete(self): """Suppression d'une clé""" # On initialise le nom de la clé et de la valeur associée key = "vigilo_test_delete" value = "test_delete" # On ajoute la clé 'key'. connection = yield self._connect() LOGGER.info("Connected using %r", connection) yield connection.set(key, value) # On tente à nouveau de supprimer la clé 'key' yield self.cache.delete(key) # On s'assure que la clé a bien été supprimée value = yield connection.get(key) self.assertEquals(None, value[-1]) @deferred(timeout=60) @defer.inlineCallbacks def test_key_spaces(self): """Gestion des espaces dans une clé""" # On initialise le nom de la clé et de la valeur associée key = "vigilo test espace" value = "test_espace" set_value = yield self.cache.set(key, value) LOGGER.info("'%s' set to '%s'", key, set_value) get_value = yield self.cache.get(key, value) LOGGER.info("'%s' value is '%s'", key, get_value) yield self.cache.delete(key) LOGGER.info("'%s' deleted", key) @deferred(timeout=60) @defer.inlineCallbacks def test_reconnection(self): """Reconnexion automatique à memcached""" yield self.cache.set("test", 42) # On coupe la connexion. Comme la factory n'a pas demandé # cette coupure, elle va automatiquement tenter une reconnexion. self.cache._cache._instance.transport.loseConnection() # Ce deferred va émettre une exception car la connexion # a été perdue entre temps et car le deferred avait été # créé AVANT que la perte de connexion ne soit détectée. # Avant Twisted 9.0, l'exception est une defer.TimoutError, # ensuite il s'agit d'une error.ConnectionDone try: yield self.cache.get("test") self.fail("A TimeoutError exception was expected, got nothing.") except (defer.TimeoutError, error.ConnectionDone, KeyboardInterrupt): pass except Exception, e: self.fail("A TimeoutError exception was expected, got %r" % e) # Ce get() fonctionnera car la connexion a été rétablie # entre temps (reconnexion automatique). value = yield self.cache.get("test") self.assertEquals(42, value)