]> git.rkrishnan.org Git - tahoe-lafs/tahoe-lafs.git/blob - src/allmydata/web/root.py
de-Service-ify Helper, pass in storage_broker and secret_holder directly.
[tahoe-lafs/tahoe-lafs.git] / src / allmydata / web / root.py
1 import time
2
3 from twisted.internet import address
4 from twisted.web import http
5 from nevow import rend, url, loaders, tags as T
6 from nevow.inevow import IRequest
7 from nevow.static import File as nevow_File # TODO: merge with static.File?
8 from nevow.util import resource_filename
9 from formless import webform
10
11 import allmydata # to display import path
12 from allmydata import get_package_versions_string
13 from allmydata import provisioning
14 from allmydata.util import idlib, log
15 from allmydata.interfaces import IFileNode, UnhandledCapTypeError
16 from allmydata.web import filenode, directory, unlinked, status, operations
17 from allmydata.web import reliability, storage
18 from allmydata.web.common import abbreviate_size, getxmlfile, WebError, \
19      get_arg, RenderMixin, boolean_of_arg
20
21
22 class URIHandler(RenderMixin, rend.Page):
23     # I live at /uri . There are several operations defined on /uri itself,
24     # mostly involved with creation of unlinked files and directories.
25
26     def __init__(self, client):
27         rend.Page.__init__(self, client)
28         self.client = client
29
30     def render_GET(self, ctx):
31         req = IRequest(ctx)
32         uri = get_arg(req, "uri", None)
33         if uri is None:
34             raise WebError("GET /uri requires uri=")
35         there = url.URL.fromContext(ctx)
36         there = there.clear("uri")
37         # I thought about escaping the childcap that we attach to the URL
38         # here, but it seems that nevow does that for us.
39         there = there.child(uri)
40         return there
41
42     def render_PUT(self, ctx):
43         req = IRequest(ctx)
44         # either "PUT /uri" to create an unlinked file, or
45         # "PUT /uri?t=mkdir" to create an unlinked directory
46         t = get_arg(req, "t", "").strip()
47         if t == "":
48             mutable = boolean_of_arg(get_arg(req, "mutable", "false").strip())
49             if mutable:
50                 return unlinked.PUTUnlinkedSSK(req, self.client)
51             else:
52                 return unlinked.PUTUnlinkedCHK(req, self.client)
53         if t == "mkdir":
54             return unlinked.PUTUnlinkedCreateDirectory(req, self.client)
55         errmsg = ("/uri accepts only PUT, PUT?t=mkdir, POST?t=upload, "
56                   "and POST?t=mkdir")
57         raise WebError(errmsg, http.BAD_REQUEST)
58
59     def render_POST(self, ctx):
60         # "POST /uri?t=upload&file=newfile" to upload an
61         # unlinked file or "POST /uri?t=mkdir" to create a
62         # new directory
63         req = IRequest(ctx)
64         t = get_arg(req, "t", "").strip()
65         if t in ("", "upload"):
66             mutable = bool(get_arg(req, "mutable", "").strip())
67             if mutable:
68                 return unlinked.POSTUnlinkedSSK(req, self.client)
69             else:
70                 return unlinked.POSTUnlinkedCHK(req, self.client)
71         if t == "mkdir":
72             return unlinked.POSTUnlinkedCreateDirectory(req, self.client)
73         errmsg = ("/uri accepts only PUT, PUT?t=mkdir, POST?t=upload, "
74                   "and POST?t=mkdir")
75         raise WebError(errmsg, http.BAD_REQUEST)
76
77     def childFactory(self, ctx, name):
78         # 'name' is expected to be a URI
79         try:
80             node = self.client.create_node_from_uri(name)
81             return directory.make_handler_for(node, self.client)
82         except (TypeError, UnhandledCapTypeError, AssertionError):
83             raise WebError("'%s' is not a valid file- or directory- cap"
84                            % name)
85
86 class FileHandler(rend.Page):
87     # I handle /file/$FILECAP[/IGNORED] , which provides a URL from which a
88     # file can be downloaded correctly by tools like "wget".
89
90     def __init__(self, client):
91         rend.Page.__init__(self, client)
92         self.client = client
93
94     def childFactory(self, ctx, name):
95         req = IRequest(ctx)
96         if req.method not in ("GET", "HEAD"):
97             raise WebError("/file can only be used with GET or HEAD")
98         # 'name' must be a file URI
99         try:
100             node = self.client.create_node_from_uri(name)
101         except (TypeError, UnhandledCapTypeError, AssertionError):
102             # I think this can no longer be reached
103             raise WebError("'%s' is not a valid file- or directory- cap"
104                            % name)
105         if not IFileNode.providedBy(node):
106             raise WebError("'%s' is not a file-cap" % name)
107         return filenode.FileNodeDownloadHandler(self.client, node)
108
109     def renderHTTP(self, ctx):
110         raise WebError("/file must be followed by a file-cap and a name",
111                        http.NOT_FOUND)
112
113 class IncidentReporter(RenderMixin, rend.Page):
114     def render_POST(self, ctx):
115         req = IRequest(ctx)
116         log.msg(format="User reports incident through web page: %(details)s",
117                 details=get_arg(req, "details", ""),
118                 level=log.WEIRD, umid="LkD9Pw")
119         req.setHeader("content-type", "text/plain")
120         return "Thank you for your report!"
121
122 class NoReliability(rend.Page):
123     docFactory = loaders.xmlstr('''\
124 <html xmlns:n="http://nevow.com/ns/nevow/0.1">
125   <head>
126     <title>AllMyData - Tahoe</title>
127     <link href="/webform_css" rel="stylesheet" type="text/css"/>
128     <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
129   </head>
130   <body>
131   <h2>"Reliability" page not available</h2>
132   <p>Please install the python "NumPy" module to enable this page.</p>
133   </body>
134 </html>
135 ''')
136
137 class Root(rend.Page):
138
139     addSlash = True
140     docFactory = getxmlfile("welcome.xhtml")
141
142     def __init__(self, client):
143         rend.Page.__init__(self, client)
144         self.client = client
145         self.child_operations = operations.OphandleTable()
146         try:
147             s = client.getServiceNamed("storage")
148         except KeyError:
149             s = None
150         self.child_storage = storage.StorageStatus(s)
151
152         self.child_uri = URIHandler(client)
153         self.child_cap = URIHandler(client)
154
155         self.child_file = FileHandler(client)
156         self.child_named = FileHandler(client)
157         self.child_status = status.Status(client.get_history())
158         self.child_statistics = status.Statistics(client.stats_provider)
159
160     def child_helper_status(self, ctx):
161         # the Helper isn't attached until after the Tub starts, so this child
162         # needs to created on each request
163         return status.HelperStatus(self.client.helper)
164
165     child_webform_css = webform.defaultCSS
166     child_tahoe_css = nevow_File(resource_filename('allmydata.web', 'tahoe.css'))
167
168     child_provisioning = provisioning.ProvisioningTool()
169     if reliability.is_available():
170         child_reliability = reliability.ReliabilityTool()
171     else:
172         child_reliability = NoReliability()
173
174     child_report_incident = IncidentReporter()
175     #child_server # let's reserve this for storage-server-over-HTTP
176
177     # FIXME: This code is duplicated in root.py and introweb.py.
178     def data_version(self, ctx, data):
179         return get_package_versions_string()
180     def data_import_path(self, ctx, data):
181         return str(allmydata)
182     def data_my_nodeid(self, ctx, data):
183         return idlib.nodeid_b2a(self.client.nodeid)
184     def data_my_nickname(self, ctx, data):
185         return self.client.nickname
186
187     def render_services(self, ctx, data):
188         ul = T.ul()
189         try:
190             ss = self.client.getServiceNamed("storage")
191             stats = ss.get_stats()
192             if stats["storage_server.accepting_immutable_shares"]:
193                 msg = "accepting new shares"
194             else:
195                 msg = "not accepting new shares (read-only)"
196             available = stats.get("storage_server.disk_avail")
197             if available is not None:
198                 msg += ", %s available" % abbreviate_size(available)
199             ul[T.li[T.a(href="storage")["Storage Server"], ": ", msg]]
200         except KeyError:
201             ul[T.li["Not running storage server"]]
202
203         if self.client.helper:
204             stats = self.client.helper.get_stats()
205             active_uploads = stats["chk_upload_helper.active_uploads"]
206             ul[T.li["Helper: %d active uploads" % (active_uploads,)]]
207         else:
208             ul[T.li["Not running helper"]]
209
210         return ctx.tag[ul]
211
212     def data_introducer_furl(self, ctx, data):
213         return self.client.introducer_furl
214     def data_connected_to_introducer(self, ctx, data):
215         if self.client.connected_to_introducer():
216             return "yes"
217         return "no"
218
219     def data_helper_furl(self, ctx, data):
220         try:
221             uploader = self.client.getServiceNamed("uploader")
222         except KeyError:
223             return None
224         furl, connected = uploader.get_helper_info()
225         return furl
226     def data_connected_to_helper(self, ctx, data):
227         try:
228             uploader = self.client.getServiceNamed("uploader")
229         except KeyError:
230             return "no" # we don't even have an Uploader
231         furl, connected = uploader.get_helper_info()
232         if connected:
233             return "yes"
234         return "no"
235
236     def data_known_storage_servers(self, ctx, data):
237         sb = self.client.get_storage_broker()
238         return len(sb.get_all_serverids())
239
240     def data_connected_storage_servers(self, ctx, data):
241         sb = self.client.get_storage_broker()
242         return len(sb.get_all_servers())
243
244     def data_services(self, ctx, data):
245         sb = self.client.get_storage_broker()
246         return sb.get_all_descriptors()
247
248     def render_service_row(self, ctx, descriptor):
249         nodeid = descriptor.get_serverid()
250
251         ctx.fillSlots("peerid", idlib.nodeid_b2a(nodeid))
252         ctx.fillSlots("nickname", descriptor.get_nickname())
253         rhost = descriptor.get_remote_host()
254         if rhost:
255             if nodeid == self.client.nodeid:
256                 rhost_s = "(loopback)"
257             elif isinstance(rhost, address.IPv4Address):
258                 rhost_s = "%s:%d" % (rhost.host, rhost.port)
259             else:
260                 rhost_s = str(rhost)
261             connected = "Yes: to " + rhost_s
262             since = descriptor.get_last_connect_time()
263         else:
264             connected = "No"
265             since = descriptor.get_last_loss_time()
266         announced = descriptor.get_announcement_time()
267         announcement = descriptor.get_announcement()
268         version = announcement["version"]
269         service_name = announcement["service-name"]
270
271         TIME_FORMAT = "%H:%M:%S %d-%b-%Y"
272         ctx.fillSlots("connected", connected)
273         ctx.fillSlots("connected-bool", bool(rhost))
274         ctx.fillSlots("since", time.strftime(TIME_FORMAT,
275                                              time.localtime(since)))
276         ctx.fillSlots("announced", time.strftime(TIME_FORMAT,
277                                                  time.localtime(announced)))
278         ctx.fillSlots("version", version)
279         ctx.fillSlots("service_name", service_name)
280
281         return ctx.tag
282
283     def render_download_form(self, ctx, data):
284         # this is a form where users can download files by URI
285         form = T.form(action="uri", method="get",
286                       enctype="multipart/form-data")[
287             T.fieldset[
288             T.legend(class_="freeform-form-label")["Download a file"],
289             T.div["Tahoe-URI to download: ",
290                   T.input(type="text", name="uri")],
291             T.div["Filename to download as: ",
292                   T.input(type="text", name="filename")],
293             T.input(type="submit", value="Download!"),
294             ]]
295         return T.div[form]
296
297     def render_view_form(self, ctx, data):
298         # this is a form where users can download files by URI, or jump to a
299         # named directory
300         form = T.form(action="uri", method="get",
301                       enctype="multipart/form-data")[
302             T.fieldset[
303             T.legend(class_="freeform-form-label")["View a file or directory"],
304             "Tahoe-URI to view: ",
305             T.input(type="text", name="uri"), " ",
306             T.input(type="submit", value="View!"),
307             ]]
308         return T.div[form]
309
310     def render_upload_form(self, ctx, data):
311         # this is a form where users can upload unlinked files
312         form = T.form(action="uri", method="post",
313                       enctype="multipart/form-data")[
314             T.fieldset[
315             T.legend(class_="freeform-form-label")["Upload a file"],
316             T.div["Choose a file: ",
317                   T.input(type="file", name="file", class_="freeform-input-file")],
318             T.input(type="hidden", name="t", value="upload"),
319             T.div[T.input(type="checkbox", name="mutable"), T.label(for_="mutable")["Create mutable file"],
320                   " ", T.input(type="submit", value="Upload!")],
321             ]]
322         return T.div[form]
323
324     def render_mkdir_form(self, ctx, data):
325         # this is a form where users can create new directories
326         form = T.form(action="uri", method="post",
327                       enctype="multipart/form-data")[
328             T.fieldset[
329             T.legend(class_="freeform-form-label")["Create a directory"],
330             T.input(type="hidden", name="t", value="mkdir"),
331             T.input(type="hidden", name="redirect_to_result", value="true"),
332             T.input(type="submit", value="Create a directory"),
333             ]]
334         return T.div[form]
335
336     def render_incident_button(self, ctx, data):
337         # this button triggers a foolscap-logging "incident"
338         form = T.form(action="report_incident", method="post",
339                       enctype="multipart/form-data")[
340             T.fieldset[
341             T.legend(class_="freeform-form-label")["Report an Incident"],
342             T.input(type="hidden", name="t", value="report-incident"),
343             "What went wrong?: ",
344             T.input(type="text", name="details"), " ",
345             T.input(type="submit", value="Report!"),
346             ]]
347         return T.div[form]