PageRenderTime 53ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 0ms

/boto-2.5.2/boto/manage/task.py

#
Python | 175 lines | 142 code | 4 blank | 29 comment | 6 complexity | ee0f158a790cc1e42f03684c3e799264 MD5 | raw file
  1. # Copyright (c) 2006-2009 Mitch Garnaat http://garnaat.org/
  2. #
  3. # Permission is hereby granted, free of charge, to any person obtaining a
  4. # copy of this software and associated documentation files (the
  5. # "Software"), to deal in the Software without restriction, including
  6. # without limitation the rights to use, copy, modify, merge, publish, dis-
  7. # tribute, sublicense, and/or sell copies of the Software, and to permit
  8. # persons to whom the Software is furnished to do so, subject to the fol-
  9. # lowing conditions:
  10. #
  11. # The above copyright notice and this permission notice shall be included
  12. # in all copies or substantial portions of the Software.
  13. #
  14. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
  15. # OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
  16. # ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
  17. # SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
  18. # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  19. # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
  20. # IN THE SOFTWARE.
  21. #
  22. import boto
  23. from boto.sdb.db.property import StringProperty, DateTimeProperty, IntegerProperty
  24. from boto.sdb.db.model import Model
  25. import datetime, subprocess, StringIO, time
  26. def check_hour(val):
  27. if val == '*':
  28. return
  29. if int(val) < 0 or int(val) > 23:
  30. raise ValueError
  31. class Task(Model):
  32. """
  33. A scheduled, repeating task that can be executed by any participating servers.
  34. The scheduling is similar to cron jobs. Each task has an hour attribute.
  35. The allowable values for hour are [0-23|*].
  36. To keep the operation reasonably efficient and not cause excessive polling,
  37. the minimum granularity of a Task is hourly. Some examples:
  38. hour='*' - the task would be executed each hour
  39. hour='3' - the task would be executed at 3AM GMT each day.
  40. """
  41. name = StringProperty()
  42. hour = StringProperty(required=True, validator=check_hour, default='*')
  43. command = StringProperty(required=True)
  44. last_executed = DateTimeProperty()
  45. last_status = IntegerProperty()
  46. last_output = StringProperty()
  47. message_id = StringProperty()
  48. @classmethod
  49. def start_all(cls, queue_name):
  50. for task in cls.all():
  51. task.start(queue_name)
  52. def __init__(self, id=None, **kw):
  53. Model.__init__(self, id, **kw)
  54. self.hourly = self.hour == '*'
  55. self.daily = self.hour != '*'
  56. self.now = datetime.datetime.utcnow()
  57. def check(self):
  58. """
  59. Determine how long until the next scheduled time for a Task.
  60. Returns the number of seconds until the next scheduled time or zero
  61. if the task needs to be run immediately.
  62. If it's an hourly task and it's never been run, run it now.
  63. If it's a daily task and it's never been run and the hour is right, run it now.
  64. """
  65. boto.log.info('checking Task[%s]-now=%s, last=%s' % (self.name, self.now, self.last_executed))
  66. if self.hourly and not self.last_executed:
  67. return 0
  68. if self.daily and not self.last_executed:
  69. if int(self.hour) == self.now.hour:
  70. return 0
  71. else:
  72. return max( (int(self.hour)-self.now.hour), (self.now.hour-int(self.hour)) )*60*60
  73. delta = self.now - self.last_executed
  74. if self.hourly:
  75. if delta.seconds >= 60*60:
  76. return 0
  77. else:
  78. return 60*60 - delta.seconds
  79. else:
  80. if int(self.hour) == self.now.hour:
  81. if delta.days >= 1:
  82. return 0
  83. else:
  84. return 82800 # 23 hours, just to be safe
  85. else:
  86. return max( (int(self.hour)-self.now.hour), (self.now.hour-int(self.hour)) )*60*60
  87. def _run(self, msg, vtimeout):
  88. boto.log.info('Task[%s] - running:%s' % (self.name, self.command))
  89. log_fp = StringIO.StringIO()
  90. process = subprocess.Popen(self.command, shell=True, stdin=subprocess.PIPE,
  91. stdout=subprocess.PIPE, stderr=subprocess.PIPE)
  92. nsecs = 5
  93. current_timeout = vtimeout
  94. while process.poll() == None:
  95. boto.log.info('nsecs=%s, timeout=%s' % (nsecs, current_timeout))
  96. if nsecs >= current_timeout:
  97. current_timeout += vtimeout
  98. boto.log.info('Task[%s] - setting timeout to %d seconds' % (self.name, current_timeout))
  99. if msg:
  100. msg.change_visibility(current_timeout)
  101. time.sleep(5)
  102. nsecs += 5
  103. t = process.communicate()
  104. log_fp.write(t[0])
  105. log_fp.write(t[1])
  106. boto.log.info('Task[%s] - output: %s' % (self.name, log_fp.getvalue()))
  107. self.last_executed = self.now
  108. self.last_status = process.returncode
  109. self.last_output = log_fp.getvalue()[0:1023]
  110. def run(self, msg, vtimeout=60):
  111. delay = self.check()
  112. boto.log.info('Task[%s] - delay=%s seconds' % (self.name, delay))
  113. if delay == 0:
  114. self._run(msg, vtimeout)
  115. queue = msg.queue
  116. new_msg = queue.new_message(self.id)
  117. new_msg = queue.write(new_msg)
  118. self.message_id = new_msg.id
  119. self.put()
  120. boto.log.info('Task[%s] - new message id=%s' % (self.name, new_msg.id))
  121. msg.delete()
  122. boto.log.info('Task[%s] - deleted message %s' % (self.name, msg.id))
  123. else:
  124. boto.log.info('new_vtimeout: %d' % delay)
  125. msg.change_visibility(delay)
  126. def start(self, queue_name):
  127. boto.log.info('Task[%s] - starting with queue: %s' % (self.name, queue_name))
  128. queue = boto.lookup('sqs', queue_name)
  129. msg = queue.new_message(self.id)
  130. msg = queue.write(msg)
  131. self.message_id = msg.id
  132. self.put()
  133. boto.log.info('Task[%s] - start successful' % self.name)
  134. class TaskPoller(object):
  135. def __init__(self, queue_name):
  136. self.sqs = boto.connect_sqs()
  137. self.queue = self.sqs.lookup(queue_name)
  138. def poll(self, wait=60, vtimeout=60):
  139. while True:
  140. m = self.queue.read(vtimeout)
  141. if m:
  142. task = Task.get_by_id(m.get_body())
  143. if task:
  144. if not task.message_id or m.id == task.message_id:
  145. boto.log.info('Task[%s] - read message %s' % (task.name, m.id))
  146. task.run(m, vtimeout)
  147. else:
  148. boto.log.info('Task[%s] - found extraneous message, ignoring' % task.name)
  149. else:
  150. time.sleep(wait)