1 import os.path, re, urllib
3 from StringIO import StringIO
4 from twisted.application import service
5 from twisted.trial import unittest
6 from twisted.internet import defer, reactor
7 from twisted.web import client, error, http
8 from twisted.python import failure, log
10 from allmydata import interfaces, uri, webish, dirnode
11 from allmydata.storage.shares import get_share_file
12 from allmydata.storage_client import StorageFarmBroker
13 from allmydata.immutable import upload, download
14 from allmydata.dirnode import DirectoryNode
15 from allmydata.nodemaker import NodeMaker
16 from allmydata.unknown import UnknownNode
17 from allmydata.web import status, common
18 from allmydata.scripts.debug import CorruptShareOptions, corrupt_share
19 from allmydata.util import fileutil, base32
20 from allmydata.util.consumer import download_to_data
21 from allmydata.util.netstring import split_netstring
22 from allmydata.test.common import FakeCHKFileNode, FakeMutableFileNode, \
23 create_chk_filenode, WebErrorMixin, ShouldFailMixin, make_mutable_file_uri
24 from allmydata.interfaces import IMutableFileNode
25 from allmydata.mutable import servermap, publish, retrieve
26 import common_util as testutil
27 from allmydata.test.no_network import GridTestMixin
28 from allmydata.test.common_web import HTTPClientGETFactory, \
30 from allmydata.client import Client, SecretHolder
32 # create a fake uploader/downloader, and a couple of fake dirnodes, then
33 # create a webserver that works against them
35 timeout = 480 # Most of these take longer than 240 seconds on Francois's arm box.
37 class FakeStatsProvider:
39 stats = {'stats': {}, 'counters': {}}
42 class FakeNodeMaker(NodeMaker):
43 def _create_lit(self, cap):
44 return FakeCHKFileNode(cap)
45 def _create_immutable(self, cap):
46 return FakeCHKFileNode(cap)
47 def _create_mutable(self, cap):
48 return FakeMutableFileNode(None, None, None, None).init_from_cap(cap)
49 def create_mutable_file(self, contents="", keysize=None):
50 n = FakeMutableFileNode(None, None, None, None)
51 return n.create(contents)
53 class FakeUploader(service.Service):
55 def upload(self, uploadable, history=None):
56 d = uploadable.get_size()
57 d.addCallback(lambda size: uploadable.read(size))
60 n = create_chk_filenode(data)
61 results = upload.UploadResults()
62 results.uri = n.get_uri()
64 d.addCallback(_got_data)
66 def get_helper_info(self):
70 _all_upload_status = [upload.UploadStatus()]
71 _all_download_status = [download.DownloadStatus()]
72 _all_mapupdate_statuses = [servermap.UpdateStatus()]
73 _all_publish_statuses = [publish.PublishStatus()]
74 _all_retrieve_statuses = [retrieve.RetrieveStatus()]
76 def list_all_upload_statuses(self):
77 return self._all_upload_status
78 def list_all_download_statuses(self):
79 return self._all_download_status
80 def list_all_mapupdate_statuses(self):
81 return self._all_mapupdate_statuses
82 def list_all_publish_statuses(self):
83 return self._all_publish_statuses
84 def list_all_retrieve_statuses(self):
85 return self._all_retrieve_statuses
86 def list_all_helper_statuses(self):
89 class FakeClient(Client):
91 # don't upcall to Client.__init__, since we only want to initialize a
93 service.MultiService.__init__(self)
94 self.nodeid = "fake_nodeid"
95 self.nickname = "fake_nickname"
96 self.introducer_furl = "None"
97 self.stats_provider = FakeStatsProvider()
98 self._secret_holder = SecretHolder("lease secret", "convergence secret")
100 self.convergence = "some random string"
101 self.storage_broker = StorageFarmBroker(None, permute_peers=True)
102 self.introducer_client = None
103 self.history = FakeHistory()
104 self.uploader = FakeUploader()
105 self.uploader.setServiceParent(self)
106 self.nodemaker = FakeNodeMaker(None, self._secret_holder, None,
107 self.uploader, None, None,
110 def startService(self):
111 return service.MultiService.startService(self)
112 def stopService(self):
113 return service.MultiService.stopService(self)
115 MUTABLE_SIZELIMIT = FakeMutableFileNode.MUTABLE_SIZELIMIT
117 class WebMixin(object):
119 self.s = FakeClient()
120 self.s.startService()
121 self.staticdir = self.mktemp()
122 self.ws = webish.WebishServer(self.s, "0", staticdir=self.staticdir)
123 self.ws.setServiceParent(self.s)
124 self.webish_port = port = self.ws.listener._port.getHost().port
125 self.webish_url = "http://localhost:%d" % port
127 l = [ self.s.create_dirnode() for x in range(6) ]
128 d = defer.DeferredList(l)
130 self.public_root = res[0][1]
131 assert interfaces.IDirectoryNode.providedBy(self.public_root), res
132 self.public_url = "/uri/" + self.public_root.get_uri()
133 self.private_root = res[1][1]
137 self._foo_uri = foo.get_uri()
138 self._foo_readonly_uri = foo.get_readonly_uri()
139 self._foo_verifycap = foo.get_verify_cap().to_string()
140 # NOTE: we ignore the deferred on all set_uri() calls, because we
141 # know the fake nodes do these synchronously
142 self.public_root.set_uri(u"foo", foo.get_uri(),
143 foo.get_readonly_uri())
145 self.BAR_CONTENTS, n, self._bar_txt_uri = self.makefile(0)
146 foo.set_uri(u"bar.txt", self._bar_txt_uri, self._bar_txt_uri)
147 self._bar_txt_verifycap = n.get_verify_cap().to_string()
149 foo.set_uri(u"empty", res[3][1].get_uri(),
150 res[3][1].get_readonly_uri())
151 sub_uri = res[4][1].get_uri()
152 self._sub_uri = sub_uri
153 foo.set_uri(u"sub", sub_uri, sub_uri)
154 sub = self.s.create_node_from_uri(sub_uri)
156 _ign, n, blocking_uri = self.makefile(1)
157 foo.set_uri(u"blockingfile", blocking_uri, blocking_uri)
159 unicode_filename = u"n\u00fc.txt" # n u-umlaut . t x t
160 # ok, unicode calls it LATIN SMALL LETTER U WITH DIAERESIS but I
161 # still think of it as an umlaut
162 foo.set_uri(unicode_filename, self._bar_txt_uri, self._bar_txt_uri)
164 _ign, n, baz_file = self.makefile(2)
165 self._baz_file_uri = baz_file
166 sub.set_uri(u"baz.txt", baz_file, baz_file)
168 _ign, n, self._bad_file_uri = self.makefile(3)
169 # this uri should not be downloadable
170 del FakeCHKFileNode.all_contents[self._bad_file_uri]
173 self.public_root.set_uri(u"reedownlee", rodir.get_readonly_uri(),
174 rodir.get_readonly_uri())
175 rodir.set_uri(u"nor", baz_file, baz_file)
180 # public/foo/blockingfile
183 # public/foo/sub/baz.txt
185 # public/reedownlee/nor
186 self.NEWFILE_CONTENTS = "newfile contents\n"
188 return foo.get_metadata_for(u"bar.txt")
190 def _got_metadata(metadata):
191 self._bar_txt_metadata = metadata
192 d.addCallback(_got_metadata)
195 def makefile(self, number):
196 contents = "contents of file %s\n" % number
197 n = create_chk_filenode(contents)
198 return contents, n, n.get_uri()
201 return self.s.stopService()
203 def failUnlessIsBarDotTxt(self, res):
204 self.failUnlessEqual(res, self.BAR_CONTENTS, res)
206 def failUnlessIsBarJSON(self, res):
207 data = simplejson.loads(res)
208 self.failUnless(isinstance(data, list))
209 self.failUnlessEqual(data[0], u"filenode")
210 self.failUnless(isinstance(data[1], dict))
211 self.failIf(data[1]["mutable"])
212 self.failIf("rw_uri" in data[1]) # immutable
213 self.failUnlessEqual(data[1]["ro_uri"], self._bar_txt_uri)
214 self.failUnlessEqual(data[1]["verify_uri"], self._bar_txt_verifycap)
215 self.failUnlessEqual(data[1]["size"], len(self.BAR_CONTENTS))
217 def failUnlessIsFooJSON(self, res):
218 data = simplejson.loads(res)
219 self.failUnless(isinstance(data, list))
220 self.failUnlessEqual(data[0], "dirnode", res)
221 self.failUnless(isinstance(data[1], dict))
222 self.failUnless(data[1]["mutable"])
223 self.failUnless("rw_uri" in data[1]) # mutable
224 self.failUnlessEqual(data[1]["rw_uri"], self._foo_uri)
225 self.failUnlessEqual(data[1]["ro_uri"], self._foo_readonly_uri)
226 self.failUnlessEqual(data[1]["verify_uri"], self._foo_verifycap)
228 kidnames = sorted([unicode(n) for n in data[1]["children"]])
229 self.failUnlessEqual(kidnames,
230 [u"bar.txt", u"blockingfile", u"empty",
231 u"n\u00fc.txt", u"sub"])
232 kids = dict( [(unicode(name),value)
234 in data[1]["children"].iteritems()] )
235 self.failUnlessEqual(kids[u"sub"][0], "dirnode")
236 self.failUnless("metadata" in kids[u"sub"][1])
237 self.failUnless("ctime" in kids[u"sub"][1]["metadata"])
238 self.failUnless("mtime" in kids[u"sub"][1]["metadata"])
239 self.failUnlessEqual(kids[u"bar.txt"][0], "filenode")
240 self.failUnlessEqual(kids[u"bar.txt"][1]["size"], len(self.BAR_CONTENTS))
241 self.failUnlessEqual(kids[u"bar.txt"][1]["ro_uri"], self._bar_txt_uri)
242 self.failUnlessEqual(kids[u"bar.txt"][1]["verify_uri"],
243 self._bar_txt_verifycap)
244 self.failUnlessEqual(kids[u"bar.txt"][1]["metadata"]["ctime"],
245 self._bar_txt_metadata["ctime"])
246 self.failUnlessEqual(kids[u"n\u00fc.txt"][1]["ro_uri"],
249 def GET(self, urlpath, followRedirect=False, return_response=False,
251 # if return_response=True, this fires with (data, statuscode,
252 # respheaders) instead of just data.
253 assert not isinstance(urlpath, unicode)
254 url = self.webish_url + urlpath
255 factory = HTTPClientGETFactory(url, method="GET",
256 followRedirect=followRedirect, **kwargs)
257 reactor.connectTCP("localhost", self.webish_port, factory)
260 return (data, factory.status, factory.response_headers)
262 d.addCallback(_got_data)
263 return factory.deferred
265 def HEAD(self, urlpath, return_response=False, **kwargs):
266 # this requires some surgery, because twisted.web.client doesn't want
267 # to give us back the response headers.
268 factory = HTTPClientHEADFactory(urlpath, method="HEAD", **kwargs)
269 reactor.connectTCP("localhost", self.webish_port, factory)
272 return (data, factory.status, factory.response_headers)
274 d.addCallback(_got_data)
275 return factory.deferred
277 def PUT(self, urlpath, data, **kwargs):
278 url = self.webish_url + urlpath
279 return client.getPage(url, method="PUT", postdata=data, **kwargs)
281 def DELETE(self, urlpath):
282 url = self.webish_url + urlpath
283 return client.getPage(url, method="DELETE")
285 def POST(self, urlpath, followRedirect=False, **fields):
286 sepbase = "boogabooga"
290 form.append('Content-Disposition: form-data; name="_charset"')
294 for name, value in fields.iteritems():
295 if isinstance(value, tuple):
296 filename, value = value
297 form.append('Content-Disposition: form-data; name="%s"; '
298 'filename="%s"' % (name, filename.encode("utf-8")))
300 form.append('Content-Disposition: form-data; name="%s"' % name)
302 if isinstance(value, unicode):
303 value = value.encode("utf-8")
306 assert isinstance(value, str)
313 body = "\r\n".join(form) + "\r\n"
314 headers["content-type"] = "multipart/form-data; boundary=%s" % sepbase
315 return self.POST2(urlpath, body, headers, followRedirect)
317 def POST2(self, urlpath, body="", headers={}, followRedirect=False):
318 url = self.webish_url + urlpath
319 return client.getPage(url, method="POST", postdata=body,
320 headers=headers, followRedirect=followRedirect)
322 def shouldFail(self, res, expected_failure, which,
323 substring=None, response_substring=None):
324 if isinstance(res, failure.Failure):
325 res.trap(expected_failure)
327 self.failUnless(substring in str(res),
328 "substring '%s' not in '%s'"
329 % (substring, str(res)))
330 if response_substring:
331 self.failUnless(response_substring in res.value.response,
332 "response substring '%s' not in '%s'"
333 % (response_substring, res.value.response))
335 self.fail("%s was supposed to raise %s, not get '%s'" %
336 (which, expected_failure, res))
338 def shouldFail2(self, expected_failure, which, substring,
340 callable, *args, **kwargs):
341 assert substring is None or isinstance(substring, str)
342 assert response_substring is None or isinstance(response_substring, str)
343 d = defer.maybeDeferred(callable, *args, **kwargs)
345 if isinstance(res, failure.Failure):
346 res.trap(expected_failure)
348 self.failUnless(substring in str(res),
349 "%s: substring '%s' not in '%s'"
350 % (which, substring, str(res)))
351 if response_substring:
352 self.failUnless(response_substring in res.value.response,
353 "%s: response substring '%s' not in '%s'"
355 response_substring, res.value.response))
357 self.fail("%s was supposed to raise %s, not get '%s'" %
358 (which, expected_failure, res))
362 def should404(self, res, which):
363 if isinstance(res, failure.Failure):
364 res.trap(error.Error)
365 self.failUnlessEqual(res.value.status, "404")
367 self.fail("%s was supposed to Error(404), not get '%s'" %
371 class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase):
372 def test_create(self):
375 def test_welcome(self):
378 self.failUnless('Welcome To Tahoe-LAFS' in res, res)
380 self.s.basedir = 'web/test_welcome'
381 fileutil.make_dirs("web/test_welcome")
382 fileutil.make_dirs("web/test_welcome/private")
384 d.addCallback(_check)
387 def test_provisioning(self):
388 d = self.GET("/provisioning/")
390 self.failUnless('Tahoe Provisioning Tool' in res)
391 fields = {'filled': True,
392 "num_users": int(50e3),
393 "files_per_user": 1000,
394 "space_per_user": int(1e9),
395 "sharing_ratio": 1.0,
396 "encoding_parameters": "3-of-10-5",
398 "ownership_mode": "A",
399 "download_rate": 100,
404 return self.POST("/provisioning/", **fields)
406 d.addCallback(_check)
408 self.failUnless('Tahoe Provisioning Tool' in res)
409 self.failUnless("Share space consumed: 167.01TB" in res)
411 fields = {'filled': True,
412 "num_users": int(50e6),
413 "files_per_user": 1000,
414 "space_per_user": int(5e9),
415 "sharing_ratio": 1.0,
416 "encoding_parameters": "25-of-100-50",
417 "num_servers": 30000,
418 "ownership_mode": "E",
419 "drive_failure_model": "U",
421 "download_rate": 1000,
426 return self.POST("/provisioning/", **fields)
427 d.addCallback(_check2)
429 self.failUnless("Share space consumed: huge!" in res)
430 fields = {'filled': True}
431 return self.POST("/provisioning/", **fields)
432 d.addCallback(_check3)
434 self.failUnless("Share space consumed:" in res)
435 d.addCallback(_check4)
438 def test_reliability_tool(self):
440 from allmydata import reliability
441 _hush_pyflakes = reliability
444 raise unittest.SkipTest("reliability tool requires NumPy")
446 d = self.GET("/reliability/")
448 self.failUnless('Tahoe Reliability Tool' in res)
449 fields = {'drive_lifetime': "8Y",
454 "check_period": "1M",
455 "report_period": "3M",
458 return self.POST("/reliability/", **fields)
460 d.addCallback(_check)
462 self.failUnless('Tahoe Reliability Tool' in res)
463 r = r'Probability of loss \(no maintenance\):\s+<span>0.033591'
464 self.failUnless(re.search(r, res), res)
465 d.addCallback(_check2)
468 def test_status(self):
469 h = self.s.get_history()
470 dl_num = h.list_all_download_statuses()[0].get_counter()
471 ul_num = h.list_all_upload_statuses()[0].get_counter()
472 mu_num = h.list_all_mapupdate_statuses()[0].get_counter()
473 pub_num = h.list_all_publish_statuses()[0].get_counter()
474 ret_num = h.list_all_retrieve_statuses()[0].get_counter()
475 d = self.GET("/status", followRedirect=True)
477 self.failUnless('Upload and Download Status' in res, res)
478 self.failUnless('"down-%d"' % dl_num in res, res)
479 self.failUnless('"up-%d"' % ul_num in res, res)
480 self.failUnless('"mapupdate-%d"' % mu_num in res, res)
481 self.failUnless('"publish-%d"' % pub_num in res, res)
482 self.failUnless('"retrieve-%d"' % ret_num in res, res)
483 d.addCallback(_check)
484 d.addCallback(lambda res: self.GET("/status/?t=json"))
485 def _check_json(res):
486 data = simplejson.loads(res)
487 self.failUnless(isinstance(data, dict))
488 #active = data["active"]
489 # TODO: test more. We need a way to fake an active operation
491 d.addCallback(_check_json)
493 d.addCallback(lambda res: self.GET("/status/down-%d" % dl_num))
495 self.failUnless("File Download Status" in res, res)
496 d.addCallback(_check_dl)
497 d.addCallback(lambda res: self.GET("/status/up-%d" % ul_num))
499 self.failUnless("File Upload Status" in res, res)
500 d.addCallback(_check_ul)
501 d.addCallback(lambda res: self.GET("/status/mapupdate-%d" % mu_num))
502 def _check_mapupdate(res):
503 self.failUnless("Mutable File Servermap Update Status" in res, res)
504 d.addCallback(_check_mapupdate)
505 d.addCallback(lambda res: self.GET("/status/publish-%d" % pub_num))
506 def _check_publish(res):
507 self.failUnless("Mutable File Publish Status" in res, res)
508 d.addCallback(_check_publish)
509 d.addCallback(lambda res: self.GET("/status/retrieve-%d" % ret_num))
510 def _check_retrieve(res):
511 self.failUnless("Mutable File Retrieve Status" in res, res)
512 d.addCallback(_check_retrieve)
516 def test_status_numbers(self):
517 drrm = status.DownloadResultsRendererMixin()
518 self.failUnlessEqual(drrm.render_time(None, None), "")
519 self.failUnlessEqual(drrm.render_time(None, 2.5), "2.50s")
520 self.failUnlessEqual(drrm.render_time(None, 0.25), "250ms")
521 self.failUnlessEqual(drrm.render_time(None, 0.0021), "2.1ms")
522 self.failUnlessEqual(drrm.render_time(None, 0.000123), "123us")
523 self.failUnlessEqual(drrm.render_rate(None, None), "")
524 self.failUnlessEqual(drrm.render_rate(None, 2500000), "2.50MBps")
525 self.failUnlessEqual(drrm.render_rate(None, 30100), "30.1kBps")
526 self.failUnlessEqual(drrm.render_rate(None, 123), "123Bps")
528 urrm = status.UploadResultsRendererMixin()
529 self.failUnlessEqual(urrm.render_time(None, None), "")
530 self.failUnlessEqual(urrm.render_time(None, 2.5), "2.50s")
531 self.failUnlessEqual(urrm.render_time(None, 0.25), "250ms")
532 self.failUnlessEqual(urrm.render_time(None, 0.0021), "2.1ms")
533 self.failUnlessEqual(urrm.render_time(None, 0.000123), "123us")
534 self.failUnlessEqual(urrm.render_rate(None, None), "")
535 self.failUnlessEqual(urrm.render_rate(None, 2500000), "2.50MBps")
536 self.failUnlessEqual(urrm.render_rate(None, 30100), "30.1kBps")
537 self.failUnlessEqual(urrm.render_rate(None, 123), "123Bps")
539 def test_GET_FILEURL(self):
540 d = self.GET(self.public_url + "/foo/bar.txt")
541 d.addCallback(self.failUnlessIsBarDotTxt)
544 def test_GET_FILEURL_range(self):
545 headers = {"range": "bytes=1-10"}
546 d = self.GET(self.public_url + "/foo/bar.txt", headers=headers,
547 return_response=True)
548 def _got((res, status, headers)):
549 self.failUnlessEqual(int(status), 206)
550 self.failUnless(headers.has_key("content-range"))
551 self.failUnlessEqual(headers["content-range"][0],
552 "bytes 1-10/%d" % len(self.BAR_CONTENTS))
553 self.failUnlessEqual(res, self.BAR_CONTENTS[1:11])
557 def test_GET_FILEURL_partial_range(self):
558 headers = {"range": "bytes=5-"}
559 length = len(self.BAR_CONTENTS)
560 d = self.GET(self.public_url + "/foo/bar.txt", headers=headers,
561 return_response=True)
562 def _got((res, status, headers)):
563 self.failUnlessEqual(int(status), 206)
564 self.failUnless(headers.has_key("content-range"))
565 self.failUnlessEqual(headers["content-range"][0],
566 "bytes 5-%d/%d" % (length-1, length))
567 self.failUnlessEqual(res, self.BAR_CONTENTS[5:])
571 def test_HEAD_FILEURL_range(self):
572 headers = {"range": "bytes=1-10"}
573 d = self.HEAD(self.public_url + "/foo/bar.txt", headers=headers,
574 return_response=True)
575 def _got((res, status, headers)):
576 self.failUnlessEqual(res, "")
577 self.failUnlessEqual(int(status), 206)
578 self.failUnless(headers.has_key("content-range"))
579 self.failUnlessEqual(headers["content-range"][0],
580 "bytes 1-10/%d" % len(self.BAR_CONTENTS))
584 def test_HEAD_FILEURL_partial_range(self):
585 headers = {"range": "bytes=5-"}
586 length = len(self.BAR_CONTENTS)
587 d = self.HEAD(self.public_url + "/foo/bar.txt", headers=headers,
588 return_response=True)
589 def _got((res, status, headers)):
590 self.failUnlessEqual(int(status), 206)
591 self.failUnless(headers.has_key("content-range"))
592 self.failUnlessEqual(headers["content-range"][0],
593 "bytes 5-%d/%d" % (length-1, length))
597 def test_GET_FILEURL_range_bad(self):
598 headers = {"range": "BOGUS=fizbop-quarnak"}
599 d = self.shouldFail2(error.Error, "test_GET_FILEURL_range_bad",
601 "Syntactically invalid http range header",
602 self.GET, self.public_url + "/foo/bar.txt",
606 def test_HEAD_FILEURL(self):
607 d = self.HEAD(self.public_url + "/foo/bar.txt", return_response=True)
608 def _got((res, status, headers)):
609 self.failUnlessEqual(res, "")
610 self.failUnlessEqual(headers["content-length"][0],
611 str(len(self.BAR_CONTENTS)))
612 self.failUnlessEqual(headers["content-type"], ["text/plain"])
616 def test_GET_FILEURL_named(self):
617 base = "/file/%s" % urllib.quote(self._bar_txt_uri)
618 base2 = "/named/%s" % urllib.quote(self._bar_txt_uri)
619 d = self.GET(base + "/@@name=/blah.txt")
620 d.addCallback(self.failUnlessIsBarDotTxt)
621 d.addCallback(lambda res: self.GET(base + "/blah.txt"))
622 d.addCallback(self.failUnlessIsBarDotTxt)
623 d.addCallback(lambda res: self.GET(base + "/ignore/lots/blah.txt"))
624 d.addCallback(self.failUnlessIsBarDotTxt)
625 d.addCallback(lambda res: self.GET(base2 + "/@@name=/blah.txt"))
626 d.addCallback(self.failUnlessIsBarDotTxt)
627 save_url = base + "?save=true&filename=blah.txt"
628 d.addCallback(lambda res: self.GET(save_url))
629 d.addCallback(self.failUnlessIsBarDotTxt) # TODO: check headers
630 u_filename = u"n\u00e9wer.txt" # n e-acute w e r . t x t
631 u_fn_e = urllib.quote(u_filename.encode("utf-8"))
632 u_url = base + "?save=true&filename=" + u_fn_e
633 d.addCallback(lambda res: self.GET(u_url))
634 d.addCallback(self.failUnlessIsBarDotTxt) # TODO: check headers
637 def test_PUT_FILEURL_named_bad(self):
638 base = "/file/%s" % urllib.quote(self._bar_txt_uri)
639 d = self.shouldFail2(error.Error, "test_PUT_FILEURL_named_bad",
641 "/file can only be used with GET or HEAD",
642 self.PUT, base + "/@@name=/blah.txt", "")
645 def test_GET_DIRURL_named_bad(self):
646 base = "/file/%s" % urllib.quote(self._foo_uri)
647 d = self.shouldFail2(error.Error, "test_PUT_DIRURL_named_bad",
650 self.GET, base + "/@@name=/blah.txt")
653 def test_GET_slash_file_bad(self):
654 d = self.shouldFail2(error.Error, "test_GET_slash_file_bad",
656 "/file must be followed by a file-cap and a name",
660 def test_GET_unhandled_URI_named(self):
661 contents, n, newuri = self.makefile(12)
662 verifier_cap = n.get_verify_cap().to_string()
663 base = "/file/%s" % urllib.quote(verifier_cap)
664 # client.create_node_from_uri() can't handle verify-caps
665 d = self.shouldFail2(error.Error, "GET_unhandled_URI_named",
666 "400 Bad Request", "is not a file-cap",
670 def test_GET_unhandled_URI(self):
671 contents, n, newuri = self.makefile(12)
672 verifier_cap = n.get_verify_cap().to_string()
673 base = "/uri/%s" % urllib.quote(verifier_cap)
674 # client.create_node_from_uri() can't handle verify-caps
675 d = self.shouldFail2(error.Error, "test_GET_unhandled_URI",
677 "GET unknown URI type: can only do t=info",
681 def test_GET_FILE_URI(self):
682 base = "/uri/%s" % urllib.quote(self._bar_txt_uri)
684 d.addCallback(self.failUnlessIsBarDotTxt)
687 def test_GET_FILE_URI_badchild(self):
688 base = "/uri/%s/boguschild" % urllib.quote(self._bar_txt_uri)
689 errmsg = "Files have no children, certainly not named 'boguschild'"
690 d = self.shouldFail2(error.Error, "test_GET_FILE_URI_badchild",
691 "400 Bad Request", errmsg,
695 def test_PUT_FILE_URI_badchild(self):
696 base = "/uri/%s/boguschild" % urllib.quote(self._bar_txt_uri)
697 errmsg = "Cannot create directory 'boguschild', because its parent is a file, not a directory"
698 d = self.shouldFail2(error.Error, "test_GET_FILE_URI_badchild",
699 "400 Bad Request", errmsg,
703 # TODO: version of this with a Unicode filename
704 def test_GET_FILEURL_save(self):
705 d = self.GET(self.public_url + "/foo/bar.txt?filename=bar.txt&save=true",
706 return_response=True)
707 def _got((res, statuscode, headers)):
708 content_disposition = headers["content-disposition"][0]
709 self.failUnless(content_disposition == 'attachment; filename="bar.txt"', content_disposition)
710 self.failUnlessIsBarDotTxt(res)
714 def test_GET_FILEURL_missing(self):
715 d = self.GET(self.public_url + "/foo/missing")
716 d.addBoth(self.should404, "test_GET_FILEURL_missing")
719 def test_PUT_overwrite_only_files(self):
720 # create a directory, put a file in that directory.
721 contents, n, filecap = self.makefile(8)
722 d = self.PUT(self.public_url + "/foo/dir?t=mkdir", "")
723 d.addCallback(lambda res:
724 self.PUT(self.public_url + "/foo/dir/file1.txt",
725 self.NEWFILE_CONTENTS))
726 # try to overwrite the file with replace=only-files
728 d.addCallback(lambda res:
729 self.PUT(self.public_url + "/foo/dir/file1.txt?t=uri&replace=only-files",
731 d.addCallback(lambda res:
732 self.shouldFail2(error.Error, "PUT_bad_t", "409 Conflict",
733 "There was already a child by that name, and you asked me "
735 self.PUT, self.public_url + "/foo/dir?t=uri&replace=only-files",
739 def test_PUT_NEWFILEURL(self):
740 d = self.PUT(self.public_url + "/foo/new.txt", self.NEWFILE_CONTENTS)
741 # TODO: we lose the response code, so we can't check this
742 #self.failUnlessEqual(responsecode, 201)
743 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"new.txt")
744 d.addCallback(lambda res:
745 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
746 self.NEWFILE_CONTENTS))
749 def test_PUT_NEWFILEURL_not_mutable(self):
750 d = self.PUT(self.public_url + "/foo/new.txt?mutable=false",
751 self.NEWFILE_CONTENTS)
752 # TODO: we lose the response code, so we can't check this
753 #self.failUnlessEqual(responsecode, 201)
754 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"new.txt")
755 d.addCallback(lambda res:
756 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
757 self.NEWFILE_CONTENTS))
760 def test_PUT_NEWFILEURL_range_bad(self):
761 headers = {"content-range": "bytes 1-10/%d" % len(self.NEWFILE_CONTENTS)}
762 target = self.public_url + "/foo/new.txt"
763 d = self.shouldFail2(error.Error, "test_PUT_NEWFILEURL_range_bad",
764 "501 Not Implemented",
765 "Content-Range in PUT not yet supported",
766 # (and certainly not for immutable files)
767 self.PUT, target, self.NEWFILE_CONTENTS[1:11],
769 d.addCallback(lambda res:
770 self.failIfNodeHasChild(self._foo_node, u"new.txt"))
773 def test_PUT_NEWFILEURL_mutable(self):
774 d = self.PUT(self.public_url + "/foo/new.txt?mutable=true",
775 self.NEWFILE_CONTENTS)
776 # TODO: we lose the response code, so we can't check this
777 #self.failUnlessEqual(responsecode, 201)
779 u = uri.from_string_mutable_filenode(res)
780 self.failUnless(u.is_mutable())
781 self.failIf(u.is_readonly())
783 d.addCallback(_check_uri)
784 d.addCallback(self.failUnlessURIMatchesRWChild, self._foo_node, u"new.txt")
785 d.addCallback(lambda res:
786 self.failUnlessMutableChildContentsAre(self._foo_node,
788 self.NEWFILE_CONTENTS))
791 def test_PUT_NEWFILEURL_mutable_toobig(self):
792 d = self.shouldFail2(error.Error, "test_PUT_NEWFILEURL_mutable_toobig",
793 "413 Request Entity Too Large",
794 "SDMF is limited to one segment, and 10001 > 10000",
796 self.public_url + "/foo/new.txt?mutable=true",
797 "b" * (self.s.MUTABLE_SIZELIMIT+1))
800 def test_PUT_NEWFILEURL_replace(self):
801 d = self.PUT(self.public_url + "/foo/bar.txt", self.NEWFILE_CONTENTS)
802 # TODO: we lose the response code, so we can't check this
803 #self.failUnlessEqual(responsecode, 200)
804 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"bar.txt")
805 d.addCallback(lambda res:
806 self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
807 self.NEWFILE_CONTENTS))
810 def test_PUT_NEWFILEURL_bad_t(self):
811 d = self.shouldFail2(error.Error, "PUT_bad_t", "400 Bad Request",
812 "PUT to a file: bad t=bogus",
813 self.PUT, self.public_url + "/foo/bar.txt?t=bogus",
817 def test_PUT_NEWFILEURL_no_replace(self):
818 d = self.PUT(self.public_url + "/foo/bar.txt?replace=false",
819 self.NEWFILE_CONTENTS)
820 d.addBoth(self.shouldFail, error.Error, "PUT_NEWFILEURL_no_replace",
822 "There was already a child by that name, and you asked me "
826 def test_PUT_NEWFILEURL_mkdirs(self):
827 d = self.PUT(self.public_url + "/foo/newdir/new.txt", self.NEWFILE_CONTENTS)
829 d.addCallback(self.failUnlessURIMatchesROChild, fn, u"newdir/new.txt")
830 d.addCallback(lambda res: self.failIfNodeHasChild(fn, u"new.txt"))
831 d.addCallback(lambda res: self.failUnlessNodeHasChild(fn, u"newdir"))
832 d.addCallback(lambda res:
833 self.failUnlessChildContentsAre(fn, u"newdir/new.txt",
834 self.NEWFILE_CONTENTS))
837 def test_PUT_NEWFILEURL_blocked(self):
838 d = self.PUT(self.public_url + "/foo/blockingfile/new.txt",
839 self.NEWFILE_CONTENTS)
840 d.addBoth(self.shouldFail, error.Error, "PUT_NEWFILEURL_blocked",
842 "Unable to create directory 'blockingfile': a file was in the way")
845 def test_PUT_NEWFILEURL_emptyname(self):
846 # an empty pathname component (i.e. a double-slash) is disallowed
847 d = self.shouldFail2(error.Error, "test_PUT_NEWFILEURL_emptyname",
849 "The webapi does not allow empty pathname components",
850 self.PUT, self.public_url + "/foo//new.txt", "")
853 def test_DELETE_FILEURL(self):
854 d = self.DELETE(self.public_url + "/foo/bar.txt")
855 d.addCallback(lambda res:
856 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
859 def test_DELETE_FILEURL_missing(self):
860 d = self.DELETE(self.public_url + "/foo/missing")
861 d.addBoth(self.should404, "test_DELETE_FILEURL_missing")
864 def test_DELETE_FILEURL_missing2(self):
865 d = self.DELETE(self.public_url + "/missing/missing")
866 d.addBoth(self.should404, "test_DELETE_FILEURL_missing2")
869 def failUnlessHasBarDotTxtMetadata(self, res):
870 data = simplejson.loads(res)
871 self.failUnless(isinstance(data, list))
872 self.failUnless(data[1].has_key("metadata"))
873 self.failUnless(data[1]["metadata"].has_key("ctime"))
874 self.failUnless(data[1]["metadata"].has_key("mtime"))
875 self.failUnlessEqual(data[1]["metadata"]["ctime"],
876 self._bar_txt_metadata["ctime"])
878 def test_GET_FILEURL_json(self):
879 # twisted.web.http.parse_qs ignores any query args without an '=', so
880 # I can't do "GET /path?json", I have to do "GET /path/t=json"
881 # instead. This may make it tricky to emulate the S3 interface
883 d = self.GET(self.public_url + "/foo/bar.txt?t=json")
885 self.failUnlessIsBarJSON(data)
886 self.failUnlessHasBarDotTxtMetadata(data)
888 d.addCallback(_check1)
891 def test_GET_FILEURL_json_missing(self):
892 d = self.GET(self.public_url + "/foo/missing?json")
893 d.addBoth(self.should404, "test_GET_FILEURL_json_missing")
896 def test_GET_FILEURL_uri(self):
897 d = self.GET(self.public_url + "/foo/bar.txt?t=uri")
899 self.failUnlessEqual(res, self._bar_txt_uri)
900 d.addCallback(_check)
901 d.addCallback(lambda res:
902 self.GET(self.public_url + "/foo/bar.txt?t=readonly-uri"))
904 # for now, for files, uris and readonly-uris are the same
905 self.failUnlessEqual(res, self._bar_txt_uri)
906 d.addCallback(_check2)
909 def test_GET_FILEURL_badtype(self):
910 d = self.shouldHTTPError("GET t=bogus", 400, "Bad Request",
913 self.public_url + "/foo/bar.txt?t=bogus")
916 def test_GET_FILEURL_uri_missing(self):
917 d = self.GET(self.public_url + "/foo/missing?t=uri")
918 d.addBoth(self.should404, "test_GET_FILEURL_uri_missing")
921 def test_GET_DIRURL(self):
922 # the addSlash means we get a redirect here
923 # from /uri/$URI/foo/ , we need ../../../ to get back to the root
925 d = self.GET(self.public_url + "/foo", followRedirect=True)
927 self.failUnless(('<a href="%s">Return to Welcome page' % ROOT)
929 # the FILE reference points to a URI, but it should end in bar.txt
930 bar_url = ("%s/file/%s/@@named=/bar.txt" %
931 (ROOT, urllib.quote(self._bar_txt_uri)))
932 get_bar = "".join([r'<td>FILE</td>',
934 r'<a href="%s">bar.txt</a>' % bar_url,
936 r'\s+<td>%d</td>' % len(self.BAR_CONTENTS),
938 self.failUnless(re.search(get_bar, res), res)
939 for line in res.split("\n"):
940 # find the line that contains the delete button for bar.txt
941 if ("form action" in line and
942 'value="delete"' in line and
943 'value="bar.txt"' in line):
944 # the form target should use a relative URL
945 foo_url = urllib.quote("%s/uri/%s/" % (ROOT, self._foo_uri))
946 self.failUnless(('action="%s"' % foo_url) in line, line)
947 # and the when_done= should too
948 #done_url = urllib.quote(???)
949 #self.failUnless(('name="when_done" value="%s"' % done_url)
953 self.fail("unable to find delete-bar.txt line", res)
955 # the DIR reference just points to a URI
956 sub_url = ("%s/uri/%s/" % (ROOT, urllib.quote(self._sub_uri)))
957 get_sub = ((r'<td>DIR</td>')
958 +r'\s+<td><a href="%s">sub</a></td>' % sub_url)
959 self.failUnless(re.search(get_sub, res), res)
960 d.addCallback(_check)
962 # look at a readonly directory
963 d.addCallback(lambda res:
964 self.GET(self.public_url + "/reedownlee", followRedirect=True))
966 self.failUnless("(read-only)" in res, res)
967 self.failIf("Upload a file" in res, res)
968 d.addCallback(_check2)
970 # and at a directory that contains a readonly directory
971 d.addCallback(lambda res:
972 self.GET(self.public_url, followRedirect=True))
974 self.failUnless(re.search('<td>DIR-RO</td>'
975 r'\s+<td><a href="[\.\/]+/uri/URI%3ADIR2-RO%3A[^"]+">reedownlee</a></td>', res), res)
976 d.addCallback(_check3)
978 # and an empty directory
979 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty/"))
981 self.failUnless("directory is empty" in res, res)
982 MKDIR_BUTTON_RE=re.compile('<input type="hidden" name="t" value="mkdir" />.*<legend class="freeform-form-label">Create a new directory in this directory</legend>.*<input type="submit" value="Create" />', re.I)
983 self.failUnless(MKDIR_BUTTON_RE.search(res), res)
984 d.addCallback(_check4)
988 def test_GET_DIRURL_badtype(self):
989 d = self.shouldHTTPError("test_GET_DIRURL_badtype",
993 self.public_url + "/foo?t=bogus")
996 def test_GET_DIRURL_json(self):
997 d = self.GET(self.public_url + "/foo?t=json")
998 d.addCallback(self.failUnlessIsFooJSON)
1002 def test_POST_DIRURL_manifest_no_ophandle(self):
1003 d = self.shouldFail2(error.Error,
1004 "test_POST_DIRURL_manifest_no_ophandle",
1006 "slow operation requires ophandle=",
1007 self.POST, self.public_url, t="start-manifest")
1010 def test_POST_DIRURL_manifest(self):
1011 d = defer.succeed(None)
1012 def getman(ignored, output):
1013 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=125",
1014 followRedirect=True)
1015 d.addCallback(self.wait_for_operation, "125")
1016 d.addCallback(self.get_operation_results, "125", output)
1018 d.addCallback(getman, None)
1019 def _got_html(manifest):
1020 self.failUnless("Manifest of SI=" in manifest)
1021 self.failUnless("<td>sub</td>" in manifest)
1022 self.failUnless(self._sub_uri in manifest)
1023 self.failUnless("<td>sub/baz.txt</td>" in manifest)
1024 d.addCallback(_got_html)
1026 # both t=status and unadorned GET should be identical
1027 d.addCallback(lambda res: self.GET("/operations/125"))
1028 d.addCallback(_got_html)
1030 d.addCallback(getman, "html")
1031 d.addCallback(_got_html)
1032 d.addCallback(getman, "text")
1033 def _got_text(manifest):
1034 self.failUnless("\nsub " + self._sub_uri + "\n" in manifest)
1035 self.failUnless("\nsub/baz.txt URI:CHK:" in manifest)
1036 d.addCallback(_got_text)
1037 d.addCallback(getman, "JSON")
1039 data = res["manifest"]
1041 for (path_list, cap) in data:
1042 got[tuple(path_list)] = cap
1043 self.failUnlessEqual(got[(u"sub",)], self._sub_uri)
1044 self.failUnless((u"sub",u"baz.txt") in got)
1045 self.failUnless("finished" in res)
1046 self.failUnless("origin" in res)
1047 self.failUnless("storage-index" in res)
1048 self.failUnless("verifycaps" in res)
1049 self.failUnless("stats" in res)
1050 d.addCallback(_got_json)
1053 def test_POST_DIRURL_deepsize_no_ophandle(self):
1054 d = self.shouldFail2(error.Error,
1055 "test_POST_DIRURL_deepsize_no_ophandle",
1057 "slow operation requires ophandle=",
1058 self.POST, self.public_url, t="start-deep-size")
1061 def test_POST_DIRURL_deepsize(self):
1062 d = self.POST(self.public_url + "/foo/?t=start-deep-size&ophandle=126",
1063 followRedirect=True)
1064 d.addCallback(self.wait_for_operation, "126")
1065 d.addCallback(self.get_operation_results, "126", "json")
1066 def _got_json(data):
1067 self.failUnlessEqual(data["finished"], True)
1069 self.failUnless(size > 1000)
1070 d.addCallback(_got_json)
1071 d.addCallback(self.get_operation_results, "126", "text")
1073 mo = re.search(r'^size: (\d+)$', res, re.M)
1074 self.failUnless(mo, res)
1075 size = int(mo.group(1))
1076 # with directories, the size varies.
1077 self.failUnless(size > 1000)
1078 d.addCallback(_got_text)
1081 def test_POST_DIRURL_deepstats_no_ophandle(self):
1082 d = self.shouldFail2(error.Error,
1083 "test_POST_DIRURL_deepstats_no_ophandle",
1085 "slow operation requires ophandle=",
1086 self.POST, self.public_url, t="start-deep-stats")
1089 def test_POST_DIRURL_deepstats(self):
1090 d = self.POST(self.public_url + "/foo/?t=start-deep-stats&ophandle=127",
1091 followRedirect=True)
1092 d.addCallback(self.wait_for_operation, "127")
1093 d.addCallback(self.get_operation_results, "127", "json")
1094 def _got_json(stats):
1095 expected = {"count-immutable-files": 3,
1096 "count-mutable-files": 0,
1097 "count-literal-files": 0,
1099 "count-directories": 3,
1100 "size-immutable-files": 57,
1101 "size-literal-files": 0,
1102 #"size-directories": 1912, # varies
1103 #"largest-directory": 1590,
1104 "largest-directory-children": 5,
1105 "largest-immutable-file": 19,
1107 for k,v in expected.iteritems():
1108 self.failUnlessEqual(stats[k], v,
1109 "stats[%s] was %s, not %s" %
1111 self.failUnlessEqual(stats["size-files-histogram"],
1113 d.addCallback(_got_json)
1116 def test_POST_DIRURL_stream_manifest(self):
1117 d = self.POST(self.public_url + "/foo/?t=stream-manifest")
1119 self.failUnless(res.endswith("\n"))
1120 units = [simplejson.loads(t) for t in res[:-1].split("\n")]
1121 self.failUnlessEqual(len(units), 7)
1122 self.failUnlessEqual(units[-1]["type"], "stats")
1124 self.failUnlessEqual(first["path"], [])
1125 self.failUnlessEqual(first["cap"], self._foo_uri)
1126 self.failUnlessEqual(first["type"], "directory")
1127 baz = [u for u in units[:-1] if u["cap"] == self._baz_file_uri][0]
1128 self.failUnlessEqual(baz["path"], ["sub", "baz.txt"])
1129 self.failIfEqual(baz["storage-index"], None)
1130 self.failIfEqual(baz["verifycap"], None)
1131 self.failIfEqual(baz["repaircap"], None)
1133 d.addCallback(_check)
1136 def test_GET_DIRURL_uri(self):
1137 d = self.GET(self.public_url + "/foo?t=uri")
1139 self.failUnlessEqual(res, self._foo_uri)
1140 d.addCallback(_check)
1143 def test_GET_DIRURL_readonly_uri(self):
1144 d = self.GET(self.public_url + "/foo?t=readonly-uri")
1146 self.failUnlessEqual(res, self._foo_readonly_uri)
1147 d.addCallback(_check)
1150 def test_PUT_NEWDIRURL(self):
1151 d = self.PUT(self.public_url + "/foo/newdir?t=mkdir", "")
1152 d.addCallback(lambda res:
1153 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1154 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1155 d.addCallback(self.failUnlessNodeKeysAre, [])
1158 def test_POST_NEWDIRURL(self):
1159 d = self.POST2(self.public_url + "/foo/newdir?t=mkdir", "")
1160 d.addCallback(lambda res:
1161 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1162 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1163 d.addCallback(self.failUnlessNodeKeysAre, [])
1166 def test_POST_NEWDIRURL_emptyname(self):
1167 # an empty pathname component (i.e. a double-slash) is disallowed
1168 d = self.shouldFail2(error.Error, "test_POST_NEWDIRURL_emptyname",
1170 "The webapi does not allow empty pathname components, i.e. a double slash",
1171 self.POST, self.public_url + "//?t=mkdir")
1174 def test_POST_NEWDIRURL_initial_children(self):
1175 (newkids, caps) = self._create_initial_children()
1176 d = self.POST2(self.public_url + "/foo/newdir?t=mkdir-with-children",
1177 simplejson.dumps(newkids))
1179 n = self.s.create_node_from_uri(uri.strip())
1180 d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
1181 d2.addCallback(lambda ign:
1182 self.failUnlessROChildURIIs(n, u"child-imm",
1184 d2.addCallback(lambda ign:
1185 self.failUnlessRWChildURIIs(n, u"child-mutable",
1187 d2.addCallback(lambda ign:
1188 self.failUnlessROChildURIIs(n, u"child-mutable-ro",
1190 d2.addCallback(lambda ign:
1191 self.failUnlessROChildURIIs(n, u"unknownchild-ro",
1192 caps['unknown_rocap']))
1193 d2.addCallback(lambda ign:
1194 self.failUnlessRWChildURIIs(n, u"unknownchild-rw",
1195 caps['unknown_rwcap']))
1196 d2.addCallback(lambda ign:
1197 self.failUnlessROChildURIIs(n, u"unknownchild-imm",
1198 caps['unknown_immcap']))
1199 d2.addCallback(lambda ign:
1200 self.failUnlessRWChildURIIs(n, u"dirchild",
1203 d.addCallback(_check)
1204 d.addCallback(lambda res:
1205 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1206 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1207 d.addCallback(self.failUnlessNodeKeysAre, newkids.keys())
1208 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1209 d.addCallback(self.failUnlessROChildURIIs, u"child-imm", caps['filecap1'])
1212 def test_POST_NEWDIRURL_immutable(self):
1213 (newkids, caps) = self._create_immutable_children()
1214 d = self.POST2(self.public_url + "/foo/newdir?t=mkdir-immutable",
1215 simplejson.dumps(newkids))
1217 n = self.s.create_node_from_uri(uri.strip())
1218 d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
1219 d2.addCallback(lambda ign:
1220 self.failUnlessROChildURIIs(n, u"child-imm",
1222 d2.addCallback(lambda ign:
1223 self.failUnlessROChildURIIs(n, u"unknownchild-imm",
1224 caps['unknown_immcap']))
1225 d2.addCallback(lambda ign:
1226 self.failUnlessROChildURIIs(n, u"dirchild-imm",
1229 d.addCallback(_check)
1230 d.addCallback(lambda res:
1231 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1232 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1233 d.addCallback(self.failUnlessNodeKeysAre, newkids.keys())
1234 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1235 d.addCallback(self.failUnlessROChildURIIs, u"child-imm", caps['filecap1'])
1236 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1237 d.addCallback(self.failUnlessROChildURIIs, u"unknownchild-imm", caps['unknown_immcap'])
1238 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1239 d.addCallback(self.failUnlessROChildURIIs, u"dirchild-imm", caps['immdircap'])
1240 d.addErrback(self.explain_web_error)
1243 def test_POST_NEWDIRURL_immutable_bad(self):
1244 (newkids, caps) = self._create_initial_children()
1245 d = self.shouldFail2(error.Error, "test_POST_NEWDIRURL_immutable_bad",
1247 "needed to be immutable but was not",
1249 self.public_url + "/foo/newdir?t=mkdir-immutable",
1250 simplejson.dumps(newkids))
1253 def test_PUT_NEWDIRURL_exists(self):
1254 d = self.PUT(self.public_url + "/foo/sub?t=mkdir", "")
1255 d.addCallback(lambda res:
1256 self.failUnlessNodeHasChild(self._foo_node, u"sub"))
1257 d.addCallback(lambda res: self._foo_node.get(u"sub"))
1258 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
1261 def test_PUT_NEWDIRURL_blocked(self):
1262 d = self.shouldFail2(error.Error, "PUT_NEWDIRURL_blocked",
1263 "409 Conflict", "Unable to create directory 'bar.txt': a file was in the way",
1265 self.public_url + "/foo/bar.txt/sub?t=mkdir", "")
1266 d.addCallback(lambda res:
1267 self.failUnlessNodeHasChild(self._foo_node, u"sub"))
1268 d.addCallback(lambda res: self._foo_node.get(u"sub"))
1269 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
1272 def test_PUT_NEWDIRURL_mkdir_p(self):
1273 d = defer.succeed(None)
1274 d.addCallback(lambda res: self.POST(self.public_url + "/foo", t='mkdir', name='mkp'))
1275 d.addCallback(lambda res: self.failUnlessNodeHasChild(self._foo_node, u"mkp"))
1276 d.addCallback(lambda res: self._foo_node.get(u"mkp"))
1277 def mkdir_p(mkpnode):
1278 url = '/uri/%s?t=mkdir-p&path=/sub1/sub2' % urllib.quote(mkpnode.get_uri())
1280 def made_subsub(ssuri):
1281 d = self._foo_node.get_child_at_path(u"mkp/sub1/sub2")
1282 d.addCallback(lambda ssnode: self.failUnlessEqual(ssnode.get_uri(), ssuri))
1284 d.addCallback(lambda uri2: self.failUnlessEqual(uri2, ssuri))
1286 d.addCallback(made_subsub)
1288 d.addCallback(mkdir_p)
1291 def test_PUT_NEWDIRURL_mkdirs(self):
1292 d = self.PUT(self.public_url + "/foo/subdir/newdir?t=mkdir", "")
1293 d.addCallback(lambda res:
1294 self.failIfNodeHasChild(self._foo_node, u"newdir"))
1295 d.addCallback(lambda res:
1296 self.failUnlessNodeHasChild(self._foo_node, u"subdir"))
1297 d.addCallback(lambda res:
1298 self._foo_node.get_child_at_path(u"subdir/newdir"))
1299 d.addCallback(self.failUnlessNodeKeysAre, [])
1302 def test_DELETE_DIRURL(self):
1303 d = self.DELETE(self.public_url + "/foo")
1304 d.addCallback(lambda res:
1305 self.failIfNodeHasChild(self.public_root, u"foo"))
1308 def test_DELETE_DIRURL_missing(self):
1309 d = self.DELETE(self.public_url + "/foo/missing")
1310 d.addBoth(self.should404, "test_DELETE_DIRURL_missing")
1311 d.addCallback(lambda res:
1312 self.failUnlessNodeHasChild(self.public_root, u"foo"))
1315 def test_DELETE_DIRURL_missing2(self):
1316 d = self.DELETE(self.public_url + "/missing")
1317 d.addBoth(self.should404, "test_DELETE_DIRURL_missing2")
1320 def dump_root(self):
1322 w = webish.DirnodeWalkerMixin()
1323 def visitor(childpath, childnode, metadata):
1325 d = w.walk(self.public_root, visitor)
1328 def failUnlessNodeKeysAre(self, node, expected_keys):
1329 for k in expected_keys:
1330 assert isinstance(k, unicode)
1332 def _check(children):
1333 self.failUnlessEqual(sorted(children.keys()), sorted(expected_keys))
1334 d.addCallback(_check)
1336 def failUnlessNodeHasChild(self, node, name):
1337 assert isinstance(name, unicode)
1339 def _check(children):
1340 self.failUnless(name in children)
1341 d.addCallback(_check)
1343 def failIfNodeHasChild(self, node, name):
1344 assert isinstance(name, unicode)
1346 def _check(children):
1347 self.failIf(name in children)
1348 d.addCallback(_check)
1351 def failUnlessChildContentsAre(self, node, name, expected_contents):
1352 assert isinstance(name, unicode)
1353 d = node.get_child_at_path(name)
1354 d.addCallback(lambda node: download_to_data(node))
1355 def _check(contents):
1356 self.failUnlessEqual(contents, expected_contents)
1357 d.addCallback(_check)
1360 def failUnlessMutableChildContentsAre(self, node, name, expected_contents):
1361 assert isinstance(name, unicode)
1362 d = node.get_child_at_path(name)
1363 d.addCallback(lambda node: node.download_best_version())
1364 def _check(contents):
1365 self.failUnlessEqual(contents, expected_contents)
1366 d.addCallback(_check)
1369 def failUnlessRWChildURIIs(self, node, name, expected_uri):
1370 assert isinstance(name, unicode)
1371 d = node.get_child_at_path(name)
1373 self.failUnless(child.is_unknown() or not child.is_readonly())
1374 self.failUnlessEqual(child.get_uri(), expected_uri.strip())
1375 self.failUnlessEqual(child.get_write_uri(), expected_uri.strip())
1376 expected_ro_uri = self._make_readonly(expected_uri)
1378 self.failUnlessEqual(child.get_readonly_uri(), expected_ro_uri.strip())
1379 d.addCallback(_check)
1382 def failUnlessROChildURIIs(self, node, name, expected_uri):
1383 assert isinstance(name, unicode)
1384 d = node.get_child_at_path(name)
1386 self.failUnless(child.is_unknown() or child.is_readonly())
1387 self.failUnlessEqual(child.get_write_uri(), None)
1388 self.failUnlessEqual(child.get_uri(), expected_uri.strip())
1389 self.failUnlessEqual(child.get_readonly_uri(), expected_uri.strip())
1390 d.addCallback(_check)
1393 def failUnlessURIMatchesRWChild(self, got_uri, node, name):
1394 assert isinstance(name, unicode)
1395 d = node.get_child_at_path(name)
1397 self.failUnless(child.is_unknown() or not child.is_readonly())
1398 self.failUnlessEqual(child.get_uri(), got_uri.strip())
1399 self.failUnlessEqual(child.get_write_uri(), got_uri.strip())
1400 expected_ro_uri = self._make_readonly(got_uri)
1402 self.failUnlessEqual(child.get_readonly_uri(), expected_ro_uri.strip())
1403 d.addCallback(_check)
1406 def failUnlessURIMatchesROChild(self, got_uri, node, name):
1407 assert isinstance(name, unicode)
1408 d = node.get_child_at_path(name)
1410 self.failUnless(child.is_unknown() or child.is_readonly())
1411 self.failUnlessEqual(child.get_write_uri(), None)
1412 self.failUnlessEqual(got_uri.strip(), child.get_uri())
1413 self.failUnlessEqual(got_uri.strip(), child.get_readonly_uri())
1414 d.addCallback(_check)
1417 def failUnlessCHKURIHasContents(self, got_uri, contents):
1418 self.failUnless(FakeCHKFileNode.all_contents[got_uri] == contents)
1420 def test_POST_upload(self):
1421 d = self.POST(self.public_url + "/foo", t="upload",
1422 file=("new.txt", self.NEWFILE_CONTENTS))
1424 d.addCallback(self.failUnlessURIMatchesROChild, fn, u"new.txt")
1425 d.addCallback(lambda res:
1426 self.failUnlessChildContentsAre(fn, u"new.txt",
1427 self.NEWFILE_CONTENTS))
1430 def test_POST_upload_unicode(self):
1431 filename = u"n\u00e9wer.txt" # n e-acute w e r . t x t
1432 d = self.POST(self.public_url + "/foo", t="upload",
1433 file=(filename, self.NEWFILE_CONTENTS))
1435 d.addCallback(self.failUnlessURIMatchesROChild, fn, filename)
1436 d.addCallback(lambda res:
1437 self.failUnlessChildContentsAre(fn, filename,
1438 self.NEWFILE_CONTENTS))
1439 target_url = self.public_url + "/foo/" + filename.encode("utf-8")
1440 d.addCallback(lambda res: self.GET(target_url))
1441 d.addCallback(lambda contents: self.failUnlessEqual(contents,
1442 self.NEWFILE_CONTENTS,
1446 def test_POST_upload_unicode_named(self):
1447 filename = u"n\u00e9wer.txt" # n e-acute w e r . t x t
1448 d = self.POST(self.public_url + "/foo", t="upload",
1450 file=("overridden", self.NEWFILE_CONTENTS))
1452 d.addCallback(self.failUnlessURIMatchesROChild, fn, filename)
1453 d.addCallback(lambda res:
1454 self.failUnlessChildContentsAre(fn, filename,
1455 self.NEWFILE_CONTENTS))
1456 target_url = self.public_url + "/foo/" + filename.encode("utf-8")
1457 d.addCallback(lambda res: self.GET(target_url))
1458 d.addCallback(lambda contents: self.failUnlessEqual(contents,
1459 self.NEWFILE_CONTENTS,
1463 def test_POST_upload_no_link(self):
1464 d = self.POST("/uri", t="upload",
1465 file=("new.txt", self.NEWFILE_CONTENTS))
1466 def _check_upload_results(page):
1467 # this should be a page which describes the results of the upload
1468 # that just finished.
1469 self.failUnless("Upload Results:" in page)
1470 self.failUnless("URI:" in page)
1471 uri_re = re.compile("URI: <tt><span>(.*)</span>")
1472 mo = uri_re.search(page)
1473 self.failUnless(mo, page)
1474 new_uri = mo.group(1)
1476 d.addCallback(_check_upload_results)
1477 d.addCallback(self.failUnlessCHKURIHasContents, self.NEWFILE_CONTENTS)
1480 def test_POST_upload_no_link_whendone(self):
1481 d = self.POST("/uri", t="upload", when_done="/",
1482 file=("new.txt", self.NEWFILE_CONTENTS))
1483 d.addBoth(self.shouldRedirect, "/")
1486 def shouldRedirect2(self, which, checker, callable, *args, **kwargs):
1487 d = defer.maybeDeferred(callable, *args, **kwargs)
1489 if isinstance(res, failure.Failure):
1490 res.trap(error.PageRedirect)
1491 statuscode = res.value.status
1492 target = res.value.location
1493 return checker(statuscode, target)
1494 self.fail("%s: callable was supposed to redirect, not return '%s'"
1499 def test_POST_upload_no_link_whendone_results(self):
1500 def check(statuscode, target):
1501 self.failUnlessEqual(statuscode, str(http.FOUND))
1502 self.failUnless(target.startswith(self.webish_url), target)
1503 return client.getPage(target, method="GET")
1504 d = self.shouldRedirect2("test_POST_upload_no_link_whendone_results",
1506 self.POST, "/uri", t="upload",
1507 when_done="/uri/%(uri)s",
1508 file=("new.txt", self.NEWFILE_CONTENTS))
1509 d.addCallback(lambda res:
1510 self.failUnlessEqual(res, self.NEWFILE_CONTENTS))
1513 def test_POST_upload_no_link_mutable(self):
1514 d = self.POST("/uri", t="upload", mutable="true",
1515 file=("new.txt", self.NEWFILE_CONTENTS))
1516 def _check(filecap):
1517 filecap = filecap.strip()
1518 self.failUnless(filecap.startswith("URI:SSK:"), filecap)
1519 self.filecap = filecap
1520 u = uri.WriteableSSKFileURI.init_from_string(filecap)
1521 self.failUnless(u.storage_index in FakeMutableFileNode.all_contents)
1522 n = self.s.create_node_from_uri(filecap)
1523 return n.download_best_version()
1524 d.addCallback(_check)
1526 self.failUnlessEqual(data, self.NEWFILE_CONTENTS)
1527 return self.GET("/uri/%s" % urllib.quote(self.filecap))
1528 d.addCallback(_check2)
1530 self.failUnlessEqual(data, self.NEWFILE_CONTENTS)
1531 return self.GET("/file/%s" % urllib.quote(self.filecap))
1532 d.addCallback(_check3)
1534 self.failUnlessEqual(data, self.NEWFILE_CONTENTS)
1535 d.addCallback(_check4)
1538 def test_POST_upload_no_link_mutable_toobig(self):
1539 d = self.shouldFail2(error.Error,
1540 "test_POST_upload_no_link_mutable_toobig",
1541 "413 Request Entity Too Large",
1542 "SDMF is limited to one segment, and 10001 > 10000",
1544 "/uri", t="upload", mutable="true",
1546 "b" * (self.s.MUTABLE_SIZELIMIT+1)) )
1549 def test_POST_upload_mutable(self):
1550 # this creates a mutable file
1551 d = self.POST(self.public_url + "/foo", t="upload", mutable="true",
1552 file=("new.txt", self.NEWFILE_CONTENTS))
1554 d.addCallback(self.failUnlessURIMatchesRWChild, fn, u"new.txt")
1555 d.addCallback(lambda res:
1556 self.failUnlessMutableChildContentsAre(fn, u"new.txt",
1557 self.NEWFILE_CONTENTS))
1558 d.addCallback(lambda res: self._foo_node.get(u"new.txt"))
1560 self.failUnless(IMutableFileNode.providedBy(newnode))
1561 self.failUnless(newnode.is_mutable())
1562 self.failIf(newnode.is_readonly())
1563 self._mutable_node = newnode
1564 self._mutable_uri = newnode.get_uri()
1567 # now upload it again and make sure that the URI doesn't change
1568 NEWER_CONTENTS = self.NEWFILE_CONTENTS + "newer\n"
1569 d.addCallback(lambda res:
1570 self.POST(self.public_url + "/foo", t="upload",
1572 file=("new.txt", NEWER_CONTENTS)))
1573 d.addCallback(self.failUnlessURIMatchesRWChild, fn, u"new.txt")
1574 d.addCallback(lambda res:
1575 self.failUnlessMutableChildContentsAre(fn, u"new.txt",
1577 d.addCallback(lambda res: self._foo_node.get(u"new.txt"))
1579 self.failUnless(IMutableFileNode.providedBy(newnode))
1580 self.failUnless(newnode.is_mutable())
1581 self.failIf(newnode.is_readonly())
1582 self.failUnlessEqual(self._mutable_uri, newnode.get_uri())
1583 d.addCallback(_got2)
1585 # upload a second time, using PUT instead of POST
1586 NEW2_CONTENTS = NEWER_CONTENTS + "overwrite with PUT\n"
1587 d.addCallback(lambda res:
1588 self.PUT(self.public_url + "/foo/new.txt", NEW2_CONTENTS))
1589 d.addCallback(self.failUnlessURIMatchesRWChild, fn, u"new.txt")
1590 d.addCallback(lambda res:
1591 self.failUnlessMutableChildContentsAre(fn, u"new.txt",
1594 # finally list the directory, since mutable files are displayed
1595 # slightly differently
1597 d.addCallback(lambda res:
1598 self.GET(self.public_url + "/foo/",
1599 followRedirect=True))
1600 def _check_page(res):
1601 # TODO: assert more about the contents
1602 self.failUnless("SSK" in res)
1604 d.addCallback(_check_page)
1606 d.addCallback(lambda res: self._foo_node.get(u"new.txt"))
1608 self.failUnless(IMutableFileNode.providedBy(newnode))
1609 self.failUnless(newnode.is_mutable())
1610 self.failIf(newnode.is_readonly())
1611 self.failUnlessEqual(self._mutable_uri, newnode.get_uri())
1612 d.addCallback(_got3)
1614 # look at the JSON form of the enclosing directory
1615 d.addCallback(lambda res:
1616 self.GET(self.public_url + "/foo/?t=json",
1617 followRedirect=True))
1618 def _check_page_json(res):
1619 parsed = simplejson.loads(res)
1620 self.failUnlessEqual(parsed[0], "dirnode")
1621 children = dict( [(unicode(name),value)
1623 in parsed[1]["children"].iteritems()] )
1624 self.failUnless("new.txt" in children)
1625 new_json = children["new.txt"]
1626 self.failUnlessEqual(new_json[0], "filenode")
1627 self.failUnless(new_json[1]["mutable"])
1628 self.failUnlessEqual(new_json[1]["rw_uri"], self._mutable_uri)
1629 ro_uri = unicode(self._mutable_node.get_readonly().to_string())
1630 self.failUnlessEqual(new_json[1]["ro_uri"], ro_uri)
1631 d.addCallback(_check_page_json)
1633 # and the JSON form of the file
1634 d.addCallback(lambda res:
1635 self.GET(self.public_url + "/foo/new.txt?t=json"))
1636 def _check_file_json(res):
1637 parsed = simplejson.loads(res)
1638 self.failUnlessEqual(parsed[0], "filenode")
1639 self.failUnless(parsed[1]["mutable"])
1640 self.failUnlessEqual(parsed[1]["rw_uri"], self._mutable_uri)
1641 ro_uri = unicode(self._mutable_node.get_readonly().to_string())
1642 self.failUnlessEqual(parsed[1]["ro_uri"], ro_uri)
1643 d.addCallback(_check_file_json)
1645 # and look at t=uri and t=readonly-uri
1646 d.addCallback(lambda res:
1647 self.GET(self.public_url + "/foo/new.txt?t=uri"))
1648 d.addCallback(lambda res: self.failUnlessEqual(res, self._mutable_uri))
1649 d.addCallback(lambda res:
1650 self.GET(self.public_url + "/foo/new.txt?t=readonly-uri"))
1651 def _check_ro_uri(res):
1652 ro_uri = unicode(self._mutable_node.get_readonly().to_string())
1653 self.failUnlessEqual(res, ro_uri)
1654 d.addCallback(_check_ro_uri)
1656 # make sure we can get to it from /uri/URI
1657 d.addCallback(lambda res:
1658 self.GET("/uri/%s" % urllib.quote(self._mutable_uri)))
1659 d.addCallback(lambda res:
1660 self.failUnlessEqual(res, NEW2_CONTENTS))
1662 # and that HEAD computes the size correctly
1663 d.addCallback(lambda res:
1664 self.HEAD(self.public_url + "/foo/new.txt",
1665 return_response=True))
1666 def _got_headers((res, status, headers)):
1667 self.failUnlessEqual(res, "")
1668 self.failUnlessEqual(headers["content-length"][0],
1669 str(len(NEW2_CONTENTS)))
1670 self.failUnlessEqual(headers["content-type"], ["text/plain"])
1671 d.addCallback(_got_headers)
1673 # make sure that size errors are displayed correctly for overwrite
1674 d.addCallback(lambda res:
1675 self.shouldFail2(error.Error,
1676 "test_POST_upload_mutable-toobig",
1677 "413 Request Entity Too Large",
1678 "SDMF is limited to one segment, and 10001 > 10000",
1680 self.public_url + "/foo", t="upload",
1683 "b" * (self.s.MUTABLE_SIZELIMIT+1)),
1686 d.addErrback(self.dump_error)
1689 def test_POST_upload_mutable_toobig(self):
1690 d = self.shouldFail2(error.Error,
1691 "test_POST_upload_mutable_toobig",
1692 "413 Request Entity Too Large",
1693 "SDMF is limited to one segment, and 10001 > 10000",
1695 self.public_url + "/foo",
1696 t="upload", mutable="true",
1698 "b" * (self.s.MUTABLE_SIZELIMIT+1)) )
1701 def dump_error(self, f):
1702 # if the web server returns an error code (like 400 Bad Request),
1703 # web.client.getPage puts the HTTP response body into the .response
1704 # attribute of the exception object that it gives back. It does not
1705 # appear in the Failure's repr(), so the ERROR that trial displays
1706 # will be rather terse and unhelpful. addErrback this method to the
1707 # end of your chain to get more information out of these errors.
1708 if f.check(error.Error):
1709 print "web.error.Error:"
1711 print f.value.response
1714 def test_POST_upload_replace(self):
1715 d = self.POST(self.public_url + "/foo", t="upload",
1716 file=("bar.txt", self.NEWFILE_CONTENTS))
1718 d.addCallback(self.failUnlessURIMatchesROChild, fn, u"bar.txt")
1719 d.addCallback(lambda res:
1720 self.failUnlessChildContentsAre(fn, u"bar.txt",
1721 self.NEWFILE_CONTENTS))
1724 def test_POST_upload_no_replace_ok(self):
1725 d = self.POST(self.public_url + "/foo?replace=false", t="upload",
1726 file=("new.txt", self.NEWFILE_CONTENTS))
1727 d.addCallback(lambda res: self.GET(self.public_url + "/foo/new.txt"))
1728 d.addCallback(lambda res: self.failUnlessEqual(res,
1729 self.NEWFILE_CONTENTS))
1732 def test_POST_upload_no_replace_queryarg(self):
1733 d = self.POST(self.public_url + "/foo?replace=false", t="upload",
1734 file=("bar.txt", self.NEWFILE_CONTENTS))
1735 d.addBoth(self.shouldFail, error.Error,
1736 "POST_upload_no_replace_queryarg",
1738 "There was already a child by that name, and you asked me "
1739 "to not replace it")
1740 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
1741 d.addCallback(self.failUnlessIsBarDotTxt)
1744 def test_POST_upload_no_replace_field(self):
1745 d = self.POST(self.public_url + "/foo", t="upload", replace="false",
1746 file=("bar.txt", self.NEWFILE_CONTENTS))
1747 d.addBoth(self.shouldFail, error.Error, "POST_upload_no_replace_field",
1749 "There was already a child by that name, and you asked me "
1750 "to not replace it")
1751 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
1752 d.addCallback(self.failUnlessIsBarDotTxt)
1755 def test_POST_upload_whendone(self):
1756 d = self.POST(self.public_url + "/foo", t="upload", when_done="/THERE",
1757 file=("new.txt", self.NEWFILE_CONTENTS))
1758 d.addBoth(self.shouldRedirect, "/THERE")
1760 d.addCallback(lambda res:
1761 self.failUnlessChildContentsAre(fn, u"new.txt",
1762 self.NEWFILE_CONTENTS))
1765 def test_POST_upload_named(self):
1767 d = self.POST(self.public_url + "/foo", t="upload",
1768 name="new.txt", file=self.NEWFILE_CONTENTS)
1769 d.addCallback(self.failUnlessURIMatchesROChild, fn, u"new.txt")
1770 d.addCallback(lambda res:
1771 self.failUnlessChildContentsAre(fn, u"new.txt",
1772 self.NEWFILE_CONTENTS))
1775 def test_POST_upload_named_badfilename(self):
1776 d = self.POST(self.public_url + "/foo", t="upload",
1777 name="slashes/are/bad.txt", file=self.NEWFILE_CONTENTS)
1778 d.addBoth(self.shouldFail, error.Error,
1779 "test_POST_upload_named_badfilename",
1781 "name= may not contain a slash",
1783 # make sure that nothing was added
1784 d.addCallback(lambda res:
1785 self.failUnlessNodeKeysAre(self._foo_node,
1786 [u"bar.txt", u"blockingfile",
1787 u"empty", u"n\u00fc.txt",
1791 def test_POST_FILEURL_check(self):
1792 bar_url = self.public_url + "/foo/bar.txt"
1793 d = self.POST(bar_url, t="check")
1795 self.failUnless("Healthy :" in res)
1796 d.addCallback(_check)
1797 redir_url = "http://allmydata.org/TARGET"
1798 def _check2(statuscode, target):
1799 self.failUnlessEqual(statuscode, str(http.FOUND))
1800 self.failUnlessEqual(target, redir_url)
1801 d.addCallback(lambda res:
1802 self.shouldRedirect2("test_POST_FILEURL_check",
1806 when_done=redir_url))
1807 d.addCallback(lambda res:
1808 self.POST(bar_url, t="check", return_to=redir_url))
1810 self.failUnless("Healthy :" in res)
1811 self.failUnless("Return to file" in res)
1812 self.failUnless(redir_url in res)
1813 d.addCallback(_check3)
1815 d.addCallback(lambda res:
1816 self.POST(bar_url, t="check", output="JSON"))
1817 def _check_json(res):
1818 data = simplejson.loads(res)
1819 self.failUnless("storage-index" in data)
1820 self.failUnless(data["results"]["healthy"])
1821 d.addCallback(_check_json)
1825 def test_POST_FILEURL_check_and_repair(self):
1826 bar_url = self.public_url + "/foo/bar.txt"
1827 d = self.POST(bar_url, t="check", repair="true")
1829 self.failUnless("Healthy :" in res)
1830 d.addCallback(_check)
1831 redir_url = "http://allmydata.org/TARGET"
1832 def _check2(statuscode, target):
1833 self.failUnlessEqual(statuscode, str(http.FOUND))
1834 self.failUnlessEqual(target, redir_url)
1835 d.addCallback(lambda res:
1836 self.shouldRedirect2("test_POST_FILEURL_check_and_repair",
1839 t="check", repair="true",
1840 when_done=redir_url))
1841 d.addCallback(lambda res:
1842 self.POST(bar_url, t="check", return_to=redir_url))
1844 self.failUnless("Healthy :" in res)
1845 self.failUnless("Return to file" in res)
1846 self.failUnless(redir_url in res)
1847 d.addCallback(_check3)
1850 def test_POST_DIRURL_check(self):
1851 foo_url = self.public_url + "/foo/"
1852 d = self.POST(foo_url, t="check")
1854 self.failUnless("Healthy :" in res, res)
1855 d.addCallback(_check)
1856 redir_url = "http://allmydata.org/TARGET"
1857 def _check2(statuscode, target):
1858 self.failUnlessEqual(statuscode, str(http.FOUND))
1859 self.failUnlessEqual(target, redir_url)
1860 d.addCallback(lambda res:
1861 self.shouldRedirect2("test_POST_DIRURL_check",
1865 when_done=redir_url))
1866 d.addCallback(lambda res:
1867 self.POST(foo_url, t="check", return_to=redir_url))
1869 self.failUnless("Healthy :" in res, res)
1870 self.failUnless("Return to file/directory" in res)
1871 self.failUnless(redir_url in res)
1872 d.addCallback(_check3)
1874 d.addCallback(lambda res:
1875 self.POST(foo_url, t="check", output="JSON"))
1876 def _check_json(res):
1877 data = simplejson.loads(res)
1878 self.failUnless("storage-index" in data)
1879 self.failUnless(data["results"]["healthy"])
1880 d.addCallback(_check_json)
1884 def test_POST_DIRURL_check_and_repair(self):
1885 foo_url = self.public_url + "/foo/"
1886 d = self.POST(foo_url, t="check", repair="true")
1888 self.failUnless("Healthy :" in res, res)
1889 d.addCallback(_check)
1890 redir_url = "http://allmydata.org/TARGET"
1891 def _check2(statuscode, target):
1892 self.failUnlessEqual(statuscode, str(http.FOUND))
1893 self.failUnlessEqual(target, redir_url)
1894 d.addCallback(lambda res:
1895 self.shouldRedirect2("test_POST_DIRURL_check_and_repair",
1898 t="check", repair="true",
1899 when_done=redir_url))
1900 d.addCallback(lambda res:
1901 self.POST(foo_url, t="check", return_to=redir_url))
1903 self.failUnless("Healthy :" in res)
1904 self.failUnless("Return to file/directory" in res)
1905 self.failUnless(redir_url in res)
1906 d.addCallback(_check3)
1909 def wait_for_operation(self, ignored, ophandle):
1910 url = "/operations/" + ophandle
1911 url += "?t=status&output=JSON"
1914 data = simplejson.loads(res)
1915 if not data["finished"]:
1916 d = self.stall(delay=1.0)
1917 d.addCallback(self.wait_for_operation, ophandle)
1923 def get_operation_results(self, ignored, ophandle, output=None):
1924 url = "/operations/" + ophandle
1927 url += "&output=" + output
1930 if output and output.lower() == "json":
1931 return simplejson.loads(res)
1936 def test_POST_DIRURL_deepcheck_no_ophandle(self):
1937 d = self.shouldFail2(error.Error,
1938 "test_POST_DIRURL_deepcheck_no_ophandle",
1940 "slow operation requires ophandle=",
1941 self.POST, self.public_url, t="start-deep-check")
1944 def test_POST_DIRURL_deepcheck(self):
1945 def _check_redirect(statuscode, target):
1946 self.failUnlessEqual(statuscode, str(http.FOUND))
1947 self.failUnless(target.endswith("/operations/123"))
1948 d = self.shouldRedirect2("test_POST_DIRURL_deepcheck", _check_redirect,
1949 self.POST, self.public_url,
1950 t="start-deep-check", ophandle="123")
1951 d.addCallback(self.wait_for_operation, "123")
1952 def _check_json(data):
1953 self.failUnlessEqual(data["finished"], True)
1954 self.failUnlessEqual(data["count-objects-checked"], 8)
1955 self.failUnlessEqual(data["count-objects-healthy"], 8)
1956 d.addCallback(_check_json)
1957 d.addCallback(self.get_operation_results, "123", "html")
1958 def _check_html(res):
1959 self.failUnless("Objects Checked: <span>8</span>" in res)
1960 self.failUnless("Objects Healthy: <span>8</span>" in res)
1961 d.addCallback(_check_html)
1963 d.addCallback(lambda res:
1964 self.GET("/operations/123/"))
1965 d.addCallback(_check_html) # should be the same as without the slash
1967 d.addCallback(lambda res:
1968 self.shouldFail2(error.Error, "one", "404 Not Found",
1969 "No detailed results for SI bogus",
1970 self.GET, "/operations/123/bogus"))
1972 foo_si = self._foo_node.get_storage_index()
1973 foo_si_s = base32.b2a(foo_si)
1974 d.addCallback(lambda res:
1975 self.GET("/operations/123/%s?output=JSON" % foo_si_s))
1976 def _check_foo_json(res):
1977 data = simplejson.loads(res)
1978 self.failUnlessEqual(data["storage-index"], foo_si_s)
1979 self.failUnless(data["results"]["healthy"])
1980 d.addCallback(_check_foo_json)
1983 def test_POST_DIRURL_deepcheck_and_repair(self):
1984 d = self.POST(self.public_url, t="start-deep-check", repair="true",
1985 ophandle="124", output="json", followRedirect=True)
1986 d.addCallback(self.wait_for_operation, "124")
1987 def _check_json(data):
1988 self.failUnlessEqual(data["finished"], True)
1989 self.failUnlessEqual(data["count-objects-checked"], 8)
1990 self.failUnlessEqual(data["count-objects-healthy-pre-repair"], 8)
1991 self.failUnlessEqual(data["count-objects-unhealthy-pre-repair"], 0)
1992 self.failUnlessEqual(data["count-corrupt-shares-pre-repair"], 0)
1993 self.failUnlessEqual(data["count-repairs-attempted"], 0)
1994 self.failUnlessEqual(data["count-repairs-successful"], 0)
1995 self.failUnlessEqual(data["count-repairs-unsuccessful"], 0)
1996 self.failUnlessEqual(data["count-objects-healthy-post-repair"], 8)
1997 self.failUnlessEqual(data["count-objects-unhealthy-post-repair"], 0)
1998 self.failUnlessEqual(data["count-corrupt-shares-post-repair"], 0)
1999 d.addCallback(_check_json)
2000 d.addCallback(self.get_operation_results, "124", "html")
2001 def _check_html(res):
2002 self.failUnless("Objects Checked: <span>8</span>" in res)
2004 self.failUnless("Objects Healthy (before repair): <span>8</span>" in res)
2005 self.failUnless("Objects Unhealthy (before repair): <span>0</span>" in res)
2006 self.failUnless("Corrupt Shares (before repair): <span>0</span>" in res)
2008 self.failUnless("Repairs Attempted: <span>0</span>" in res)
2009 self.failUnless("Repairs Successful: <span>0</span>" in res)
2010 self.failUnless("Repairs Unsuccessful: <span>0</span>" in res)
2012 self.failUnless("Objects Healthy (after repair): <span>8</span>" in res)
2013 self.failUnless("Objects Unhealthy (after repair): <span>0</span>" in res)
2014 self.failUnless("Corrupt Shares (after repair): <span>0</span>" in res)
2015 d.addCallback(_check_html)
2018 def test_POST_FILEURL_bad_t(self):
2019 d = self.shouldFail2(error.Error, "POST_bad_t", "400 Bad Request",
2020 "POST to file: bad t=bogus",
2021 self.POST, self.public_url + "/foo/bar.txt",
2025 def test_POST_mkdir(self): # return value?
2026 d = self.POST(self.public_url + "/foo", t="mkdir", name="newdir")
2027 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2028 d.addCallback(self.failUnlessNodeKeysAre, [])
2031 def test_POST_mkdir_initial_children(self):
2032 (newkids, caps) = self._create_initial_children()
2033 d = self.POST2(self.public_url +
2034 "/foo?t=mkdir-with-children&name=newdir",
2035 simplejson.dumps(newkids))
2036 d.addCallback(lambda res:
2037 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
2038 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2039 d.addCallback(self.failUnlessNodeKeysAre, newkids.keys())
2040 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2041 d.addCallback(self.failUnlessROChildURIIs, u"child-imm", caps['filecap1'])
2044 def test_POST_mkdir_immutable(self):
2045 (newkids, caps) = self._create_immutable_children()
2046 d = self.POST2(self.public_url +
2047 "/foo?t=mkdir-immutable&name=newdir",
2048 simplejson.dumps(newkids))
2049 d.addCallback(lambda res:
2050 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
2051 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2052 d.addCallback(self.failUnlessNodeKeysAre, newkids.keys())
2053 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2054 d.addCallback(self.failUnlessROChildURIIs, u"child-imm", caps['filecap1'])
2055 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2056 d.addCallback(self.failUnlessROChildURIIs, u"unknownchild-imm", caps['unknown_immcap'])
2057 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2058 d.addCallback(self.failUnlessROChildURIIs, u"dirchild-imm", caps['immdircap'])
2061 def test_POST_mkdir_immutable_bad(self):
2062 (newkids, caps) = self._create_initial_children()
2063 d = self.shouldFail2(error.Error, "test_POST_mkdir_immutable_bad",
2065 "needed to be immutable but was not",
2068 "/foo?t=mkdir-immutable&name=newdir",
2069 simplejson.dumps(newkids))
2072 def test_POST_mkdir_2(self):
2073 d = self.POST(self.public_url + "/foo/newdir?t=mkdir", "")
2074 d.addCallback(lambda res:
2075 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
2076 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2077 d.addCallback(self.failUnlessNodeKeysAre, [])
2080 def test_POST_mkdirs_2(self):
2081 d = self.POST(self.public_url + "/foo/bardir/newdir?t=mkdir", "")
2082 d.addCallback(lambda res:
2083 self.failUnlessNodeHasChild(self._foo_node, u"bardir"))
2084 d.addCallback(lambda res: self._foo_node.get(u"bardir"))
2085 d.addCallback(lambda bardirnode: bardirnode.get(u"newdir"))
2086 d.addCallback(self.failUnlessNodeKeysAre, [])
2089 def test_POST_mkdir_no_parentdir_noredirect(self):
2090 d = self.POST("/uri?t=mkdir")
2091 def _after_mkdir(res):
2092 uri.DirectoryURI.init_from_string(res)
2093 d.addCallback(_after_mkdir)
2096 def test_POST_mkdir_no_parentdir_noredirect2(self):
2097 # make sure form-based arguments (as on the welcome page) still work
2098 d = self.POST("/uri", t="mkdir")
2099 def _after_mkdir(res):
2100 uri.DirectoryURI.init_from_string(res)
2101 d.addCallback(_after_mkdir)
2102 d.addErrback(self.explain_web_error)
2105 def test_POST_mkdir_no_parentdir_redirect(self):
2106 d = self.POST("/uri?t=mkdir&redirect_to_result=true")
2107 d.addBoth(self.shouldRedirect, None, statuscode='303')
2108 def _check_target(target):
2109 target = urllib.unquote(target)
2110 self.failUnless(target.startswith("uri/URI:DIR2:"), target)
2111 d.addCallback(_check_target)
2114 def test_POST_mkdir_no_parentdir_redirect2(self):
2115 d = self.POST("/uri", t="mkdir", redirect_to_result="true")
2116 d.addBoth(self.shouldRedirect, None, statuscode='303')
2117 def _check_target(target):
2118 target = urllib.unquote(target)
2119 self.failUnless(target.startswith("uri/URI:DIR2:"), target)
2120 d.addCallback(_check_target)
2121 d.addErrback(self.explain_web_error)
2124 def _make_readonly(self, u):
2125 ro_uri = uri.from_string(u).get_readonly()
2128 return ro_uri.to_string()
2130 def _create_initial_children(self):
2131 contents, n, filecap1 = self.makefile(12)
2132 md1 = {"metakey1": "metavalue1"}
2133 filecap2 = make_mutable_file_uri()
2134 node3 = self.s.create_node_from_uri(make_mutable_file_uri())
2135 filecap3 = node3.get_readonly_uri()
2136 unknown_rwcap = "lafs://from_the_future"
2137 unknown_rocap = "ro.lafs://readonly_from_the_future"
2138 unknown_immcap = "imm.lafs://immutable_from_the_future"
2139 node4 = self.s.create_node_from_uri(make_mutable_file_uri())
2140 dircap = DirectoryNode(node4, None, None).get_uri()
2141 newkids = {u"child-imm": ["filenode", {"rw_uri": filecap1,
2142 "ro_uri": self._make_readonly(filecap1),
2143 "metadata": md1, }],
2144 u"child-mutable": ["filenode", {"rw_uri": filecap2,
2145 "ro_uri": self._make_readonly(filecap2)}],
2146 u"child-mutable-ro": ["filenode", {"ro_uri": filecap3}],
2147 u"unknownchild-rw": ["unknown", {"rw_uri": unknown_rwcap,
2148 "ro_uri": unknown_rocap}],
2149 u"unknownchild-ro": ["unknown", {"ro_uri": unknown_rocap}],
2150 u"unknownchild-imm": ["unknown", {"ro_uri": unknown_immcap}],
2151 u"dirchild": ["dirnode", {"rw_uri": dircap,
2152 "ro_uri": self._make_readonly(dircap)}],
2154 return newkids, {'filecap1': filecap1,
2155 'filecap2': filecap2,
2156 'filecap3': filecap3,
2157 'unknown_rwcap': unknown_rwcap,
2158 'unknown_rocap': unknown_rocap,
2159 'unknown_immcap': unknown_immcap,
2162 def _create_immutable_children(self):
2163 contents, n, filecap1 = self.makefile(12)
2164 md1 = {"metakey1": "metavalue1"}
2165 tnode = create_chk_filenode("immutable directory contents\n"*10)
2166 dnode = DirectoryNode(tnode, None, None)
2167 assert not dnode.is_mutable()
2168 unknown_immcap = "imm.lafs://immutable_from_the_future"
2169 immdircap = dnode.get_uri()
2170 newkids = {u"child-imm": ["filenode", {"ro_uri": filecap1,
2171 "metadata": md1, }],
2172 u"unknownchild-imm": ["unknown", {"ro_uri": unknown_immcap}],
2173 u"dirchild-imm": ["dirnode", {"ro_uri": immdircap}],
2175 return newkids, {'filecap1': filecap1,
2176 'unknown_immcap': unknown_immcap,
2177 'immdircap': immdircap}
2179 def test_POST_mkdir_no_parentdir_initial_children(self):
2180 (newkids, caps) = self._create_initial_children()
2181 d = self.POST2("/uri?t=mkdir-with-children", simplejson.dumps(newkids))
2182 def _after_mkdir(res):
2183 self.failUnless(res.startswith("URI:DIR"), res)
2184 n = self.s.create_node_from_uri(res)
2185 d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
2186 d2.addCallback(lambda ign:
2187 self.failUnlessROChildURIIs(n, u"child-imm",
2189 d2.addCallback(lambda ign:
2190 self.failUnlessRWChildURIIs(n, u"child-mutable",
2192 d2.addCallback(lambda ign:
2193 self.failUnlessROChildURIIs(n, u"child-mutable-ro",
2195 d2.addCallback(lambda ign:
2196 self.failUnlessRWChildURIIs(n, u"unknownchild-rw",
2197 caps['unknown_rwcap']))
2198 d2.addCallback(lambda ign:
2199 self.failUnlessROChildURIIs(n, u"unknownchild-ro",
2200 caps['unknown_rocap']))
2201 d2.addCallback(lambda ign:
2202 self.failUnlessROChildURIIs(n, u"unknownchild-imm",
2203 caps['unknown_immcap']))
2204 d2.addCallback(lambda ign:
2205 self.failUnlessRWChildURIIs(n, u"dirchild",
2208 d.addCallback(_after_mkdir)
2211 def test_POST_mkdir_no_parentdir_unexpected_children(self):
2212 # the regular /uri?t=mkdir operation is specified to ignore its body.
2213 # Only t=mkdir-with-children pays attention to it.
2214 (newkids, caps) = self._create_initial_children()
2215 d = self.shouldHTTPError("POST t=mkdir unexpected children",
2217 "t=mkdir does not accept children=, "
2218 "try t=mkdir-with-children instead",
2219 self.POST2, "/uri?t=mkdir", # without children
2220 simplejson.dumps(newkids))
2223 def test_POST_noparent_bad(self):
2224 d = self.shouldHTTPError("POST /uri?t=bogus", 400, "Bad Request",
2225 "/uri accepts only PUT, PUT?t=mkdir, "
2226 "POST?t=upload, and POST?t=mkdir",
2227 self.POST, "/uri?t=bogus")
2230 def test_POST_mkdir_no_parentdir_immutable(self):
2231 (newkids, caps) = self._create_immutable_children()
2232 d = self.POST2("/uri?t=mkdir-immutable", simplejson.dumps(newkids))
2233 def _after_mkdir(res):
2234 self.failUnless(res.startswith("URI:DIR"), res)
2235 n = self.s.create_node_from_uri(res)
2236 d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
2237 d2.addCallback(lambda ign:
2238 self.failUnlessROChildURIIs(n, u"child-imm",
2240 d2.addCallback(lambda ign:
2241 self.failUnlessROChildURIIs(n, u"unknownchild-imm",
2242 caps['unknown_immcap']))
2243 d2.addCallback(lambda ign:
2244 self.failUnlessROChildURIIs(n, u"dirchild-imm",
2247 d.addCallback(_after_mkdir)
2250 def test_POST_mkdir_no_parentdir_immutable_bad(self):
2251 (newkids, caps) = self._create_initial_children()
2252 d = self.shouldFail2(error.Error,
2253 "test_POST_mkdir_no_parentdir_immutable_bad",
2255 "needed to be immutable but was not",
2257 "/uri?t=mkdir-immutable",
2258 simplejson.dumps(newkids))
2261 def test_welcome_page_mkdir_button(self):
2262 # Fetch the welcome page.
2264 def _after_get_welcome_page(res):
2265 MKDIR_BUTTON_RE = re.compile(
2266 '<form action="([^"]*)" method="post".*?'
2267 '<input type="hidden" name="t" value="([^"]*)" />'
2268 '<input type="hidden" name="([^"]*)" value="([^"]*)" />'
2269 '<input type="submit" value="Create a directory" />',
2271 mo = MKDIR_BUTTON_RE.search(res)
2272 formaction = mo.group(1)
2274 formaname = mo.group(3)
2275 formavalue = mo.group(4)
2276 return (formaction, formt, formaname, formavalue)
2277 d.addCallback(_after_get_welcome_page)
2278 def _after_parse_form(res):
2279 (formaction, formt, formaname, formavalue) = res
2280 return self.POST("/%s?t=%s&%s=%s" % (formaction, formt, formaname, formavalue))
2281 d.addCallback(_after_parse_form)
2282 d.addBoth(self.shouldRedirect, None, statuscode='303')
2285 def test_POST_mkdir_replace(self): # return value?
2286 d = self.POST(self.public_url + "/foo", t="mkdir", name="sub")
2287 d.addCallback(lambda res: self._foo_node.get(u"sub"))
2288 d.addCallback(self.failUnlessNodeKeysAre, [])
2291 def test_POST_mkdir_no_replace_queryarg(self): # return value?
2292 d = self.POST(self.public_url + "/foo?replace=false", t="mkdir", name="sub")
2293 d.addBoth(self.shouldFail, error.Error,
2294 "POST_mkdir_no_replace_queryarg",
2296 "There was already a child by that name, and you asked me "
2297 "to not replace it")
2298 d.addCallback(lambda res: self._foo_node.get(u"sub"))
2299 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
2302 def test_POST_mkdir_no_replace_field(self): # return value?
2303 d = self.POST(self.public_url + "/foo", t="mkdir", name="sub",
2305 d.addBoth(self.shouldFail, error.Error, "POST_mkdir_no_replace_field",
2307 "There was already a child by that name, and you asked me "
2308 "to not replace it")
2309 d.addCallback(lambda res: self._foo_node.get(u"sub"))
2310 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
2313 def test_POST_mkdir_whendone_field(self):
2314 d = self.POST(self.public_url + "/foo",
2315 t="mkdir", name="newdir", when_done="/THERE")
2316 d.addBoth(self.shouldRedirect, "/THERE")
2317 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2318 d.addCallback(self.failUnlessNodeKeysAre, [])
2321 def test_POST_mkdir_whendone_queryarg(self):
2322 d = self.POST(self.public_url + "/foo?when_done=/THERE",
2323 t="mkdir", name="newdir")
2324 d.addBoth(self.shouldRedirect, "/THERE")
2325 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2326 d.addCallback(self.failUnlessNodeKeysAre, [])
2329 def test_POST_bad_t(self):
2330 d = self.shouldFail2(error.Error, "POST_bad_t", "400 Bad Request",
2331 "POST to a directory with bad t=BOGUS",
2332 self.POST, self.public_url + "/foo", t="BOGUS")
2335 def test_POST_set_children(self, command_name="set_children"):
2336 contents9, n9, newuri9 = self.makefile(9)
2337 contents10, n10, newuri10 = self.makefile(10)
2338 contents11, n11, newuri11 = self.makefile(11)
2341 "atomic_added_1": [ "filenode", { "rw_uri": "%s",
2344 "ctime": 1002777696.7564139,
2345 "mtime": 1002777696.7564139
2348 "atomic_added_2": [ "filenode", { "rw_uri": "%s",
2351 "ctime": 1002777696.7564139,
2352 "mtime": 1002777696.7564139
2355 "atomic_added_3": [ "filenode", { "rw_uri": "%s",
2358 "ctime": 1002777696.7564139,
2359 "mtime": 1002777696.7564139
2362 }""" % (newuri9, newuri10, newuri11)
2364 url = self.webish_url + self.public_url + "/foo" + "?t=" + command_name
2366 d = client.getPage(url, method="POST", postdata=reqbody)
2368 self.failUnlessURIMatchesROChild(newuri9, self._foo_node, u"atomic_added_1")
2369 self.failUnlessURIMatchesROChild(newuri10, self._foo_node, u"atomic_added_2")
2370 self.failUnlessURIMatchesROChild(newuri11, self._foo_node, u"atomic_added_3")
2372 d.addCallback(_then)
2373 d.addErrback(self.dump_error)
2376 def test_POST_set_children_with_hyphen(self):
2377 return self.test_POST_set_children(command_name="set-children")
2379 def test_POST_link_uri(self):
2380 contents, n, newuri = self.makefile(8)
2381 d = self.POST(self.public_url + "/foo", t="uri", name="new.txt", uri=newuri)
2382 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"new.txt")
2383 d.addCallback(lambda res:
2384 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
2388 def test_POST_link_uri_replace(self):
2389 contents, n, newuri = self.makefile(8)
2390 d = self.POST(self.public_url + "/foo", t="uri", name="bar.txt", uri=newuri)
2391 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"bar.txt")
2392 d.addCallback(lambda res:
2393 self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
2397 def test_POST_link_uri_unknown_bad(self):
2398 newuri = "lafs://from_the_future"
2399 d = self.POST(self.public_url + "/foo", t="uri", name="future.txt", uri=newuri)
2400 d.addBoth(self.shouldFail, error.Error,
2401 "POST_link_uri_unknown_bad",
2403 "unknown cap in a write slot")
2406 def test_POST_link_uri_unknown_ro_good(self):
2407 newuri = "ro.lafs://readonly_from_the_future"
2408 d = self.POST(self.public_url + "/foo", t="uri", name="future-ro.txt", uri=newuri)
2409 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"future-ro.txt")
2412 def test_POST_link_uri_unknown_imm_good(self):
2413 newuri = "imm.lafs://immutable_from_the_future"
2414 d = self.POST(self.public_url + "/foo", t="uri", name="future-imm.txt", uri=newuri)
2415 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"future-imm.txt")
2418 def test_POST_link_uri_no_replace_queryarg(self):
2419 contents, n, newuri = self.makefile(8)
2420 d = self.POST(self.public_url + "/foo?replace=false", t="uri",
2421 name="bar.txt", uri=newuri)
2422 d.addBoth(self.shouldFail, error.Error,
2423 "POST_link_uri_no_replace_queryarg",
2425 "There was already a child by that name, and you asked me "
2426 "to not replace it")
2427 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
2428 d.addCallback(self.failUnlessIsBarDotTxt)
2431 def test_POST_link_uri_no_replace_field(self):
2432 contents, n, newuri = self.makefile(8)
2433 d = self.POST(self.public_url + "/foo", t="uri", replace="false",
2434 name="bar.txt", uri=newuri)
2435 d.addBoth(self.shouldFail, error.Error,
2436 "POST_link_uri_no_replace_field",
2438 "There was already a child by that name, and you asked me "
2439 "to not replace it")
2440 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
2441 d.addCallback(self.failUnlessIsBarDotTxt)
2444 def test_POST_delete(self):
2445 d = self.POST(self.public_url + "/foo", t="delete", name="bar.txt")
2446 d.addCallback(lambda res: self._foo_node.list())
2447 def _check(children):
2448 self.failIf(u"bar.txt" in children)
2449 d.addCallback(_check)
2452 def test_POST_rename_file(self):
2453 d = self.POST(self.public_url + "/foo", t="rename",
2454 from_name="bar.txt", to_name='wibble.txt')
2455 d.addCallback(lambda res:
2456 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
2457 d.addCallback(lambda res:
2458 self.failUnlessNodeHasChild(self._foo_node, u"wibble.txt"))
2459 d.addCallback(lambda res: self.GET(self.public_url + "/foo/wibble.txt"))
2460 d.addCallback(self.failUnlessIsBarDotTxt)
2461 d.addCallback(lambda res: self.GET(self.public_url + "/foo/wibble.txt?t=json"))
2462 d.addCallback(self.failUnlessIsBarJSON)
2465 def test_POST_rename_file_redundant(self):
2466 d = self.POST(self.public_url + "/foo", t="rename",
2467 from_name="bar.txt", to_name='bar.txt')
2468 d.addCallback(lambda res:
2469 self.failUnlessNodeHasChild(self._foo_node, u"bar.txt"))
2470 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
2471 d.addCallback(self.failUnlessIsBarDotTxt)
2472 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt?t=json"))
2473 d.addCallback(self.failUnlessIsBarJSON)
2476 def test_POST_rename_file_replace(self):
2477 # rename a file and replace a directory with it
2478 d = self.POST(self.public_url + "/foo", t="rename",
2479 from_name="bar.txt", to_name='empty')
2480 d.addCallback(lambda res:
2481 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
2482 d.addCallback(lambda res:
2483 self.failUnlessNodeHasChild(self._foo_node, u"empty"))
2484 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty"))
2485 d.addCallback(self.failUnlessIsBarDotTxt)
2486 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
2487 d.addCallback(self.failUnlessIsBarJSON)
2490 def test_POST_rename_file_no_replace_queryarg(self):
2491 # rename a file and replace a directory with it
2492 d = self.POST(self.public_url + "/foo?replace=false", t="rename",
2493 from_name="bar.txt", to_name='empty')
2494 d.addBoth(self.shouldFail, error.Error,
2495 "POST_rename_file_no_replace_queryarg",
2497 "There was already a child by that name, and you asked me "
2498 "to not replace it")
2499 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
2500 d.addCallback(self.failUnlessIsEmptyJSON)
2503 def test_POST_rename_file_no_replace_field(self):
2504 # rename a file and replace a directory with it
2505 d = self.POST(self.public_url + "/foo", t="rename", replace="false",
2506 from_name="bar.txt", to_name='empty')
2507 d.addBoth(self.shouldFail, error.Error,
2508 "POST_rename_file_no_replace_field",
2510 "There was already a child by that name, and you asked me "
2511 "to not replace it")
2512 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
2513 d.addCallback(self.failUnlessIsEmptyJSON)
2516 def failUnlessIsEmptyJSON(self, res):
2517 data = simplejson.loads(res)
2518 self.failUnlessEqual(data[0], "dirnode", data)
2519 self.failUnlessEqual(len(data[1]["children"]), 0)
2521 def test_POST_rename_file_slash_fail(self):
2522 d = self.POST(self.public_url + "/foo", t="rename",
2523 from_name="bar.txt", to_name='kirk/spock.txt')
2524 d.addBoth(self.shouldFail, error.Error,
2525 "test_POST_rename_file_slash_fail",
2527 "to_name= may not contain a slash",
2529 d.addCallback(lambda res:
2530 self.failUnlessNodeHasChild(self._foo_node, u"bar.txt"))
2533 def test_POST_rename_dir(self):
2534 d = self.POST(self.public_url, t="rename",
2535 from_name="foo", to_name='plunk')
2536 d.addCallback(lambda res:
2537 self.failIfNodeHasChild(self.public_root, u"foo"))
2538 d.addCallback(lambda res:
2539 self.failUnlessNodeHasChild(self.public_root, u"plunk"))
2540 d.addCallback(lambda res: self.GET(self.public_url + "/plunk?t=json"))
2541 d.addCallback(self.failUnlessIsFooJSON)
2544 def shouldRedirect(self, res, target=None, statuscode=None, which=""):
2545 """ If target is not None then the redirection has to go to target. If
2546 statuscode is not None then the redirection has to be accomplished with
2547 that HTTP status code."""
2548 if not isinstance(res, failure.Failure):
2549 to_where = (target is None) and "somewhere" or ("to " + target)
2550 self.fail("%s: we were expecting to get redirected %s, not get an"
2551 " actual page: %s" % (which, to_where, res))
2552 res.trap(error.PageRedirect)
2553 if statuscode is not None:
2554 self.failUnlessEqual(res.value.status, statuscode,
2555 "%s: not a redirect" % which)
2556 if target is not None:
2557 # the PageRedirect does not seem to capture the uri= query arg
2558 # properly, so we can't check for it.
2559 realtarget = self.webish_url + target
2560 self.failUnlessEqual(res.value.location, realtarget,
2561 "%s: wrong target" % which)
2562 return res.value.location
2564 def test_GET_URI_form(self):
2565 base = "/uri?uri=%s" % self._bar_txt_uri
2566 # this is supposed to give us a redirect to /uri/$URI, plus arguments
2567 targetbase = "/uri/%s" % urllib.quote(self._bar_txt_uri)
2569 d.addBoth(self.shouldRedirect, targetbase)
2570 d.addCallback(lambda res: self.GET(base+"&filename=bar.txt"))
2571 d.addBoth(self.shouldRedirect, targetbase+"?filename=bar.txt")
2572 d.addCallback(lambda res: self.GET(base+"&t=json"))
2573 d.addBoth(self.shouldRedirect, targetbase+"?t=json")
2574 d.addCallback(self.log, "about to get file by uri")
2575 d.addCallback(lambda res: self.GET(base, followRedirect=True))
2576 d.addCallback(self.failUnlessIsBarDotTxt)
2577 d.addCallback(self.log, "got file by uri, about to get dir by uri")
2578 d.addCallback(lambda res: self.GET("/uri?uri=%s&t=json" % self._foo_uri,
2579 followRedirect=True))
2580 d.addCallback(self.failUnlessIsFooJSON)
2581 d.addCallback(self.log, "got dir by uri")
2585 def test_GET_URI_form_bad(self):
2586 d = self.shouldFail2(error.Error, "test_GET_URI_form_bad",
2587 "400 Bad Request", "GET /uri requires uri=",
2591 def test_GET_rename_form(self):
2592 d = self.GET(self.public_url + "/foo?t=rename-form&name=bar.txt",
2593 followRedirect=True)
2595 self.failUnless('name="when_done" value="."' in res, res)
2596 self.failUnless(re.search(r'name="from_name" value="bar\.txt"', res))
2597 d.addCallback(_check)
2600 def log(self, res, msg):
2601 #print "MSG: %s RES: %s" % (msg, res)
2605 def test_GET_URI_URL(self):
2606 base = "/uri/%s" % self._bar_txt_uri
2608 d.addCallback(self.failUnlessIsBarDotTxt)
2609 d.addCallback(lambda res: self.GET(base+"?filename=bar.txt"))
2610 d.addCallback(self.failUnlessIsBarDotTxt)
2611 d.addCallback(lambda res: self.GET(base+"?filename=bar.txt&save=true"))
2612 d.addCallback(self.failUnlessIsBarDotTxt)
2615 def test_GET_URI_URL_dir(self):
2616 base = "/uri/%s?t=json" % self._foo_uri
2618 d.addCallback(self.failUnlessIsFooJSON)
2621 def test_GET_URI_URL_missing(self):
2622 base = "/uri/%s" % self._bad_file_uri
2623 d = self.shouldHTTPError("test_GET_URI_URL_missing",
2624 http.GONE, None, "NotEnoughSharesError",
2626 # TODO: how can we exercise both sides of WebDownloadTarget.fail
2627 # here? we must arrange for a download to fail after target.open()
2628 # has been called, and then inspect the response to see that it is
2629 # shorter than we expected.
2632 def test_PUT_DIRURL_uri(self):
2633 d = self.s.create_dirnode()
2635 new_uri = dn.get_uri()
2636 # replace /foo with a new (empty) directory
2637 d = self.PUT(self.public_url + "/foo?t=uri", new_uri)
2638 d.addCallback(lambda res:
2639 self.failUnlessEqual(res.strip(), new_uri))
2640 d.addCallback(lambda res:
2641 self.failUnlessRWChildURIIs(self.public_root,
2645 d.addCallback(_made_dir)
2648 def test_PUT_DIRURL_uri_noreplace(self):
2649 d = self.s.create_dirnode()
2651 new_uri = dn.get_uri()
2652 # replace /foo with a new (empty) directory, but ask that
2653 # replace=false, so it should fail
2654 d = self.shouldFail2(error.Error, "test_PUT_DIRURL_uri_noreplace",
2655 "409 Conflict", "There was already a child by that name, and you asked me to not replace it",
2657 self.public_url + "/foo?t=uri&replace=false",
2659 d.addCallback(lambda res:
2660 self.failUnlessRWChildURIIs(self.public_root,
2664 d.addCallback(_made_dir)
2667 def test_PUT_DIRURL_bad_t(self):
2668 d = self.shouldFail2(error.Error, "test_PUT_DIRURL_bad_t",
2669 "400 Bad Request", "PUT to a directory",
2670 self.PUT, self.public_url + "/foo?t=BOGUS", "")
2671 d.addCallback(lambda res:
2672 self.failUnlessRWChildURIIs(self.public_root,
2677 def test_PUT_NEWFILEURL_uri(self):
2678 contents, n, new_uri = self.makefile(8)
2679 d = self.PUT(self.public_url + "/foo/new.txt?t=uri", new_uri)
2680 d.addCallback(lambda res: self.failUnlessEqual(res.strip(), new_uri))
2681 d.addCallback(lambda res:
2682 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
2686 def test_PUT_NEWFILEURL_uri_replace(self):
2687 contents, n, new_uri = self.makefile(8)
2688 d = self.PUT(self.public_url + "/foo/bar.txt?t=uri", new_uri)
2689 d.addCallback(lambda res: self.failUnlessEqual(res.strip(), new_uri))
2690 d.addCallback(lambda res:
2691 self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
2695 def test_PUT_NEWFILEURL_uri_no_replace(self):
2696 contents, n, new_uri = self.makefile(8)
2697 d = self.PUT(self.public_url + "/foo/bar.txt?t=uri&replace=false", new_uri)
2698 d.addBoth(self.shouldFail, error.Error, "PUT_NEWFILEURL_uri_no_replace",
2700 "There was already a child by that name, and you asked me "
2701 "to not replace it")
2704 def test_PUT_NEWFILEURL_uri_unknown_bad(self):
2705 new_uri = "lafs://from_the_future"
2706 d = self.PUT(self.public_url + "/foo/put-future.txt?t=uri", new_uri)
2707 d.addBoth(self.shouldFail, error.Error,
2708 "POST_put_uri_unknown_bad",
2710 "unknown cap in a write slot")
2713 def test_PUT_NEWFILEURL_uri_unknown_ro_good(self):
2714 new_uri = "ro.lafs://readonly_from_the_future"
2715 d = self.PUT(self.public_url + "/foo/put-future-ro.txt?t=uri", new_uri)
2716 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node,
2717 u"put-future-ro.txt")
2720 def test_PUT_NEWFILEURL_uri_unknown_imm_good(self):
2721 new_uri = "imm.lafs://immutable_from_the_future"
2722 d = self.PUT(self.public_url + "/foo/put-future-imm.txt?t=uri", new_uri)
2723 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node,
2724 u"put-future-imm.txt")
2727 def test_PUT_NEWFILE_URI(self):
2728 file_contents = "New file contents here\n"
2729 d = self.PUT("/uri", file_contents)
2731 assert isinstance(uri, str), uri
2732 self.failUnless(uri in FakeCHKFileNode.all_contents)
2733 self.failUnlessEqual(FakeCHKFileNode.all_contents[uri],
2735 return self.GET("/uri/%s" % uri)
2736 d.addCallback(_check)
2738 self.failUnlessEqual(res, file_contents)
2739 d.addCallback(_check2)
2742 def test_PUT_NEWFILE_URI_not_mutable(self):
2743 file_contents = "New file contents here\n"
2744 d = self.PUT("/uri?mutable=false", file_contents)
2746 assert isinstance(uri, str), uri
2747 self.failUnless(uri in FakeCHKFileNode.all_contents)
2748 self.failUnlessEqual(FakeCHKFileNode.all_contents[uri],
2750 return self.GET("/uri/%s" % uri)
2751 d.addCallback(_check)
2753 self.failUnlessEqual(res, file_contents)
2754 d.addCallback(_check2)
2757 def test_PUT_NEWFILE_URI_only_PUT(self):
2758 d = self.PUT("/uri?t=bogus", "")
2759 d.addBoth(self.shouldFail, error.Error,
2760 "PUT_NEWFILE_URI_only_PUT",
2762 "/uri accepts only PUT, PUT?t=mkdir, POST?t=upload, and POST?t=mkdir")
2765 def test_PUT_NEWFILE_URI_mutable(self):
2766 file_contents = "New file contents here\n"
2767 d = self.PUT("/uri?mutable=true", file_contents)
2768 def _check1(filecap):
2769 filecap = filecap.strip()
2770 self.failUnless(filecap.startswith("URI:SSK:"), filecap)
2771 self.filecap = filecap
2772 u = uri.WriteableSSKFileURI.init_from_string(filecap)
2773 self.failUnless(u.storage_index in FakeMutableFileNode.all_contents)
2774 n = self.s.create_node_from_uri(filecap)
2775 return n.download_best_version()
2776 d.addCallback(_check1)
2778 self.failUnlessEqual(data, file_contents)
2779 return self.GET("/uri/%s" % urllib.quote(self.filecap))
2780 d.addCallback(_check2)
2782 self.failUnlessEqual(res, file_contents)
2783 d.addCallback(_check3)
2786 def test_PUT_mkdir(self):
2787 d = self.PUT("/uri?t=mkdir", "")
2789 n = self.s.create_node_from_uri(uri.strip())
2790 d2 = self.failUnlessNodeKeysAre(n, [])
2791 d2.addCallback(lambda res:
2792 self.GET("/uri/%s?t=json" % uri))
2794 d.addCallback(_check)
2795 d.addCallback(self.failUnlessIsEmptyJSON)
2798 def test_POST_check(self):
2799 d = self.POST(self.public_url + "/foo", t="check", name="bar.txt")
2801 # this returns a string form of the results, which are probably
2802 # None since we're using fake filenodes.
2803 # TODO: verify that the check actually happened, by changing
2804 # FakeCHKFileNode to count how many times .check() has been
2807 d.addCallback(_done)
2810 def test_bad_method(self):
2811 url = self.webish_url + self.public_url + "/foo/bar.txt"
2812 d = self.shouldHTTPError("test_bad_method",
2813 501, "Not Implemented",
2814 "I don't know how to treat a BOGUS request.",
2815 client.getPage, url, method="BOGUS")
2818 def test_short_url(self):
2819 url = self.webish_url + "/uri"
2820 d = self.shouldHTTPError("test_short_url", 501, "Not Implemented",
2821 "I don't know how to treat a DELETE request.",
2822 client.getPage, url, method="DELETE")
2825 def test_ophandle_bad(self):
2826 url = self.webish_url + "/operations/bogus?t=status"
2827 d = self.shouldHTTPError("test_ophandle_bad", 404, "404 Not Found",
2828 "unknown/expired handle 'bogus'",
2829 client.getPage, url)
2832 def test_ophandle_cancel(self):
2833 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=128",
2834 followRedirect=True)
2835 d.addCallback(lambda ignored:
2836 self.GET("/operations/128?t=status&output=JSON"))
2838 data = simplejson.loads(res)
2839 self.failUnless("finished" in data, res)
2840 monitor = self.ws.root.child_operations.handles["128"][0]
2841 d = self.POST("/operations/128?t=cancel&output=JSON")
2843 data = simplejson.loads(res)
2844 self.failUnless("finished" in data, res)
2845 # t=cancel causes the handle to be forgotten
2846 self.failUnless(monitor.is_cancelled())
2847 d.addCallback(_check2)
2849 d.addCallback(_check1)
2850 d.addCallback(lambda ignored:
2851 self.shouldHTTPError("test_ophandle_cancel",
2852 404, "404 Not Found",
2853 "unknown/expired handle '128'",
2855 "/operations/128?t=status&output=JSON"))
2858 def test_ophandle_retainfor(self):
2859 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=129&retain-for=60",
2860 followRedirect=True)
2861 d.addCallback(lambda ignored:
2862 self.GET("/operations/129?t=status&output=JSON&retain-for=0"))
2864 data = simplejson.loads(res)
2865 self.failUnless("finished" in data, res)
2866 d.addCallback(_check1)
2867 # the retain-for=0 will cause the handle to be expired very soon
2868 d.addCallback(self.stall, 2.0)
2869 d.addCallback(lambda ignored:
2870 self.shouldHTTPError("test_ophandle_retainfor",
2871 404, "404 Not Found",
2872 "unknown/expired handle '129'",
2874 "/operations/129?t=status&output=JSON"))
2877 def test_ophandle_release_after_complete(self):
2878 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=130",
2879 followRedirect=True)
2880 d.addCallback(self.wait_for_operation, "130")
2881 d.addCallback(lambda ignored:
2882 self.GET("/operations/130?t=status&output=JSON&release-after-complete=true"))
2883 # the release-after-complete=true will cause the handle to be expired
2884 d.addCallback(lambda ignored:
2885 self.shouldHTTPError("test_ophandle_release_after_complete",
2886 404, "404 Not Found",
2887 "unknown/expired handle '130'",
2889 "/operations/130?t=status&output=JSON"))
2892 def test_incident(self):
2893 d = self.POST("/report_incident", details="eek")
2895 self.failUnless("Thank you for your report!" in res, res)
2896 d.addCallback(_done)
2899 def test_static(self):
2900 webdir = os.path.join(self.staticdir, "subdir")
2901 fileutil.make_dirs(webdir)
2902 f = open(os.path.join(webdir, "hello.txt"), "wb")
2906 d = self.GET("/static/subdir/hello.txt")
2908 self.failUnlessEqual(res, "hello")
2909 d.addCallback(_check)
2913 class Util(unittest.TestCase, ShouldFailMixin):
2914 def test_parse_replace_arg(self):
2915 self.failUnlessEqual(common.parse_replace_arg("true"), True)
2916 self.failUnlessEqual(common.parse_replace_arg("false"), False)
2917 self.failUnlessEqual(common.parse_replace_arg("only-files"),
2919 self.shouldFail(AssertionError, "test_parse_replace_arg", "",
2920 common.parse_replace_arg, "only_fles")
2922 def test_abbreviate_time(self):
2923 self.failUnlessEqual(common.abbreviate_time(None), "")
2924 self.failUnlessEqual(common.abbreviate_time(1.234), "1.23s")
2925 self.failUnlessEqual(common.abbreviate_time(0.123), "123ms")
2926 self.failUnlessEqual(common.abbreviate_time(0.00123), "1.2ms")
2927 self.failUnlessEqual(common.abbreviate_time(0.000123), "123us")
2929 def test_abbreviate_rate(self):
2930 self.failUnlessEqual(common.abbreviate_rate(None), "")
2931 self.failUnlessEqual(common.abbreviate_rate(1234000), "1.23MBps")
2932 self.failUnlessEqual(common.abbreviate_rate(12340), "12.3kBps")
2933 self.failUnlessEqual(common.abbreviate_rate(123), "123Bps")
2935 def test_abbreviate_size(self):
2936 self.failUnlessEqual(common.abbreviate_size(None), "")
2937 self.failUnlessEqual(common.abbreviate_size(1.23*1000*1000*1000), "1.23GB")
2938 self.failUnlessEqual(common.abbreviate_size(1.23*1000*1000), "1.23MB")
2939 self.failUnlessEqual(common.abbreviate_size(1230), "1.2kB")
2940 self.failUnlessEqual(common.abbreviate_size(123), "123B")
2942 def test_plural(self):
2944 return "%d second%s" % (s, status.plural(s))
2945 self.failUnlessEqual(convert(0), "0 seconds")
2946 self.failUnlessEqual(convert(1), "1 second")
2947 self.failUnlessEqual(convert(2), "2 seconds")
2949 return "has share%s: %s" % (status.plural(s), ",".join(s))
2950 self.failUnlessEqual(convert2([]), "has shares: ")
2951 self.failUnlessEqual(convert2(["1"]), "has share: 1")
2952 self.failUnlessEqual(convert2(["1","2"]), "has shares: 1,2")
2955 class Grid(GridTestMixin, WebErrorMixin, unittest.TestCase, ShouldFailMixin):
2957 def CHECK(self, ign, which, args, clientnum=0):
2958 fileurl = self.fileurls[which]
2959 url = fileurl + "?" + args
2960 return self.GET(url, method="POST", clientnum=clientnum)
2962 def test_filecheck(self):
2963 self.basedir = "web/Grid/filecheck"
2965 c0 = self.g.clients[0]
2968 d = c0.upload(upload.Data(DATA, convergence=""))
2969 def _stash_uri(ur, which):
2970 self.uris[which] = ur.uri
2971 d.addCallback(_stash_uri, "good")
2972 d.addCallback(lambda ign:
2973 c0.upload(upload.Data(DATA+"1", convergence="")))
2974 d.addCallback(_stash_uri, "sick")
2975 d.addCallback(lambda ign:
2976 c0.upload(upload.Data(DATA+"2", convergence="")))
2977 d.addCallback(_stash_uri, "dead")
2978 def _stash_mutable_uri(n, which):
2979 self.uris[which] = n.get_uri()
2980 assert isinstance(self.uris[which], str)
2981 d.addCallback(lambda ign: c0.create_mutable_file(DATA+"3"))
2982 d.addCallback(_stash_mutable_uri, "corrupt")
2983 d.addCallback(lambda ign:
2984 c0.upload(upload.Data("literal", convergence="")))
2985 d.addCallback(_stash_uri, "small")
2986 d.addCallback(lambda ign: c0.create_immutable_dirnode({}))
2987 d.addCallback(_stash_mutable_uri, "smalldir")
2989 def _compute_fileurls(ignored):
2991 for which in self.uris:
2992 self.fileurls[which] = "uri/" + urllib.quote(self.uris[which])
2993 d.addCallback(_compute_fileurls)
2995 def _clobber_shares(ignored):
2996 good_shares = self.find_shares(self.uris["good"])
2997 self.failUnlessEqual(len(good_shares), 10)
2998 sick_shares = self.find_shares(self.uris["sick"])
2999 os.unlink(sick_shares[0][2])
3000 dead_shares = self.find_shares(self.uris["dead"])
3001 for i in range(1, 10):
3002 os.unlink(dead_shares[i][2])
3003 c_shares = self.find_shares(self.uris["corrupt"])
3004 cso = CorruptShareOptions()
3005 cso.stdout = StringIO()
3006 cso.parseOptions([c_shares[0][2]])
3008 d.addCallback(_clobber_shares)
3010 d.addCallback(self.CHECK, "good", "t=check")
3011 def _got_html_good(res):
3012 self.failUnless("Healthy" in res, res)
3013 self.failIf("Not Healthy" in res, res)
3014 d.addCallback(_got_html_good)
3015 d.addCallback(self.CHECK, "good", "t=check&return_to=somewhere")
3016 def _got_html_good_return_to(res):
3017 self.failUnless("Healthy" in res, res)
3018 self.failIf("Not Healthy" in res, res)
3019 self.failUnless('<a href="somewhere">Return to file'
3021 d.addCallback(_got_html_good_return_to)
3022 d.addCallback(self.CHECK, "good", "t=check&output=json")
3023 def _got_json_good(res):
3024 r = simplejson.loads(res)
3025 self.failUnlessEqual(r["summary"], "Healthy")
3026 self.failUnless(r["results"]["healthy"])
3027 self.failIf(r["results"]["needs-rebalancing"])
3028 self.failUnless(r["results"]["recoverable"])
3029 d.addCallback(_got_json_good)
3031 d.addCallback(self.CHECK, "small", "t=check")
3032 def _got_html_small(res):
3033 self.failUnless("Literal files are always healthy" in res, res)
3034 self.failIf("Not Healthy" in res, res)
3035 d.addCallback(_got_html_small)
3036 d.addCallback(self.CHECK, "small", "t=check&return_to=somewhere")
3037 def _got_html_small_return_to(res):
3038 self.failUnless("Literal files are always healthy" in res, res)
3039 self.failIf("Not Healthy" in res, res)
3040 self.failUnless('<a href="somewhere">Return to file'
3042 d.addCallback(_got_html_small_return_to)
3043 d.addCallback(self.CHECK, "small", "t=check&output=json")
3044 def _got_json_small(res):
3045 r = simplejson.loads(res)
3046 self.failUnlessEqual(r["storage-index"], "")
3047 self.failUnless(r["results"]["healthy"])
3048 d.addCallback(_got_json_small)
3050 d.addCallback(self.CHECK, "smalldir", "t=check")
3051 def _got_html_smalldir(res):
3052 self.failUnless("Literal files are always healthy" in res, res)
3053 self.failIf("Not Healthy" in res, res)
3054 d.addCallback(_got_html_smalldir)
3055 d.addCallback(self.CHECK, "smalldir", "t=check&output=json")
3056 def _got_json_smalldir(res):
3057 r = simplejson.loads(res)
3058 self.failUnlessEqual(r["storage-index"], "")
3059 self.failUnless(r["results"]["healthy"])
3060 d.addCallback(_got_json_smalldir)
3062 d.addCallback(self.CHECK, "sick", "t=check")
3063 def _got_html_sick(res):
3064 self.failUnless("Not Healthy" in res, res)
3065 d.addCallback(_got_html_sick)
3066 d.addCallback(self.CHECK, "sick", "t=check&output=json")
3067 def _got_json_sick(res):
3068 r = simplejson.loads(res)
3069 self.failUnlessEqual(r["summary"],
3070 "Not Healthy: 9 shares (enc 3-of-10)")
3071 self.failIf(r["results"]["healthy"])
3072 self.failIf(r["results"]["needs-rebalancing"])
3073 self.failUnless(r["results"]["recoverable"])
3074 d.addCallback(_got_json_sick)
3076 d.addCallback(self.CHECK, "dead", "t=check")
3077 def _got_html_dead(res):
3078 self.failUnless("Not Healthy" in res, res)
3079 d.addCallback(_got_html_dead)
3080 d.addCallback(self.CHECK, "dead", "t=check&output=json")
3081 def _got_json_dead(res):
3082 r = simplejson.loads(res)
3083 self.failUnlessEqual(r["summary"],
3084 "Not Healthy: 1 shares (enc 3-of-10)")
3085 self.failIf(r["results"]["healthy"])
3086 self.failIf(r["results"]["needs-rebalancing"])
3087 self.failIf(r["results"]["recoverable"])
3088 d.addCallback(_got_json_dead)
3090 d.addCallback(self.CHECK, "corrupt", "t=check&verify=true")
3091 def _got_html_corrupt(res):
3092 self.failUnless("Not Healthy! : Unhealthy" in res, res)
3093 d.addCallback(_got_html_corrupt)
3094 d.addCallback(self.CHECK, "corrupt", "t=check&verify=true&output=json")
3095 def _got_json_corrupt(res):
3096 r = simplejson.loads(res)
3097 self.failUnless("Unhealthy: 9 shares (enc 3-of-10)" in r["summary"],
3099 self.failIf(r["results"]["healthy"])
3100 self.failUnless(r["results"]["recoverable"])
3101 self.failUnlessEqual(r["results"]["count-shares-good"], 9)
3102 self.failUnlessEqual(r["results"]["count-corrupt-shares"], 1)
3103 d.addCallback(_got_json_corrupt)
3105 d.addErrback(self.explain_web_error)
3108 def test_repair_html(self):
3109 self.basedir = "web/Grid/repair_html"
3111 c0 = self.g.clients[0]
3114 d = c0.upload(upload.Data(DATA, convergence=""))
3115 def _stash_uri(ur, which):
3116 self.uris[which] = ur.uri
3117 d.addCallback(_stash_uri, "good")
3118 d.addCallback(lambda ign:
3119 c0.upload(upload.Data(DATA+"1", convergence="")))
3120 d.addCallback(_stash_uri, "sick")
3121 d.addCallback(lambda ign:
3122 c0.upload(upload.Data(DATA+"2", convergence="")))
3123 d.addCallback(_stash_uri, "dead")
3124 def _stash_mutable_uri(n, which):
3125 self.uris[which] = n.get_uri()
3126 assert isinstance(self.uris[which], str)
3127 d.addCallback(lambda ign: c0.create_mutable_file(DATA+"3"))
3128 d.addCallback(_stash_mutable_uri, "corrupt")
3130 def _compute_fileurls(ignored):
3132 for which in self.uris:
3133 self.fileurls[which] = "uri/" + urllib.quote(self.uris[which])
3134 d.addCallback(_compute_fileurls)
3136 def _clobber_shares(ignored):
3137 good_shares = self.find_shares(self.uris["good"])
3138 self.failUnlessEqual(len(good_shares), 10)
3139 sick_shares = self.find_shares(self.uris["sick"])
3140 os.unlink(sick_shares[0][2])
3141 dead_shares = self.find_shares(self.uris["dead"])
3142 for i in range(1, 10):
3143 os.unlink(dead_shares[i][2])
3144 c_shares = self.find_shares(self.uris["corrupt"])
3145 cso = CorruptShareOptions()
3146 cso.stdout = StringIO()
3147 cso.parseOptions([c_shares[0][2]])
3149 d.addCallback(_clobber_shares)
3151 d.addCallback(self.CHECK, "good", "t=check&repair=true")
3152 def _got_html_good(res):
3153 self.failUnless("Healthy" in res, res)
3154 self.failIf("Not Healthy" in res, res)
3155 self.failUnless("No repair necessary" in res, res)
3156 d.addCallback(_got_html_good)
3158 d.addCallback(self.CHECK, "sick", "t=check&repair=true")
3159 def _got_html_sick(res):
3160 self.failUnless("Healthy : healthy" in res, res)
3161 self.failIf("Not Healthy" in res, res)
3162 self.failUnless("Repair successful" in res, res)
3163 d.addCallback(_got_html_sick)
3165 # repair of a dead file will fail, of course, but it isn't yet
3166 # clear how this should be reported. Right now it shows up as
3169 #d.addCallback(self.CHECK, "dead", "t=check&repair=true")
3170 #def _got_html_dead(res):
3172 # self.failUnless("Healthy : healthy" in res, res)
3173 # self.failIf("Not Healthy" in res, res)
3174 # self.failUnless("No repair necessary" in res, res)
3175 #d.addCallback(_got_html_dead)
3177 d.addCallback(self.CHECK, "corrupt", "t=check&verify=true&repair=true")
3178 def _got_html_corrupt(res):
3179 self.failUnless("Healthy : Healthy" in res, res)
3180 self.failIf("Not Healthy" in res, res)
3181 self.failUnless("Repair successful" in res, res)
3182 d.addCallback(_got_html_corrupt)
3184 d.addErrback(self.explain_web_error)
3187 def test_repair_json(self):
3188 self.basedir = "web/Grid/repair_json"
3190 c0 = self.g.clients[0]
3193 d = c0.upload(upload.Data(DATA+"1", convergence=""))
3194 def _stash_uri(ur, which):
3195 self.uris[which] = ur.uri
3196 d.addCallback(_stash_uri, "sick")
3198 def _compute_fileurls(ignored):
3200 for which in self.uris:
3201 self.fileurls[which] = "uri/" + urllib.quote(self.uris[which])
3202 d.addCallback(_compute_fileurls)
3204 def _clobber_shares(ignored):
3205 sick_shares = self.find_shares(self.uris["sick"])
3206 os.unlink(sick_shares[0][2])
3207 d.addCallback(_clobber_shares)
3209 d.addCallback(self.CHECK, "sick", "t=check&repair=true&output=json")
3210 def _got_json_sick(res):
3211 r = simplejson.loads(res)
3212 self.failUnlessEqual(r["repair-attempted"], True)
3213 self.failUnlessEqual(r["repair-successful"], True)
3214 self.failUnlessEqual(r["pre-repair-results"]["summary"],
3215 "Not Healthy: 9 shares (enc 3-of-10)")
3216 self.failIf(r["pre-repair-results"]["results"]["healthy"])
3217 self.failUnlessEqual(r["post-repair-results"]["summary"], "healthy")
3218 self.failUnless(r["post-repair-results"]["results"]["healthy"])
3219 d.addCallback(_got_json_sick)
3221 d.addErrback(self.explain_web_error)
3224 def test_unknown(self, immutable=False):
3225 self.basedir = "web/Grid/unknown"
3227 self.basedir = "web/Grid/unknown-immutable"
3230 c0 = self.g.clients[0]
3234 future_write_uri = "x-tahoe-crazy://I_am_from_the_future."
3235 future_read_uri = "x-tahoe-crazy-readonly://I_am_from_the_future."
3236 # the future cap format may contain slashes, which must be tolerated
3237 expected_info_url = "uri/%s?t=info" % urllib.quote(future_write_uri,
3241 name = u"future-imm"
3242 future_node = UnknownNode(None, future_read_uri, deep_immutable=True)
3243 d = c0.create_immutable_dirnode({name: (future_node, {})})
3246 future_node = UnknownNode(future_write_uri, future_read_uri)
3247 d = c0.create_dirnode()
3249 def _stash_root_and_create_file(n):
3251 self.rooturl = "uri/" + urllib.quote(n.get_uri()) + "/"
3252 self.rourl = "uri/" + urllib.quote(n.get_readonly_uri()) + "/"
3254 return self.rootnode.set_node(name, future_node)
3255 d.addCallback(_stash_root_and_create_file)
3257 # make sure directory listing tolerates unknown nodes
3258 d.addCallback(lambda ign: self.GET(self.rooturl))
3259 def _check_directory_html(res, expected_type_suffix):
3260 pattern = re.compile(r'<td>\?%s</td>[ \t\n\r]*'
3261 '<td>%s</td>' % (expected_type_suffix, str(name)),
3263 self.failUnless(re.search(pattern, res), res)
3264 # find the More Info link for name, should be relative
3265 mo = re.search(r'<a href="([^"]+)">More Info</a>', res)
3266 info_url = mo.group(1)
3267 self.failUnlessEqual(info_url, "%s?t=info" % (str(name),))
3269 d.addCallback(_check_directory_html, "-IMM")
3271 d.addCallback(_check_directory_html, "")
3273 d.addCallback(lambda ign: self.GET(self.rooturl+"?t=json"))
3274 def _check_directory_json(res, expect_rw_uri):
3275 data = simplejson.loads(res)
3276 self.failUnlessEqual(data[0], "dirnode")
3277 f = data[1]["children"][name]
3278 self.failUnlessEqual(f[0], "unknown")
3280 self.failUnlessEqual(f[1]["rw_uri"], future_write_uri)
3282 self.failIfIn("rw_uri", f[1])
3284 self.failUnlessEqual(f[1]["ro_uri"], "imm." + future_read_uri)
3286 self.failUnlessEqual(f[1]["ro_uri"], "ro." + future_read_uri)
3287 self.failUnless("metadata" in f[1])
3288 d.addCallback(_check_directory_json, expect_rw_uri=not immutable)
3290 def _check_info(res, expect_rw_uri, expect_ro_uri):
3291 self.failUnlessIn("Object Type: <span>unknown</span>", res)
3293 self.failUnlessIn(future_write_uri, res)
3295 self.failUnlessIn(future_read_uri, res)
3297 self.failIfIn(future_read_uri, res)
3298 self.failIfIn("Raw data as", res)
3299 self.failIfIn("Directory writecap", res)
3300 self.failIfIn("Checker Operations", res)
3301 self.failIfIn("Mutable File Operations", res)
3302 self.failIfIn("Directory Operations", res)
3304 # FIXME: these should have expect_rw_uri=not immutable; I don't know
3305 # why they fail. Possibly related to ticket #922.
3307 d.addCallback(lambda ign: self.GET(expected_info_url))
3308 d.addCallback(_check_info, expect_rw_uri=False, expect_ro_uri=False)
3309 d.addCallback(lambda ign: self.GET("%s%s?t=info" % (self.rooturl, str(name))))
3310 d.addCallback(_check_info, expect_rw_uri=False, expect_ro_uri=True)
3312 def _check_json(res, expect_rw_uri):
3313 data = simplejson.loads(res)
3314 self.failUnlessEqual(data[0], "unknown")
3316 self.failUnlessEqual(data[1]["rw_uri"], future_write_uri)
3318 self.failIfIn("rw_uri", data[1])
3321 self.failUnlessEqual(data[1]["ro_uri"], "imm." + future_read_uri)
3322 self.failUnlessEqual(data[1]["mutable"], False)
3324 self.failUnlessEqual(data[1]["ro_uri"], "ro." + future_read_uri)
3325 self.failUnlessEqual(data[1]["mutable"], True)
3327 self.failUnlessEqual(data[1]["ro_uri"], "ro." + future_read_uri)
3328 self.failIf("mutable" in data[1], data[1])
3330 # TODO: check metadata contents
3331 self.failUnless("metadata" in data[1])
3333 d.addCallback(lambda ign: self.GET("%s%s?t=json" % (self.rooturl, str(name))))
3334 d.addCallback(_check_json, expect_rw_uri=not immutable)
3336 # and make sure that a read-only version of the directory can be
3337 # rendered too. This version will not have future_write_uri, whether
3338 # or not future_node was immutable.
3339 d.addCallback(lambda ign: self.GET(self.rourl))
3341 d.addCallback(_check_directory_html, "-IMM")
3343 d.addCallback(_check_directory_html, "-RO")
3345 d.addCallback(lambda ign: self.GET(self.rourl+"?t=json"))
3346 d.addCallback(_check_directory_json, expect_rw_uri=False)
3348 d.addCallback(lambda ign: self.GET("%s%s?t=json" % (self.rourl, str(name))))
3349 d.addCallback(_check_json, expect_rw_uri=False)
3351 # TODO: check that getting t=info from the Info link in the ro directory
3352 # works, and does not include the writecap URI.
3355 def test_immutable_unknown(self):
3356 return self.test_unknown(immutable=True)
3358 def test_mutant_dirnodes_are_omitted(self):
3359 self.basedir = "web/Grid/mutant_dirnodes_are_omitted"
3362 c = self.g.clients[0]
3367 lonely_uri = "URI:LIT:n5xgk" # LIT for "one"
3368 mut_write_uri = "URI:SSK:vfvcbdfbszyrsaxchgevhmmlii:euw4iw7bbnkrrwpzuburbhppuxhc3gwxv26f6imekhz7zyw2ojnq"
3369 mut_read_uri = "URI:SSK-RO:e3mdrzfwhoq42hy5ubcz6rp3o4:ybyibhnp3vvwuq2vaw2ckjmesgkklfs6ghxleztqidihjyofgw7q"
3371 # This method tests mainly dirnode, but we'd have to duplicate code in order to
3372 # test the dirnode and web layers separately.
3374 # 'lonely' is a valid LIT child, 'ro' is a mutant child with an SSK-RO readcap,
3375 # and 'write-in-ro' is a mutant child with an SSK writecap in the ro_uri field.
3376 # When the directory is read, the mutants should be silently disposed of, leaving
3377 # their lonely sibling.
3378 # We don't test the case of a retrieving a cap from the encrypted rw_uri field,
3379 # because immutable directories don't have a writecap and therefore that field
3380 # isn't (and can't be) decrypted.
3381 # TODO: The field still exists in the netstring. Technically we should check what
3382 # happens if something is put there (_unpack_contents should raise ValueError),
3383 # but that can wait.
3385 lonely_child = nm.create_from_cap(lonely_uri)
3386 mutant_ro_child = nm.create_from_cap(mut_read_uri)
3387 mutant_write_in_ro_child = nm.create_from_cap(mut_write_uri)
3389 def _by_hook_or_by_crook():
3391 for n in [mutant_ro_child, mutant_write_in_ro_child]:
3392 n.is_allowed_in_immutable_directory = _by_hook_or_by_crook
3394 mutant_write_in_ro_child.get_write_uri = lambda: None
3395 mutant_write_in_ro_child.get_readonly_uri = lambda: mut_write_uri
3397 kids = {u"lonely": (lonely_child, {}),
3398 u"ro": (mutant_ro_child, {}),
3399 u"write-in-ro": (mutant_write_in_ro_child, {}),
3401 d = c.create_immutable_dirnode(kids)
3404 self.failUnless(isinstance(dn, dirnode.DirectoryNode))
3405 self.failIf(dn.is_mutable())
3406 self.failUnless(dn.is_readonly())
3407 # This checks that if we somehow ended up calling dn._decrypt_rwcapdata, it would fail.
3408 self.failIf(hasattr(dn._node, 'get_writekey'))
3410 self.failUnless("RO-IMM" in rep)
3412 self.failUnlessIn("CHK", cap.to_string())
3415 self.rooturl = "uri/" + urllib.quote(dn.get_uri()) + "/"
3416 return download_to_data(dn._node)
3417 d.addCallback(_created)
3419 def _check_data(data):
3420 # Decode the netstring representation of the directory to check that all children
3421 # are present. This is a bit of an abstraction violation, but there's not really
3422 # any other way to do it given that the real DirectoryNode._unpack_contents would
3423 # strip the mutant children out (which is what we're trying to test, later).
3426 while position < len(data):
3427 entries, position = split_netstring(data, 1, position)
3429 (name_utf8, ro_uri, rwcapdata, metadata_s), subpos = split_netstring(entry, 4)
3430 name = name_utf8.decode("utf-8")
3431 self.failUnless(rwcapdata == "")
3432 self.failUnless(name in kids)
3433 (expected_child, ign) = kids[name]
3434 self.failUnlessEqual(ro_uri, expected_child.get_readonly_uri())
3437 self.failUnlessEqual(numkids, 3)
3438 return self.rootnode.list()
3439 d.addCallback(_check_data)
3441 # Now when we use the real directory listing code, the mutants should be absent.
3442 def _check_kids(children):
3443 self.failUnlessEqual(sorted(children.keys()), [u"lonely"])
3444 lonely_node, lonely_metadata = children[u"lonely"]
3446 self.failUnlessEqual(lonely_node.get_write_uri(), None)
3447 self.failUnlessEqual(lonely_node.get_readonly_uri(), lonely_uri)
3448 d.addCallback(_check_kids)
3450 d.addCallback(lambda ign: nm.create_from_cap(self.cap.to_string()))
3451 d.addCallback(lambda n: n.list())
3452 d.addCallback(_check_kids) # again with dirnode recreated from cap
3454 # Make sure the lonely child can be listed in HTML...
3455 d.addCallback(lambda ign: self.GET(self.rooturl))
3456 def _check_html(res):
3457 self.failIfIn("URI:SSK", res)
3458 get_lonely = "".join([r'<td>FILE</td>',
3460 r'<a href="[^"]+%s[^"]+">lonely</a>' % (urllib.quote(lonely_uri),),
3462 r'\s+<td>%d</td>' % len("one"),
3464 self.failUnless(re.search(get_lonely, res), res)
3466 # find the More Info link for name, should be relative
3467 mo = re.search(r'<a href="([^"]+)">More Info</a>', res)
3468 info_url = mo.group(1)
3469 self.failUnless(info_url.endswith(urllib.quote(lonely_uri) + "?t=info"), info_url)
3470 d.addCallback(_check_html)
3473 d.addCallback(lambda ign: self.GET(self.rooturl+"?t=json"))
3474 def _check_json(res):
3475 data = simplejson.loads(res)
3476 self.failUnlessEqual(data[0], "dirnode")
3477 listed_children = data[1]["children"]
3478 self.failUnlessEqual(sorted(listed_children.keys()), [u"lonely"])
3479 ll_type, ll_data = listed_children[u"lonely"]
3480 self.failUnlessEqual(ll_type, "filenode")
3481 self.failIf("rw_uri" in ll_data)
3482 self.failUnlessEqual(ll_data["ro_uri"], lonely_uri)
3483 d.addCallback(_check_json)
3486 def test_deep_check(self):
3487 self.basedir = "web/Grid/deep_check"
3489 c0 = self.g.clients[0]
3493 d = c0.create_dirnode()
3494 def _stash_root_and_create_file(n):
3496 self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri()) + "/"
3497 return n.add_file(u"good", upload.Data(DATA, convergence=""))
3498 d.addCallback(_stash_root_and_create_file)
3499 def _stash_uri(fn, which):
3500 self.uris[which] = fn.get_uri()
3502 d.addCallback(_stash_uri, "good")
3503 d.addCallback(lambda ign:
3504 self.rootnode.add_file(u"small",
3505 upload.Data("literal",
3507 d.addCallback(_stash_uri, "small")
3508 d.addCallback(lambda ign:
3509 self.rootnode.add_file(u"sick",
3510 upload.Data(DATA+"1",
3512 d.addCallback(_stash_uri, "sick")
3514 # this tests that deep-check and stream-manifest will ignore
3515 # UnknownNode instances. Hopefully this will also cover deep-stats.
3516 future_write_uri = "x-tahoe-crazy://I_am_from_the_future."
3517 future_read_uri = "x-tahoe-crazy-readonly://I_am_from_the_future."
3518 future_node = UnknownNode(future_write_uri, future_read_uri)
3519 d.addCallback(lambda ign: self.rootnode.set_node(u"future", future_node))
3521 def _clobber_shares(ignored):
3522 self.delete_shares_numbered(self.uris["sick"], [0,1])
3523 d.addCallback(_clobber_shares)
3531 d.addCallback(self.CHECK, "root", "t=stream-deep-check")
3534 units = [simplejson.loads(line)
3535 for line in res.splitlines()
3538 print "response is:", res
3539 print "undecodeable line was '%s'" % line
3541 self.failUnlessEqual(len(units), 5+1)
3542 # should be parent-first
3544 self.failUnlessEqual(u0["path"], [])
3545 self.failUnlessEqual(u0["type"], "directory")
3546 self.failUnlessEqual(u0["cap"], self.rootnode.get_uri())
3547 u0cr = u0["check-results"]
3548 self.failUnlessEqual(u0cr["results"]["count-shares-good"], 10)
3550 ugood = [u for u in units
3551 if u["type"] == "file" and u["path"] == [u"good"]][0]
3552 self.failUnlessEqual(ugood["cap"], self.uris["good"])
3553 ugoodcr = ugood["check-results"]
3554 self.failUnlessEqual(ugoodcr["results"]["count-shares-good"], 10)
3557 self.failUnlessEqual(stats["type"], "stats")
3559 self.failUnlessEqual(s["count-immutable-files"], 2)
3560 self.failUnlessEqual(s["count-literal-files"], 1)
3561 self.failUnlessEqual(s["count-directories"], 1)
3562 self.failUnlessEqual(s["count-unknown"], 1)
3563 d.addCallback(_done)
3565 d.addCallback(self.CHECK, "root", "t=stream-manifest")
3566 def _check_manifest(res):
3567 self.failUnless(res.endswith("\n"))
3568 units = [simplejson.loads(t) for t in res[:-1].split("\n")]
3569 self.failUnlessEqual(len(units), 5+1)
3570 self.failUnlessEqual(units[-1]["type"], "stats")
3572 self.failUnlessEqual(first["path"], [])
3573 self.failUnlessEqual(first["cap"], self.rootnode.get_uri())
3574 self.failUnlessEqual(first["type"], "directory")
3575 stats = units[-1]["stats"]
3576 self.failUnlessEqual(stats["count-immutable-files"], 2)
3577 self.failUnlessEqual(stats["count-literal-files"], 1)
3578 self.failUnlessEqual(stats["count-mutable-files"], 0)
3579 self.failUnlessEqual(stats["count-immutable-files"], 2)
3580 self.failUnlessEqual(stats["count-unknown"], 1)
3581 d.addCallback(_check_manifest)
3583 # now add root/subdir and root/subdir/grandchild, then make subdir
3584 # unrecoverable, then see what happens
3586 d.addCallback(lambda ign:
3587 self.rootnode.create_subdirectory(u"subdir"))
3588 d.addCallback(_stash_uri, "subdir")
3589 d.addCallback(lambda subdir_node:
3590 subdir_node.add_file(u"grandchild",
3591 upload.Data(DATA+"2",
3593 d.addCallback(_stash_uri, "grandchild")
3595 d.addCallback(lambda ign:
3596 self.delete_shares_numbered(self.uris["subdir"],
3604 # root/subdir [unrecoverable]
3605 # root/subdir/grandchild
3607 # how should a streaming-JSON API indicate fatal error?
3608 # answer: emit ERROR: instead of a JSON string
3610 d.addCallback(self.CHECK, "root", "t=stream-manifest")
3611 def _check_broken_manifest(res):
3612 lines = res.splitlines()
3614 for (i,line) in enumerate(lines)
3615 if line.startswith("ERROR:")]
3617 self.fail("no ERROR: in output: %s" % (res,))
3618 first_error = error_lines[0]
3619 error_line = lines[first_error]
3620 error_msg = lines[first_error+1:]
3621 error_msg_s = "\n".join(error_msg) + "\n"
3622 self.failUnlessIn("ERROR: UnrecoverableFileError(no recoverable versions)",
3624 self.failUnless(len(error_msg) > 2, error_msg_s) # some traceback
3625 units = [simplejson.loads(line) for line in lines[:first_error]]
3626 self.failUnlessEqual(len(units), 6) # includes subdir
3627 last_unit = units[-1]
3628 self.failUnlessEqual(last_unit["path"], ["subdir"])
3629 d.addCallback(_check_broken_manifest)
3631 d.addCallback(self.CHECK, "root", "t=stream-deep-check")
3632 def _check_broken_deepcheck(res):
3633 lines = res.splitlines()
3635 for (i,line) in enumerate(lines)
3636 if line.startswith("ERROR:")]
3638 self.fail("no ERROR: in output: %s" % (res,))
3639 first_error = error_lines[0]
3640 error_line = lines[first_error]
3641 error_msg = lines[first_error+1:]
3642 error_msg_s = "\n".join(error_msg) + "\n"
3643 self.failUnlessIn("ERROR: UnrecoverableFileError(no recoverable versions)",
3645 self.failUnless(len(error_msg) > 2, error_msg_s) # some traceback
3646 units = [simplejson.loads(line) for line in lines[:first_error]]
3647 self.failUnlessEqual(len(units), 6) # includes subdir
3648 last_unit = units[-1]
3649 self.failUnlessEqual(last_unit["path"], ["subdir"])
3650 r = last_unit["check-results"]["results"]
3651 self.failUnlessEqual(r["count-recoverable-versions"], 0)
3652 self.failUnlessEqual(r["count-shares-good"], 1)
3653 self.failUnlessEqual(r["recoverable"], False)
3654 d.addCallback(_check_broken_deepcheck)
3656 d.addErrback(self.explain_web_error)
3659 def test_deep_check_and_repair(self):
3660 self.basedir = "web/Grid/deep_check_and_repair"
3662 c0 = self.g.clients[0]
3666 d = c0.create_dirnode()
3667 def _stash_root_and_create_file(n):
3669 self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri()) + "/"
3670 return n.add_file(u"good", upload.Data(DATA, convergence=""))
3671 d.addCallback(_stash_root_and_create_file)
3672 def _stash_uri(fn, which):
3673 self.uris[which] = fn.get_uri()
3674 d.addCallback(_stash_uri, "good")
3675 d.addCallback(lambda ign:
3676 self.rootnode.add_file(u"small",
3677 upload.Data("literal",
3679 d.addCallback(_stash_uri, "small")
3680 d.addCallback(lambda ign:
3681 self.rootnode.add_file(u"sick",
3682 upload.Data(DATA+"1",
3684 d.addCallback(_stash_uri, "sick")
3685 #d.addCallback(lambda ign:
3686 # self.rootnode.add_file(u"dead",
3687 # upload.Data(DATA+"2",
3689 #d.addCallback(_stash_uri, "dead")
3691 #d.addCallback(lambda ign: c0.create_mutable_file("mutable"))
3692 #d.addCallback(lambda fn: self.rootnode.set_node(u"corrupt", fn))
3693 #d.addCallback(_stash_uri, "corrupt")
3695 def _clobber_shares(ignored):
3696 good_shares = self.find_shares(self.uris["good"])
3697 self.failUnlessEqual(len(good_shares), 10)
3698 sick_shares = self.find_shares(self.uris["sick"])
3699 os.unlink(sick_shares[0][2])
3700 #dead_shares = self.find_shares(self.uris["dead"])
3701 #for i in range(1, 10):
3702 # os.unlink(dead_shares[i][2])
3704 #c_shares = self.find_shares(self.uris["corrupt"])
3705 #cso = CorruptShareOptions()
3706 #cso.stdout = StringIO()
3707 #cso.parseOptions([c_shares[0][2]])
3709 d.addCallback(_clobber_shares)
3712 # root/good CHK, 10 shares
3714 # root/sick CHK, 9 shares
3716 d.addCallback(self.CHECK, "root", "t=stream-deep-check&repair=true")
3718 units = [simplejson.loads(line)
3719 for line in res.splitlines()
3721 self.failUnlessEqual(len(units), 4+1)
3722 # should be parent-first
3724 self.failUnlessEqual(u0["path"], [])
3725 self.failUnlessEqual(u0["type"], "directory")
3726 self.failUnlessEqual(u0["cap"], self.rootnode.get_uri())
3727 u0crr = u0["check-and-repair-results"]
3728 self.failUnlessEqual(u0crr["repair-attempted"], False)
3729 self.failUnlessEqual(u0crr["pre-repair-results"]["results"]["count-shares-good"], 10)
3731 ugood = [u for u in units
3732 if u["type"] == "file" and u["path"] == [u"good"]][0]
3733 self.failUnlessEqual(ugood["cap"], self.uris["good"])
3734 ugoodcrr = ugood["check-and-repair-results"]
3735 self.failUnlessEqual(ugoodcrr["repair-attempted"], False)
3736 self.failUnlessEqual(ugoodcrr["pre-repair-results"]["results"]["count-shares-good"], 10)
3738 usick = [u for u in units
3739 if u["type"] == "file" and u["path"] == [u"sick"]][0]
3740 self.failUnlessEqual(usick["cap"], self.uris["sick"])
3741 usickcrr = usick["check-and-repair-results"]
3742 self.failUnlessEqual(usickcrr["repair-attempted"], True)
3743 self.failUnlessEqual(usickcrr["repair-successful"], True)
3744 self.failUnlessEqual(usickcrr["pre-repair-results"]["results"]["count-shares-good"], 9)
3745 self.failUnlessEqual(usickcrr["post-repair-results"]["results"]["count-shares-good"], 10)
3748 self.failUnlessEqual(stats["type"], "stats")
3750 self.failUnlessEqual(s["count-immutable-files"], 2)
3751 self.failUnlessEqual(s["count-literal-files"], 1)
3752 self.failUnlessEqual(s["count-directories"], 1)
3753 d.addCallback(_done)
3755 d.addErrback(self.explain_web_error)
3758 def _count_leases(self, ignored, which):
3759 u = self.uris[which]
3760 shares = self.find_shares(u)
3762 for shnum, serverid, fn in shares:
3763 sf = get_share_file(fn)
3764 num_leases = len(list(sf.get_leases()))
3765 lease_counts.append( (fn, num_leases) )
3768 def _assert_leasecount(self, lease_counts, expected):
3769 for (fn, num_leases) in lease_counts:
3770 if num_leases != expected:
3771 self.fail("expected %d leases, have %d, on %s" %
3772 (expected, num_leases, fn))
3774 def test_add_lease(self):
3775 self.basedir = "web/Grid/add_lease"
3776 self.set_up_grid(num_clients=2)
3777 c0 = self.g.clients[0]
3780 d = c0.upload(upload.Data(DATA, convergence=""))
3781 def _stash_uri(ur, which):
3782 self.uris[which] = ur.uri
3783 d.addCallback(_stash_uri, "one")
3784 d.addCallback(lambda ign:
3785 c0.upload(upload.Data(DATA+"1", convergence="")))
3786 d.addCallback(_stash_uri, "two")
3787 def _stash_mutable_uri(n, which):
3788 self.uris[which] = n.get_uri()
3789 assert isinstance(self.uris[which], str)
3790 d.addCallback(lambda ign: c0.create_mutable_file(DATA+"2"))
3791 d.addCallback(_stash_mutable_uri, "mutable")
3793 def _compute_fileurls(ignored):
3795 for which in self.uris:
3796 self.fileurls[which] = "uri/" + urllib.quote(self.uris[which])
3797 d.addCallback(_compute_fileurls)
3799 d.addCallback(self._count_leases, "one")
3800 d.addCallback(self._assert_leasecount, 1)
3801 d.addCallback(self._count_leases, "two")
3802 d.addCallback(self._assert_leasecount, 1)
3803 d.addCallback(self._count_leases, "mutable")
3804 d.addCallback(self._assert_leasecount, 1)
3806 d.addCallback(self.CHECK, "one", "t=check") # no add-lease
3807 def _got_html_good(res):
3808 self.failUnless("Healthy" in res, res)
3809 self.failIf("Not Healthy" in res, res)
3810 d.addCallback(_got_html_good)
3812 d.addCallback(self._count_leases, "one")
3813 d.addCallback(self._assert_leasecount, 1)
3814 d.addCallback(self._count_leases, "two")
3815 d.addCallback(self._assert_leasecount, 1)
3816 d.addCallback(self._count_leases, "mutable")
3817 d.addCallback(self._assert_leasecount, 1)
3819 # this CHECK uses the original client, which uses the same
3820 # lease-secrets, so it will just renew the original lease
3821 d.addCallback(self.CHECK, "one", "t=check&add-lease=true")
3822 d.addCallback(_got_html_good)
3824 d.addCallback(self._count_leases, "one")
3825 d.addCallback(self._assert_leasecount, 1)
3826 d.addCallback(self._count_leases, "two")
3827 d.addCallback(self._assert_leasecount, 1)
3828 d.addCallback(self._count_leases, "mutable")
3829 d.addCallback(self._assert_leasecount, 1)
3831 # this CHECK uses an alternate client, which adds a second lease
3832 d.addCallback(self.CHECK, "one", "t=check&add-lease=true", clientnum=1)
3833 d.addCallback(_got_html_good)
3835 d.addCallback(self._count_leases, "one")
3836 d.addCallback(self._assert_leasecount, 2)
3837 d.addCallback(self._count_leases, "two")
3838 d.addCallback(self._assert_leasecount, 1)
3839 d.addCallback(self._count_leases, "mutable")
3840 d.addCallback(self._assert_leasecount, 1)
3842 d.addCallback(self.CHECK, "mutable", "t=check&add-lease=true")
3843 d.addCallback(_got_html_good)
3845 d.addCallback(self._count_leases, "one")
3846 d.addCallback(self._assert_leasecount, 2)
3847 d.addCallback(self._count_leases, "two")
3848 d.addCallback(self._assert_leasecount, 1)
3849 d.addCallback(self._count_leases, "mutable")
3850 d.addCallback(self._assert_leasecount, 1)
3852 d.addCallback(self.CHECK, "mutable", "t=check&add-lease=true",
3854 d.addCallback(_got_html_good)
3856 d.addCallback(self._count_leases, "one")
3857 d.addCallback(self._assert_leasecount, 2)
3858 d.addCallback(self._count_leases, "two")
3859 d.addCallback(self._assert_leasecount, 1)
3860 d.addCallback(self._count_leases, "mutable")
3861 d.addCallback(self._assert_leasecount, 2)
3863 d.addErrback(self.explain_web_error)
3866 def test_deep_add_lease(self):
3867 self.basedir = "web/Grid/deep_add_lease"
3868 self.set_up_grid(num_clients=2)
3869 c0 = self.g.clients[0]
3873 d = c0.create_dirnode()
3874 def _stash_root_and_create_file(n):
3876 self.uris["root"] = n.get_uri()
3877 self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri()) + "/"
3878 return n.add_file(u"one", upload.Data(DATA, convergence=""))
3879 d.addCallback(_stash_root_and_create_file)
3880 def _stash_uri(fn, which):
3881 self.uris[which] = fn.get_uri()
3882 d.addCallback(_stash_uri, "one")
3883 d.addCallback(lambda ign:
3884 self.rootnode.add_file(u"small",
3885 upload.Data("literal",
3887 d.addCallback(_stash_uri, "small")
3889 d.addCallback(lambda ign: c0.create_mutable_file("mutable"))
3890 d.addCallback(lambda fn: self.rootnode.set_node(u"mutable", fn))
3891 d.addCallback(_stash_uri, "mutable")
3893 d.addCallback(self.CHECK, "root", "t=stream-deep-check") # no add-lease
3895 units = [simplejson.loads(line)
3896 for line in res.splitlines()
3898 # root, one, small, mutable, stats
3899 self.failUnlessEqual(len(units), 4+1)
3900 d.addCallback(_done)
3902 d.addCallback(self._count_leases, "root")
3903 d.addCallback(self._assert_leasecount, 1)
3904 d.addCallback(self._count_leases, "one")
3905 d.addCallback(self._assert_leasecount, 1)
3906 d.addCallback(self._count_leases, "mutable")
3907 d.addCallback(self._assert_leasecount, 1)
3909 d.addCallback(self.CHECK, "root", "t=stream-deep-check&add-lease=true")
3910 d.addCallback(_done)
3912 d.addCallback(self._count_leases, "root")
3913 d.addCallback(self._assert_leasecount, 1)
3914 d.addCallback(self._count_leases, "one")
3915 d.addCallback(self._assert_leasecount, 1)
3916 d.addCallback(self._count_leases, "mutable")
3917 d.addCallback(self._assert_leasecount, 1)
3919 d.addCallback(self.CHECK, "root", "t=stream-deep-check&add-lease=true",
3921 d.addCallback(_done)
3923 d.addCallback(self._count_leases, "root")
3924 d.addCallback(self._assert_leasecount, 2)
3925 d.addCallback(self._count_leases, "one")
3926 d.addCallback(self._assert_leasecount, 2)
3927 d.addCallback(self._count_leases, "mutable")
3928 d.addCallback(self._assert_leasecount, 2)
3930 d.addErrback(self.explain_web_error)
3934 def test_exceptions(self):
3935 self.basedir = "web/Grid/exceptions"
3936 self.set_up_grid(num_clients=1, num_servers=2)
3937 c0 = self.g.clients[0]
3940 d = c0.create_dirnode()
3942 self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri()) + "/"
3943 self.fileurls["imaginary"] = self.fileurls["root"] + "imaginary"
3945 d.addCallback(_stash_root)
3946 d.addCallback(lambda ign: c0.upload(upload.Data(DATA, convergence="")))
3948 self.fileurls["1share"] = "uri/" + urllib.quote(ur.uri)
3949 self.delete_shares_numbered(ur.uri, range(1,10))
3951 u = uri.from_string(ur.uri)
3952 u.key = testutil.flip_bit(u.key, 0)
3953 baduri = u.to_string()
3954 self.fileurls["0shares"] = "uri/" + urllib.quote(baduri)
3955 d.addCallback(_stash_bad)
3956 d.addCallback(lambda ign: c0.create_dirnode())
3957 def _mangle_dirnode_1share(n):
3959 url = self.fileurls["dir-1share"] = "uri/" + urllib.quote(u) + "/"
3960 self.fileurls["dir-1share-json"] = url + "?t=json"
3961 self.delete_shares_numbered(u, range(1,10))
3962 d.addCallback(_mangle_dirnode_1share)
3963 d.addCallback(lambda ign: c0.create_dirnode())
3964 def _mangle_dirnode_0share(n):
3966 url = self.fileurls["dir-0share"] = "uri/" + urllib.quote(u) + "/"
3967 self.fileurls["dir-0share-json"] = url + "?t=json"
3968 self.delete_shares_numbered(u, range(0,10))
3969 d.addCallback(_mangle_dirnode_0share)
3971 # NotEnoughSharesError should be reported sensibly, with a
3972 # text/plain explanation of the problem, and perhaps some
3973 # information on which shares *could* be found.
3975 d.addCallback(lambda ignored:
3976 self.shouldHTTPError("GET unrecoverable",
3977 410, "Gone", "NoSharesError",
3978 self.GET, self.fileurls["0shares"]))
3979 def _check_zero_shares(body):
3980 self.failIf("<html>" in body, body)
3981 body = " ".join(body.strip().split())
3982 exp = ("NoSharesError: no shares could be found. "
3983 "Zero shares usually indicates a corrupt URI, or that "
3984 "no servers were connected, but it might also indicate "
3985 "severe corruption. You should perform a filecheck on "
3986 "this object to learn more. The full error message is: "
3987 "Failed to get enough shareholders: have 0, need 3")
3988 self.failUnlessEqual(exp, body)
3989 d.addCallback(_check_zero_shares)
3992 d.addCallback(lambda ignored:
3993 self.shouldHTTPError("GET 1share",
3994 410, "Gone", "NotEnoughSharesError",
3995 self.GET, self.fileurls["1share"]))
3996 def _check_one_share(body):
3997 self.failIf("<html>" in body, body)
3998 body = " ".join(body.strip().split())
3999 exp = ("NotEnoughSharesError: This indicates that some "
4000 "servers were unavailable, or that shares have been "
4001 "lost to server departure, hard drive failure, or disk "
4002 "corruption. You should perform a filecheck on "
4003 "this object to learn more. The full error message is:"
4004 " Failed to get enough shareholders: have 1, need 3")
4005 self.failUnlessEqual(exp, body)
4006 d.addCallback(_check_one_share)
4008 d.addCallback(lambda ignored:
4009 self.shouldHTTPError("GET imaginary",
4010 404, "Not Found", None,
4011 self.GET, self.fileurls["imaginary"]))
4012 def _missing_child(body):
4013 self.failUnless("No such child: imaginary" in body, body)
4014 d.addCallback(_missing_child)
4016 d.addCallback(lambda ignored: self.GET(self.fileurls["dir-0share"]))
4017 def _check_0shares_dir_html(body):
4018 self.failUnless("<html>" in body, body)
4019 # we should see the regular page, but without the child table or
4021 body = " ".join(body.strip().split())
4022 self.failUnlessIn('href="?t=info">More info on this directory',
4024 exp = ("UnrecoverableFileError: the directory (or mutable file) "
4025 "could not be retrieved, because there were insufficient "
4026 "good shares. This might indicate that no servers were "
4027 "connected, insufficient servers were connected, the URI "
4028 "was corrupt, or that shares have been lost due to server "
4029 "departure, hard drive failure, or disk corruption. You "
4030 "should perform a filecheck on this object to learn more.")
4031 self.failUnlessIn(exp, body)
4032 self.failUnlessIn("No upload forms: directory is unreadable", body)
4033 d.addCallback(_check_0shares_dir_html)
4035 d.addCallback(lambda ignored: self.GET(self.fileurls["dir-1share"]))
4036 def _check_1shares_dir_html(body):
4037 # at some point, we'll split UnrecoverableFileError into 0-shares
4038 # and some-shares like we did for immutable files (since there
4039 # are different sorts of advice to offer in each case). For now,
4040 # they present the same way.
4041 self.failUnless("<html>" in body, body)
4042 body = " ".join(body.strip().split())
4043 self.failUnlessIn('href="?t=info">More info on this directory',
4045 exp = ("UnrecoverableFileError: the directory (or mutable file) "
4046 "could not be retrieved, because there were insufficient "
4047 "good shares. This might indicate that no servers were "
4048 "connected, insufficient servers were connected, the URI "
4049 "was corrupt, or that shares have been lost due to server "
4050 "departure, hard drive failure, or disk corruption. You "
4051 "should perform a filecheck on this object to learn more.")
4052 self.failUnlessIn(exp, body)
4053 self.failUnlessIn("No upload forms: directory is unreadable", body)
4054 d.addCallback(_check_1shares_dir_html)
4056 d.addCallback(lambda ignored:
4057 self.shouldHTTPError("GET dir-0share-json",
4058 410, "Gone", "UnrecoverableFileError",
4060 self.fileurls["dir-0share-json"]))
4061 def _check_unrecoverable_file(body):
4062 self.failIf("<html>" in body, body)
4063 body = " ".join(body.strip().split())
4064 exp = ("UnrecoverableFileError: the directory (or mutable file) "
4065 "could not be retrieved, because there were insufficient "
4066 "good shares. This might indicate that no servers were "
4067 "connected, insufficient servers were connected, the URI "
4068 "was corrupt, or that shares have been lost due to server "
4069 "departure, hard drive failure, or disk corruption. You "
4070 "should perform a filecheck on this object to learn more.")
4071 self.failUnlessEqual(exp, body)
4072 d.addCallback(_check_unrecoverable_file)
4074 d.addCallback(lambda ignored:
4075 self.shouldHTTPError("GET dir-1share-json",
4076 410, "Gone", "UnrecoverableFileError",
4078 self.fileurls["dir-1share-json"]))
4079 d.addCallback(_check_unrecoverable_file)
4081 d.addCallback(lambda ignored:
4082 self.shouldHTTPError("GET imaginary",
4083 404, "Not Found", None,
4084 self.GET, self.fileurls["imaginary"]))
4086 # attach a webapi child that throws a random error, to test how it
4088 w = c0.getServiceNamed("webish")
4089 w.root.putChild("ERRORBOOM", ErrorBoom())
4091 # "Accept: */*" : should get a text/html stack trace
4092 # "Accept: text/plain" : should get a text/plain stack trace
4093 # "Accept: text/plain, application/octet-stream" : text/plain (CLI)
4094 # no Accept header: should get a text/html stack trace
4096 d.addCallback(lambda ignored:
4097 self.shouldHTTPError("GET errorboom_html",
4098 500, "Internal Server Error", None,
4099 self.GET, "ERRORBOOM",
4100 headers={"accept": ["*/*"]}))
4101 def _internal_error_html1(body):
4102 self.failUnless("<html>" in body, "expected HTML, not '%s'" % body)
4103 d.addCallback(_internal_error_html1)
4105 d.addCallback(lambda ignored:
4106 self.shouldHTTPError("GET errorboom_text",
4107 500, "Internal Server Error", None,
4108 self.GET, "ERRORBOOM",
4109 headers={"accept": ["text/plain"]}))
4110 def _internal_error_text2(body):
4111 self.failIf("<html>" in body, body)
4112 self.failUnless(body.startswith("Traceback "), body)
4113 d.addCallback(_internal_error_text2)
4115 CLI_accepts = "text/plain, application/octet-stream"
4116 d.addCallback(lambda ignored:
4117 self.shouldHTTPError("GET errorboom_text",
4118 500, "Internal Server Error", None,
4119 self.GET, "ERRORBOOM",
4120 headers={"accept": [CLI_accepts]}))
4121 def _internal_error_text3(body):
4122 self.failIf("<html>" in body, body)
4123 self.failUnless(body.startswith("Traceback "), body)
4124 d.addCallback(_internal_error_text3)
4126 d.addCallback(lambda ignored:
4127 self.shouldHTTPError("GET errorboom_text",
4128 500, "Internal Server Error", None,
4129 self.GET, "ERRORBOOM"))
4130 def _internal_error_html4(body):
4131 self.failUnless("<html>" in body, "expected HTML, not '%s'" % body)
4132 d.addCallback(_internal_error_html4)
4134 def _flush_errors(res):
4135 # Trial: please ignore the CompletelyUnhandledError in the logs
4136 self.flushLoggedErrors(CompletelyUnhandledError)
4138 d.addBoth(_flush_errors)
4142 class CompletelyUnhandledError(Exception):
4144 class ErrorBoom(rend.Page):
4145 def beforeRender(self, ctx):
4146 raise CompletelyUnhandledError("whoops")