d06edae7597372f9005da126c7d4361e7fc37918
[monitor.git] / monitor / model.py
1 #!/usr/bin/python
2
3 from monitor import database
4
5 from monitor.wrapper import plc, plccache
6 from monitor.wrapper import mailer
7 import time
8
9 from monitor.const import *
10 from monitor import util
11 from monitor import config
12
13 import time
14 from datetime import datetime, timedelta
15 import re
16
17 def gethostlist(hostlist_file):
18         return util.file.getListFromFile(hostlist_file)
19
20 def array_to_priority_map(array):
21         """ Create a mapping where each entry of array is given a priority equal
22         to its position in the array.  This is useful for subsequent use in the
23         cmpMap() function."""
24         map = {}
25         count = 0
26         for i in array:
27                 map[i] = count
28                 count += 1
29         return map
30
31 def cmpValMap(v1, v2, map):
32         if v1 in map and v2 in map and map[v1] < map[v2]:
33                 return 1
34         elif v1 in map and v2 in map and map[v1] > map[v2]:
35                 return -1
36         elif v1 in map and v2 in map:
37                 return 0
38         else:
39                 raise Exception("No index %s or %s in map" % (v1, v2))
40
41 def cmpCategoryVal(v1, v2):
42         # Terrible hack to manage migration to no more 'ALPHA' states.
43         if v1 == 'ALPHA': v1 = "PROD"
44         if v2 == 'ALPHA': v2 = "PROD"
45         #map = array_to_priority_map([ None, 'PROD', 'ALPHA', 'OLDBOOTCD', 'UNKNOWN', 'FORCED', 'ERROR', ])
46         map = array_to_priority_map([ None, 'ALPHA', 'PROD', 'OLDBOOTCD', 'UNKNOWN', 'FORCED', 'ERROR', ])
47         return cmpValMap(v1,v2,map)
48
49
50 class PCU:
51         def __init__(self, hostname):
52                 self.hostname = hostname
53
54         def reboot(self):
55                 return True
56         def available(self):
57                 return True
58         def previous_attempt(self):
59                 return True
60         def setValidMapping(self):
61                 pass
62
63 class Penalty:
64         def __init__(self, key, valuepattern, action):
65                 pass
66
67 class PenaltyMap:
68         def __init__(self):
69                 pass
70
71         # connect one penalty to another, in a FSM diagram.  After one
72         #       condition/penalty is applied, move to the next phase.
73
74
75 class RT(object):
76         def __init__(self, ticket_id = None):
77                 self.ticket_id = ticket_id
78                 if self.ticket_id:
79                         print "getting ticket status",
80                         self.status = mailer.getTicketStatus(self.ticket_id)
81                         print self.status
82
83         def setTicketStatus(self, status):
84                 mailer.setTicketStatus(self.ticket_id, status)
85                 self.status = mailer.getTicketStatus(self.ticket_id)
86                 return True
87         
88         def getTicketStatus(self):
89                 if not self.status:
90                         self.status = mailer.getTicketStatus(self.ticket_id)
91                 return self.status
92
93         def closeTicket(self):
94                 mailer.closeTicketViaRT(self.ticket_id, "Ticket CLOSED automatically by SiteAssist.") 
95
96         def email(self, subject, body, to):
97                 self.ticket_id = mailer.emailViaRT(subject, body, to, self.ticket_id)
98                 return self.ticket_id
99
100 class Message(object):
101         def __init__(self, subject, message, via_rt=True, ticket_id=None, **kwargs):
102                 self.via_rt = via_rt
103                 self.subject = subject
104                 self.message = message
105                 self.rt = RT(ticket_id)
106
107         def send(self, to):
108                 if self.via_rt:
109                         return self.rt.email(self.subject, self.message, to)
110                 else:
111                         return mailer.email(self.subject, self.message, to)
112
113 class Recent(object):
114         def __init__(self, withintime):
115                 self.withintime = withintime
116
117                 try:
118                         self.time = self.__getattribute__('time')
119                 except:
120                         self.time = time.time()- 7*24*60*60
121
122                 #self.time = time.time()
123                 #self.action_taken = False
124
125         def isRecent(self):
126                 if self.time + self.withintime < time.time():
127                         self.action_taken = False
128
129                 if self.time + self.withintime > time.time() and self.action_taken:
130                         return True
131                 else:
132                         return False
133
134         def unsetRecent(self):
135                 self.action_taken = False
136                 self.time = time.time()
137                 return True
138
139         def setRecent(self):
140                 self.action_taken = True
141                 self.time = time.time()
142                 return True
143                 
144 class PersistFlags(Recent):
145         def __new__(typ, id, *args, **kwargs):
146                 if 'db' in kwargs:
147                         db = kwargs['db']
148                         del kwargs['db']
149                 else:
150                         db = "persistflags"
151
152                 try:
153                         pm = database.dbLoad(db)
154                 except:
155                         database.dbDump(db, {})
156                         pm = database.dbLoad(db)
157                 #print pm
158                 if id in pm:
159                         obj = pm[id]
160                 else:
161                         obj = super(PersistFlags, typ).__new__(typ, *args, **kwargs)
162                         for key in kwargs.keys():
163                                 obj.__setattr__(key, kwargs[key])
164                         obj.time = time.time()
165                         obj.action_taken = False
166
167                 obj.db = db
168                 return obj
169
170         def __init__(self, id, withintime, **kwargs):
171                 self.id = id
172                 Recent.__init__(self, withintime)
173
174         def save(self):
175                 pm = database.dbLoad(self.db)
176                 pm[self.id] = self
177                 database.dbDump(self.db, pm)
178
179         def resetFlag(self, name):
180                 self.__setattr__(name, False)
181
182         def setFlag(self, name):
183                 self.__setattr__(name, True)
184                 
185         def getFlag(self, name):
186                 try:
187                         return self.__getattribute__(name)
188                 except:
189                         self.__setattr__(name, False)
190                         return False
191
192         def resetRecentFlag(self, name):
193                 self.resetFlag(name)
194                 self.unsetRecent()
195
196         def setRecentFlag(self, name):
197                 self.setFlag(name)
198                 self.setRecent()
199
200         def getRecentFlag(self, name):
201                 # if recent and flag set -> true
202                 # else false
203                 try:
204                         return self.isRecent() & self.__getattribute__(name)
205                 except:
206                         self.__setattr__(name, False)
207                         return False
208
209         def checkattr(self, name):
210                 try:
211                         x = self.__getattribute__(name)
212                         return True
213                 except:
214                         return False
215                 
216
217 class PersistMessage(Message):
218         def __new__(typ, id, subject, message, via_rt, **kwargs):
219                 if 'db' in kwargs:
220                         db = kwargs['db']
221                 else:
222                         db = "persistmessages"
223
224                 try:
225                         pm = database.dbLoad(db)
226                 except:
227                         database.dbDump(db, {})
228                         pm = database.dbLoad(db)
229
230                 #print pm
231                 if id in pm:
232                         #print "Using existing object"
233                         obj = pm[id]
234                 else:
235                         #print "creating new object"
236                         obj = super(PersistMessage, typ).__new__(typ, [id, subject, message, via_rt], **kwargs)
237                         obj.id = id
238                         obj.actiontracker = Recent(1*60*60*24)
239                         obj.ticket_id = None
240
241                 if 'ticket_id' in kwargs and kwargs['ticket_id'] is not None:
242                         obj.ticket_id = kwargs['ticket_id']
243
244                 obj.db = db
245                 return obj
246
247         def __init__(self, id, subject, message, via_rt=True, **kwargs):
248                 print "initializing object: %s" % self.ticket_id
249                 self.id = id
250                 Message.__init__(self, subject, message, via_rt, self.ticket_id)
251
252         def reset(self):
253                 self.actiontracker.unsetRecent()
254
255         def save(self):
256                 pm = database.dbLoad(self.db)
257                 pm[self.id] = self
258                 database.dbDump(self.db, pm)
259
260         def send(self, to):
261                 if not self.actiontracker.isRecent():
262                         self.ticket_id = Message.send(self, to)
263                         self.actiontracker.setRecent()
264                         self.save()
265                 else:
266                         # NOTE: only send a new message every week, regardless.
267                         # NOTE: can cause thank-you messages to be lost, for instance when node comes back online within window.
268                         print "Not sending to host b/c not within window of %s days" % (self.actiontracker.withintime // (60*60*24))
269
270 class MonitorMessage(object):
271         def __new__(typ, id, *args, **kwargs):
272                 if 'db' in kwargs:
273                         db = kwargs['db']
274                 else:
275                         db = "monitormessages"
276
277                 try:
278                         if 'reset' in kwargs and kwargs['reset'] == True:
279                                 database.dbDump(db, {})
280                         pm = database.dbLoad(db)
281                 except:
282                         database.dbDump(db, {})
283                         pm = database.dbLoad(db)
284
285                 #print pm
286                 if id in pm:
287                         print "Using existing object"
288                         obj = pm[id]
289                 else:
290                         print "creating new object"
291                         obj = super(object, typ).__new__(typ, id, *args, **kwargs)
292                         obj.id = id
293                         obj.sp = PersistSitePenalty(id, 0)
294
295                 obj.db = db
296                 return obj
297
298         def __init__(self, id, message):
299                 pass
300                 
301
302 class SitePenalty(object):
303         penalty_map = [] 
304         penalty_map.append( { 'name': 'noop',                   'enable'   : lambda host: None,
305                                                                                                         'disable'  : lambda host: None } )
306         penalty_map.append( { 'name': 'nocreate',               'enable'   : lambda host: plc.removeSliceCreation(host),
307                                                                                                         'disable'  : lambda host: plc.enableSliceCreation(host) } )
308         penalty_map.append( { 'name': 'suspendslices',  'enable'   : lambda host: plc.suspendSlices(host),
309                                                                                                         'disable'  : lambda host: plc.enableSlices(host) } )
310
311         #def __init__(self, index=0, **kwargs):
312         #       self.index = index
313
314         def get_penalties(self):
315                 # TODO: get penalties actually applied to a node from PLC DB.
316                 return [ n['name'] for n in SitePenalty.penalty_map ] 
317
318         def increase(self):
319                 self.index = self.index + 1
320                 if self.index > len(SitePenalty.penalty_map)-1: self.index = len(SitePenalty.penalty_map)-1
321                 return True
322
323         def decrease(self):
324                 self.index = self.index - 1
325                 if self.index < 0: self.index = 0
326                 return True
327
328         def apply(self, host):
329
330                 for i in range(len(SitePenalty.penalty_map)-1,self.index,-1):
331                         print "\tdisabling %s on %s" % (SitePenalty.penalty_map[i]['name'], host)
332                         SitePenalty.penalty_map[i]['disable'](host)
333
334                 for i in range(0,self.index+1):
335                         print "\tapplying %s on %s" % (SitePenalty.penalty_map[i]['name'], host)
336                         SitePenalty.penalty_map[i]['enable'](host)
337
338                 return
339
340
341
342 class PersistSitePenalty(SitePenalty):
343         def __new__(typ, id, index, **kwargs):
344                 if 'db' in kwargs:
345                         db = kwargs['db']
346                 else:
347                         db = "persistpenalties"
348
349                 try:
350                         if 'reset' in kwargs and kwargs['reset'] == True:
351                                 database.dbDump(db, {})
352                         pm = database.dbLoad(db)
353                 except:
354                         database.dbDump(db, {})
355                         pm = database.dbLoad(db)
356
357                 #print pm
358                 if id in pm:
359                         print "Using existing object"
360                         obj = pm[id]
361                 else:
362                         print "creating new object"
363                         obj = super(PersistSitePenalty, typ).__new__(typ, [index], **kwargs)
364                         obj.id = id
365                         obj.index = index
366
367                 obj.db = db
368                 return obj
369
370         def __init__(self, id, index, **kwargs):
371                 self.id = id
372
373         def save(self):
374                 pm = database.dbLoad(self.db)
375                 pm[self.id] = self
376                 database.dbDump(self.db, pm)
377
378
379 class Target:
380         """
381                 Each host has a target set of attributes.  Some may be set manually,
382                 or others are set globally for the preferred target.
383
384                 For instance:
385                         All nodes in the Alpha or Beta group would have constraints like:
386                                 [ { 'state' : 'BOOT', 'kernel' : '2.6.22' } ]
387         """
388         def __init__(self, constraints):
389                 self.constraints = constraints
390
391         def verify(self, data):
392                 """
393                         self.constraints is a list of key, value pairs.
394                         # [ {... : ...}==AND , ... , ... , ] == OR
395                 """
396                 con_or_true = False
397                 for con in self.constraints:
398                         #print "con: %s" % con
399                         con_and_true = True
400                         for key in con.keys():
401                                 #print "looking at key: %s" % key
402                                 if key in data: 
403                                         #print "%s %s" % (con[key], data[key])
404                                         con_and_true = con_and_true & (con[key] in data[key])
405                                 elif key not in data:
406                                         print "missing key %s" % key
407                                         con_and_true = False
408
409                         con_or_true = con_or_true | con_and_true
410
411                 return con_or_true
412
413 class Record(object):
414
415         def __init__(self, hostname, data):
416                 self.hostname = hostname
417                 self.data = data
418                 self.plcdb_hn2lb = plccache.plcdb_hn2lb
419                 self.loginbase = self.plcdb_hn2lb[self.hostname]
420                 return
421
422
423         def stageIswaitforever(self):
424                 if 'waitforever' in self.data['stage']:
425                         return True
426                 else:
427                         return False
428
429         def severity(self):
430                 category = self.data['category']
431                 prev_category = self.data['prev_category']
432                 #print "SEVERITY: ", category, prev_category
433                 val = cmpCategoryVal(category, prev_category)
434                 return val 
435
436         def improved(self):
437                 return self.severity() > 0
438         
439         def end_record(self):
440                 return node_end_record(self.hostname)
441
442         def reset_stage(self):
443                 self.data['stage'] = 'findbad'
444                 return True
445         
446         def getCategory(self):
447                 return self.data['category'].lower()
448
449         def getState(self):
450                 return self.data['state'].lower()
451
452         def getDaysDown(cls, diag_record):
453                 daysdown = -1
454                 if diag_record['comonstats']['uptime'] != "null" and diag_record['comonstats']['uptime'] != "-1":
455                         daysdown = - int(float(diag_record['comonstats']['uptime'])) // (60*60*24)
456                 #elif diag_record['comonstats']['sshstatus'] != "null":
457                 #       daysdown = int(diag_record['comonstats']['sshstatus']) // (60*60*24)
458                 #elif diag_record['comonstats']['lastcotop'] != "null":
459                 #       daysdown = int(diag_record['comonstats']['lastcotop']) // (60*60*24)
460                 else:
461                         now = time.time()
462                         last_contact = diag_record['plcnode']['last_contact']
463                         if last_contact == None:
464                                 # the node has never been up, so give it a break
465                                 daysdown = -1
466                         else:
467                                 diff = now - last_contact
468                                 daysdown = diff // (60*60*24)
469                 return daysdown
470         getDaysDown = classmethod(getDaysDown)
471
472         def getStrDaysDown(cls, diag_record):
473                 daysdown = "unknown"
474                 last_contact = diag_record['plcnode']['last_contact']
475                 date_created = diag_record['plcnode']['date_created']
476
477                 if      diag_record['comonstats']['uptime'] != "null" and \
478                         diag_record['comonstats']['uptime'] != "-1":
479                         daysdown = int(float(diag_record['comonstats']['uptime'])) // (60*60*24)
480                         daysdown = "%d days up" % daysdown
481
482                 elif last_contact is None:
483                         if date_created is not None:
484                                 now = time.time()
485                                 diff = now - date_created
486                                 daysdown = diff // (60*60*24)
487                                 daysdown = "Never contacted PLC, created %s days ago" % daysdown
488                         else:
489                                 daysdown = "Never contacted PLC"
490                 else:
491                         now = time.time()
492                         diff = now - last_contact
493                         daysdown = diff // (60*60*24)
494                         daysdown = "%s days down" % daysdown
495                 return daysdown
496         getStrDaysDown = classmethod(getStrDaysDown)
497
498         def getSendEmailFlag(self):
499                 if not config.mail:
500                         return False
501
502                 # resend if open & created longer than 30 days ago.
503                 if  'rt' in self.data and \
504                         'Status' in self.data['rt'] and \
505                         "open" in self.data['rt']['Status'] and \
506                         self.data['rt']['Created'] > int(time.time() - 60*60*24*30):
507                         # if created-time is greater than the thirty days ago from the current time
508                         return False
509
510                 return True
511
512         def getMostRecentStage(self):
513                 lastact = self.data['last_action_record']
514                 return lastact.stage
515
516         def getMostRecentTime(self):
517                 lastact = self.data['last_action_record']
518                 return lastact.date_action_taken
519
520         def takeAction(self, index=0):
521                 pp = PersistSitePenalty(self.hostname, 0, db='persistpenalty_hostnames')
522                 if 'improvement' in self.data['stage'] or self.improved() or \
523                         'monitor-end-record' in self.data['stage']:
524                         print "takeAction: decreasing penalty for %s"%self.hostname
525                         pp.decrease()
526                         pp.decrease()
527                 else:
528                         print "takeAction: increasing penalty for %s"%self.hostname
529                         pp.increase()
530                 pp.index = index
531                 pp.apply(self.hostname)
532                 pp.save()
533
534         def _format_diaginfo(self):
535                 info = self.data['info']
536                 print "FORMAT : STAGE: ", self.data['stage']
537                 if self.data['stage'] == 'monitor-end-record':
538                         if info[2] == "ALPHA": info = (info[0], info[1], "PROD")
539                         hlist = "    %s went from '%s' to '%s'\n" % (info[0], info[1], info[2]) 
540                 else:
541                         hlist = "    %s %s - %s\n" % (info[0], info[2], info[1]) #(node,ver,daysdn)
542                 return hlist
543         def saveAction(self):
544                 if 'save_act_all' in self.data and self.data['save_act_all'] == True:
545                         return True
546                 else:
547                         return False
548
549         def getMessage(self, ticket_id=None):
550                 self.data['args']['hostname'] = self.hostname
551                 self.data['args']['loginbase'] = self.loginbase
552                 self.data['args']['hostname_list'] = self._format_diaginfo()
553                 #print self.data['message']
554                 if self.data['message']:
555                         message = PersistMessage(self.hostname, 
556                                                                  self.data['message'][0] % self.data['args'],
557                                                                  self.data['message'][1] % self.data['args'],
558                                                                  True, db='monitor_persistmessages',
559                                                                  ticket_id=ticket_id)
560                         if self.data['stage'] == "improvement":
561                                 message.reset()
562                         return message
563                 else:
564                         return None
565         
566         def getContacts(self):
567                 roles = self.data['email']
568
569                 if not config.mail and not config.debug and config.bcc:
570                         roles = ADMIN
571                 if config.mail and config.debug:
572                         roles = ADMIN
573
574                 # build targets
575                 contacts = []
576                 if ADMIN & roles:
577                         contacts += [config.email]
578                 if TECH & roles:
579                         #contacts += [TECHEMAIL % self.loginbase]
580                         contacts += plc.getTechEmails(self.loginbase)
581                 if PI & roles:
582                         #contacts += [PIEMAIL % self.loginbase]
583                         contacts += plc.getSliceUserEmails(self.loginbase)
584                 if USER & roles:
585                         contacts += plc.getSliceUserEmails(self.loginbase)
586                         slices = plc.slices(self.loginbase)
587                         if len(slices) >= 1:
588                                 #for slice in slices:
589                                 #       contacts += [SLICEMAIL % slice]
590                                 print "SLIC: %20s : %d slices" % (self.loginbase, len(slices))
591                         else:
592                                 print "SLIC: %20s : 0 slices" % self.loginbase
593
594                 return contacts
595
596
597 class NodeRecord:
598         def __init__(self, hostname, target):
599                 self.hostname = hostname
600                 self.ticket = None
601                 self.target = target
602
603
604 class MonRecord(object):
605         def __init__(self, data):
606                 self.keys = data.keys()
607                 self.keys.sort()
608                 self.__dict__.update(data)
609                 return
610
611         def get(self):
612                 ret= {}
613                 for k in self.keys:
614                         ret[k] = self.__dict__[k]
615                 return ret
616
617         def __repr__(self):
618                 str = ""
619                 str += self.host + "\n"
620                 for k in self.keys:
621                         if "message" in k or "msg" in k:
622                                 continue
623                         if 'time' in k:
624                                 s_time=time.strftime("%Y/%m/%d %H:%M:%S", 
625                                                         time.gmtime(self.__dict__[k]))
626                                 str += "\t'%s' : %s\n" % (k, s_time)
627                         else:
628                                 str += "\t'%s' : %s\n" % (k, self.__dict__[k])
629                 str += "\t--"
630                 return str
631
632         def delField(self, field):
633                 if field in self.__dict__:
634                         del self.__dict__[field]
635                 
636                 if field in self.keys:
637                         for i in range(0,len(self.keys)):
638                                 if self.keys[i] == field:
639                                         del self.keys[i]
640                                         break
641
642 class Action(MonRecord):
643         def __init__(self, host, data):
644                 self.host = host
645                 MonRecord.__init__(self, data)
646                 return
647
648         def deltaDays(self, delta):
649                 t = datetime.fromtimestamp(self.__dict__['time'])
650                 d = t + timedelta(delta)
651                 self.__dict__['time'] = time.mktime(d.timetuple())
652                 
653 def node_end_record(node):
654         act_all = database.dbLoad("act_all")
655         if node not in act_all:
656                 del act_all
657                 return False
658
659         if len(act_all[node]) == 0:
660                 del act_all
661                 return False
662
663         pm = database.dbLoad("monitor_persistmessages")
664         if node not in pm:
665                 del pm
666                 return False
667         else:
668                 print "deleting node record"
669                 del pm[node]
670                 database.dbDump("monitor_persistmessages", pm)
671
672         a = Action(node, act_all[node][0])
673         a.delField('rt')
674         a.delField('found_rt_ticket')
675         a.delField('second-mail-at-oneweek')
676         a.delField('second-mail-at-twoweeks')
677         a.delField('first-found')
678         rec = a.get()
679         rec['action'] = ["close_rt"]
680         rec['category'] = "ALPHA"       # assume that it's up...
681         rec['stage'] = "monitor-end-record"
682         rec['ticket_id'] = None
683         rec['time'] = time.time() - 7*60*60*24
684         act_all[node].insert(0,rec)
685         database.dbDump("act_all", act_all)
686         del act_all
687         return True
688
689
690 class LogRoll:
691         def __init__(self, list=None):
692                 self.list = list
693                 if self.list == None:
694                         self.list = {}
695
696         def find(self, host, filter, timerange):
697                 if host not in self.list:
698                         return None
699
700                 host_log_list = self.list[host]
701                 for log in host_log_list:
702                         for key in filter.keys():
703                                 #print "searching key %s in log keys" % key
704                                 if key in log.keys:
705                                         #print "%s in log.keys" % key
706                                         cmp = re.compile(filter[key])
707                                         res = cmp.search(log.__getattribute__(key))
708                                         if res != None:
709                                                 #print "found match in log: %s  %s ~=~ %s" % (log, key, filter[key])
710                                                 if log.time > time.time() - timerange:
711                                                         print "returning log b/c it occured within time."
712                                                         return log
713                 return None
714                 
715
716         def get(self, host):
717                 if host in self.list:
718                         return self.list[host][0]
719                 else:
720                         return None
721
722         def add(self, log):
723                 if log.host not in self.list:
724                         self.list[log.host] = []
725
726                 self.list[log.host].insert(0,log)
727
728 class Log(MonRecord):
729         def __init__(self, host, data):
730                 self.host = host
731                 MonRecord.__init__(self, data)
732                 return
733
734         def __repr__(self):
735                 str = " "
736                 str += self.host + " : { "
737                 for k in self.keys:
738                         if "message" in k or "msg" in k:
739                                 continue
740                         if 'time' in k:
741                                 s_time=time.strftime("%Y/%m/%d %H:%M:%S", 
742                                                         time.gmtime(self.__dict__[k]))
743                                 #str += " '%s' : %s, " % (k, s_time)
744                         elif 'action' in k:
745                                 str += "'%s' : %s, " % (k, self.__dict__[k])
746                 str += "}"
747                 return str
748         
749
750 class Diagnose(MonRecord):
751         def __init__(self, host):
752                 self.host = host
753                 MonRecord.__init__(self, data)
754                 return
755
756
757
758 if __name__ == "__main__":
759         #r = RT()
760         #r.email("test", "body of test message", ['database@cs.princeton.edu'])
761         #from emailTxt import mailtxt
762         print "loaded"
763         #database.dbDump("persistmessages", {});
764         #args = {'url_list': 'http://www.planet-lab.org/bootcds/planet1.usb\n','hostname': 'planet1','hostname_list': ' blahblah -  days down\n'}
765         #m = PersistMessage("blue", "test 1", mailtxt.newdown_one[1] % args, True)
766         #m.send(['soltesz@cs.utk.edu'])
767         #m = PersistMessage("blue", "test 1 - part 2", mailtxt.newalphacd_one[1] % args, True)
768         # TRICK timer to thinking some time has passed.
769         #m.actiontracker.time = time.time() - 6*60*60*24
770         #m.send(['soltesz@cs.utk.edu'])