Package zephir :: Package monitor :: Package agentmanager :: Module agent
[hide private]
[frames] | no frames]

Source Code for Module zephir.monitor.agentmanager.agent

  1  # -*- coding: UTF-8 -*- 
  2  ########################################################################### 
  3  # Eole NG - 2007 
  4  # Copyright Pole de Competence Eole  (Ministere Education - Academie Dijon) 
  5  # Licence CeCill  cf /root/LicenceEole.txt 
  6  # eole@ac-dijon.fr 
  7  ########################################################################### 
  8   
  9  try: _ # localized string fetch function 
 10  except NameError: _ = str 
 11   
 12  import os, traceback 
 13  from datetime import datetime, timedelta 
 14  from twisted.python import log 
 15  from twisted.internet import defer 
 16  from twisted.web.microdom import * 
 17  # from twisted.persisted import marmalade 
 18  from twisted.persisted import aot 
 19   
 20  from zephir.monitor.agentmanager import config as cfg, rrd, status, util 
 21  from zephir.monitor.agentmanager.data import * 
 22   
 23   
 24   
25 -class AgentData:
26 """Persistance et accès aux données d'un agent. 27 28 Cette classe contient la « charge utile » d'un agent ; avant envoi 29 au serveur Zephir tous les agents sont convertis en une instance 30 de C{L{AgentData}}. 31 32 Attributs : 33 34 - C{name} : nom de l'agent 35 36 - C{period} : intervalle de mesures en secondes ; C{period=0} 37 désactive les mesures automatiques. 38 39 - C{description} : description de cette instance d'agent, 40 apparaissant sur la page de l'agent. 41 42 - C{max_status} : L{status<zephir.monitor.agentmanager.status>} le plus grave ; réinitialisé à chaque 43 envoi au serveur Zephir ou sur demande via XML-RPC. 44 45 - C{last_measure_date} : date de la dernière mesure. 46 47 - C{data} : liste d'objets représentant les données associées à 48 l'agent (cf. C{L{zephir.monitor.agentmanager.data}}). 49 """ 50
51 - def __init__(self, name, period, description, section, 52 max_status, max_status_date, last_status, last_measure_date, data, measure_data={}):
53 self.name = name 54 self.period = period 55 self.description = description 56 self.section = section 57 self.max_status = max_status 58 self.last_status = last_status 59 self.max_status_date = max_status_date 60 self.last_measure_date = last_measure_date 61 self.data = data 62 self.measure_data = measure_data
63 64
65 - def from_agent(self, agent):
66 """I{Factory Method} 67 68 @param agent: un agent concret 69 70 @return: une copie de C{agent} ne contenant plus que les 71 données utiles au serveur Zephir (instance de C{AgentData} 72 """ 73 result = self(agent.name, agent.period, agent.description, agent.section, 74 agent.max_status, agent.max_status_date, agent.last_status, agent.last_measure_date, agent.data, agent.measure_data) 75 return result
76 from_agent = classmethod(from_agent) 77 78
79 - def from_archive(self, archive_dir):
80 """I{Factory Method} 81 82 @param archive_dir: le chemin d'un répertoire contenant les 83 données d'un agent 84 85 @return: une instance de C{AgentData} chargée depuis le 86 système de fichiers 87 """ 88 xml_filename = os.path.join(archive_dir, 'agent.xml') 89 xml_file = file(xml_filename, 'r') 90 try: 91 # passage de persisted.marmalade à persisted.aot 92 try: 93 result = aot.unjellyFromSource(xml_file) 94 except: 95 xml_file.seek(0) 96 from twisted.persisted import marmalade 97 result = marmalade.unjellyFromXML(xml_file) 98 except Exception, e: 99 error_agent = LoadErrorAgent( 100 os.path.basename(archive_dir), 101 title = _("Loading archive %s failed") % archive_dir, 102 message = _("Loading agent from archive %s failed:\n%s") % (archive_dir, e)) 103 result = AgentData.from_agent(error_agent) 104 xml_file.close() 105 return result
106 from_archive = classmethod(from_archive) 107 108
109 - def archive(self, archive_dir):
110 """Sérialise l'agent sur disque, cf. L{from_archive}""" 111 xml_filename = os.path.join(archive_dir, 'agent.xml') 112 xml_file = file(xml_filename, 'w') 113 # écriture avec aot (marmalade deprecated) 114 aot.jellyToSource(self, xml_file) 115 # marmalade.jellyToXML(self, xml_file) 116 xml_file.close()
117
118 - def ensure_data_uptodate(self):
119 """Met à jour les données de l'agent sur disque""" 120 pass
121 122 123 124 125 STATUS_GRAPH_OPTIONS = [ 126 "-send-7days", "-l0", "-u1", "-g", 127 "-w112", "-h10", "-xHOUR:6:DAY:1:DAY:1:0:%d", 128 "CDEF:unknown=status,0,EQ", 129 "CDEF:ok=status,1,EQ", 130 "CDEF:warn=status,2,EQ", 131 "CDEF:error=status,3,EQ", 132 "AREA:unknown#666666", 133 "AREA:ok#33BB33", 134 "AREA:warn#CC6600", 135 "AREA:error#BB3333", 136 ] 137 138 STATUS_GRAPH_OPTIONS_MONTHLY = [ 139 "-send-1month", "-l0", "-u1", "-g", 140 "-w300", "-h10", "-xDAY:1:WEEK:1:DAY:7:0:%d", 141 "CDEF:unknown=status,0,EQ", 142 "CDEF:ok=status,1,EQ", 143 "CDEF:warn=status,2,EQ", 144 "CDEF:error=status,3,EQ", 145 "AREA:unknown#666666", 146 "AREA:ok#33BB33", 147 "AREA:warn#CC6600", 148 "AREA:error#BB3333", 149 ] 150 151
152 -class Agent(AgentData):
153 """Classe abstraite des agents. 154 155 Un agent concret est une sous-classe d'C{L{Agent}} implémentant 156 (en particulier) la méthode C{L{measure()}}. 157 """ 158
159 - def __init__(self, name, 160 period = 60, 161 fields = ['value'], 162 description = None, 163 section = None, 164 modules = None, 165 requires = [], 166 **params):
167 if description is None: 168 description = self.__class__.__doc__ 169 AgentData.__init__(self, name, period, description, section, 170 status.Unknown(), "", status.Unknown(), "", [], {}) 171 self.fields = fields 172 self.data_needs_update = False 173 # those will be set by loader, eg UpdaterService 174 self.archive_dir = None 175 self.status_rrd = None 176 self.manager = None 177 # dépendances fonctionnelles 178 self.requires = requires 179 self.modules = modules 180 self.last_measure = None
181
182 - def init_data(self, archive_dir):
183 """Mémorise et initialise le répertoire d'archivage 184 185 Cette méthode sera appelée par le framework après chargement 186 de l'agent, afin de terminer les initialisations pour 187 lesquelles l'agent a besoin de connaître l'emplacement de ses 188 données sur disque. 189 """ 190 self.archive_dir = archive_dir 191 self.ensure_datadirs() 192 if self.period != 0: # period = 0 => no agent measures scheduled 193 status_period, xff = self.period, 0.75 194 else: 195 status_period, xff = 60, 1 196 197 # on récupère le dernier problème et sa date dans l'archive si elle est présente 198 xml_file = os.path.join(self.archive_dir, 'agent.xml') 199 if os.path.exists(xml_file): 200 try: 201 a = AgentData("temp", 60, "agent temporaire",status.Unknown(), "", status.Unknown(), "", [], {}) 202 a = a.from_archive(self.archive_dir) 203 self.max_status = a.max_status 204 self.max_status_date = a.max_status_date 205 del a 206 except: 207 pass 208 209 statusname = os.path.join(self.archive_dir, 'status') 210 self.status_rrd = rrd.Database(statusname + '.rrd', 211 step = status_period) 212 self.status_rrd.new_datasource( # status history 213 name = "status", 214 min_bound = 0, max_bound = len(status.STATUS_ORDER)) 215 self.status_rrd.new_archive( # 1 record per hour/pixel on 1 week 216 rows = 24*7, steps = 3600/status_period, #FIXME break for period > 1h 217 consolidation = 'MAX', xfiles_factor = 0.75) 218 self.status_rrd.new_archive( # 1 record per period on 1 day 219 rows = 24*3600/status_period, steps = 1, #FIXME break for period > 1day 220 consolidation = 'MAX', xfiles_factor = 0.75) 221 self.status_rrd.new_graph(pngname = statusname + '.png', 222 vnamedefs = { "status": ("status", 'MAX') }, 223 options = STATUS_GRAPH_OPTIONS) 224 self.status_rrd.new_graph(pngname = statusname + '_long.png', 225 vnamedefs = { "status": ("status", 'MAX') }, 226 options = STATUS_GRAPH_OPTIONS_MONTHLY) 227 self.status_rrd.create()
228 229 230 # Méthodes à spécialiser dans les agents concrets 231
232 - def measure(self):
233 """Prend concrètement une mesure. 234 235 Pour implémenter un agent, il faut implémenter au moins cette 236 méthode. 237 238 @return: Résultat de la mesure, un dictionnaire C{{champ: 239 valeur}} ou un objet C{L{twisted.internet.defer.Deferred}} 240 renvoyant ce dictionnaire. 241 """ 242 raise NotImplementedError
243 244
245 - def check_status(self):
246 """Renvoie le diagnostic de fonctionnement de l'agent. 247 248 L'implémentation par défaut dans C{L{Agent}} renvoie un statut 249 neutre. Les agents concrets doivent donc redéfinir cette 250 méthode pour annoncer un diagnostic utile. 251 """ 252 log.msg(_("Warning: agent class %s does not redefine method check_status()") 253 % self.__class__.__name__) 254 return status.Unknown()
255
256 - def update_status(self):
257 new_status = None 258 # si possible, on vérifie le dernier état des agents dont on dépend 259 if self.manager is not None: 260 for agent in self.requires: 261 try: 262 if self.manager.agents[agent].last_status == status.Error(): 263 new_status = status.Dependant() 264 except: 265 # agent non chargé ou état inconnu 266 pass 267 if new_status is None: 268 new_status = self.check_status() 269 # mise à jour de l'état 270 self.set_status(new_status)
271
272 - def set_status(self, s, reset = False):
273 """Mémorise le statut et met à jour C{statut_max} 274 275 @param s: statut actuel 276 @param reset: réinitialise C{max_status} à C{s} si C{reset==True} 277 """ 278 # Si on détecte un nouveau problème, on met à jour max_status et max_status_date 279 if s > self.last_status and s not in [status.OK(),status.Dependant()]: 280 self.max_status = s 281 self.max_status_date = self.last_measure_date 282 self.last_status = s
283
284 - def reset_max_status(self):
285 """Réinitialise C{max_status} à la valeur courante du status 286 """ 287 self.set_status(self.check_status(), reset = True)
288 289 290 # Gestion des mesures
291 - def scheduled_measure(self):
292 """Déclenche une mesure programmée. 293 294 Prend une mesure et mémorise le résultat et l'heure. 295 """ 296 now = util.utcnow() 297 try: 298 m = self.measure() 299 except Exception, e: 300 print "erreur mesure (%s)" % self.name 301 traceback.print_exc() 302 self.handle_measure_exception(e) 303 else: 304 if isinstance(m, defer.Deferred): 305 m.addCallbacks( 306 lambda m: self.save_measure(Measure(now, m)), 307 lambda f: self.handle_measure_exception(f.trap(Exception))) 308 else: 309 self.save_measure(Measure(now, m))
310 311
312 - def save_measure(self, measure):
313 """Mémorise une mesure donnée. 314 315 Méthode à redéfinir dans les sous-classes concrètes de C{L{Agent}}. 316 (callback de succès pour C{L{scheduled_measure()}}) 317 """ 318 # for d in self.data: 319 # d.needs_update = True 320 self.last_measure_date = measure.get_strdate() 321 self.data_needs_update = True 322 self.last_measure = measure 323 self.status_rrd.update({'status': self.last_status.num_level()}, 324 util.utcnow())
325 326
327 - def handle_measure_exception(self, exc):
328 """Callback d'erreur pour C{L{scheduled_measure()}} 329 """ 330 log.msg(_("/!\ Agent %s, exception during measure: %s") 331 % (self.name, str(exc))) 332 self.set_status(status.Error(str(exc)))
333 334
335 - def archive(self):
336 """Crée l'archive de l'agent sur disque 337 """ 338 self.ensure_data_uptodate() 339 AgentData.from_agent(self).archive(self.archive_dir)
340 341
342 - def ensure_data_uptodate(self):
343 self.ensure_datadirs() 344 if self.data_needs_update: 345 # would need locking here if multithreaded (archive must have 346 # up-to-date datas) 347 # for d in self.data: 348 # d.ensure_uptodate() 349 self.write_data() 350 self.update_status() 351 self.data_needs_update = False;
352
353 - def write_data(self):
354 """Écrit les données générées par l'agent sur disque 355 356 Méthode à redéfinir si nécessaire dans les sous-classes. 357 """ 358 additional_args = [] 359 this_morning = util.utcnow().replace(hour = 0, minute = 0, second = 0, microsecond = 0) 360 for i in range(7): 361 t = this_morning - i*timedelta(days = 1) 362 additional_args += "VRULE:%s#000000" % rrd.rrd_date_from_datetime(t) 363 self.status_rrd.graph_all()
364 365 # convenience method
366 - def ensure_datadirs(self):
367 """Méthode de convenance, cf C{L{zephir.monitor.agentmanager.util.ensure_dir}} 368 """ 369 assert self.archive_dir is not None 370 util.ensure_dirs(self.archive_dir)
371 372
373 -class TableAgent(Agent):
374 """Agent concret mémorisant ses mesures dans une table. 375 376 Les valeurs mesurées peuvent être non-numériques. 377 """ 378
379 - def __init__(self, name, 380 max_measures=100, 381 **params):
382 """ 383 @param max_measures: nombre maximal de mesures mémorisées 384 """ 385 Agent.__init__(self, name, **params) 386 assert max_measures >= 1 387 self.max_measures = max_measures 388 self.measures = [] 389 self.data = [MeasuresData(self.measures, self.fields)]
390 391
392 - def save_measure(self, measure):
393 """Maintient la table de mesures triée et en dessous de la taille 394 maximale (cf. C{Agent.save_measure}). 395 """ 396 Agent.save_measure(self, measure) 397 # drop old measures to keep list size under max_measures 398 # can't slice because the list must be modified in place 399 for x in range(max(0, len(self.measures) - self.max_measures +1)): 400 self.measures.pop(0) 401 self.measures.append(measure) 402 self.measures.sort()
403 404 405 406
407 -class MultiRRDAgent(Agent):
408 """Classe abstraite pour les agents utilisant RRDtool. 409 410 Les valeurs mesurées étant visualisées sous forme de graphes, 411 elles doivent être numériques. 412 413 Les agents de cette classe maintiennent plusieurs bases de données RRD et 414 génèrent des graphes au format PNG de leurs données. 415 """ 416
417 - def __init__(self, name, 418 datasources, archives, graphs, 419 **params):
420 """ 421 Les paramètres C{datasources}, C{archives} et C{graphs} sont 422 des listes de paramètres pour la configuration d'une L{base 423 RRD<agentmanager.rrd.Database>}. 424 """ 425 Agent.__init__(self, name, **params) 426 self.datasources = datasources 427 self.archives = archives 428 self.graphs = graphs 429 self.data = []
430 431
432 - def init_data(self, archive_dir):
433 """Crée et initialise la base RRD dans C{archive_dir}. 434 """ 435 Agent.init_data(self, archive_dir) 436 self.rrd = {} 437 arch_names = self.archives.keys() 438 arch_names.sort() 439 for name in arch_names: 440 rrdname = name + '.rrd' 441 self.rrd[name] = rrd.Database(os.path.join(self.archive_dir, rrdname), 442 step = self.period) 443 self.data.append(RRDFileData(rrdname)) 444 for ds in self.datasources[name]: self.rrd[name].new_datasource(**ds) 445 for rra in self.archives[name]: self.rrd[name].new_archive(**rra) 446 for g in self.graphs[name]: 447 self.data.append(ImgFileData(g['pngname'])) 448 self.data.append(HTMLData('<br>')) 449 g['pngname'] = os.path.join(self.archive_dir, g['pngname']) 450 self.rrd[name].new_graph(**g) 451 self.rrd[name].create()
452
453 - def save_measure(self, measure):
454 Agent.save_measure(self, measure) 455 # on ne prend que les mesures déclarées comme datasources 456 for name in self.archives.keys(): 457 rrd_values = {} 458 for datasource in self.datasources[name]: 459 if measure.value.has_key(datasource['name']): 460 rrd_values[datasource['name']] = measure.value[datasource['name']] 461 # update des bases rrd 462 self.rrd[name].update(rrd_values, measure.get_date())
463
464 - def write_data(self):
465 Agent.write_data(self) 466 for name in self.archives.keys(): 467 self.rrd[name].graph_all()
468 469
470 -class RRDAgent(Agent):
471 """Classe abstraite pour les agents utilisant RRDtool. 472 473 Les valeurs mesurées étant visualisées sous forme de graphes, 474 elles doivent être numériques. 475 476 Les agents de cette classe maintiennent une base de données RRD et 477 génèrent des graphes au format PNG de leurs données. 478 """ 479
480 - def __init__(self, name, 481 datasources, archives, graphs, 482 **params):
483 """ 484 Les paramètres C{datasources}, C{archives} et C{graphs} sont 485 des listes de paramètres pour la configuration d'une L{base 486 RRD<agentmanager.rrd.Database>}. 487 """ 488 Agent.__init__(self, name, **params) 489 self.datasources = datasources 490 self.archives = archives 491 self.graphs = graphs 492 self.data = []
493 494
495 - def init_data(self, archive_dir):
496 """Crée et initialise la base RRD dans C{archive_dir}. 497 """ 498 Agent.init_data(self, archive_dir) 499 rrdname = self.name + '.rrd' 500 self.rrd = rrd.Database(os.path.join(self.archive_dir, rrdname), 501 step = self.period) 502 self.data.append(RRDFileData(rrdname)) 503 for ds in self.datasources: self.rrd.new_datasource(**ds) 504 for rra in self.archives: self.rrd.new_archive(**rra) 505 for g in self.graphs: 506 self.data.append(ImgFileData(g['pngname'])) 507 self.data.append(HTMLData('<br>')) 508 g['pngname'] = os.path.join(self.archive_dir, g['pngname']) 509 self.rrd.new_graph(**g) 510 self.rrd.create()
511 512
513 - def save_measure(self, measure):
514 Agent.save_measure(self, measure) 515 # on ne prend que les mesures déclarées comme datasources 516 rrd_values = {} 517 for datasource in self.datasources: 518 rrd_values[datasource['name']] = measure.value[datasource['name']] 519 # update des bases rrd 520 self.rrd.update(rrd_values, measure.get_date())
521 522
523 - def write_data(self):
524 Agent.write_data(self) 525 self.rrd.graph_all()
526 527 528 529
530 -class LoadErrorAgent(Agent):
531 """Pseudo-agent représentant une erreur de chargement d'un fichier 532 de configuration. 533 """ 534 535 HTML = """<p class="error">%s</p>""" 536
537 - def __init__(self, name, 538 title=_("Error while loading agent"), 539 message="", 540 **params):
541 Agent.__init__(self, name, **params) 542 self.max_status = status.Error(title) 543 m = '<br />'.join(message.splitlines()) 544 self.html = HTMLData(LoadErrorAgent.HTML % m) 545 self.data = [self.html]
546
547 - def check_status(self):
548 return self.max_status()
549 550 551 552 # def test_main(): 553 # test_support.run_unittest(UserStringTest) 554 555 # if __name__ == "__main__": 556 # test_main() 557