2008-10-21 17:03:07 -07:00
|
|
|
|
2008-10-21 22:13:54 -07:00
|
|
|
import time
|
2020-06-18 18:29:27 -04:00
|
|
|
from nevow import url
|
2020-06-02 11:56:59 -04:00
|
|
|
from twisted.web.template import (
|
|
|
|
renderer,
|
|
|
|
tags as T,
|
|
|
|
)
|
2008-11-06 21:53:09 -07:00
|
|
|
from twisted.python.failure import Failure
|
|
|
|
from twisted.internet import reactor, defer
|
2020-06-18 18:29:27 -04:00
|
|
|
from twisted.web import resource
|
2008-10-21 22:13:54 -07:00
|
|
|
from twisted.web.http import NOT_FOUND
|
|
|
|
from twisted.web.html import escape
|
|
|
|
from twisted.application import service
|
2008-10-21 17:03:07 -07:00
|
|
|
|
2020-06-01 16:16:04 -04:00
|
|
|
from allmydata.web.common import (
|
|
|
|
WebError,
|
|
|
|
get_root,
|
|
|
|
get_arg,
|
|
|
|
boolean_of_arg,
|
2020-09-21 16:26:51 -04:00
|
|
|
exception_to_child,
|
2020-06-01 16:16:04 -04:00
|
|
|
)
|
2008-10-21 17:03:07 -07:00
|
|
|
|
2008-10-21 22:13:54 -07:00
|
|
|
MINUTE = 60
|
|
|
|
HOUR = 60*MINUTE
|
2010-02-20 17:05:12 -08:00
|
|
|
DAY = 24*HOUR
|
2008-10-21 22:13:54 -07:00
|
|
|
|
|
|
|
(MONITOR, RENDERER, WHEN_ADDED) = range(3)
|
|
|
|
|
2020-06-18 18:29:27 -04:00
|
|
|
class OphandleTable(resource.Resource, service.Service):
|
2020-06-02 08:49:41 -04:00
|
|
|
"""Renders /operations/%d."""
|
|
|
|
|
2019-08-07 17:47:52 -06:00
|
|
|
name = "operations"
|
2008-10-21 17:03:07 -07:00
|
|
|
|
2010-02-20 17:05:12 -08:00
|
|
|
UNCOLLECTED_HANDLE_LIFETIME = 4*DAY
|
|
|
|
COLLECTED_HANDLE_LIFETIME = 1*DAY
|
2008-10-21 22:13:54 -07:00
|
|
|
|
Change OphandleTable to use a deterministic clock, so we can test it
To test the changes for #577, we need a deterministic way to simulate
the passage of long periods of time. twisted.internet.task.Clock seems,
from my Googling, to be the way to go for this functionality. I changed
a few things so that OphandleTable would use twisted.internet.task.Clock
when testing:
* WebishServer.__init___ now takes an optional 'clock' parameter,
* which it passes to the root.Root instance it creates.
* root.Root.__init__ now takes an optional 'clock' parameter, which it
passes to the OphandleTable.__init__ method.
* OphandleTable.__init__ now takes an optional 'clock' parameter. If
it is provided, and it isn't None, its callLater method will be used
to schedule ophandle expirations (as opposed to using
reactor.callLater, which is what OphandleTable does normally).
* The WebMixin object in test_web.py now sets a self.clock parameter,
which is a twisted.internet.task.Clock that it feeds to the
WebishServer it creates.
Tests using the WebMixin can control the passage of time in
OphandleTable by accessing self.clock.
2010-02-20 13:07:13 -08:00
|
|
|
def __init__(self, clock=None):
|
2020-06-18 18:29:27 -04:00
|
|
|
super(OphandleTable, self).__init__()
|
2008-10-21 22:13:54 -07:00
|
|
|
# both of these are indexed by ophandle
|
|
|
|
self.handles = {} # tuple of (monitor, renderer, when_added)
|
|
|
|
self.timers = {}
|
Change OphandleTable to use a deterministic clock, so we can test it
To test the changes for #577, we need a deterministic way to simulate
the passage of long periods of time. twisted.internet.task.Clock seems,
from my Googling, to be the way to go for this functionality. I changed
a few things so that OphandleTable would use twisted.internet.task.Clock
when testing:
* WebishServer.__init___ now takes an optional 'clock' parameter,
* which it passes to the root.Root instance it creates.
* root.Root.__init__ now takes an optional 'clock' parameter, which it
passes to the OphandleTable.__init__ method.
* OphandleTable.__init__ now takes an optional 'clock' parameter. If
it is provided, and it isn't None, its callLater method will be used
to schedule ophandle expirations (as opposed to using
reactor.callLater, which is what OphandleTable does normally).
* The WebMixin object in test_web.py now sets a self.clock parameter,
which is a twisted.internet.task.Clock that it feeds to the
WebishServer it creates.
Tests using the WebMixin can control the passage of time in
OphandleTable by accessing self.clock.
2010-02-20 13:07:13 -08:00
|
|
|
# The tests will provide a deterministic clock
|
|
|
|
# (twisted.internet.task.Clock) that they can control so that
|
|
|
|
# they can test ophandle expiration. If this is provided, I'll
|
|
|
|
# use it schedule the expiration of ophandles.
|
|
|
|
self.clock = clock
|
2008-10-21 22:13:54 -07:00
|
|
|
|
|
|
|
def stopService(self):
|
|
|
|
for t in self.timers.values():
|
|
|
|
if t.active():
|
|
|
|
t.cancel()
|
|
|
|
del self.handles # this is not restartable
|
|
|
|
del self.timers
|
|
|
|
return service.Service.stopService(self)
|
|
|
|
|
2020-06-01 18:19:08 -04:00
|
|
|
def add_monitor(self, req, monitor, renderer):
|
2020-06-02 08:49:41 -04:00
|
|
|
"""
|
|
|
|
:param allmydata.webish.MyRequest req:
|
|
|
|
:param allmydata.monitor.Monitor monitor:
|
|
|
|
:param allmydata.web.directory.ManifestResults renderer:
|
|
|
|
"""
|
2020-06-01 18:19:08 -04:00
|
|
|
ophandle = get_arg(req, "ophandle")
|
2008-10-21 22:13:54 -07:00
|
|
|
assert ophandle
|
|
|
|
now = time.time()
|
|
|
|
self.handles[ophandle] = (monitor, renderer, now)
|
2020-06-01 18:19:08 -04:00
|
|
|
retain_for = get_arg(req, "retain-for", None)
|
2008-10-21 22:13:54 -07:00
|
|
|
if retain_for is not None:
|
|
|
|
self._set_timer(ophandle, int(retain_for))
|
|
|
|
monitor.when_done().addBoth(self._operation_complete, ophandle)
|
2008-10-21 17:03:07 -07:00
|
|
|
|
2008-10-21 22:13:54 -07:00
|
|
|
def _operation_complete(self, res, ophandle):
|
|
|
|
if ophandle in self.handles:
|
|
|
|
if ophandle not in self.timers:
|
|
|
|
# the client has not provided a retain-for= value for this
|
|
|
|
# handle, so we set our own.
|
|
|
|
now = time.time()
|
|
|
|
added = self.handles[ophandle][WHEN_ADDED]
|
|
|
|
when = max(self.UNCOLLECTED_HANDLE_LIFETIME, now - added)
|
|
|
|
self._set_timer(ophandle, when)
|
|
|
|
# if we already have a timer, the client must have provided the
|
|
|
|
# retain-for= value, so don't touch it.
|
2008-10-21 17:03:07 -07:00
|
|
|
|
2020-06-01 18:19:08 -04:00
|
|
|
def redirect_to(self, req):
|
2020-06-02 08:49:41 -04:00
|
|
|
"""
|
|
|
|
:param allmydata.webish.MyRequest req:
|
|
|
|
"""
|
2020-06-01 18:19:08 -04:00
|
|
|
ophandle = get_arg(req, "ophandle")
|
2008-10-21 22:13:54 -07:00
|
|
|
assert ophandle
|
2020-06-01 18:19:08 -04:00
|
|
|
target = get_root(req) + "/operations/" + ophandle
|
|
|
|
output = get_arg(req, "output")
|
2008-10-21 17:03:07 -07:00
|
|
|
if output:
|
2008-10-23 15:56:58 -07:00
|
|
|
target = target + "?output=%s" % output
|
2020-06-18 18:30:18 -04:00
|
|
|
|
|
|
|
# XXX: We have to use nevow.url here because nevow.appserver
|
|
|
|
# is unhappy with anything else; so this gets its own ticket.
|
|
|
|
# https://tahoe-lafs.org/trac/tahoe-lafs/ticket/3314
|
2008-10-21 17:03:07 -07:00
|
|
|
return url.URL.fromString(target)
|
|
|
|
|
2020-09-21 16:26:51 -04:00
|
|
|
@exception_to_child
|
2020-06-18 18:29:27 -04:00
|
|
|
def getChild(self, name, req):
|
2008-10-21 17:03:07 -07:00
|
|
|
ophandle = name
|
|
|
|
if ophandle not in self.handles:
|
2008-10-21 22:13:54 -07:00
|
|
|
raise WebError("unknown/expired handle '%s'" % escape(ophandle),
|
|
|
|
NOT_FOUND)
|
|
|
|
(monitor, renderer, when_added) = self.handles[ophandle]
|
|
|
|
|
2020-06-18 18:29:27 -04:00
|
|
|
t = get_arg(req, "t", "status")
|
|
|
|
if t == "cancel" and req.method == "POST":
|
2008-10-21 17:03:07 -07:00
|
|
|
monitor.cancel()
|
2008-10-21 22:13:54 -07:00
|
|
|
# return the status anyways, but release the handle
|
|
|
|
self._release_ophandle(ophandle)
|
|
|
|
|
|
|
|
else:
|
2020-06-18 18:29:27 -04:00
|
|
|
retain_for = get_arg(req, "retain-for", None)
|
2008-10-21 22:13:54 -07:00
|
|
|
if retain_for is not None:
|
|
|
|
self._set_timer(ophandle, int(retain_for))
|
|
|
|
|
|
|
|
if monitor.is_finished():
|
2020-06-18 18:29:27 -04:00
|
|
|
if boolean_of_arg(get_arg(req, "release-after-complete", "false")):
|
2008-10-21 22:13:54 -07:00
|
|
|
self._release_ophandle(ophandle)
|
|
|
|
if retain_for is None:
|
|
|
|
# this GET is collecting the ophandle, so change its timer
|
|
|
|
self._set_timer(ophandle, self.COLLECTED_HANDLE_LIFETIME)
|
|
|
|
|
2008-11-06 21:53:09 -07:00
|
|
|
status = monitor.get_status()
|
|
|
|
if isinstance(status, Failure):
|
|
|
|
return defer.fail(status)
|
|
|
|
|
2008-10-21 22:13:54 -07:00
|
|
|
return renderer
|
|
|
|
|
|
|
|
def _set_timer(self, ophandle, when):
|
|
|
|
if ophandle in self.timers and self.timers[ophandle].active():
|
|
|
|
self.timers[ophandle].cancel()
|
Change OphandleTable to use a deterministic clock, so we can test it
To test the changes for #577, we need a deterministic way to simulate
the passage of long periods of time. twisted.internet.task.Clock seems,
from my Googling, to be the way to go for this functionality. I changed
a few things so that OphandleTable would use twisted.internet.task.Clock
when testing:
* WebishServer.__init___ now takes an optional 'clock' parameter,
* which it passes to the root.Root instance it creates.
* root.Root.__init__ now takes an optional 'clock' parameter, which it
passes to the OphandleTable.__init__ method.
* OphandleTable.__init__ now takes an optional 'clock' parameter. If
it is provided, and it isn't None, its callLater method will be used
to schedule ophandle expirations (as opposed to using
reactor.callLater, which is what OphandleTable does normally).
* The WebMixin object in test_web.py now sets a self.clock parameter,
which is a twisted.internet.task.Clock that it feeds to the
WebishServer it creates.
Tests using the WebMixin can control the passage of time in
OphandleTable by accessing self.clock.
2010-02-20 13:07:13 -08:00
|
|
|
if self.clock:
|
|
|
|
t = self.clock.callLater(when, self._release_ophandle, ophandle)
|
|
|
|
else:
|
|
|
|
t = reactor.callLater(when, self._release_ophandle, ophandle)
|
2008-10-21 22:13:54 -07:00
|
|
|
self.timers[ophandle] = t
|
2008-10-21 17:03:07 -07:00
|
|
|
|
2008-10-21 22:13:54 -07:00
|
|
|
def _release_ophandle(self, ophandle):
|
|
|
|
if ophandle in self.timers and self.timers[ophandle].active():
|
|
|
|
self.timers[ophandle].cancel()
|
|
|
|
self.timers.pop(ophandle, None)
|
2009-01-14 19:14:59 -07:00
|
|
|
self.handles.pop(ophandle, None)
|
2008-10-21 17:03:07 -07:00
|
|
|
|
2020-06-18 18:29:27 -04:00
|
|
|
|
2019-05-15 08:17:44 +02:00
|
|
|
class ReloadMixin(object):
|
2008-10-22 09:48:42 -07:00
|
|
|
REFRESH_TIME = 1*MINUTE
|
|
|
|
|
2020-06-02 11:56:59 -04:00
|
|
|
@renderer
|
|
|
|
def refresh(self, req, tag):
|
2008-10-22 09:48:42 -07:00
|
|
|
if self.monitor.is_finished():
|
|
|
|
return ""
|
|
|
|
# dreid suggests ctx.tag(**dict([("http-equiv", "refresh")]))
|
|
|
|
# but I can't tell if he's joking or not
|
2020-06-02 11:56:59 -04:00
|
|
|
tag.attributes["http-equiv"] = "refresh"
|
|
|
|
tag.attributes["content"] = str(self.REFRESH_TIME)
|
|
|
|
return tag
|
2008-10-21 17:03:07 -07:00
|
|
|
|
2020-06-02 11:56:59 -04:00
|
|
|
@renderer
|
|
|
|
def reload(self, req, tag):
|
2008-10-21 17:03:07 -07:00
|
|
|
if self.monitor.is_finished():
|
|
|
|
return ""
|
|
|
|
# url.gethere would break a proxy, so the correct thing to do is
|
|
|
|
# req.path[-1] + queryargs
|
|
|
|
ophandle = req.prepath[-1]
|
2008-10-23 15:56:58 -07:00
|
|
|
reload_target = ophandle + "?output=html"
|
2008-10-21 17:03:07 -07:00
|
|
|
cancel_target = ophandle + "?t=cancel"
|
2020-06-02 11:56:59 -04:00
|
|
|
cancel_button = T.form(T.input(type="submit", value="Cancel"),
|
|
|
|
action=cancel_target,
|
|
|
|
method="POST",
|
|
|
|
enctype="multipart/form-data",)
|
|
|
|
|
|
|
|
return (T.h2("Operation still running: ",
|
|
|
|
T.a("Reload", href=reload_target),
|
|
|
|
),
|
|
|
|
cancel_button,)
|