Remove obsolete test code from setup.py
[cds-indico.git] / indico / modules / scheduler / tasks / __init__.py
blob1d5f90652ee63b240a074aa00e1ca8bfa39b0e7d
1 # This file is part of Indico.
2 # Copyright (C) 2002 - 2015 European Organization for Nuclear Research (CERN).
4 # Indico is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License as
6 # published by the Free Software Foundation; either version 3 of the
7 # License, or (at your option) any later version.
9 # Indico is distributed in the hope that it will be useful, but
10 # WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 # General Public License for more details.
14 # You should have received a copy of the GNU General Public License
15 # along with Indico; if not, see <http://www.gnu.org/licenses/>.
17 import logging
18 import urllib
20 import zope.interface
21 from persistent import Persistent
22 from flask import render_template
24 from indico.util.fossilize import fossilizes, Fossilizable
25 from indico.util.date_time import int_timestamp, format_datetime
26 from indico.modules.scheduler.fossils import ITaskFossil
27 from indico.modules.scheduler import base
28 from indico.modules.users.legacy import AvatarUserWrapper
29 from indico.core.db import db
30 from indico.core.db.sqlalchemy.util.session import update_session_options
31 from indico.core.index import IUniqueIdProvider, IIndexableByArbitraryDateTime
32 from indico.core.config import Config
33 from indico.web.flask.util import url_for
34 from indico.util.i18n import _
37 # Defines base classes for tasks, and some specific tasks as well
40 class TimedEvent(Persistent, Fossilizable):
42 zope.interface.implements(IUniqueIdProvider,
43 IIndexableByArbitraryDateTime)
45 def getIndexingDateTime(self):
46 return int_timestamp(self._getCurrentDateTime())
48 def _getCurrentDateTime(self):
49 # just get current date/time
50 return base.TimeSource.get().getCurrentTime()
52 def __conform__(self, proto):
54 if proto == IIndexableByArbitraryDateTime:
55 return self.getIndexingDateTime()
56 else:
57 return None
60 class BaseTask(TimedEvent):
61 """
62 A base class for tasks.
63 `expiryDate` is the last point in time when the task can run. A task will
64 refuse to run if current time is past `expiryDate`
65 """
67 DISABLE_ZODB_HOOK = False
69 fossilizes(ITaskFossil)
71 # seconds to consider a task AWOL
72 _AWOLThresold = 6000
74 def __init__(self, expiryDate=None):
75 self.createdOn = self._getCurrentDateTime()
76 self.expiryDate = expiryDate
77 self.typeId = self.__class__.__name__
78 self.id = None
79 self.reset()
80 self.status = 0
82 self.startedOn = None
83 self.endedOn = None
85 def __cmp__(self, obj):
86 from indico.modules.scheduler.tasks.periodic import TaskOccurrence
87 if obj is None:
88 return 1
89 elif isinstance(obj, TaskOccurrence):
90 task_cmp = cmp(self, obj.getTask())
91 if task_cmp == 0:
92 return -1
93 else:
94 return task_cmp
95 # This condition will mostlike never happen
96 elif not isinstance(obj, BaseTask) or (self.id == obj.id and self.id is None):
97 return cmp(hash(self), hash(obj))
98 # This is the 'default case' where we are comparing 2 Tasks
99 else:
100 return cmp(self.id, obj.id)
102 def reset(self):
103 '''Resets a task to its state before being run'''
105 self.running = False
106 self.onRunningListSince = None
108 # Time methods
110 def getCreatedOn(self):
111 return self.createdOn
113 def getEndedOn(self):
114 return self.endedOn
116 def setEndedOn(self, dateTime):
117 self.endedOn = dateTime
119 def getStartedOn(self):
120 return self.startedOn
122 def setStartedOn(self, dateTime):
123 self.startedOn = dateTime
125 def setOnRunningListSince(self, sometime):
126 self.onRunningListSince = sometime
127 self._p_changed = 1
129 def getOnRunningListSince(self):
130 return self.onRunningListSince
132 def setStatus(self, newstatus):
133 self.getLogger().info("%s set status %s" % (self, base.status(newstatus)))
134 self.status = newstatus
136 def getStatus(self):
137 return self.status
139 def getId(self):
140 return self.id
142 def getUniqueId(self):
143 return "task%s" % self.id
145 def getTypeId(self):
146 return self.typeId
148 def initialize(self, newid, newstatus):
149 self.id = newid
150 self.setStatus(newstatus)
152 def plugLogger(self, logger):
153 self._v_logger = logger
155 def getLogger(self):
156 if not hasattr(self, '_v_logger') or not self._v_logger:
157 self._v_logger = logging.getLogger('task/%s' % self.typeId)
158 return self._v_logger
160 def prepare(self):
162 This information will be saved regardless of the task being repeated or not
165 curTime = self._getCurrentDateTime()
166 tsDiff = int_timestamp(self.getStartOn()) - int_timestamp(curTime)
168 if tsDiff > 0:
169 self.getLogger().debug('Task %s will wait for some time. (%s) > (%s)' % \
170 (self.id, self.getStartOn(), curTime))
171 base.TimeSource.get().sleep(tsDiff)
173 if self.expiryDate and curTime > self.expiryDate:
174 self.getLogger().warning(
175 'Task %s will not be executed, expiryDate (%s) < current time (%s)' % \
176 (self.id, self.expiryDate, curTime))
177 return False
179 self.startedOn = curTime
180 self.running = True
181 self.setStatus(base.TASK_STATUS_RUNNING)
183 def start(self, delay):
184 if self.DISABLE_ZODB_HOOK:
185 update_session_options(db)
186 self._executionDelay = delay
187 try:
188 self.run()
189 self.endedOn = self._getCurrentDateTime()
190 finally:
191 self.running = False
193 def tearDown(self):
195 If a task needs to do something once it has run and been removed
196 from runningList, overload this method
198 pass
200 def __str__(self):
201 return "[%s:%s|%s]" % (self.typeId, self.id,
202 base.status(self.status))
205 class OneShotTask(BaseTask):
207 Tasks that are executed only once
210 def __init__(self, startDateTime, expiryDate = None):
211 super(OneShotTask, self).__init__(expiryDate = expiryDate)
212 self.startDateTime = startDateTime
214 def getStartOn(self):
215 return self.startDateTime
217 def setStartOn(self, newtime):
218 self.startDateTime = newtime
220 def suicide(self):
221 self.setStatus(base.TASK_STATUS_TERMINATED)
222 self.setEndedOn(self._getCurrentDateTime())
225 class SendMailTask(OneShotTask):
228 def __init__(self, startDateTime):
229 super(SendMailTask, self).__init__(startDateTime)
230 self.fromAddr = ""
231 self.toAddr = []
232 self.toUser = []
233 self.ccAddr = []
234 self.subject = ""
235 self.text = ""
236 self.smtpServer = Config.getInstance().getSmtpServer()
238 def _prepare(self, check):
240 Overloaded by descendants
243 def run(self, check=True):
244 import smtplib
245 from MaKaC.webinterface.mail import GenericMailer, GenericNotification
247 # prepare the mail
248 send = self._prepare(check=check)
250 # _prepare decided we shouldn't send the mail?
251 if not send:
252 return
254 # just in case some ill-behaved code generates empty addresses
255 addrs = list(smtplib.quoteaddr(x) for x in self.toAddr if x)
256 ccaddrs = list(smtplib.quoteaddr(x) for x in self.ccAddr if x)
258 if len(addrs) + len(ccaddrs) == 0:
259 self.getLogger().warning("Attention: no recipients, mail won't be sent")
260 else:
261 self.getLogger().info("Sending mail To: %s, CC: %s" % (addrs, ccaddrs))
263 for user in self.toUser:
264 addrs.append(smtplib.quoteaddr(user.getEmail()))
266 if addrs or ccaddrs:
267 GenericMailer.send(GenericNotification({"fromAddr": self.fromAddr,
268 "toList": addrs,
269 "ccList": ccaddrs,
270 "subject": self.subject,
271 "body": self.text }))
273 def setFromAddr(self, addr):
274 self.fromAddr = addr
275 self._p_changed = 1
277 def getFromAddr(self):
278 return self.fromAddr
280 def addToAddr(self, addr):
281 if not addr in self.toAddr:
282 self.toAddr.append(addr)
283 self._p_changed = 1
285 def addCcAddr(self, addr):
286 if not addr in self.ccAddr:
287 self.ccAddr.append(addr)
288 self._p_changed = 1
290 def removeToAddr(self, addr):
291 if addr in self.toAddr:
292 self.toAddr.remove(addr)
293 self._p_changed = 1
295 def setToAddrList(self, addrList):
296 """Params: addrList -- addresses of type : list of str."""
297 self.toAddr = addrList
298 self._p_changed = 1
300 def getToAddrList(self):
301 return self.toAddr
303 def setCcAddrList(self, addrList):
304 """Params: addrList -- addresses of type : list of str."""
305 self.ccAddr = addrList
306 self._p_changed = 1
308 def getCcAddrList(self):
309 return self.ccAddr
311 def addToUser(self, user):
312 if not user in self.toUser:
313 self.toUser.append(user)
314 self._p_changed = 1
316 def removeToUser(self, user):
317 if user in self.toUser:
318 self.toUser.remove(user)
319 self._p_changed = 1
321 def getToUserList(self):
322 return self.toUser
324 def setSubject(self, subject):
325 self.subject = subject
327 def getSubject(self):
328 return self.subject
330 def setText(self, text):
331 self.text = text
333 def getText(self):
334 return self.text
337 class HTTPTask(OneShotTask):
338 def __init__(self, url, data=None):
339 super(HTTPTask, self).__init__(base.TimeSource.get().getCurrentTime())
340 self._url = url
341 self._data = data
343 def run(self):
344 method = 'POST' if self._data is not None else 'GET'
345 self.getLogger().info('Executing HTTP task: %s %s' % (method, self._url))
346 data = urllib.urlencode(self._data) if self._data is not None else None
347 req = urllib.urlopen(self._url, data)
348 req.close()
351 class SampleOneShotTask(OneShotTask):
352 def run(self):
353 self.getLogger().debug('Now i shall sleeeeeeeep!')
354 base.TimeSource.get().sleep(1)
355 self.getLogger().debug('%s executed' % self.__class__.__name__)
358 class DeletedTask(BaseTask):
359 """ZODB migration class to avoid broken objects for deleted tasks"""
361 def getStartOn(self):
362 try:
363 return self._nextOccurrence
364 except AttributeError:
365 return self.startDateTime