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'" %
370 def should302(self, res, which):
371 if isinstance(res, failure.Failure):
372 res.trap(error.Error)
373 self.failUnlessEqual(res.value.status, "302")
375 self.fail("%s was supposed to Error(302), not get '%s'" %
379 class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase):
380 def test_create(self):
383 def test_welcome(self):
386 self.failUnless('Welcome To Tahoe-LAFS' in res, res)
388 self.s.basedir = 'web/test_welcome'
389 fileutil.make_dirs("web/test_welcome")
390 fileutil.make_dirs("web/test_welcome/private")
392 d.addCallback(_check)
395 def test_provisioning(self):
396 d = self.GET("/provisioning/")
398 self.failUnless('Tahoe Provisioning Tool' in res)
399 fields = {'filled': True,
400 "num_users": int(50e3),
401 "files_per_user": 1000,
402 "space_per_user": int(1e9),
403 "sharing_ratio": 1.0,
404 "encoding_parameters": "3-of-10-5",
406 "ownership_mode": "A",
407 "download_rate": 100,
412 return self.POST("/provisioning/", **fields)
414 d.addCallback(_check)
416 self.failUnless('Tahoe Provisioning Tool' in res)
417 self.failUnless("Share space consumed: 167.01TB" in res)
419 fields = {'filled': True,
420 "num_users": int(50e6),
421 "files_per_user": 1000,
422 "space_per_user": int(5e9),
423 "sharing_ratio": 1.0,
424 "encoding_parameters": "25-of-100-50",
425 "num_servers": 30000,
426 "ownership_mode": "E",
427 "drive_failure_model": "U",
429 "download_rate": 1000,
434 return self.POST("/provisioning/", **fields)
435 d.addCallback(_check2)
437 self.failUnless("Share space consumed: huge!" in res)
438 fields = {'filled': True}
439 return self.POST("/provisioning/", **fields)
440 d.addCallback(_check3)
442 self.failUnless("Share space consumed:" in res)
443 d.addCallback(_check4)
446 def test_reliability_tool(self):
448 from allmydata import reliability
449 _hush_pyflakes = reliability
452 raise unittest.SkipTest("reliability tool requires NumPy")
454 d = self.GET("/reliability/")
456 self.failUnless('Tahoe Reliability Tool' in res)
457 fields = {'drive_lifetime': "8Y",
462 "check_period": "1M",
463 "report_period": "3M",
466 return self.POST("/reliability/", **fields)
468 d.addCallback(_check)
470 self.failUnless('Tahoe Reliability Tool' in res)
471 r = r'Probability of loss \(no maintenance\):\s+<span>0.033591'
472 self.failUnless(re.search(r, res), res)
473 d.addCallback(_check2)
476 def test_status(self):
477 h = self.s.get_history()
478 dl_num = h.list_all_download_statuses()[0].get_counter()
479 ul_num = h.list_all_upload_statuses()[0].get_counter()
480 mu_num = h.list_all_mapupdate_statuses()[0].get_counter()
481 pub_num = h.list_all_publish_statuses()[0].get_counter()
482 ret_num = h.list_all_retrieve_statuses()[0].get_counter()
483 d = self.GET("/status", followRedirect=True)
485 self.failUnless('Upload and Download Status' in res, res)
486 self.failUnless('"down-%d"' % dl_num in res, res)
487 self.failUnless('"up-%d"' % ul_num in res, res)
488 self.failUnless('"mapupdate-%d"' % mu_num in res, res)
489 self.failUnless('"publish-%d"' % pub_num in res, res)
490 self.failUnless('"retrieve-%d"' % ret_num in res, res)
491 d.addCallback(_check)
492 d.addCallback(lambda res: self.GET("/status/?t=json"))
493 def _check_json(res):
494 data = simplejson.loads(res)
495 self.failUnless(isinstance(data, dict))
496 #active = data["active"]
497 # TODO: test more. We need a way to fake an active operation
499 d.addCallback(_check_json)
501 d.addCallback(lambda res: self.GET("/status/down-%d" % dl_num))
503 self.failUnless("File Download Status" in res, res)
504 d.addCallback(_check_dl)
505 d.addCallback(lambda res: self.GET("/status/up-%d" % ul_num))
507 self.failUnless("File Upload Status" in res, res)
508 d.addCallback(_check_ul)
509 d.addCallback(lambda res: self.GET("/status/mapupdate-%d" % mu_num))
510 def _check_mapupdate(res):
511 self.failUnless("Mutable File Servermap Update Status" in res, res)
512 d.addCallback(_check_mapupdate)
513 d.addCallback(lambda res: self.GET("/status/publish-%d" % pub_num))
514 def _check_publish(res):
515 self.failUnless("Mutable File Publish Status" in res, res)
516 d.addCallback(_check_publish)
517 d.addCallback(lambda res: self.GET("/status/retrieve-%d" % ret_num))
518 def _check_retrieve(res):
519 self.failUnless("Mutable File Retrieve Status" in res, res)
520 d.addCallback(_check_retrieve)
524 def test_status_numbers(self):
525 drrm = status.DownloadResultsRendererMixin()
526 self.failUnlessEqual(drrm.render_time(None, None), "")
527 self.failUnlessEqual(drrm.render_time(None, 2.5), "2.50s")
528 self.failUnlessEqual(drrm.render_time(None, 0.25), "250ms")
529 self.failUnlessEqual(drrm.render_time(None, 0.0021), "2.1ms")
530 self.failUnlessEqual(drrm.render_time(None, 0.000123), "123us")
531 self.failUnlessEqual(drrm.render_rate(None, None), "")
532 self.failUnlessEqual(drrm.render_rate(None, 2500000), "2.50MBps")
533 self.failUnlessEqual(drrm.render_rate(None, 30100), "30.1kBps")
534 self.failUnlessEqual(drrm.render_rate(None, 123), "123Bps")
536 urrm = status.UploadResultsRendererMixin()
537 self.failUnlessEqual(urrm.render_time(None, None), "")
538 self.failUnlessEqual(urrm.render_time(None, 2.5), "2.50s")
539 self.failUnlessEqual(urrm.render_time(None, 0.25), "250ms")
540 self.failUnlessEqual(urrm.render_time(None, 0.0021), "2.1ms")
541 self.failUnlessEqual(urrm.render_time(None, 0.000123), "123us")
542 self.failUnlessEqual(urrm.render_rate(None, None), "")
543 self.failUnlessEqual(urrm.render_rate(None, 2500000), "2.50MBps")
544 self.failUnlessEqual(urrm.render_rate(None, 30100), "30.1kBps")
545 self.failUnlessEqual(urrm.render_rate(None, 123), "123Bps")
547 def test_GET_FILEURL(self):
548 d = self.GET(self.public_url + "/foo/bar.txt")
549 d.addCallback(self.failUnlessIsBarDotTxt)
552 def test_GET_FILEURL_range(self):
553 headers = {"range": "bytes=1-10"}
554 d = self.GET(self.public_url + "/foo/bar.txt", headers=headers,
555 return_response=True)
556 def _got((res, status, headers)):
557 self.failUnlessEqual(int(status), 206)
558 self.failUnless(headers.has_key("content-range"))
559 self.failUnlessEqual(headers["content-range"][0],
560 "bytes 1-10/%d" % len(self.BAR_CONTENTS))
561 self.failUnlessEqual(res, self.BAR_CONTENTS[1:11])
565 def test_GET_FILEURL_partial_range(self):
566 headers = {"range": "bytes=5-"}
567 length = len(self.BAR_CONTENTS)
568 d = self.GET(self.public_url + "/foo/bar.txt", headers=headers,
569 return_response=True)
570 def _got((res, status, headers)):
571 self.failUnlessEqual(int(status), 206)
572 self.failUnless(headers.has_key("content-range"))
573 self.failUnlessEqual(headers["content-range"][0],
574 "bytes 5-%d/%d" % (length-1, length))
575 self.failUnlessEqual(res, self.BAR_CONTENTS[5:])
579 def test_HEAD_FILEURL_range(self):
580 headers = {"range": "bytes=1-10"}
581 d = self.HEAD(self.public_url + "/foo/bar.txt", headers=headers,
582 return_response=True)
583 def _got((res, status, headers)):
584 self.failUnlessEqual(res, "")
585 self.failUnlessEqual(int(status), 206)
586 self.failUnless(headers.has_key("content-range"))
587 self.failUnlessEqual(headers["content-range"][0],
588 "bytes 1-10/%d" % len(self.BAR_CONTENTS))
592 def test_HEAD_FILEURL_partial_range(self):
593 headers = {"range": "bytes=5-"}
594 length = len(self.BAR_CONTENTS)
595 d = self.HEAD(self.public_url + "/foo/bar.txt", headers=headers,
596 return_response=True)
597 def _got((res, status, headers)):
598 self.failUnlessEqual(int(status), 206)
599 self.failUnless(headers.has_key("content-range"))
600 self.failUnlessEqual(headers["content-range"][0],
601 "bytes 5-%d/%d" % (length-1, length))
605 def test_GET_FILEURL_range_bad(self):
606 headers = {"range": "BOGUS=fizbop-quarnak"}
607 d = self.shouldFail2(error.Error, "test_GET_FILEURL_range_bad",
609 "Syntactically invalid http range header",
610 self.GET, self.public_url + "/foo/bar.txt",
614 def test_HEAD_FILEURL(self):
615 d = self.HEAD(self.public_url + "/foo/bar.txt", return_response=True)
616 def _got((res, status, headers)):
617 self.failUnlessEqual(res, "")
618 self.failUnlessEqual(headers["content-length"][0],
619 str(len(self.BAR_CONTENTS)))
620 self.failUnlessEqual(headers["content-type"], ["text/plain"])
624 def test_GET_FILEURL_named(self):
625 base = "/file/%s" % urllib.quote(self._bar_txt_uri)
626 base2 = "/named/%s" % urllib.quote(self._bar_txt_uri)
627 d = self.GET(base + "/@@name=/blah.txt")
628 d.addCallback(self.failUnlessIsBarDotTxt)
629 d.addCallback(lambda res: self.GET(base + "/blah.txt"))
630 d.addCallback(self.failUnlessIsBarDotTxt)
631 d.addCallback(lambda res: self.GET(base + "/ignore/lots/blah.txt"))
632 d.addCallback(self.failUnlessIsBarDotTxt)
633 d.addCallback(lambda res: self.GET(base2 + "/@@name=/blah.txt"))
634 d.addCallback(self.failUnlessIsBarDotTxt)
635 save_url = base + "?save=true&filename=blah.txt"
636 d.addCallback(lambda res: self.GET(save_url))
637 d.addCallback(self.failUnlessIsBarDotTxt) # TODO: check headers
638 u_filename = u"n\u00e9wer.txt" # n e-acute w e r . t x t
639 u_fn_e = urllib.quote(u_filename.encode("utf-8"))
640 u_url = base + "?save=true&filename=" + u_fn_e
641 d.addCallback(lambda res: self.GET(u_url))
642 d.addCallback(self.failUnlessIsBarDotTxt) # TODO: check headers
645 def test_PUT_FILEURL_named_bad(self):
646 base = "/file/%s" % urllib.quote(self._bar_txt_uri)
647 d = self.shouldFail2(error.Error, "test_PUT_FILEURL_named_bad",
649 "/file can only be used with GET or HEAD",
650 self.PUT, base + "/@@name=/blah.txt", "")
653 def test_GET_DIRURL_named_bad(self):
654 base = "/file/%s" % urllib.quote(self._foo_uri)
655 d = self.shouldFail2(error.Error, "test_PUT_DIRURL_named_bad",
658 self.GET, base + "/@@name=/blah.txt")
661 def test_GET_slash_file_bad(self):
662 d = self.shouldFail2(error.Error, "test_GET_slash_file_bad",
664 "/file must be followed by a file-cap and a name",
668 def test_GET_unhandled_URI_named(self):
669 contents, n, newuri = self.makefile(12)
670 verifier_cap = n.get_verify_cap().to_string()
671 base = "/file/%s" % urllib.quote(verifier_cap)
672 # client.create_node_from_uri() can't handle verify-caps
673 d = self.shouldFail2(error.Error, "GET_unhandled_URI_named",
674 "400 Bad Request", "is not a file-cap",
678 def test_GET_unhandled_URI(self):
679 contents, n, newuri = self.makefile(12)
680 verifier_cap = n.get_verify_cap().to_string()
681 base = "/uri/%s" % urllib.quote(verifier_cap)
682 # client.create_node_from_uri() can't handle verify-caps
683 d = self.shouldFail2(error.Error, "test_GET_unhandled_URI",
685 "GET unknown URI type: can only do t=info",
689 def test_GET_FILE_URI(self):
690 base = "/uri/%s" % urllib.quote(self._bar_txt_uri)
692 d.addCallback(self.failUnlessIsBarDotTxt)
695 def test_GET_FILE_URI_badchild(self):
696 base = "/uri/%s/boguschild" % urllib.quote(self._bar_txt_uri)
697 errmsg = "Files have no children, certainly not named 'boguschild'"
698 d = self.shouldFail2(error.Error, "test_GET_FILE_URI_badchild",
699 "400 Bad Request", errmsg,
703 def test_PUT_FILE_URI_badchild(self):
704 base = "/uri/%s/boguschild" % urllib.quote(self._bar_txt_uri)
705 errmsg = "Cannot create directory 'boguschild', because its parent is a file, not a directory"
706 d = self.shouldFail2(error.Error, "test_GET_FILE_URI_badchild",
707 "400 Bad Request", errmsg,
711 # TODO: version of this with a Unicode filename
712 def test_GET_FILEURL_save(self):
713 d = self.GET(self.public_url + "/foo/bar.txt?filename=bar.txt&save=true",
714 return_response=True)
715 def _got((res, statuscode, headers)):
716 content_disposition = headers["content-disposition"][0]
717 self.failUnless(content_disposition == 'attachment; filename="bar.txt"', content_disposition)
718 self.failUnlessIsBarDotTxt(res)
722 def test_GET_FILEURL_missing(self):
723 d = self.GET(self.public_url + "/foo/missing")
724 d.addBoth(self.should404, "test_GET_FILEURL_missing")
727 def test_PUT_overwrite_only_files(self):
728 # create a directory, put a file in that directory.
729 contents, n, filecap = self.makefile(8)
730 d = self.PUT(self.public_url + "/foo/dir?t=mkdir", "")
731 d.addCallback(lambda res:
732 self.PUT(self.public_url + "/foo/dir/file1.txt",
733 self.NEWFILE_CONTENTS))
734 # try to overwrite the file with replace=only-files
736 d.addCallback(lambda res:
737 self.PUT(self.public_url + "/foo/dir/file1.txt?t=uri&replace=only-files",
739 d.addCallback(lambda res:
740 self.shouldFail2(error.Error, "PUT_bad_t", "409 Conflict",
741 "There was already a child by that name, and you asked me "
743 self.PUT, self.public_url + "/foo/dir?t=uri&replace=only-files",
747 def test_PUT_NEWFILEURL(self):
748 d = self.PUT(self.public_url + "/foo/new.txt", self.NEWFILE_CONTENTS)
749 # TODO: we lose the response code, so we can't check this
750 #self.failUnlessEqual(responsecode, 201)
751 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"new.txt")
752 d.addCallback(lambda res:
753 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
754 self.NEWFILE_CONTENTS))
757 def test_PUT_NEWFILEURL_not_mutable(self):
758 d = self.PUT(self.public_url + "/foo/new.txt?mutable=false",
759 self.NEWFILE_CONTENTS)
760 # TODO: we lose the response code, so we can't check this
761 #self.failUnlessEqual(responsecode, 201)
762 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"new.txt")
763 d.addCallback(lambda res:
764 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
765 self.NEWFILE_CONTENTS))
768 def test_PUT_NEWFILEURL_range_bad(self):
769 headers = {"content-range": "bytes 1-10/%d" % len(self.NEWFILE_CONTENTS)}
770 target = self.public_url + "/foo/new.txt"
771 d = self.shouldFail2(error.Error, "test_PUT_NEWFILEURL_range_bad",
772 "501 Not Implemented",
773 "Content-Range in PUT not yet supported",
774 # (and certainly not for immutable files)
775 self.PUT, target, self.NEWFILE_CONTENTS[1:11],
777 d.addCallback(lambda res:
778 self.failIfNodeHasChild(self._foo_node, u"new.txt"))
781 def test_PUT_NEWFILEURL_mutable(self):
782 d = self.PUT(self.public_url + "/foo/new.txt?mutable=true",
783 self.NEWFILE_CONTENTS)
784 # TODO: we lose the response code, so we can't check this
785 #self.failUnlessEqual(responsecode, 201)
787 u = uri.from_string_mutable_filenode(res)
788 self.failUnless(u.is_mutable())
789 self.failIf(u.is_readonly())
791 d.addCallback(_check_uri)
792 d.addCallback(self.failUnlessURIMatchesRWChild, self._foo_node, u"new.txt")
793 d.addCallback(lambda res:
794 self.failUnlessMutableChildContentsAre(self._foo_node,
796 self.NEWFILE_CONTENTS))
799 def test_PUT_NEWFILEURL_mutable_toobig(self):
800 d = self.shouldFail2(error.Error, "test_PUT_NEWFILEURL_mutable_toobig",
801 "413 Request Entity Too Large",
802 "SDMF is limited to one segment, and 10001 > 10000",
804 self.public_url + "/foo/new.txt?mutable=true",
805 "b" * (self.s.MUTABLE_SIZELIMIT+1))
808 def test_PUT_NEWFILEURL_replace(self):
809 d = self.PUT(self.public_url + "/foo/bar.txt", self.NEWFILE_CONTENTS)
810 # TODO: we lose the response code, so we can't check this
811 #self.failUnlessEqual(responsecode, 200)
812 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"bar.txt")
813 d.addCallback(lambda res:
814 self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
815 self.NEWFILE_CONTENTS))
818 def test_PUT_NEWFILEURL_bad_t(self):
819 d = self.shouldFail2(error.Error, "PUT_bad_t", "400 Bad Request",
820 "PUT to a file: bad t=bogus",
821 self.PUT, self.public_url + "/foo/bar.txt?t=bogus",
825 def test_PUT_NEWFILEURL_no_replace(self):
826 d = self.PUT(self.public_url + "/foo/bar.txt?replace=false",
827 self.NEWFILE_CONTENTS)
828 d.addBoth(self.shouldFail, error.Error, "PUT_NEWFILEURL_no_replace",
830 "There was already a child by that name, and you asked me "
834 def test_PUT_NEWFILEURL_mkdirs(self):
835 d = self.PUT(self.public_url + "/foo/newdir/new.txt", self.NEWFILE_CONTENTS)
837 d.addCallback(self.failUnlessURIMatchesROChild, fn, u"newdir/new.txt")
838 d.addCallback(lambda res: self.failIfNodeHasChild(fn, u"new.txt"))
839 d.addCallback(lambda res: self.failUnlessNodeHasChild(fn, u"newdir"))
840 d.addCallback(lambda res:
841 self.failUnlessChildContentsAre(fn, u"newdir/new.txt",
842 self.NEWFILE_CONTENTS))
845 def test_PUT_NEWFILEURL_blocked(self):
846 d = self.PUT(self.public_url + "/foo/blockingfile/new.txt",
847 self.NEWFILE_CONTENTS)
848 d.addBoth(self.shouldFail, error.Error, "PUT_NEWFILEURL_blocked",
850 "Unable to create directory 'blockingfile': a file was in the way")
853 def test_PUT_NEWFILEURL_emptyname(self):
854 # an empty pathname component (i.e. a double-slash) is disallowed
855 d = self.shouldFail2(error.Error, "test_PUT_NEWFILEURL_emptyname",
857 "The webapi does not allow empty pathname components",
858 self.PUT, self.public_url + "/foo//new.txt", "")
861 def test_DELETE_FILEURL(self):
862 d = self.DELETE(self.public_url + "/foo/bar.txt")
863 d.addCallback(lambda res:
864 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
867 def test_DELETE_FILEURL_missing(self):
868 d = self.DELETE(self.public_url + "/foo/missing")
869 d.addBoth(self.should404, "test_DELETE_FILEURL_missing")
872 def test_DELETE_FILEURL_missing2(self):
873 d = self.DELETE(self.public_url + "/missing/missing")
874 d.addBoth(self.should404, "test_DELETE_FILEURL_missing2")
877 def failUnlessHasBarDotTxtMetadata(self, res):
878 data = simplejson.loads(res)
879 self.failUnless(isinstance(data, list))
880 self.failUnless(data[1].has_key("metadata"))
881 self.failUnless(data[1]["metadata"].has_key("ctime"))
882 self.failUnless(data[1]["metadata"].has_key("mtime"))
883 self.failUnlessEqual(data[1]["metadata"]["ctime"],
884 self._bar_txt_metadata["ctime"])
886 def test_GET_FILEURL_json(self):
887 # twisted.web.http.parse_qs ignores any query args without an '=', so
888 # I can't do "GET /path?json", I have to do "GET /path/t=json"
889 # instead. This may make it tricky to emulate the S3 interface
891 d = self.GET(self.public_url + "/foo/bar.txt?t=json")
893 self.failUnlessIsBarJSON(data)
894 self.failUnlessHasBarDotTxtMetadata(data)
896 d.addCallback(_check1)
899 def test_GET_FILEURL_json_missing(self):
900 d = self.GET(self.public_url + "/foo/missing?json")
901 d.addBoth(self.should404, "test_GET_FILEURL_json_missing")
904 def test_GET_FILEURL_uri(self):
905 d = self.GET(self.public_url + "/foo/bar.txt?t=uri")
907 self.failUnlessEqual(res, self._bar_txt_uri)
908 d.addCallback(_check)
909 d.addCallback(lambda res:
910 self.GET(self.public_url + "/foo/bar.txt?t=readonly-uri"))
912 # for now, for files, uris and readonly-uris are the same
913 self.failUnlessEqual(res, self._bar_txt_uri)
914 d.addCallback(_check2)
917 def test_GET_FILEURL_badtype(self):
918 d = self.shouldHTTPError("GET t=bogus", 400, "Bad Request",
921 self.public_url + "/foo/bar.txt?t=bogus")
924 def test_GET_FILEURL_uri_missing(self):
925 d = self.GET(self.public_url + "/foo/missing?t=uri")
926 d.addBoth(self.should404, "test_GET_FILEURL_uri_missing")
929 def test_GET_DIRURL(self):
930 # the addSlash means we get a redirect here
931 # from /uri/$URI/foo/ , we need ../../../ to get back to the root
933 d = self.GET(self.public_url + "/foo", followRedirect=True)
935 self.failUnless(('<a href="%s">Return to Welcome page' % ROOT)
937 # the FILE reference points to a URI, but it should end in bar.txt
938 bar_url = ("%s/file/%s/@@named=/bar.txt" %
939 (ROOT, urllib.quote(self._bar_txt_uri)))
940 get_bar = "".join([r'<td>FILE</td>',
942 r'<a href="%s">bar.txt</a>' % bar_url,
944 r'\s+<td>%d</td>' % len(self.BAR_CONTENTS),
946 self.failUnless(re.search(get_bar, res), res)
947 for line in res.split("\n"):
948 # find the line that contains the delete button for bar.txt
949 if ("form action" in line and
950 'value="delete"' in line and
951 'value="bar.txt"' in line):
952 # the form target should use a relative URL
953 foo_url = urllib.quote("%s/uri/%s/" % (ROOT, self._foo_uri))
954 self.failUnless(('action="%s"' % foo_url) in line, line)
955 # and the when_done= should too
956 #done_url = urllib.quote(???)
957 #self.failUnless(('name="when_done" value="%s"' % done_url)
961 self.fail("unable to find delete-bar.txt line", res)
963 # the DIR reference just points to a URI
964 sub_url = ("%s/uri/%s/" % (ROOT, urllib.quote(self._sub_uri)))
965 get_sub = ((r'<td>DIR</td>')
966 +r'\s+<td><a href="%s">sub</a></td>' % sub_url)
967 self.failUnless(re.search(get_sub, res), res)
968 d.addCallback(_check)
970 # look at a readonly directory
971 d.addCallback(lambda res:
972 self.GET(self.public_url + "/reedownlee", followRedirect=True))
974 self.failUnless("(read-only)" in res, res)
975 self.failIf("Upload a file" in res, res)
976 d.addCallback(_check2)
978 # and at a directory that contains a readonly directory
979 d.addCallback(lambda res:
980 self.GET(self.public_url, followRedirect=True))
982 self.failUnless(re.search('<td>DIR-RO</td>'
983 r'\s+<td><a href="[\.\/]+/uri/URI%3ADIR2-RO%3A[^"]+">reedownlee</a></td>', res), res)
984 d.addCallback(_check3)
986 # and an empty directory
987 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty/"))
989 self.failUnless("directory is empty" in res, res)
990 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)
991 self.failUnless(MKDIR_BUTTON_RE.search(res), res)
992 d.addCallback(_check4)
996 def test_GET_DIRURL_badtype(self):
997 d = self.shouldHTTPError("test_GET_DIRURL_badtype",
1001 self.public_url + "/foo?t=bogus")
1004 def test_GET_DIRURL_json(self):
1005 d = self.GET(self.public_url + "/foo?t=json")
1006 d.addCallback(self.failUnlessIsFooJSON)
1010 def test_POST_DIRURL_manifest_no_ophandle(self):
1011 d = self.shouldFail2(error.Error,
1012 "test_POST_DIRURL_manifest_no_ophandle",
1014 "slow operation requires ophandle=",
1015 self.POST, self.public_url, t="start-manifest")
1018 def test_POST_DIRURL_manifest(self):
1019 d = defer.succeed(None)
1020 def getman(ignored, output):
1021 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=125",
1022 followRedirect=True)
1023 d.addCallback(self.wait_for_operation, "125")
1024 d.addCallback(self.get_operation_results, "125", output)
1026 d.addCallback(getman, None)
1027 def _got_html(manifest):
1028 self.failUnless("Manifest of SI=" in manifest)
1029 self.failUnless("<td>sub</td>" in manifest)
1030 self.failUnless(self._sub_uri in manifest)
1031 self.failUnless("<td>sub/baz.txt</td>" in manifest)
1032 d.addCallback(_got_html)
1034 # both t=status and unadorned GET should be identical
1035 d.addCallback(lambda res: self.GET("/operations/125"))
1036 d.addCallback(_got_html)
1038 d.addCallback(getman, "html")
1039 d.addCallback(_got_html)
1040 d.addCallback(getman, "text")
1041 def _got_text(manifest):
1042 self.failUnless("\nsub " + self._sub_uri + "\n" in manifest)
1043 self.failUnless("\nsub/baz.txt URI:CHK:" in manifest)
1044 d.addCallback(_got_text)
1045 d.addCallback(getman, "JSON")
1047 data = res["manifest"]
1049 for (path_list, cap) in data:
1050 got[tuple(path_list)] = cap
1051 self.failUnlessEqual(got[(u"sub",)], self._sub_uri)
1052 self.failUnless((u"sub",u"baz.txt") in got)
1053 self.failUnless("finished" in res)
1054 self.failUnless("origin" in res)
1055 self.failUnless("storage-index" in res)
1056 self.failUnless("verifycaps" in res)
1057 self.failUnless("stats" in res)
1058 d.addCallback(_got_json)
1061 def test_POST_DIRURL_deepsize_no_ophandle(self):
1062 d = self.shouldFail2(error.Error,
1063 "test_POST_DIRURL_deepsize_no_ophandle",
1065 "slow operation requires ophandle=",
1066 self.POST, self.public_url, t="start-deep-size")
1069 def test_POST_DIRURL_deepsize(self):
1070 d = self.POST(self.public_url + "/foo/?t=start-deep-size&ophandle=126",
1071 followRedirect=True)
1072 d.addCallback(self.wait_for_operation, "126")
1073 d.addCallback(self.get_operation_results, "126", "json")
1074 def _got_json(data):
1075 self.failUnlessEqual(data["finished"], True)
1077 self.failUnless(size > 1000)
1078 d.addCallback(_got_json)
1079 d.addCallback(self.get_operation_results, "126", "text")
1081 mo = re.search(r'^size: (\d+)$', res, re.M)
1082 self.failUnless(mo, res)
1083 size = int(mo.group(1))
1084 # with directories, the size varies.
1085 self.failUnless(size > 1000)
1086 d.addCallback(_got_text)
1089 def test_POST_DIRURL_deepstats_no_ophandle(self):
1090 d = self.shouldFail2(error.Error,
1091 "test_POST_DIRURL_deepstats_no_ophandle",
1093 "slow operation requires ophandle=",
1094 self.POST, self.public_url, t="start-deep-stats")
1097 def test_POST_DIRURL_deepstats(self):
1098 d = self.POST(self.public_url + "/foo/?t=start-deep-stats&ophandle=127",
1099 followRedirect=True)
1100 d.addCallback(self.wait_for_operation, "127")
1101 d.addCallback(self.get_operation_results, "127", "json")
1102 def _got_json(stats):
1103 expected = {"count-immutable-files": 3,
1104 "count-mutable-files": 0,
1105 "count-literal-files": 0,
1107 "count-directories": 3,
1108 "size-immutable-files": 57,
1109 "size-literal-files": 0,
1110 #"size-directories": 1912, # varies
1111 #"largest-directory": 1590,
1112 "largest-directory-children": 5,
1113 "largest-immutable-file": 19,
1115 for k,v in expected.iteritems():
1116 self.failUnlessEqual(stats[k], v,
1117 "stats[%s] was %s, not %s" %
1119 self.failUnlessEqual(stats["size-files-histogram"],
1121 d.addCallback(_got_json)
1124 def test_POST_DIRURL_stream_manifest(self):
1125 d = self.POST(self.public_url + "/foo/?t=stream-manifest")
1127 self.failUnless(res.endswith("\n"))
1128 units = [simplejson.loads(t) for t in res[:-1].split("\n")]
1129 self.failUnlessEqual(len(units), 7)
1130 self.failUnlessEqual(units[-1]["type"], "stats")
1132 self.failUnlessEqual(first["path"], [])
1133 self.failUnlessEqual(first["cap"], self._foo_uri)
1134 self.failUnlessEqual(first["type"], "directory")
1135 baz = [u for u in units[:-1] if u["cap"] == self._baz_file_uri][0]
1136 self.failUnlessEqual(baz["path"], ["sub", "baz.txt"])
1137 self.failIfEqual(baz["storage-index"], None)
1138 self.failIfEqual(baz["verifycap"], None)
1139 self.failIfEqual(baz["repaircap"], None)
1141 d.addCallback(_check)
1144 def test_GET_DIRURL_uri(self):
1145 d = self.GET(self.public_url + "/foo?t=uri")
1147 self.failUnlessEqual(res, self._foo_uri)
1148 d.addCallback(_check)
1151 def test_GET_DIRURL_readonly_uri(self):
1152 d = self.GET(self.public_url + "/foo?t=readonly-uri")
1154 self.failUnlessEqual(res, self._foo_readonly_uri)
1155 d.addCallback(_check)
1158 def test_PUT_NEWDIRURL(self):
1159 d = self.PUT(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(self):
1167 d = self.POST2(self.public_url + "/foo/newdir?t=mkdir", "")
1168 d.addCallback(lambda res:
1169 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1170 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1171 d.addCallback(self.failUnlessNodeKeysAre, [])
1174 def test_POST_NEWDIRURL_emptyname(self):
1175 # an empty pathname component (i.e. a double-slash) is disallowed
1176 d = self.shouldFail2(error.Error, "test_POST_NEWDIRURL_emptyname",
1178 "The webapi does not allow empty pathname components, i.e. a double slash",
1179 self.POST, self.public_url + "//?t=mkdir")
1182 def test_POST_NEWDIRURL_initial_children(self):
1183 (newkids, caps) = self._create_initial_children()
1184 d = self.POST2(self.public_url + "/foo/newdir?t=mkdir-with-children",
1185 simplejson.dumps(newkids))
1187 n = self.s.create_node_from_uri(uri.strip())
1188 d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
1189 d2.addCallback(lambda ign:
1190 self.failUnlessROChildURIIs(n, u"child-imm",
1192 d2.addCallback(lambda ign:
1193 self.failUnlessRWChildURIIs(n, u"child-mutable",
1195 d2.addCallback(lambda ign:
1196 self.failUnlessROChildURIIs(n, u"child-mutable-ro",
1198 d2.addCallback(lambda ign:
1199 self.failUnlessROChildURIIs(n, u"unknownchild-ro",
1200 caps['unknown_rocap']))
1201 d2.addCallback(lambda ign:
1202 self.failUnlessRWChildURIIs(n, u"unknownchild-rw",
1203 caps['unknown_rwcap']))
1204 d2.addCallback(lambda ign:
1205 self.failUnlessROChildURIIs(n, u"unknownchild-imm",
1206 caps['unknown_immcap']))
1207 d2.addCallback(lambda ign:
1208 self.failUnlessRWChildURIIs(n, u"dirchild",
1211 d.addCallback(_check)
1212 d.addCallback(lambda res:
1213 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1214 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1215 d.addCallback(self.failUnlessNodeKeysAre, newkids.keys())
1216 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1217 d.addCallback(self.failUnlessROChildURIIs, u"child-imm", caps['filecap1'])
1220 def test_POST_NEWDIRURL_immutable(self):
1221 (newkids, caps) = self._create_immutable_children()
1222 d = self.POST2(self.public_url + "/foo/newdir?t=mkdir-immutable",
1223 simplejson.dumps(newkids))
1225 n = self.s.create_node_from_uri(uri.strip())
1226 d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
1227 d2.addCallback(lambda ign:
1228 self.failUnlessROChildURIIs(n, u"child-imm",
1230 d2.addCallback(lambda ign:
1231 self.failUnlessROChildURIIs(n, u"unknownchild-imm",
1232 caps['unknown_immcap']))
1233 d2.addCallback(lambda ign:
1234 self.failUnlessROChildURIIs(n, u"dirchild-imm",
1237 d.addCallback(_check)
1238 d.addCallback(lambda res:
1239 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1240 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1241 d.addCallback(self.failUnlessNodeKeysAre, newkids.keys())
1242 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1243 d.addCallback(self.failUnlessROChildURIIs, u"child-imm", caps['filecap1'])
1244 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1245 d.addCallback(self.failUnlessROChildURIIs, u"unknownchild-imm", caps['unknown_immcap'])
1246 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1247 d.addCallback(self.failUnlessROChildURIIs, u"dirchild-imm", caps['immdircap'])
1248 d.addErrback(self.explain_web_error)
1251 def test_POST_NEWDIRURL_immutable_bad(self):
1252 (newkids, caps) = self._create_initial_children()
1253 d = self.shouldFail2(error.Error, "test_POST_NEWDIRURL_immutable_bad",
1255 "needed to be immutable but was not",
1257 self.public_url + "/foo/newdir?t=mkdir-immutable",
1258 simplejson.dumps(newkids))
1261 def test_PUT_NEWDIRURL_exists(self):
1262 d = self.PUT(self.public_url + "/foo/sub?t=mkdir", "")
1263 d.addCallback(lambda res:
1264 self.failUnlessNodeHasChild(self._foo_node, u"sub"))
1265 d.addCallback(lambda res: self._foo_node.get(u"sub"))
1266 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
1269 def test_PUT_NEWDIRURL_blocked(self):
1270 d = self.shouldFail2(error.Error, "PUT_NEWDIRURL_blocked",
1271 "409 Conflict", "Unable to create directory 'bar.txt': a file was in the way",
1273 self.public_url + "/foo/bar.txt/sub?t=mkdir", "")
1274 d.addCallback(lambda res:
1275 self.failUnlessNodeHasChild(self._foo_node, u"sub"))
1276 d.addCallback(lambda res: self._foo_node.get(u"sub"))
1277 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
1280 def test_PUT_NEWDIRURL_mkdir_p(self):
1281 d = defer.succeed(None)
1282 d.addCallback(lambda res: self.POST(self.public_url + "/foo", t='mkdir', name='mkp'))
1283 d.addCallback(lambda res: self.failUnlessNodeHasChild(self._foo_node, u"mkp"))
1284 d.addCallback(lambda res: self._foo_node.get(u"mkp"))
1285 def mkdir_p(mkpnode):
1286 url = '/uri/%s?t=mkdir-p&path=/sub1/sub2' % urllib.quote(mkpnode.get_uri())
1288 def made_subsub(ssuri):
1289 d = self._foo_node.get_child_at_path(u"mkp/sub1/sub2")
1290 d.addCallback(lambda ssnode: self.failUnlessEqual(ssnode.get_uri(), ssuri))
1292 d.addCallback(lambda uri2: self.failUnlessEqual(uri2, ssuri))
1294 d.addCallback(made_subsub)
1296 d.addCallback(mkdir_p)
1299 def test_PUT_NEWDIRURL_mkdirs(self):
1300 d = self.PUT(self.public_url + "/foo/subdir/newdir?t=mkdir", "")
1301 d.addCallback(lambda res:
1302 self.failIfNodeHasChild(self._foo_node, u"newdir"))
1303 d.addCallback(lambda res:
1304 self.failUnlessNodeHasChild(self._foo_node, u"subdir"))
1305 d.addCallback(lambda res:
1306 self._foo_node.get_child_at_path(u"subdir/newdir"))
1307 d.addCallback(self.failUnlessNodeKeysAre, [])
1310 def test_DELETE_DIRURL(self):
1311 d = self.DELETE(self.public_url + "/foo")
1312 d.addCallback(lambda res:
1313 self.failIfNodeHasChild(self.public_root, u"foo"))
1316 def test_DELETE_DIRURL_missing(self):
1317 d = self.DELETE(self.public_url + "/foo/missing")
1318 d.addBoth(self.should404, "test_DELETE_DIRURL_missing")
1319 d.addCallback(lambda res:
1320 self.failUnlessNodeHasChild(self.public_root, u"foo"))
1323 def test_DELETE_DIRURL_missing2(self):
1324 d = self.DELETE(self.public_url + "/missing")
1325 d.addBoth(self.should404, "test_DELETE_DIRURL_missing2")
1328 def dump_root(self):
1330 w = webish.DirnodeWalkerMixin()
1331 def visitor(childpath, childnode, metadata):
1333 d = w.walk(self.public_root, visitor)
1336 def failUnlessNodeKeysAre(self, node, expected_keys):
1337 for k in expected_keys:
1338 assert isinstance(k, unicode)
1340 def _check(children):
1341 self.failUnlessEqual(sorted(children.keys()), sorted(expected_keys))
1342 d.addCallback(_check)
1344 def failUnlessNodeHasChild(self, node, name):
1345 assert isinstance(name, unicode)
1347 def _check(children):
1348 self.failUnless(name in children)
1349 d.addCallback(_check)
1351 def failIfNodeHasChild(self, node, name):
1352 assert isinstance(name, unicode)
1354 def _check(children):
1355 self.failIf(name in children)
1356 d.addCallback(_check)
1359 def failUnlessChildContentsAre(self, node, name, expected_contents):
1360 assert isinstance(name, unicode)
1361 d = node.get_child_at_path(name)
1362 d.addCallback(lambda node: download_to_data(node))
1363 def _check(contents):
1364 self.failUnlessEqual(contents, expected_contents)
1365 d.addCallback(_check)
1368 def failUnlessMutableChildContentsAre(self, node, name, expected_contents):
1369 assert isinstance(name, unicode)
1370 d = node.get_child_at_path(name)
1371 d.addCallback(lambda node: node.download_best_version())
1372 def _check(contents):
1373 self.failUnlessEqual(contents, expected_contents)
1374 d.addCallback(_check)
1377 def failUnlessRWChildURIIs(self, node, name, expected_uri):
1378 assert isinstance(name, unicode)
1379 d = node.get_child_at_path(name)
1381 self.failUnless(child.is_unknown() or not child.is_readonly())
1382 self.failUnlessEqual(child.get_uri(), expected_uri.strip())
1383 self.failUnlessEqual(child.get_write_uri(), expected_uri.strip())
1384 expected_ro_uri = self._make_readonly(expected_uri)
1386 self.failUnlessEqual(child.get_readonly_uri(), expected_ro_uri.strip())
1387 d.addCallback(_check)
1390 def failUnlessROChildURIIs(self, node, name, expected_uri):
1391 assert isinstance(name, unicode)
1392 d = node.get_child_at_path(name)
1394 self.failUnless(child.is_unknown() or child.is_readonly())
1395 self.failUnlessEqual(child.get_write_uri(), None)
1396 self.failUnlessEqual(child.get_uri(), expected_uri.strip())
1397 self.failUnlessEqual(child.get_readonly_uri(), expected_uri.strip())
1398 d.addCallback(_check)
1401 def failUnlessURIMatchesRWChild(self, got_uri, node, name):
1402 assert isinstance(name, unicode)
1403 d = node.get_child_at_path(name)
1405 self.failUnless(child.is_unknown() or not child.is_readonly())
1406 self.failUnlessEqual(child.get_uri(), got_uri.strip())
1407 self.failUnlessEqual(child.get_write_uri(), got_uri.strip())
1408 expected_ro_uri = self._make_readonly(got_uri)
1410 self.failUnlessEqual(child.get_readonly_uri(), expected_ro_uri.strip())
1411 d.addCallback(_check)
1414 def failUnlessURIMatchesROChild(self, got_uri, node, name):
1415 assert isinstance(name, unicode)
1416 d = node.get_child_at_path(name)
1418 self.failUnless(child.is_unknown() or child.is_readonly())
1419 self.failUnlessEqual(child.get_write_uri(), None)
1420 self.failUnlessEqual(got_uri.strip(), child.get_uri())
1421 self.failUnlessEqual(got_uri.strip(), child.get_readonly_uri())
1422 d.addCallback(_check)
1425 def failUnlessCHKURIHasContents(self, got_uri, contents):
1426 self.failUnless(FakeCHKFileNode.all_contents[got_uri] == contents)
1428 def test_POST_upload(self):
1429 d = self.POST(self.public_url + "/foo", t="upload",
1430 file=("new.txt", self.NEWFILE_CONTENTS))
1432 d.addCallback(self.failUnlessURIMatchesROChild, fn, u"new.txt")
1433 d.addCallback(lambda res:
1434 self.failUnlessChildContentsAre(fn, u"new.txt",
1435 self.NEWFILE_CONTENTS))
1438 def test_POST_upload_unicode(self):
1439 filename = u"n\u00e9wer.txt" # n e-acute w e r . t x t
1440 d = self.POST(self.public_url + "/foo", t="upload",
1441 file=(filename, self.NEWFILE_CONTENTS))
1443 d.addCallback(self.failUnlessURIMatchesROChild, fn, filename)
1444 d.addCallback(lambda res:
1445 self.failUnlessChildContentsAre(fn, filename,
1446 self.NEWFILE_CONTENTS))
1447 target_url = self.public_url + "/foo/" + filename.encode("utf-8")
1448 d.addCallback(lambda res: self.GET(target_url))
1449 d.addCallback(lambda contents: self.failUnlessEqual(contents,
1450 self.NEWFILE_CONTENTS,
1454 def test_POST_upload_unicode_named(self):
1455 filename = u"n\u00e9wer.txt" # n e-acute w e r . t x t
1456 d = self.POST(self.public_url + "/foo", t="upload",
1458 file=("overridden", self.NEWFILE_CONTENTS))
1460 d.addCallback(self.failUnlessURIMatchesROChild, fn, filename)
1461 d.addCallback(lambda res:
1462 self.failUnlessChildContentsAre(fn, filename,
1463 self.NEWFILE_CONTENTS))
1464 target_url = self.public_url + "/foo/" + filename.encode("utf-8")
1465 d.addCallback(lambda res: self.GET(target_url))
1466 d.addCallback(lambda contents: self.failUnlessEqual(contents,
1467 self.NEWFILE_CONTENTS,
1471 def test_POST_upload_no_link(self):
1472 d = self.POST("/uri", t="upload",
1473 file=("new.txt", self.NEWFILE_CONTENTS))
1474 def _check_upload_results(page):
1475 # this should be a page which describes the results of the upload
1476 # that just finished.
1477 self.failUnless("Upload Results:" in page)
1478 self.failUnless("URI:" in page)
1479 uri_re = re.compile("URI: <tt><span>(.*)</span>")
1480 mo = uri_re.search(page)
1481 self.failUnless(mo, page)
1482 new_uri = mo.group(1)
1484 d.addCallback(_check_upload_results)
1485 d.addCallback(self.failUnlessCHKURIHasContents, self.NEWFILE_CONTENTS)
1488 def test_POST_upload_no_link_whendone(self):
1489 d = self.POST("/uri", t="upload", when_done="/",
1490 file=("new.txt", self.NEWFILE_CONTENTS))
1491 d.addBoth(self.shouldRedirect, "/")
1494 def shouldRedirect2(self, which, checker, callable, *args, **kwargs):
1495 d = defer.maybeDeferred(callable, *args, **kwargs)
1497 if isinstance(res, failure.Failure):
1498 res.trap(error.PageRedirect)
1499 statuscode = res.value.status
1500 target = res.value.location
1501 return checker(statuscode, target)
1502 self.fail("%s: callable was supposed to redirect, not return '%s'"
1507 def test_POST_upload_no_link_whendone_results(self):
1508 def check(statuscode, target):
1509 self.failUnlessEqual(statuscode, str(http.FOUND))
1510 self.failUnless(target.startswith(self.webish_url), target)
1511 return client.getPage(target, method="GET")
1512 d = self.shouldRedirect2("test_POST_upload_no_link_whendone_results",
1514 self.POST, "/uri", t="upload",
1515 when_done="/uri/%(uri)s",
1516 file=("new.txt", self.NEWFILE_CONTENTS))
1517 d.addCallback(lambda res:
1518 self.failUnlessEqual(res, self.NEWFILE_CONTENTS))
1521 def test_POST_upload_no_link_mutable(self):
1522 d = self.POST("/uri", t="upload", mutable="true",
1523 file=("new.txt", self.NEWFILE_CONTENTS))
1524 def _check(filecap):
1525 filecap = filecap.strip()
1526 self.failUnless(filecap.startswith("URI:SSK:"), filecap)
1527 self.filecap = filecap
1528 u = uri.WriteableSSKFileURI.init_from_string(filecap)
1529 self.failUnless(u.storage_index in FakeMutableFileNode.all_contents)
1530 n = self.s.create_node_from_uri(filecap)
1531 return n.download_best_version()
1532 d.addCallback(_check)
1534 self.failUnlessEqual(data, self.NEWFILE_CONTENTS)
1535 return self.GET("/uri/%s" % urllib.quote(self.filecap))
1536 d.addCallback(_check2)
1538 self.failUnlessEqual(data, self.NEWFILE_CONTENTS)
1539 return self.GET("/file/%s" % urllib.quote(self.filecap))
1540 d.addCallback(_check3)
1542 self.failUnlessEqual(data, self.NEWFILE_CONTENTS)
1543 d.addCallback(_check4)
1546 def test_POST_upload_no_link_mutable_toobig(self):
1547 d = self.shouldFail2(error.Error,
1548 "test_POST_upload_no_link_mutable_toobig",
1549 "413 Request Entity Too Large",
1550 "SDMF is limited to one segment, and 10001 > 10000",
1552 "/uri", t="upload", mutable="true",
1554 "b" * (self.s.MUTABLE_SIZELIMIT+1)) )
1557 def test_POST_upload_mutable(self):
1558 # this creates a mutable file
1559 d = self.POST(self.public_url + "/foo", t="upload", mutable="true",
1560 file=("new.txt", self.NEWFILE_CONTENTS))
1562 d.addCallback(self.failUnlessURIMatchesRWChild, fn, u"new.txt")
1563 d.addCallback(lambda res:
1564 self.failUnlessMutableChildContentsAre(fn, u"new.txt",
1565 self.NEWFILE_CONTENTS))
1566 d.addCallback(lambda res: self._foo_node.get(u"new.txt"))
1568 self.failUnless(IMutableFileNode.providedBy(newnode))
1569 self.failUnless(newnode.is_mutable())
1570 self.failIf(newnode.is_readonly())
1571 self._mutable_node = newnode
1572 self._mutable_uri = newnode.get_uri()
1575 # now upload it again and make sure that the URI doesn't change
1576 NEWER_CONTENTS = self.NEWFILE_CONTENTS + "newer\n"
1577 d.addCallback(lambda res:
1578 self.POST(self.public_url + "/foo", t="upload",
1580 file=("new.txt", NEWER_CONTENTS)))
1581 d.addCallback(self.failUnlessURIMatchesRWChild, fn, u"new.txt")
1582 d.addCallback(lambda res:
1583 self.failUnlessMutableChildContentsAre(fn, u"new.txt",
1585 d.addCallback(lambda res: self._foo_node.get(u"new.txt"))
1587 self.failUnless(IMutableFileNode.providedBy(newnode))
1588 self.failUnless(newnode.is_mutable())
1589 self.failIf(newnode.is_readonly())
1590 self.failUnlessEqual(self._mutable_uri, newnode.get_uri())
1591 d.addCallback(_got2)
1593 # upload a second time, using PUT instead of POST
1594 NEW2_CONTENTS = NEWER_CONTENTS + "overwrite with PUT\n"
1595 d.addCallback(lambda res:
1596 self.PUT(self.public_url + "/foo/new.txt", NEW2_CONTENTS))
1597 d.addCallback(self.failUnlessURIMatchesRWChild, fn, u"new.txt")
1598 d.addCallback(lambda res:
1599 self.failUnlessMutableChildContentsAre(fn, u"new.txt",
1602 # finally list the directory, since mutable files are displayed
1603 # slightly differently
1605 d.addCallback(lambda res:
1606 self.GET(self.public_url + "/foo/",
1607 followRedirect=True))
1608 def _check_page(res):
1609 # TODO: assert more about the contents
1610 self.failUnless("SSK" in res)
1612 d.addCallback(_check_page)
1614 d.addCallback(lambda res: self._foo_node.get(u"new.txt"))
1616 self.failUnless(IMutableFileNode.providedBy(newnode))
1617 self.failUnless(newnode.is_mutable())
1618 self.failIf(newnode.is_readonly())
1619 self.failUnlessEqual(self._mutable_uri, newnode.get_uri())
1620 d.addCallback(_got3)
1622 # look at the JSON form of the enclosing directory
1623 d.addCallback(lambda res:
1624 self.GET(self.public_url + "/foo/?t=json",
1625 followRedirect=True))
1626 def _check_page_json(res):
1627 parsed = simplejson.loads(res)
1628 self.failUnlessEqual(parsed[0], "dirnode")
1629 children = dict( [(unicode(name),value)
1631 in parsed[1]["children"].iteritems()] )
1632 self.failUnless("new.txt" in children)
1633 new_json = children["new.txt"]
1634 self.failUnlessEqual(new_json[0], "filenode")
1635 self.failUnless(new_json[1]["mutable"])
1636 self.failUnlessEqual(new_json[1]["rw_uri"], self._mutable_uri)
1637 ro_uri = unicode(self._mutable_node.get_readonly().to_string())
1638 self.failUnlessEqual(new_json[1]["ro_uri"], ro_uri)
1639 d.addCallback(_check_page_json)
1641 # and the JSON form of the file
1642 d.addCallback(lambda res:
1643 self.GET(self.public_url + "/foo/new.txt?t=json"))
1644 def _check_file_json(res):
1645 parsed = simplejson.loads(res)
1646 self.failUnlessEqual(parsed[0], "filenode")
1647 self.failUnless(parsed[1]["mutable"])
1648 self.failUnlessEqual(parsed[1]["rw_uri"], self._mutable_uri)
1649 ro_uri = unicode(self._mutable_node.get_readonly().to_string())
1650 self.failUnlessEqual(parsed[1]["ro_uri"], ro_uri)
1651 d.addCallback(_check_file_json)
1653 # and look at t=uri and t=readonly-uri
1654 d.addCallback(lambda res:
1655 self.GET(self.public_url + "/foo/new.txt?t=uri"))
1656 d.addCallback(lambda res: self.failUnlessEqual(res, self._mutable_uri))
1657 d.addCallback(lambda res:
1658 self.GET(self.public_url + "/foo/new.txt?t=readonly-uri"))
1659 def _check_ro_uri(res):
1660 ro_uri = unicode(self._mutable_node.get_readonly().to_string())
1661 self.failUnlessEqual(res, ro_uri)
1662 d.addCallback(_check_ro_uri)
1664 # make sure we can get to it from /uri/URI
1665 d.addCallback(lambda res:
1666 self.GET("/uri/%s" % urllib.quote(self._mutable_uri)))
1667 d.addCallback(lambda res:
1668 self.failUnlessEqual(res, NEW2_CONTENTS))
1670 # and that HEAD computes the size correctly
1671 d.addCallback(lambda res:
1672 self.HEAD(self.public_url + "/foo/new.txt",
1673 return_response=True))
1674 def _got_headers((res, status, headers)):
1675 self.failUnlessEqual(res, "")
1676 self.failUnlessEqual(headers["content-length"][0],
1677 str(len(NEW2_CONTENTS)))
1678 self.failUnlessEqual(headers["content-type"], ["text/plain"])
1679 d.addCallback(_got_headers)
1681 # make sure that size errors are displayed correctly for overwrite
1682 d.addCallback(lambda res:
1683 self.shouldFail2(error.Error,
1684 "test_POST_upload_mutable-toobig",
1685 "413 Request Entity Too Large",
1686 "SDMF is limited to one segment, and 10001 > 10000",
1688 self.public_url + "/foo", t="upload",
1691 "b" * (self.s.MUTABLE_SIZELIMIT+1)),
1694 d.addErrback(self.dump_error)
1697 def test_POST_upload_mutable_toobig(self):
1698 d = self.shouldFail2(error.Error,
1699 "test_POST_upload_mutable_toobig",
1700 "413 Request Entity Too Large",
1701 "SDMF is limited to one segment, and 10001 > 10000",
1703 self.public_url + "/foo",
1704 t="upload", mutable="true",
1706 "b" * (self.s.MUTABLE_SIZELIMIT+1)) )
1709 def dump_error(self, f):
1710 # if the web server returns an error code (like 400 Bad Request),
1711 # web.client.getPage puts the HTTP response body into the .response
1712 # attribute of the exception object that it gives back. It does not
1713 # appear in the Failure's repr(), so the ERROR that trial displays
1714 # will be rather terse and unhelpful. addErrback this method to the
1715 # end of your chain to get more information out of these errors.
1716 if f.check(error.Error):
1717 print "web.error.Error:"
1719 print f.value.response
1722 def test_POST_upload_replace(self):
1723 d = self.POST(self.public_url + "/foo", t="upload",
1724 file=("bar.txt", self.NEWFILE_CONTENTS))
1726 d.addCallback(self.failUnlessURIMatchesROChild, fn, u"bar.txt")
1727 d.addCallback(lambda res:
1728 self.failUnlessChildContentsAre(fn, u"bar.txt",
1729 self.NEWFILE_CONTENTS))
1732 def test_POST_upload_no_replace_ok(self):
1733 d = self.POST(self.public_url + "/foo?replace=false", t="upload",
1734 file=("new.txt", self.NEWFILE_CONTENTS))
1735 d.addCallback(lambda res: self.GET(self.public_url + "/foo/new.txt"))
1736 d.addCallback(lambda res: self.failUnlessEqual(res,
1737 self.NEWFILE_CONTENTS))
1740 def test_POST_upload_no_replace_queryarg(self):
1741 d = self.POST(self.public_url + "/foo?replace=false", t="upload",
1742 file=("bar.txt", self.NEWFILE_CONTENTS))
1743 d.addBoth(self.shouldFail, error.Error,
1744 "POST_upload_no_replace_queryarg",
1746 "There was already a child by that name, and you asked me "
1747 "to not replace it")
1748 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
1749 d.addCallback(self.failUnlessIsBarDotTxt)
1752 def test_POST_upload_no_replace_field(self):
1753 d = self.POST(self.public_url + "/foo", t="upload", replace="false",
1754 file=("bar.txt", self.NEWFILE_CONTENTS))
1755 d.addBoth(self.shouldFail, error.Error, "POST_upload_no_replace_field",
1757 "There was already a child by that name, and you asked me "
1758 "to not replace it")
1759 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
1760 d.addCallback(self.failUnlessIsBarDotTxt)
1763 def test_POST_upload_whendone(self):
1764 d = self.POST(self.public_url + "/foo", t="upload", when_done="/THERE",
1765 file=("new.txt", self.NEWFILE_CONTENTS))
1766 d.addBoth(self.shouldRedirect, "/THERE")
1768 d.addCallback(lambda res:
1769 self.failUnlessChildContentsAre(fn, u"new.txt",
1770 self.NEWFILE_CONTENTS))
1773 def test_POST_upload_named(self):
1775 d = self.POST(self.public_url + "/foo", t="upload",
1776 name="new.txt", file=self.NEWFILE_CONTENTS)
1777 d.addCallback(self.failUnlessURIMatchesROChild, fn, u"new.txt")
1778 d.addCallback(lambda res:
1779 self.failUnlessChildContentsAre(fn, u"new.txt",
1780 self.NEWFILE_CONTENTS))
1783 def test_POST_upload_named_badfilename(self):
1784 d = self.POST(self.public_url + "/foo", t="upload",
1785 name="slashes/are/bad.txt", file=self.NEWFILE_CONTENTS)
1786 d.addBoth(self.shouldFail, error.Error,
1787 "test_POST_upload_named_badfilename",
1789 "name= may not contain a slash",
1791 # make sure that nothing was added
1792 d.addCallback(lambda res:
1793 self.failUnlessNodeKeysAre(self._foo_node,
1794 [u"bar.txt", u"blockingfile",
1795 u"empty", u"n\u00fc.txt",
1799 def test_POST_FILEURL_check(self):
1800 bar_url = self.public_url + "/foo/bar.txt"
1801 d = self.POST(bar_url, t="check")
1803 self.failUnless("Healthy :" in res)
1804 d.addCallback(_check)
1805 redir_url = "http://allmydata.org/TARGET"
1806 def _check2(statuscode, target):
1807 self.failUnlessEqual(statuscode, str(http.FOUND))
1808 self.failUnlessEqual(target, redir_url)
1809 d.addCallback(lambda res:
1810 self.shouldRedirect2("test_POST_FILEURL_check",
1814 when_done=redir_url))
1815 d.addCallback(lambda res:
1816 self.POST(bar_url, t="check", return_to=redir_url))
1818 self.failUnless("Healthy :" in res)
1819 self.failUnless("Return to file" in res)
1820 self.failUnless(redir_url in res)
1821 d.addCallback(_check3)
1823 d.addCallback(lambda res:
1824 self.POST(bar_url, t="check", output="JSON"))
1825 def _check_json(res):
1826 data = simplejson.loads(res)
1827 self.failUnless("storage-index" in data)
1828 self.failUnless(data["results"]["healthy"])
1829 d.addCallback(_check_json)
1833 def test_POST_FILEURL_check_and_repair(self):
1834 bar_url = self.public_url + "/foo/bar.txt"
1835 d = self.POST(bar_url, t="check", repair="true")
1837 self.failUnless("Healthy :" in res)
1838 d.addCallback(_check)
1839 redir_url = "http://allmydata.org/TARGET"
1840 def _check2(statuscode, target):
1841 self.failUnlessEqual(statuscode, str(http.FOUND))
1842 self.failUnlessEqual(target, redir_url)
1843 d.addCallback(lambda res:
1844 self.shouldRedirect2("test_POST_FILEURL_check_and_repair",
1847 t="check", repair="true",
1848 when_done=redir_url))
1849 d.addCallback(lambda res:
1850 self.POST(bar_url, t="check", return_to=redir_url))
1852 self.failUnless("Healthy :" in res)
1853 self.failUnless("Return to file" in res)
1854 self.failUnless(redir_url in res)
1855 d.addCallback(_check3)
1858 def test_POST_DIRURL_check(self):
1859 foo_url = self.public_url + "/foo/"
1860 d = self.POST(foo_url, t="check")
1862 self.failUnless("Healthy :" in res, res)
1863 d.addCallback(_check)
1864 redir_url = "http://allmydata.org/TARGET"
1865 def _check2(statuscode, target):
1866 self.failUnlessEqual(statuscode, str(http.FOUND))
1867 self.failUnlessEqual(target, redir_url)
1868 d.addCallback(lambda res:
1869 self.shouldRedirect2("test_POST_DIRURL_check",
1873 when_done=redir_url))
1874 d.addCallback(lambda res:
1875 self.POST(foo_url, t="check", return_to=redir_url))
1877 self.failUnless("Healthy :" in res, res)
1878 self.failUnless("Return to file/directory" in res)
1879 self.failUnless(redir_url in res)
1880 d.addCallback(_check3)
1882 d.addCallback(lambda res:
1883 self.POST(foo_url, t="check", output="JSON"))
1884 def _check_json(res):
1885 data = simplejson.loads(res)
1886 self.failUnless("storage-index" in data)
1887 self.failUnless(data["results"]["healthy"])
1888 d.addCallback(_check_json)
1892 def test_POST_DIRURL_check_and_repair(self):
1893 foo_url = self.public_url + "/foo/"
1894 d = self.POST(foo_url, t="check", repair="true")
1896 self.failUnless("Healthy :" in res, res)
1897 d.addCallback(_check)
1898 redir_url = "http://allmydata.org/TARGET"
1899 def _check2(statuscode, target):
1900 self.failUnlessEqual(statuscode, str(http.FOUND))
1901 self.failUnlessEqual(target, redir_url)
1902 d.addCallback(lambda res:
1903 self.shouldRedirect2("test_POST_DIRURL_check_and_repair",
1906 t="check", repair="true",
1907 when_done=redir_url))
1908 d.addCallback(lambda res:
1909 self.POST(foo_url, t="check", return_to=redir_url))
1911 self.failUnless("Healthy :" in res)
1912 self.failUnless("Return to file/directory" in res)
1913 self.failUnless(redir_url in res)
1914 d.addCallback(_check3)
1917 def wait_for_operation(self, ignored, ophandle):
1918 url = "/operations/" + ophandle
1919 url += "?t=status&output=JSON"
1922 data = simplejson.loads(res)
1923 if not data["finished"]:
1924 d = self.stall(delay=1.0)
1925 d.addCallback(self.wait_for_operation, ophandle)
1931 def get_operation_results(self, ignored, ophandle, output=None):
1932 url = "/operations/" + ophandle
1935 url += "&output=" + output
1938 if output and output.lower() == "json":
1939 return simplejson.loads(res)
1944 def test_POST_DIRURL_deepcheck_no_ophandle(self):
1945 d = self.shouldFail2(error.Error,
1946 "test_POST_DIRURL_deepcheck_no_ophandle",
1948 "slow operation requires ophandle=",
1949 self.POST, self.public_url, t="start-deep-check")
1952 def test_POST_DIRURL_deepcheck(self):
1953 def _check_redirect(statuscode, target):
1954 self.failUnlessEqual(statuscode, str(http.FOUND))
1955 self.failUnless(target.endswith("/operations/123"))
1956 d = self.shouldRedirect2("test_POST_DIRURL_deepcheck", _check_redirect,
1957 self.POST, self.public_url,
1958 t="start-deep-check", ophandle="123")
1959 d.addCallback(self.wait_for_operation, "123")
1960 def _check_json(data):
1961 self.failUnlessEqual(data["finished"], True)
1962 self.failUnlessEqual(data["count-objects-checked"], 8)
1963 self.failUnlessEqual(data["count-objects-healthy"], 8)
1964 d.addCallback(_check_json)
1965 d.addCallback(self.get_operation_results, "123", "html")
1966 def _check_html(res):
1967 self.failUnless("Objects Checked: <span>8</span>" in res)
1968 self.failUnless("Objects Healthy: <span>8</span>" in res)
1969 d.addCallback(_check_html)
1971 d.addCallback(lambda res:
1972 self.GET("/operations/123/"))
1973 d.addCallback(_check_html) # should be the same as without the slash
1975 d.addCallback(lambda res:
1976 self.shouldFail2(error.Error, "one", "404 Not Found",
1977 "No detailed results for SI bogus",
1978 self.GET, "/operations/123/bogus"))
1980 foo_si = self._foo_node.get_storage_index()
1981 foo_si_s = base32.b2a(foo_si)
1982 d.addCallback(lambda res:
1983 self.GET("/operations/123/%s?output=JSON" % foo_si_s))
1984 def _check_foo_json(res):
1985 data = simplejson.loads(res)
1986 self.failUnlessEqual(data["storage-index"], foo_si_s)
1987 self.failUnless(data["results"]["healthy"])
1988 d.addCallback(_check_foo_json)
1991 def test_POST_DIRURL_deepcheck_and_repair(self):
1992 d = self.POST(self.public_url, t="start-deep-check", repair="true",
1993 ophandle="124", output="json", followRedirect=True)
1994 d.addCallback(self.wait_for_operation, "124")
1995 def _check_json(data):
1996 self.failUnlessEqual(data["finished"], True)
1997 self.failUnlessEqual(data["count-objects-checked"], 8)
1998 self.failUnlessEqual(data["count-objects-healthy-pre-repair"], 8)
1999 self.failUnlessEqual(data["count-objects-unhealthy-pre-repair"], 0)
2000 self.failUnlessEqual(data["count-corrupt-shares-pre-repair"], 0)
2001 self.failUnlessEqual(data["count-repairs-attempted"], 0)
2002 self.failUnlessEqual(data["count-repairs-successful"], 0)
2003 self.failUnlessEqual(data["count-repairs-unsuccessful"], 0)
2004 self.failUnlessEqual(data["count-objects-healthy-post-repair"], 8)
2005 self.failUnlessEqual(data["count-objects-unhealthy-post-repair"], 0)
2006 self.failUnlessEqual(data["count-corrupt-shares-post-repair"], 0)
2007 d.addCallback(_check_json)
2008 d.addCallback(self.get_operation_results, "124", "html")
2009 def _check_html(res):
2010 self.failUnless("Objects Checked: <span>8</span>" in res)
2012 self.failUnless("Objects Healthy (before repair): <span>8</span>" in res)
2013 self.failUnless("Objects Unhealthy (before repair): <span>0</span>" in res)
2014 self.failUnless("Corrupt Shares (before repair): <span>0</span>" in res)
2016 self.failUnless("Repairs Attempted: <span>0</span>" in res)
2017 self.failUnless("Repairs Successful: <span>0</span>" in res)
2018 self.failUnless("Repairs Unsuccessful: <span>0</span>" in res)
2020 self.failUnless("Objects Healthy (after repair): <span>8</span>" in res)
2021 self.failUnless("Objects Unhealthy (after repair): <span>0</span>" in res)
2022 self.failUnless("Corrupt Shares (after repair): <span>0</span>" in res)
2023 d.addCallback(_check_html)
2026 def test_POST_FILEURL_bad_t(self):
2027 d = self.shouldFail2(error.Error, "POST_bad_t", "400 Bad Request",
2028 "POST to file: bad t=bogus",
2029 self.POST, self.public_url + "/foo/bar.txt",
2033 def test_POST_mkdir(self): # return value?
2034 d = self.POST(self.public_url + "/foo", t="mkdir", name="newdir")
2035 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2036 d.addCallback(self.failUnlessNodeKeysAre, [])
2039 def test_POST_mkdir_initial_children(self):
2040 (newkids, caps) = self._create_initial_children()
2041 d = self.POST2(self.public_url +
2042 "/foo?t=mkdir-with-children&name=newdir",
2043 simplejson.dumps(newkids))
2044 d.addCallback(lambda res:
2045 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
2046 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2047 d.addCallback(self.failUnlessNodeKeysAre, newkids.keys())
2048 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2049 d.addCallback(self.failUnlessROChildURIIs, u"child-imm", caps['filecap1'])
2052 def test_POST_mkdir_immutable(self):
2053 (newkids, caps) = self._create_immutable_children()
2054 d = self.POST2(self.public_url +
2055 "/foo?t=mkdir-immutable&name=newdir",
2056 simplejson.dumps(newkids))
2057 d.addCallback(lambda res:
2058 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
2059 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2060 d.addCallback(self.failUnlessNodeKeysAre, newkids.keys())
2061 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2062 d.addCallback(self.failUnlessROChildURIIs, u"child-imm", caps['filecap1'])
2063 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2064 d.addCallback(self.failUnlessROChildURIIs, u"unknownchild-imm", caps['unknown_immcap'])
2065 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2066 d.addCallback(self.failUnlessROChildURIIs, u"dirchild-imm", caps['immdircap'])
2069 def test_POST_mkdir_immutable_bad(self):
2070 (newkids, caps) = self._create_initial_children()
2071 d = self.shouldFail2(error.Error, "test_POST_mkdir_immutable_bad",
2073 "needed to be immutable but was not",
2076 "/foo?t=mkdir-immutable&name=newdir",
2077 simplejson.dumps(newkids))
2080 def test_POST_mkdir_2(self):
2081 d = self.POST(self.public_url + "/foo/newdir?t=mkdir", "")
2082 d.addCallback(lambda res:
2083 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
2084 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2085 d.addCallback(self.failUnlessNodeKeysAre, [])
2088 def test_POST_mkdirs_2(self):
2089 d = self.POST(self.public_url + "/foo/bardir/newdir?t=mkdir", "")
2090 d.addCallback(lambda res:
2091 self.failUnlessNodeHasChild(self._foo_node, u"bardir"))
2092 d.addCallback(lambda res: self._foo_node.get(u"bardir"))
2093 d.addCallback(lambda bardirnode: bardirnode.get(u"newdir"))
2094 d.addCallback(self.failUnlessNodeKeysAre, [])
2097 def test_POST_mkdir_no_parentdir_noredirect(self):
2098 d = self.POST("/uri?t=mkdir")
2099 def _after_mkdir(res):
2100 uri.DirectoryURI.init_from_string(res)
2101 d.addCallback(_after_mkdir)
2104 def test_POST_mkdir_no_parentdir_noredirect2(self):
2105 # make sure form-based arguments (as on the welcome page) still work
2106 d = self.POST("/uri", t="mkdir")
2107 def _after_mkdir(res):
2108 uri.DirectoryURI.init_from_string(res)
2109 d.addCallback(_after_mkdir)
2110 d.addErrback(self.explain_web_error)
2113 def test_POST_mkdir_no_parentdir_redirect(self):
2114 d = self.POST("/uri?t=mkdir&redirect_to_result=true")
2115 d.addBoth(self.shouldRedirect, None, statuscode='303')
2116 def _check_target(target):
2117 target = urllib.unquote(target)
2118 self.failUnless(target.startswith("uri/URI:DIR2:"), target)
2119 d.addCallback(_check_target)
2122 def test_POST_mkdir_no_parentdir_redirect2(self):
2123 d = self.POST("/uri", t="mkdir", redirect_to_result="true")
2124 d.addBoth(self.shouldRedirect, None, statuscode='303')
2125 def _check_target(target):
2126 target = urllib.unquote(target)
2127 self.failUnless(target.startswith("uri/URI:DIR2:"), target)
2128 d.addCallback(_check_target)
2129 d.addErrback(self.explain_web_error)
2132 def _make_readonly(self, u):
2133 ro_uri = uri.from_string(u).get_readonly()
2136 return ro_uri.to_string()
2138 def _create_initial_children(self):
2139 contents, n, filecap1 = self.makefile(12)
2140 md1 = {"metakey1": "metavalue1"}
2141 filecap2 = make_mutable_file_uri()
2142 node3 = self.s.create_node_from_uri(make_mutable_file_uri())
2143 filecap3 = node3.get_readonly_uri()
2144 unknown_rwcap = "lafs://from_the_future"
2145 unknown_rocap = "ro.lafs://readonly_from_the_future"
2146 unknown_immcap = "imm.lafs://immutable_from_the_future"
2147 node4 = self.s.create_node_from_uri(make_mutable_file_uri())
2148 dircap = DirectoryNode(node4, None, None).get_uri()
2149 newkids = {u"child-imm": ["filenode", {"rw_uri": filecap1,
2150 "ro_uri": self._make_readonly(filecap1),
2151 "metadata": md1, }],
2152 u"child-mutable": ["filenode", {"rw_uri": filecap2,
2153 "ro_uri": self._make_readonly(filecap2)}],
2154 u"child-mutable-ro": ["filenode", {"ro_uri": filecap3}],
2155 u"unknownchild-rw": ["unknown", {"rw_uri": unknown_rwcap,
2156 "ro_uri": unknown_rocap}],
2157 u"unknownchild-ro": ["unknown", {"ro_uri": unknown_rocap}],
2158 u"unknownchild-imm": ["unknown", {"ro_uri": unknown_immcap}],
2159 u"dirchild": ["dirnode", {"rw_uri": dircap,
2160 "ro_uri": self._make_readonly(dircap)}],
2162 return newkids, {'filecap1': filecap1,
2163 'filecap2': filecap2,
2164 'filecap3': filecap3,
2165 'unknown_rwcap': unknown_rwcap,
2166 'unknown_rocap': unknown_rocap,
2167 'unknown_immcap': unknown_immcap,
2170 def _create_immutable_children(self):
2171 contents, n, filecap1 = self.makefile(12)
2172 md1 = {"metakey1": "metavalue1"}
2173 tnode = create_chk_filenode("immutable directory contents\n"*10)
2174 dnode = DirectoryNode(tnode, None, None)
2175 assert not dnode.is_mutable()
2176 unknown_immcap = "imm.lafs://immutable_from_the_future"
2177 immdircap = dnode.get_uri()
2178 newkids = {u"child-imm": ["filenode", {"ro_uri": filecap1,
2179 "metadata": md1, }],
2180 u"unknownchild-imm": ["unknown", {"ro_uri": unknown_immcap}],
2181 u"dirchild-imm": ["dirnode", {"ro_uri": immdircap}],
2183 return newkids, {'filecap1': filecap1,
2184 'unknown_immcap': unknown_immcap,
2185 'immdircap': immdircap}
2187 def test_POST_mkdir_no_parentdir_initial_children(self):
2188 (newkids, caps) = self._create_initial_children()
2189 d = self.POST2("/uri?t=mkdir-with-children", simplejson.dumps(newkids))
2190 def _after_mkdir(res):
2191 self.failUnless(res.startswith("URI:DIR"), res)
2192 n = self.s.create_node_from_uri(res)
2193 d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
2194 d2.addCallback(lambda ign:
2195 self.failUnlessROChildURIIs(n, u"child-imm",
2197 d2.addCallback(lambda ign:
2198 self.failUnlessRWChildURIIs(n, u"child-mutable",
2200 d2.addCallback(lambda ign:
2201 self.failUnlessROChildURIIs(n, u"child-mutable-ro",
2203 d2.addCallback(lambda ign:
2204 self.failUnlessRWChildURIIs(n, u"unknownchild-rw",
2205 caps['unknown_rwcap']))
2206 d2.addCallback(lambda ign:
2207 self.failUnlessROChildURIIs(n, u"unknownchild-ro",
2208 caps['unknown_rocap']))
2209 d2.addCallback(lambda ign:
2210 self.failUnlessROChildURIIs(n, u"unknownchild-imm",
2211 caps['unknown_immcap']))
2212 d2.addCallback(lambda ign:
2213 self.failUnlessRWChildURIIs(n, u"dirchild",
2216 d.addCallback(_after_mkdir)
2219 def test_POST_mkdir_no_parentdir_unexpected_children(self):
2220 # the regular /uri?t=mkdir operation is specified to ignore its body.
2221 # Only t=mkdir-with-children pays attention to it.
2222 (newkids, caps) = self._create_initial_children()
2223 d = self.shouldHTTPError("POST t=mkdir unexpected children",
2225 "t=mkdir does not accept children=, "
2226 "try t=mkdir-with-children instead",
2227 self.POST2, "/uri?t=mkdir", # without children
2228 simplejson.dumps(newkids))
2231 def test_POST_noparent_bad(self):
2232 d = self.shouldHTTPError("POST /uri?t=bogus", 400, "Bad Request",
2233 "/uri accepts only PUT, PUT?t=mkdir, "
2234 "POST?t=upload, and POST?t=mkdir",
2235 self.POST, "/uri?t=bogus")
2238 def test_POST_mkdir_no_parentdir_immutable(self):
2239 (newkids, caps) = self._create_immutable_children()
2240 d = self.POST2("/uri?t=mkdir-immutable", simplejson.dumps(newkids))
2241 def _after_mkdir(res):
2242 self.failUnless(res.startswith("URI:DIR"), res)
2243 n = self.s.create_node_from_uri(res)
2244 d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
2245 d2.addCallback(lambda ign:
2246 self.failUnlessROChildURIIs(n, u"child-imm",
2248 d2.addCallback(lambda ign:
2249 self.failUnlessROChildURIIs(n, u"unknownchild-imm",
2250 caps['unknown_immcap']))
2251 d2.addCallback(lambda ign:
2252 self.failUnlessROChildURIIs(n, u"dirchild-imm",
2255 d.addCallback(_after_mkdir)
2258 def test_POST_mkdir_no_parentdir_immutable_bad(self):
2259 (newkids, caps) = self._create_initial_children()
2260 d = self.shouldFail2(error.Error,
2261 "test_POST_mkdir_no_parentdir_immutable_bad",
2263 "needed to be immutable but was not",
2265 "/uri?t=mkdir-immutable",
2266 simplejson.dumps(newkids))
2269 def test_welcome_page_mkdir_button(self):
2270 # Fetch the welcome page.
2272 def _after_get_welcome_page(res):
2273 MKDIR_BUTTON_RE = re.compile(
2274 '<form action="([^"]*)" method="post".*?'
2275 '<input type="hidden" name="t" value="([^"]*)" />'
2276 '<input type="hidden" name="([^"]*)" value="([^"]*)" />'
2277 '<input type="submit" value="Create a directory" />',
2279 mo = MKDIR_BUTTON_RE.search(res)
2280 formaction = mo.group(1)
2282 formaname = mo.group(3)
2283 formavalue = mo.group(4)
2284 return (formaction, formt, formaname, formavalue)
2285 d.addCallback(_after_get_welcome_page)
2286 def _after_parse_form(res):
2287 (formaction, formt, formaname, formavalue) = res
2288 return self.POST("/%s?t=%s&%s=%s" % (formaction, formt, formaname, formavalue))
2289 d.addCallback(_after_parse_form)
2290 d.addBoth(self.shouldRedirect, None, statuscode='303')
2293 def test_POST_mkdir_replace(self): # return value?
2294 d = self.POST(self.public_url + "/foo", t="mkdir", name="sub")
2295 d.addCallback(lambda res: self._foo_node.get(u"sub"))
2296 d.addCallback(self.failUnlessNodeKeysAre, [])
2299 def test_POST_mkdir_no_replace_queryarg(self): # return value?
2300 d = self.POST(self.public_url + "/foo?replace=false", t="mkdir", name="sub")
2301 d.addBoth(self.shouldFail, error.Error,
2302 "POST_mkdir_no_replace_queryarg",
2304 "There was already a child by that name, and you asked me "
2305 "to not replace it")
2306 d.addCallback(lambda res: self._foo_node.get(u"sub"))
2307 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
2310 def test_POST_mkdir_no_replace_field(self): # return value?
2311 d = self.POST(self.public_url + "/foo", t="mkdir", name="sub",
2313 d.addBoth(self.shouldFail, error.Error, "POST_mkdir_no_replace_field",
2315 "There was already a child by that name, and you asked me "
2316 "to not replace it")
2317 d.addCallback(lambda res: self._foo_node.get(u"sub"))
2318 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
2321 def test_POST_mkdir_whendone_field(self):
2322 d = self.POST(self.public_url + "/foo",
2323 t="mkdir", name="newdir", when_done="/THERE")
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_mkdir_whendone_queryarg(self):
2330 d = self.POST(self.public_url + "/foo?when_done=/THERE",
2331 t="mkdir", name="newdir")
2332 d.addBoth(self.shouldRedirect, "/THERE")
2333 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2334 d.addCallback(self.failUnlessNodeKeysAre, [])
2337 def test_POST_bad_t(self):
2338 d = self.shouldFail2(error.Error, "POST_bad_t", "400 Bad Request",
2339 "POST to a directory with bad t=BOGUS",
2340 self.POST, self.public_url + "/foo", t="BOGUS")
2343 def test_POST_set_children(self, command_name="set_children"):
2344 contents9, n9, newuri9 = self.makefile(9)
2345 contents10, n10, newuri10 = self.makefile(10)
2346 contents11, n11, newuri11 = self.makefile(11)
2349 "atomic_added_1": [ "filenode", { "rw_uri": "%s",
2352 "ctime": 1002777696.7564139,
2353 "mtime": 1002777696.7564139
2356 "atomic_added_2": [ "filenode", { "rw_uri": "%s",
2359 "ctime": 1002777696.7564139,
2360 "mtime": 1002777696.7564139
2363 "atomic_added_3": [ "filenode", { "rw_uri": "%s",
2366 "ctime": 1002777696.7564139,
2367 "mtime": 1002777696.7564139
2370 }""" % (newuri9, newuri10, newuri11)
2372 url = self.webish_url + self.public_url + "/foo" + "?t=" + command_name
2374 d = client.getPage(url, method="POST", postdata=reqbody)
2376 self.failUnlessURIMatchesROChild(newuri9, self._foo_node, u"atomic_added_1")
2377 self.failUnlessURIMatchesROChild(newuri10, self._foo_node, u"atomic_added_2")
2378 self.failUnlessURIMatchesROChild(newuri11, self._foo_node, u"atomic_added_3")
2380 d.addCallback(_then)
2381 d.addErrback(self.dump_error)
2384 def test_POST_set_children_with_hyphen(self):
2385 return self.test_POST_set_children(command_name="set-children")
2387 def test_POST_link_uri(self):
2388 contents, n, newuri = self.makefile(8)
2389 d = self.POST(self.public_url + "/foo", t="uri", name="new.txt", uri=newuri)
2390 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"new.txt")
2391 d.addCallback(lambda res:
2392 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
2396 def test_POST_link_uri_replace(self):
2397 contents, n, newuri = self.makefile(8)
2398 d = self.POST(self.public_url + "/foo", t="uri", name="bar.txt", uri=newuri)
2399 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"bar.txt")
2400 d.addCallback(lambda res:
2401 self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
2405 def test_POST_link_uri_unknown_bad(self):
2406 newuri = "lafs://from_the_future"
2407 d = self.POST(self.public_url + "/foo", t="uri", name="future.txt", uri=newuri)
2408 d.addBoth(self.shouldFail, error.Error,
2409 "POST_link_uri_unknown_bad",
2411 "unknown cap in a write slot")
2414 def test_POST_link_uri_unknown_ro_good(self):
2415 newuri = "ro.lafs://readonly_from_the_future"
2416 d = self.POST(self.public_url + "/foo", t="uri", name="future-ro.txt", uri=newuri)
2417 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"future-ro.txt")
2420 def test_POST_link_uri_unknown_imm_good(self):
2421 newuri = "imm.lafs://immutable_from_the_future"
2422 d = self.POST(self.public_url + "/foo", t="uri", name="future-imm.txt", uri=newuri)
2423 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"future-imm.txt")
2426 def test_POST_link_uri_no_replace_queryarg(self):
2427 contents, n, newuri = self.makefile(8)
2428 d = self.POST(self.public_url + "/foo?replace=false", t="uri",
2429 name="bar.txt", uri=newuri)
2430 d.addBoth(self.shouldFail, error.Error,
2431 "POST_link_uri_no_replace_queryarg",
2433 "There was already a child by that name, and you asked me "
2434 "to not replace it")
2435 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
2436 d.addCallback(self.failUnlessIsBarDotTxt)
2439 def test_POST_link_uri_no_replace_field(self):
2440 contents, n, newuri = self.makefile(8)
2441 d = self.POST(self.public_url + "/foo", t="uri", replace="false",
2442 name="bar.txt", uri=newuri)
2443 d.addBoth(self.shouldFail, error.Error,
2444 "POST_link_uri_no_replace_field",
2446 "There was already a child by that name, and you asked me "
2447 "to not replace it")
2448 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
2449 d.addCallback(self.failUnlessIsBarDotTxt)
2452 def test_POST_delete(self):
2453 d = self.POST(self.public_url + "/foo", t="delete", name="bar.txt")
2454 d.addCallback(lambda res: self._foo_node.list())
2455 def _check(children):
2456 self.failIf(u"bar.txt" in children)
2457 d.addCallback(_check)
2460 def test_POST_rename_file(self):
2461 d = self.POST(self.public_url + "/foo", t="rename",
2462 from_name="bar.txt", to_name='wibble.txt')
2463 d.addCallback(lambda res:
2464 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
2465 d.addCallback(lambda res:
2466 self.failUnlessNodeHasChild(self._foo_node, u"wibble.txt"))
2467 d.addCallback(lambda res: self.GET(self.public_url + "/foo/wibble.txt"))
2468 d.addCallback(self.failUnlessIsBarDotTxt)
2469 d.addCallback(lambda res: self.GET(self.public_url + "/foo/wibble.txt?t=json"))
2470 d.addCallback(self.failUnlessIsBarJSON)
2473 def test_POST_rename_file_redundant(self):
2474 d = self.POST(self.public_url + "/foo", t="rename",
2475 from_name="bar.txt", to_name='bar.txt')
2476 d.addCallback(lambda res:
2477 self.failUnlessNodeHasChild(self._foo_node, u"bar.txt"))
2478 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
2479 d.addCallback(self.failUnlessIsBarDotTxt)
2480 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt?t=json"))
2481 d.addCallback(self.failUnlessIsBarJSON)
2484 def test_POST_rename_file_replace(self):
2485 # rename a file and replace a directory with it
2486 d = self.POST(self.public_url + "/foo", t="rename",
2487 from_name="bar.txt", to_name='empty')
2488 d.addCallback(lambda res:
2489 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
2490 d.addCallback(lambda res:
2491 self.failUnlessNodeHasChild(self._foo_node, u"empty"))
2492 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty"))
2493 d.addCallback(self.failUnlessIsBarDotTxt)
2494 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
2495 d.addCallback(self.failUnlessIsBarJSON)
2498 def test_POST_rename_file_no_replace_queryarg(self):
2499 # rename a file and replace a directory with it
2500 d = self.POST(self.public_url + "/foo?replace=false", t="rename",
2501 from_name="bar.txt", to_name='empty')
2502 d.addBoth(self.shouldFail, error.Error,
2503 "POST_rename_file_no_replace_queryarg",
2505 "There was already a child by that name, and you asked me "
2506 "to not replace it")
2507 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
2508 d.addCallback(self.failUnlessIsEmptyJSON)
2511 def test_POST_rename_file_no_replace_field(self):
2512 # rename a file and replace a directory with it
2513 d = self.POST(self.public_url + "/foo", t="rename", replace="false",
2514 from_name="bar.txt", to_name='empty')
2515 d.addBoth(self.shouldFail, error.Error,
2516 "POST_rename_file_no_replace_field",
2518 "There was already a child by that name, and you asked me "
2519 "to not replace it")
2520 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
2521 d.addCallback(self.failUnlessIsEmptyJSON)
2524 def failUnlessIsEmptyJSON(self, res):
2525 data = simplejson.loads(res)
2526 self.failUnlessEqual(data[0], "dirnode", data)
2527 self.failUnlessEqual(len(data[1]["children"]), 0)
2529 def test_POST_rename_file_slash_fail(self):
2530 d = self.POST(self.public_url + "/foo", t="rename",
2531 from_name="bar.txt", to_name='kirk/spock.txt')
2532 d.addBoth(self.shouldFail, error.Error,
2533 "test_POST_rename_file_slash_fail",
2535 "to_name= may not contain a slash",
2537 d.addCallback(lambda res:
2538 self.failUnlessNodeHasChild(self._foo_node, u"bar.txt"))
2541 def test_POST_rename_dir(self):
2542 d = self.POST(self.public_url, t="rename",
2543 from_name="foo", to_name='plunk')
2544 d.addCallback(lambda res:
2545 self.failIfNodeHasChild(self.public_root, u"foo"))
2546 d.addCallback(lambda res:
2547 self.failUnlessNodeHasChild(self.public_root, u"plunk"))
2548 d.addCallback(lambda res: self.GET(self.public_url + "/plunk?t=json"))
2549 d.addCallback(self.failUnlessIsFooJSON)
2552 def shouldRedirect(self, res, target=None, statuscode=None, which=""):
2553 """ If target is not None then the redirection has to go to target. If
2554 statuscode is not None then the redirection has to be accomplished with
2555 that HTTP status code."""
2556 if not isinstance(res, failure.Failure):
2557 to_where = (target is None) and "somewhere" or ("to " + target)
2558 self.fail("%s: we were expecting to get redirected %s, not get an"
2559 " actual page: %s" % (which, to_where, res))
2560 res.trap(error.PageRedirect)
2561 if statuscode is not None:
2562 self.failUnlessEqual(res.value.status, statuscode,
2563 "%s: not a redirect" % which)
2564 if target is not None:
2565 # the PageRedirect does not seem to capture the uri= query arg
2566 # properly, so we can't check for it.
2567 realtarget = self.webish_url + target
2568 self.failUnlessEqual(res.value.location, realtarget,
2569 "%s: wrong target" % which)
2570 return res.value.location
2572 def test_GET_URI_form(self):
2573 base = "/uri?uri=%s" % self._bar_txt_uri
2574 # this is supposed to give us a redirect to /uri/$URI, plus arguments
2575 targetbase = "/uri/%s" % urllib.quote(self._bar_txt_uri)
2577 d.addBoth(self.shouldRedirect, targetbase)
2578 d.addCallback(lambda res: self.GET(base+"&filename=bar.txt"))
2579 d.addBoth(self.shouldRedirect, targetbase+"?filename=bar.txt")
2580 d.addCallback(lambda res: self.GET(base+"&t=json"))
2581 d.addBoth(self.shouldRedirect, targetbase+"?t=json")
2582 d.addCallback(self.log, "about to get file by uri")
2583 d.addCallback(lambda res: self.GET(base, followRedirect=True))
2584 d.addCallback(self.failUnlessIsBarDotTxt)
2585 d.addCallback(self.log, "got file by uri, about to get dir by uri")
2586 d.addCallback(lambda res: self.GET("/uri?uri=%s&t=json" % self._foo_uri,
2587 followRedirect=True))
2588 d.addCallback(self.failUnlessIsFooJSON)
2589 d.addCallback(self.log, "got dir by uri")
2593 def test_GET_URI_form_bad(self):
2594 d = self.shouldFail2(error.Error, "test_GET_URI_form_bad",
2595 "400 Bad Request", "GET /uri requires uri=",
2599 def test_GET_rename_form(self):
2600 d = self.GET(self.public_url + "/foo?t=rename-form&name=bar.txt",
2601 followRedirect=True)
2603 self.failUnless('name="when_done" value="."' in res, res)
2604 self.failUnless(re.search(r'name="from_name" value="bar\.txt"', res))
2605 d.addCallback(_check)
2608 def log(self, res, msg):
2609 #print "MSG: %s RES: %s" % (msg, res)
2613 def test_GET_URI_URL(self):
2614 base = "/uri/%s" % self._bar_txt_uri
2616 d.addCallback(self.failUnlessIsBarDotTxt)
2617 d.addCallback(lambda res: self.GET(base+"?filename=bar.txt"))
2618 d.addCallback(self.failUnlessIsBarDotTxt)
2619 d.addCallback(lambda res: self.GET(base+"?filename=bar.txt&save=true"))
2620 d.addCallback(self.failUnlessIsBarDotTxt)
2623 def test_GET_URI_URL_dir(self):
2624 base = "/uri/%s?t=json" % self._foo_uri
2626 d.addCallback(self.failUnlessIsFooJSON)
2629 def test_GET_URI_URL_missing(self):
2630 base = "/uri/%s" % self._bad_file_uri
2631 d = self.shouldHTTPError("test_GET_URI_URL_missing",
2632 http.GONE, None, "NotEnoughSharesError",
2634 # TODO: how can we exercise both sides of WebDownloadTarget.fail
2635 # here? we must arrange for a download to fail after target.open()
2636 # has been called, and then inspect the response to see that it is
2637 # shorter than we expected.
2640 def test_PUT_DIRURL_uri(self):
2641 d = self.s.create_dirnode()
2643 new_uri = dn.get_uri()
2644 # replace /foo with a new (empty) directory
2645 d = self.PUT(self.public_url + "/foo?t=uri", new_uri)
2646 d.addCallback(lambda res:
2647 self.failUnlessEqual(res.strip(), new_uri))
2648 d.addCallback(lambda res:
2649 self.failUnlessRWChildURIIs(self.public_root,
2653 d.addCallback(_made_dir)
2656 def test_PUT_DIRURL_uri_noreplace(self):
2657 d = self.s.create_dirnode()
2659 new_uri = dn.get_uri()
2660 # replace /foo with a new (empty) directory, but ask that
2661 # replace=false, so it should fail
2662 d = self.shouldFail2(error.Error, "test_PUT_DIRURL_uri_noreplace",
2663 "409 Conflict", "There was already a child by that name, and you asked me to not replace it",
2665 self.public_url + "/foo?t=uri&replace=false",
2667 d.addCallback(lambda res:
2668 self.failUnlessRWChildURIIs(self.public_root,
2672 d.addCallback(_made_dir)
2675 def test_PUT_DIRURL_bad_t(self):
2676 d = self.shouldFail2(error.Error, "test_PUT_DIRURL_bad_t",
2677 "400 Bad Request", "PUT to a directory",
2678 self.PUT, self.public_url + "/foo?t=BOGUS", "")
2679 d.addCallback(lambda res:
2680 self.failUnlessRWChildURIIs(self.public_root,
2685 def test_PUT_NEWFILEURL_uri(self):
2686 contents, n, new_uri = self.makefile(8)
2687 d = self.PUT(self.public_url + "/foo/new.txt?t=uri", new_uri)
2688 d.addCallback(lambda res: self.failUnlessEqual(res.strip(), new_uri))
2689 d.addCallback(lambda res:
2690 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
2694 def test_PUT_NEWFILEURL_uri_replace(self):
2695 contents, n, new_uri = self.makefile(8)
2696 d = self.PUT(self.public_url + "/foo/bar.txt?t=uri", new_uri)
2697 d.addCallback(lambda res: self.failUnlessEqual(res.strip(), new_uri))
2698 d.addCallback(lambda res:
2699 self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
2703 def test_PUT_NEWFILEURL_uri_no_replace(self):
2704 contents, n, new_uri = self.makefile(8)
2705 d = self.PUT(self.public_url + "/foo/bar.txt?t=uri&replace=false", new_uri)
2706 d.addBoth(self.shouldFail, error.Error, "PUT_NEWFILEURL_uri_no_replace",
2708 "There was already a child by that name, and you asked me "
2709 "to not replace it")
2712 def test_PUT_NEWFILEURL_uri_unknown_bad(self):
2713 new_uri = "lafs://from_the_future"
2714 d = self.PUT(self.public_url + "/foo/put-future.txt?t=uri", new_uri)
2715 d.addBoth(self.shouldFail, error.Error,
2716 "POST_put_uri_unknown_bad",
2718 "unknown cap in a write slot")
2721 def test_PUT_NEWFILEURL_uri_unknown_ro_good(self):
2722 new_uri = "ro.lafs://readonly_from_the_future"
2723 d = self.PUT(self.public_url + "/foo/put-future-ro.txt?t=uri", new_uri)
2724 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node,
2725 u"put-future-ro.txt")
2728 def test_PUT_NEWFILEURL_uri_unknown_imm_good(self):
2729 new_uri = "imm.lafs://immutable_from_the_future"
2730 d = self.PUT(self.public_url + "/foo/put-future-imm.txt?t=uri", new_uri)
2731 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node,
2732 u"put-future-imm.txt")
2735 def test_PUT_NEWFILE_URI(self):
2736 file_contents = "New file contents here\n"
2737 d = self.PUT("/uri", file_contents)
2739 assert isinstance(uri, str), uri
2740 self.failUnless(uri in FakeCHKFileNode.all_contents)
2741 self.failUnlessEqual(FakeCHKFileNode.all_contents[uri],
2743 return self.GET("/uri/%s" % uri)
2744 d.addCallback(_check)
2746 self.failUnlessEqual(res, file_contents)
2747 d.addCallback(_check2)
2750 def test_PUT_NEWFILE_URI_not_mutable(self):
2751 file_contents = "New file contents here\n"
2752 d = self.PUT("/uri?mutable=false", file_contents)
2754 assert isinstance(uri, str), uri
2755 self.failUnless(uri in FakeCHKFileNode.all_contents)
2756 self.failUnlessEqual(FakeCHKFileNode.all_contents[uri],
2758 return self.GET("/uri/%s" % uri)
2759 d.addCallback(_check)
2761 self.failUnlessEqual(res, file_contents)
2762 d.addCallback(_check2)
2765 def test_PUT_NEWFILE_URI_only_PUT(self):
2766 d = self.PUT("/uri?t=bogus", "")
2767 d.addBoth(self.shouldFail, error.Error,
2768 "PUT_NEWFILE_URI_only_PUT",
2770 "/uri accepts only PUT, PUT?t=mkdir, POST?t=upload, and POST?t=mkdir")
2773 def test_PUT_NEWFILE_URI_mutable(self):
2774 file_contents = "New file contents here\n"
2775 d = self.PUT("/uri?mutable=true", file_contents)
2776 def _check1(filecap):
2777 filecap = filecap.strip()
2778 self.failUnless(filecap.startswith("URI:SSK:"), filecap)
2779 self.filecap = filecap
2780 u = uri.WriteableSSKFileURI.init_from_string(filecap)
2781 self.failUnless(u.storage_index in FakeMutableFileNode.all_contents)
2782 n = self.s.create_node_from_uri(filecap)
2783 return n.download_best_version()
2784 d.addCallback(_check1)
2786 self.failUnlessEqual(data, file_contents)
2787 return self.GET("/uri/%s" % urllib.quote(self.filecap))
2788 d.addCallback(_check2)
2790 self.failUnlessEqual(res, file_contents)
2791 d.addCallback(_check3)
2794 def test_PUT_mkdir(self):
2795 d = self.PUT("/uri?t=mkdir", "")
2797 n = self.s.create_node_from_uri(uri.strip())
2798 d2 = self.failUnlessNodeKeysAre(n, [])
2799 d2.addCallback(lambda res:
2800 self.GET("/uri/%s?t=json" % uri))
2802 d.addCallback(_check)
2803 d.addCallback(self.failUnlessIsEmptyJSON)
2806 def test_POST_check(self):
2807 d = self.POST(self.public_url + "/foo", t="check", name="bar.txt")
2809 # this returns a string form of the results, which are probably
2810 # None since we're using fake filenodes.
2811 # TODO: verify that the check actually happened, by changing
2812 # FakeCHKFileNode to count how many times .check() has been
2815 d.addCallback(_done)
2818 def test_bad_method(self):
2819 url = self.webish_url + self.public_url + "/foo/bar.txt"
2820 d = self.shouldHTTPError("test_bad_method",
2821 501, "Not Implemented",
2822 "I don't know how to treat a BOGUS request.",
2823 client.getPage, url, method="BOGUS")
2826 def test_short_url(self):
2827 url = self.webish_url + "/uri"
2828 d = self.shouldHTTPError("test_short_url", 501, "Not Implemented",
2829 "I don't know how to treat a DELETE request.",
2830 client.getPage, url, method="DELETE")
2833 def test_ophandle_bad(self):
2834 url = self.webish_url + "/operations/bogus?t=status"
2835 d = self.shouldHTTPError("test_ophandle_bad", 404, "404 Not Found",
2836 "unknown/expired handle 'bogus'",
2837 client.getPage, url)
2840 def test_ophandle_cancel(self):
2841 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=128",
2842 followRedirect=True)
2843 d.addCallback(lambda ignored:
2844 self.GET("/operations/128?t=status&output=JSON"))
2846 data = simplejson.loads(res)
2847 self.failUnless("finished" in data, res)
2848 monitor = self.ws.root.child_operations.handles["128"][0]
2849 d = self.POST("/operations/128?t=cancel&output=JSON")
2851 data = simplejson.loads(res)
2852 self.failUnless("finished" in data, res)
2853 # t=cancel causes the handle to be forgotten
2854 self.failUnless(monitor.is_cancelled())
2855 d.addCallback(_check2)
2857 d.addCallback(_check1)
2858 d.addCallback(lambda ignored:
2859 self.shouldHTTPError("test_ophandle_cancel",
2860 404, "404 Not Found",
2861 "unknown/expired handle '128'",
2863 "/operations/128?t=status&output=JSON"))
2866 def test_ophandle_retainfor(self):
2867 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=129&retain-for=60",
2868 followRedirect=True)
2869 d.addCallback(lambda ignored:
2870 self.GET("/operations/129?t=status&output=JSON&retain-for=0"))
2872 data = simplejson.loads(res)
2873 self.failUnless("finished" in data, res)
2874 d.addCallback(_check1)
2875 # the retain-for=0 will cause the handle to be expired very soon
2876 d.addCallback(self.stall, 2.0)
2877 d.addCallback(lambda ignored:
2878 self.shouldHTTPError("test_ophandle_retainfor",
2879 404, "404 Not Found",
2880 "unknown/expired handle '129'",
2882 "/operations/129?t=status&output=JSON"))
2885 def test_ophandle_release_after_complete(self):
2886 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=130",
2887 followRedirect=True)
2888 d.addCallback(self.wait_for_operation, "130")
2889 d.addCallback(lambda ignored:
2890 self.GET("/operations/130?t=status&output=JSON&release-after-complete=true"))
2891 # the release-after-complete=true will cause the handle to be expired
2892 d.addCallback(lambda ignored:
2893 self.shouldHTTPError("test_ophandle_release_after_complete",
2894 404, "404 Not Found",
2895 "unknown/expired handle '130'",
2897 "/operations/130?t=status&output=JSON"))
2900 def test_uncollected_ophandle_expiration(self):
2901 # uncollected ophandles should expire after 4 days
2902 def _make_uncollected_ophandle(ophandle):
2903 d = self.POST(self.public_url +
2904 "/foo/?t=start-manifest&ophandle=%d" % ophandle,
2905 followRedirect=False)
2906 # When we start the operation, the webapi server will want
2907 # to redirect us to the page for the ophandle, so we get
2908 # confirmation that the operation has started. If the
2909 # manifest operation has finished by the time we get there,
2910 # following that redirect (by setting followRedirect=True
2911 # above) has the side effect of collecting the ophandle that
2912 # we've just created, which means that we can't use the
2913 # ophandle to test the uncollected timeout anymore. So,
2914 # instead, catch the 302 here and don't follow it.
2915 d.addBoth(self.should302, "uncollected_ophandle_creation")
2917 # Create an ophandle, don't collect it, then advance the clock by
2918 # 4 days - 1 second and make sure that the ophandle is still there.
2919 d = _make_uncollected_ophandle(131)
2920 d.addCallback(lambda ign:
2921 self.clock.advance((96*60*60) - 1)) # 96 hours = 4 days
2922 d.addCallback(lambda ign:
2923 self.GET("/operations/131?t=status&output=JSON"))
2925 data = simplejson.loads(res)
2926 self.failUnless("finished" in data, res)
2927 d.addCallback(_check1)
2928 # Create an ophandle, don't collect it, then try to collect it
2929 # after 4 days. It should be gone.
2930 d.addCallback(lambda ign:
2931 _make_uncollected_ophandle(132))
2932 d.addCallback(lambda ign:
2933 self.clock.advance(96*60*60))
2934 d.addCallback(lambda ign:
2935 self.shouldHTTPError("test_uncollected_ophandle_expired_after_100_hours",
2936 404, "404 Not Found",
2937 "unknown/expired handle '132'",
2939 "/operations/132?t=status&output=JSON"))
2942 def test_collected_ophandle_expiration(self):
2943 # collected ophandles should expire after 1 day
2944 def _make_collected_ophandle(ophandle):
2945 d = self.POST(self.public_url +
2946 "/foo/?t=start-manifest&ophandle=%d" % ophandle,
2947 followRedirect=True)
2948 # By following the initial redirect, we collect the ophandle
2949 # we've just created.
2951 # Create a collected ophandle, then collect it after 23 hours
2952 # and 59 seconds to make sure that it is still there.
2953 d = _make_collected_ophandle(133)
2954 d.addCallback(lambda ign:
2955 self.clock.advance((24*60*60) - 1))
2956 d.addCallback(lambda ign:
2957 self.GET("/operations/133?t=status&output=JSON"))
2959 data = simplejson.loads(res)
2960 self.failUnless("finished" in data, res)
2961 d.addCallback(_check1)
2962 # Create another uncollected ophandle, then try to collect it
2963 # after 24 hours to make sure that it is gone.
2964 d.addCallback(lambda ign:
2965 _make_collected_ophandle(134))
2966 d.addCallback(lambda ign:
2967 self.clock.advance(24*60*60))
2968 d.addCallback(lambda ign:
2969 self.shouldHTTPError("test_collected_ophandle_expired_after_1000_minutes",
2970 404, "404 Not Found",
2971 "unknown/expired handle '134'",
2973 "/operations/134?t=status&output=JSON"))
2976 def test_incident(self):
2977 d = self.POST("/report_incident", details="eek")
2979 self.failUnless("Thank you for your report!" in res, res)
2980 d.addCallback(_done)
2983 def test_static(self):
2984 webdir = os.path.join(self.staticdir, "subdir")
2985 fileutil.make_dirs(webdir)
2986 f = open(os.path.join(webdir, "hello.txt"), "wb")
2990 d = self.GET("/static/subdir/hello.txt")
2992 self.failUnlessEqual(res, "hello")
2993 d.addCallback(_check)
2997 class Util(unittest.TestCase, ShouldFailMixin):
2998 def test_parse_replace_arg(self):
2999 self.failUnlessEqual(common.parse_replace_arg("true"), True)
3000 self.failUnlessEqual(common.parse_replace_arg("false"), False)
3001 self.failUnlessEqual(common.parse_replace_arg("only-files"),
3003 self.shouldFail(AssertionError, "test_parse_replace_arg", "",
3004 common.parse_replace_arg, "only_fles")
3006 def test_abbreviate_time(self):
3007 self.failUnlessEqual(common.abbreviate_time(None), "")
3008 self.failUnlessEqual(common.abbreviate_time(1.234), "1.23s")
3009 self.failUnlessEqual(common.abbreviate_time(0.123), "123ms")
3010 self.failUnlessEqual(common.abbreviate_time(0.00123), "1.2ms")
3011 self.failUnlessEqual(common.abbreviate_time(0.000123), "123us")
3013 def test_abbreviate_rate(self):
3014 self.failUnlessEqual(common.abbreviate_rate(None), "")
3015 self.failUnlessEqual(common.abbreviate_rate(1234000), "1.23MBps")
3016 self.failUnlessEqual(common.abbreviate_rate(12340), "12.3kBps")
3017 self.failUnlessEqual(common.abbreviate_rate(123), "123Bps")
3019 def test_abbreviate_size(self):
3020 self.failUnlessEqual(common.abbreviate_size(None), "")
3021 self.failUnlessEqual(common.abbreviate_size(1.23*1000*1000*1000), "1.23GB")
3022 self.failUnlessEqual(common.abbreviate_size(1.23*1000*1000), "1.23MB")
3023 self.failUnlessEqual(common.abbreviate_size(1230), "1.2kB")
3024 self.failUnlessEqual(common.abbreviate_size(123), "123B")
3026 def test_plural(self):
3028 return "%d second%s" % (s, status.plural(s))
3029 self.failUnlessEqual(convert(0), "0 seconds")
3030 self.failUnlessEqual(convert(1), "1 second")
3031 self.failUnlessEqual(convert(2), "2 seconds")
3033 return "has share%s: %s" % (status.plural(s), ",".join(s))
3034 self.failUnlessEqual(convert2([]), "has shares: ")
3035 self.failUnlessEqual(convert2(["1"]), "has share: 1")
3036 self.failUnlessEqual(convert2(["1","2"]), "has shares: 1,2")
3039 class Grid(GridTestMixin, WebErrorMixin, unittest.TestCase, ShouldFailMixin):
3041 def CHECK(self, ign, which, args, clientnum=0):
3042 fileurl = self.fileurls[which]
3043 url = fileurl + "?" + args
3044 return self.GET(url, method="POST", clientnum=clientnum)
3046 def test_filecheck(self):
3047 self.basedir = "web/Grid/filecheck"
3049 c0 = self.g.clients[0]
3052 d = c0.upload(upload.Data(DATA, convergence=""))
3053 def _stash_uri(ur, which):
3054 self.uris[which] = ur.uri
3055 d.addCallback(_stash_uri, "good")
3056 d.addCallback(lambda ign:
3057 c0.upload(upload.Data(DATA+"1", convergence="")))
3058 d.addCallback(_stash_uri, "sick")
3059 d.addCallback(lambda ign:
3060 c0.upload(upload.Data(DATA+"2", convergence="")))
3061 d.addCallback(_stash_uri, "dead")
3062 def _stash_mutable_uri(n, which):
3063 self.uris[which] = n.get_uri()
3064 assert isinstance(self.uris[which], str)
3065 d.addCallback(lambda ign: c0.create_mutable_file(DATA+"3"))
3066 d.addCallback(_stash_mutable_uri, "corrupt")
3067 d.addCallback(lambda ign:
3068 c0.upload(upload.Data("literal", convergence="")))
3069 d.addCallback(_stash_uri, "small")
3070 d.addCallback(lambda ign: c0.create_immutable_dirnode({}))
3071 d.addCallback(_stash_mutable_uri, "smalldir")
3073 def _compute_fileurls(ignored):
3075 for which in self.uris:
3076 self.fileurls[which] = "uri/" + urllib.quote(self.uris[which])
3077 d.addCallback(_compute_fileurls)
3079 def _clobber_shares(ignored):
3080 good_shares = self.find_shares(self.uris["good"])
3081 self.failUnlessEqual(len(good_shares), 10)
3082 sick_shares = self.find_shares(self.uris["sick"])
3083 os.unlink(sick_shares[0][2])
3084 dead_shares = self.find_shares(self.uris["dead"])
3085 for i in range(1, 10):
3086 os.unlink(dead_shares[i][2])
3087 c_shares = self.find_shares(self.uris["corrupt"])
3088 cso = CorruptShareOptions()
3089 cso.stdout = StringIO()
3090 cso.parseOptions([c_shares[0][2]])
3092 d.addCallback(_clobber_shares)
3094 d.addCallback(self.CHECK, "good", "t=check")
3095 def _got_html_good(res):
3096 self.failUnless("Healthy" in res, res)
3097 self.failIf("Not Healthy" in res, res)
3098 d.addCallback(_got_html_good)
3099 d.addCallback(self.CHECK, "good", "t=check&return_to=somewhere")
3100 def _got_html_good_return_to(res):
3101 self.failUnless("Healthy" in res, res)
3102 self.failIf("Not Healthy" in res, res)
3103 self.failUnless('<a href="somewhere">Return to file'
3105 d.addCallback(_got_html_good_return_to)
3106 d.addCallback(self.CHECK, "good", "t=check&output=json")
3107 def _got_json_good(res):
3108 r = simplejson.loads(res)
3109 self.failUnlessEqual(r["summary"], "Healthy")
3110 self.failUnless(r["results"]["healthy"])
3111 self.failIf(r["results"]["needs-rebalancing"])
3112 self.failUnless(r["results"]["recoverable"])
3113 d.addCallback(_got_json_good)
3115 d.addCallback(self.CHECK, "small", "t=check")
3116 def _got_html_small(res):
3117 self.failUnless("Literal files are always healthy" in res, res)
3118 self.failIf("Not Healthy" in res, res)
3119 d.addCallback(_got_html_small)
3120 d.addCallback(self.CHECK, "small", "t=check&return_to=somewhere")
3121 def _got_html_small_return_to(res):
3122 self.failUnless("Literal files are always healthy" in res, res)
3123 self.failIf("Not Healthy" in res, res)
3124 self.failUnless('<a href="somewhere">Return to file'
3126 d.addCallback(_got_html_small_return_to)
3127 d.addCallback(self.CHECK, "small", "t=check&output=json")
3128 def _got_json_small(res):
3129 r = simplejson.loads(res)
3130 self.failUnlessEqual(r["storage-index"], "")
3131 self.failUnless(r["results"]["healthy"])
3132 d.addCallback(_got_json_small)
3134 d.addCallback(self.CHECK, "smalldir", "t=check")
3135 def _got_html_smalldir(res):
3136 self.failUnless("Literal files are always healthy" in res, res)
3137 self.failIf("Not Healthy" in res, res)
3138 d.addCallback(_got_html_smalldir)
3139 d.addCallback(self.CHECK, "smalldir", "t=check&output=json")
3140 def _got_json_smalldir(res):
3141 r = simplejson.loads(res)
3142 self.failUnlessEqual(r["storage-index"], "")
3143 self.failUnless(r["results"]["healthy"])
3144 d.addCallback(_got_json_smalldir)
3146 d.addCallback(self.CHECK, "sick", "t=check")
3147 def _got_html_sick(res):
3148 self.failUnless("Not Healthy" in res, res)
3149 d.addCallback(_got_html_sick)
3150 d.addCallback(self.CHECK, "sick", "t=check&output=json")
3151 def _got_json_sick(res):
3152 r = simplejson.loads(res)
3153 self.failUnlessEqual(r["summary"],
3154 "Not Healthy: 9 shares (enc 3-of-10)")
3155 self.failIf(r["results"]["healthy"])
3156 self.failIf(r["results"]["needs-rebalancing"])
3157 self.failUnless(r["results"]["recoverable"])
3158 d.addCallback(_got_json_sick)
3160 d.addCallback(self.CHECK, "dead", "t=check")
3161 def _got_html_dead(res):
3162 self.failUnless("Not Healthy" in res, res)
3163 d.addCallback(_got_html_dead)
3164 d.addCallback(self.CHECK, "dead", "t=check&output=json")
3165 def _got_json_dead(res):
3166 r = simplejson.loads(res)
3167 self.failUnlessEqual(r["summary"],
3168 "Not Healthy: 1 shares (enc 3-of-10)")
3169 self.failIf(r["results"]["healthy"])
3170 self.failIf(r["results"]["needs-rebalancing"])
3171 self.failIf(r["results"]["recoverable"])
3172 d.addCallback(_got_json_dead)
3174 d.addCallback(self.CHECK, "corrupt", "t=check&verify=true")
3175 def _got_html_corrupt(res):
3176 self.failUnless("Not Healthy! : Unhealthy" in res, res)
3177 d.addCallback(_got_html_corrupt)
3178 d.addCallback(self.CHECK, "corrupt", "t=check&verify=true&output=json")
3179 def _got_json_corrupt(res):
3180 r = simplejson.loads(res)
3181 self.failUnless("Unhealthy: 9 shares (enc 3-of-10)" in r["summary"],
3183 self.failIf(r["results"]["healthy"])
3184 self.failUnless(r["results"]["recoverable"])
3185 self.failUnlessEqual(r["results"]["count-shares-good"], 9)
3186 self.failUnlessEqual(r["results"]["count-corrupt-shares"], 1)
3187 d.addCallback(_got_json_corrupt)
3189 d.addErrback(self.explain_web_error)
3192 def test_repair_html(self):
3193 self.basedir = "web/Grid/repair_html"
3195 c0 = self.g.clients[0]
3198 d = c0.upload(upload.Data(DATA, convergence=""))
3199 def _stash_uri(ur, which):
3200 self.uris[which] = ur.uri
3201 d.addCallback(_stash_uri, "good")
3202 d.addCallback(lambda ign:
3203 c0.upload(upload.Data(DATA+"1", convergence="")))
3204 d.addCallback(_stash_uri, "sick")
3205 d.addCallback(lambda ign:
3206 c0.upload(upload.Data(DATA+"2", convergence="")))
3207 d.addCallback(_stash_uri, "dead")
3208 def _stash_mutable_uri(n, which):
3209 self.uris[which] = n.get_uri()
3210 assert isinstance(self.uris[which], str)
3211 d.addCallback(lambda ign: c0.create_mutable_file(DATA+"3"))
3212 d.addCallback(_stash_mutable_uri, "corrupt")
3214 def _compute_fileurls(ignored):
3216 for which in self.uris:
3217 self.fileurls[which] = "uri/" + urllib.quote(self.uris[which])
3218 d.addCallback(_compute_fileurls)
3220 def _clobber_shares(ignored):
3221 good_shares = self.find_shares(self.uris["good"])
3222 self.failUnlessEqual(len(good_shares), 10)
3223 sick_shares = self.find_shares(self.uris["sick"])
3224 os.unlink(sick_shares[0][2])
3225 dead_shares = self.find_shares(self.uris["dead"])
3226 for i in range(1, 10):
3227 os.unlink(dead_shares[i][2])
3228 c_shares = self.find_shares(self.uris["corrupt"])
3229 cso = CorruptShareOptions()
3230 cso.stdout = StringIO()
3231 cso.parseOptions([c_shares[0][2]])
3233 d.addCallback(_clobber_shares)
3235 d.addCallback(self.CHECK, "good", "t=check&repair=true")
3236 def _got_html_good(res):
3237 self.failUnless("Healthy" in res, res)
3238 self.failIf("Not Healthy" in res, res)
3239 self.failUnless("No repair necessary" in res, res)
3240 d.addCallback(_got_html_good)
3242 d.addCallback(self.CHECK, "sick", "t=check&repair=true")
3243 def _got_html_sick(res):
3244 self.failUnless("Healthy : healthy" in res, res)
3245 self.failIf("Not Healthy" in res, res)
3246 self.failUnless("Repair successful" in res, res)
3247 d.addCallback(_got_html_sick)
3249 # repair of a dead file will fail, of course, but it isn't yet
3250 # clear how this should be reported. Right now it shows up as
3253 #d.addCallback(self.CHECK, "dead", "t=check&repair=true")
3254 #def _got_html_dead(res):
3256 # self.failUnless("Healthy : healthy" in res, res)
3257 # self.failIf("Not Healthy" in res, res)
3258 # self.failUnless("No repair necessary" in res, res)
3259 #d.addCallback(_got_html_dead)
3261 d.addCallback(self.CHECK, "corrupt", "t=check&verify=true&repair=true")
3262 def _got_html_corrupt(res):
3263 self.failUnless("Healthy : Healthy" in res, res)
3264 self.failIf("Not Healthy" in res, res)
3265 self.failUnless("Repair successful" in res, res)
3266 d.addCallback(_got_html_corrupt)
3268 d.addErrback(self.explain_web_error)
3271 def test_repair_json(self):
3272 self.basedir = "web/Grid/repair_json"
3274 c0 = self.g.clients[0]
3277 d = c0.upload(upload.Data(DATA+"1", convergence=""))
3278 def _stash_uri(ur, which):
3279 self.uris[which] = ur.uri
3280 d.addCallback(_stash_uri, "sick")
3282 def _compute_fileurls(ignored):
3284 for which in self.uris:
3285 self.fileurls[which] = "uri/" + urllib.quote(self.uris[which])
3286 d.addCallback(_compute_fileurls)
3288 def _clobber_shares(ignored):
3289 sick_shares = self.find_shares(self.uris["sick"])
3290 os.unlink(sick_shares[0][2])
3291 d.addCallback(_clobber_shares)
3293 d.addCallback(self.CHECK, "sick", "t=check&repair=true&output=json")
3294 def _got_json_sick(res):
3295 r = simplejson.loads(res)
3296 self.failUnlessEqual(r["repair-attempted"], True)
3297 self.failUnlessEqual(r["repair-successful"], True)
3298 self.failUnlessEqual(r["pre-repair-results"]["summary"],
3299 "Not Healthy: 9 shares (enc 3-of-10)")
3300 self.failIf(r["pre-repair-results"]["results"]["healthy"])
3301 self.failUnlessEqual(r["post-repair-results"]["summary"], "healthy")
3302 self.failUnless(r["post-repair-results"]["results"]["healthy"])
3303 d.addCallback(_got_json_sick)
3305 d.addErrback(self.explain_web_error)
3308 def test_unknown(self, immutable=False):
3309 self.basedir = "web/Grid/unknown"
3311 self.basedir = "web/Grid/unknown-immutable"
3314 c0 = self.g.clients[0]
3318 future_write_uri = "x-tahoe-crazy://I_am_from_the_future."
3319 future_read_uri = "x-tahoe-crazy-readonly://I_am_from_the_future."
3320 # the future cap format may contain slashes, which must be tolerated
3321 expected_info_url = "uri/%s?t=info" % urllib.quote(future_write_uri,
3325 name = u"future-imm"
3326 future_node = UnknownNode(None, future_read_uri, deep_immutable=True)
3327 d = c0.create_immutable_dirnode({name: (future_node, {})})
3330 future_node = UnknownNode(future_write_uri, future_read_uri)
3331 d = c0.create_dirnode()
3333 def _stash_root_and_create_file(n):
3335 self.rooturl = "uri/" + urllib.quote(n.get_uri()) + "/"
3336 self.rourl = "uri/" + urllib.quote(n.get_readonly_uri()) + "/"
3338 return self.rootnode.set_node(name, future_node)
3339 d.addCallback(_stash_root_and_create_file)
3341 # make sure directory listing tolerates unknown nodes
3342 d.addCallback(lambda ign: self.GET(self.rooturl))
3343 def _check_directory_html(res, expected_type_suffix):
3344 pattern = re.compile(r'<td>\?%s</td>[ \t\n\r]*'
3345 '<td>%s</td>' % (expected_type_suffix, str(name)),
3347 self.failUnless(re.search(pattern, res), res)
3348 # find the More Info link for name, should be relative
3349 mo = re.search(r'<a href="([^"]+)">More Info</a>', res)
3350 info_url = mo.group(1)
3351 self.failUnlessEqual(info_url, "%s?t=info" % (str(name),))
3353 d.addCallback(_check_directory_html, "-IMM")
3355 d.addCallback(_check_directory_html, "")
3357 d.addCallback(lambda ign: self.GET(self.rooturl+"?t=json"))
3358 def _check_directory_json(res, expect_rw_uri):
3359 data = simplejson.loads(res)
3360 self.failUnlessEqual(data[0], "dirnode")
3361 f = data[1]["children"][name]
3362 self.failUnlessEqual(f[0], "unknown")
3364 self.failUnlessEqual(f[1]["rw_uri"], future_write_uri)
3366 self.failIfIn("rw_uri", f[1])
3368 self.failUnlessEqual(f[1]["ro_uri"], "imm." + future_read_uri)
3370 self.failUnlessEqual(f[1]["ro_uri"], "ro." + future_read_uri)
3371 self.failUnless("metadata" in f[1])
3372 d.addCallback(_check_directory_json, expect_rw_uri=not immutable)
3374 def _check_info(res, expect_rw_uri, expect_ro_uri):
3375 self.failUnlessIn("Object Type: <span>unknown</span>", res)
3377 self.failUnlessIn(future_write_uri, res)
3379 self.failUnlessIn(future_read_uri, res)
3381 self.failIfIn(future_read_uri, res)
3382 self.failIfIn("Raw data as", res)
3383 self.failIfIn("Directory writecap", res)
3384 self.failIfIn("Checker Operations", res)
3385 self.failIfIn("Mutable File Operations", res)
3386 self.failIfIn("Directory Operations", res)
3388 # FIXME: these should have expect_rw_uri=not immutable; I don't know
3389 # why they fail. Possibly related to ticket #922.
3391 d.addCallback(lambda ign: self.GET(expected_info_url))
3392 d.addCallback(_check_info, expect_rw_uri=False, expect_ro_uri=False)
3393 d.addCallback(lambda ign: self.GET("%s%s?t=info" % (self.rooturl, str(name))))
3394 d.addCallback(_check_info, expect_rw_uri=False, expect_ro_uri=True)
3396 def _check_json(res, expect_rw_uri):
3397 data = simplejson.loads(res)
3398 self.failUnlessEqual(data[0], "unknown")
3400 self.failUnlessEqual(data[1]["rw_uri"], future_write_uri)
3402 self.failIfIn("rw_uri", data[1])
3405 self.failUnlessEqual(data[1]["ro_uri"], "imm." + future_read_uri)
3406 self.failUnlessEqual(data[1]["mutable"], False)
3408 self.failUnlessEqual(data[1]["ro_uri"], "ro." + future_read_uri)
3409 self.failUnlessEqual(data[1]["mutable"], True)
3411 self.failUnlessEqual(data[1]["ro_uri"], "ro." + future_read_uri)
3412 self.failIf("mutable" in data[1], data[1])
3414 # TODO: check metadata contents
3415 self.failUnless("metadata" in data[1])
3417 d.addCallback(lambda ign: self.GET("%s%s?t=json" % (self.rooturl, str(name))))
3418 d.addCallback(_check_json, expect_rw_uri=not immutable)
3420 # and make sure that a read-only version of the directory can be
3421 # rendered too. This version will not have future_write_uri, whether
3422 # or not future_node was immutable.
3423 d.addCallback(lambda ign: self.GET(self.rourl))
3425 d.addCallback(_check_directory_html, "-IMM")
3427 d.addCallback(_check_directory_html, "-RO")
3429 d.addCallback(lambda ign: self.GET(self.rourl+"?t=json"))
3430 d.addCallback(_check_directory_json, expect_rw_uri=False)
3432 d.addCallback(lambda ign: self.GET("%s%s?t=json" % (self.rourl, str(name))))
3433 d.addCallback(_check_json, expect_rw_uri=False)
3435 # TODO: check that getting t=info from the Info link in the ro directory
3436 # works, and does not include the writecap URI.
3439 def test_immutable_unknown(self):
3440 return self.test_unknown(immutable=True)
3442 def test_mutant_dirnodes_are_omitted(self):
3443 self.basedir = "web/Grid/mutant_dirnodes_are_omitted"
3446 c = self.g.clients[0]
3451 lonely_uri = "URI:LIT:n5xgk" # LIT for "one"
3452 mut_write_uri = "URI:SSK:vfvcbdfbszyrsaxchgevhmmlii:euw4iw7bbnkrrwpzuburbhppuxhc3gwxv26f6imekhz7zyw2ojnq"
3453 mut_read_uri = "URI:SSK-RO:e3mdrzfwhoq42hy5ubcz6rp3o4:ybyibhnp3vvwuq2vaw2ckjmesgkklfs6ghxleztqidihjyofgw7q"
3455 # This method tests mainly dirnode, but we'd have to duplicate code in order to
3456 # test the dirnode and web layers separately.
3458 # 'lonely' is a valid LIT child, 'ro' is a mutant child with an SSK-RO readcap,
3459 # and 'write-in-ro' is a mutant child with an SSK writecap in the ro_uri field.
3460 # When the directory is read, the mutants should be silently disposed of, leaving
3461 # their lonely sibling.
3462 # We don't test the case of a retrieving a cap from the encrypted rw_uri field,
3463 # because immutable directories don't have a writecap and therefore that field
3464 # isn't (and can't be) decrypted.
3465 # TODO: The field still exists in the netstring. Technically we should check what
3466 # happens if something is put there (_unpack_contents should raise ValueError),
3467 # but that can wait.
3469 lonely_child = nm.create_from_cap(lonely_uri)
3470 mutant_ro_child = nm.create_from_cap(mut_read_uri)
3471 mutant_write_in_ro_child = nm.create_from_cap(mut_write_uri)
3473 def _by_hook_or_by_crook():
3475 for n in [mutant_ro_child, mutant_write_in_ro_child]:
3476 n.is_allowed_in_immutable_directory = _by_hook_or_by_crook
3478 mutant_write_in_ro_child.get_write_uri = lambda: None
3479 mutant_write_in_ro_child.get_readonly_uri = lambda: mut_write_uri
3481 kids = {u"lonely": (lonely_child, {}),
3482 u"ro": (mutant_ro_child, {}),
3483 u"write-in-ro": (mutant_write_in_ro_child, {}),
3485 d = c.create_immutable_dirnode(kids)
3488 self.failUnless(isinstance(dn, dirnode.DirectoryNode))
3489 self.failIf(dn.is_mutable())
3490 self.failUnless(dn.is_readonly())
3491 # This checks that if we somehow ended up calling dn._decrypt_rwcapdata, it would fail.
3492 self.failIf(hasattr(dn._node, 'get_writekey'))
3494 self.failUnless("RO-IMM" in rep)
3496 self.failUnlessIn("CHK", cap.to_string())
3499 self.rooturl = "uri/" + urllib.quote(dn.get_uri()) + "/"
3500 return download_to_data(dn._node)
3501 d.addCallback(_created)
3503 def _check_data(data):
3504 # Decode the netstring representation of the directory to check that all children
3505 # are present. This is a bit of an abstraction violation, but there's not really
3506 # any other way to do it given that the real DirectoryNode._unpack_contents would
3507 # strip the mutant children out (which is what we're trying to test, later).
3510 while position < len(data):
3511 entries, position = split_netstring(data, 1, position)
3513 (name_utf8, ro_uri, rwcapdata, metadata_s), subpos = split_netstring(entry, 4)
3514 name = name_utf8.decode("utf-8")
3515 self.failUnless(rwcapdata == "")
3516 self.failUnless(name in kids)
3517 (expected_child, ign) = kids[name]
3518 self.failUnlessEqual(ro_uri, expected_child.get_readonly_uri())
3521 self.failUnlessEqual(numkids, 3)
3522 return self.rootnode.list()
3523 d.addCallback(_check_data)
3525 # Now when we use the real directory listing code, the mutants should be absent.
3526 def _check_kids(children):
3527 self.failUnlessEqual(sorted(children.keys()), [u"lonely"])
3528 lonely_node, lonely_metadata = children[u"lonely"]
3530 self.failUnlessEqual(lonely_node.get_write_uri(), None)
3531 self.failUnlessEqual(lonely_node.get_readonly_uri(), lonely_uri)
3532 d.addCallback(_check_kids)
3534 d.addCallback(lambda ign: nm.create_from_cap(self.cap.to_string()))
3535 d.addCallback(lambda n: n.list())
3536 d.addCallback(_check_kids) # again with dirnode recreated from cap
3538 # Make sure the lonely child can be listed in HTML...
3539 d.addCallback(lambda ign: self.GET(self.rooturl))
3540 def _check_html(res):
3541 self.failIfIn("URI:SSK", res)
3542 get_lonely = "".join([r'<td>FILE</td>',
3544 r'<a href="[^"]+%s[^"]+">lonely</a>' % (urllib.quote(lonely_uri),),
3546 r'\s+<td>%d</td>' % len("one"),
3548 self.failUnless(re.search(get_lonely, res), res)
3550 # find the More Info link for name, should be relative
3551 mo = re.search(r'<a href="([^"]+)">More Info</a>', res)
3552 info_url = mo.group(1)
3553 self.failUnless(info_url.endswith(urllib.quote(lonely_uri) + "?t=info"), info_url)
3554 d.addCallback(_check_html)
3557 d.addCallback(lambda ign: self.GET(self.rooturl+"?t=json"))
3558 def _check_json(res):
3559 data = simplejson.loads(res)
3560 self.failUnlessEqual(data[0], "dirnode")
3561 listed_children = data[1]["children"]
3562 self.failUnlessEqual(sorted(listed_children.keys()), [u"lonely"])
3563 ll_type, ll_data = listed_children[u"lonely"]
3564 self.failUnlessEqual(ll_type, "filenode")
3565 self.failIf("rw_uri" in ll_data)
3566 self.failUnlessEqual(ll_data["ro_uri"], lonely_uri)
3567 d.addCallback(_check_json)
3570 def test_deep_check(self):
3571 self.basedir = "web/Grid/deep_check"
3573 c0 = self.g.clients[0]
3577 d = c0.create_dirnode()
3578 def _stash_root_and_create_file(n):
3580 self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri()) + "/"
3581 return n.add_file(u"good", upload.Data(DATA, convergence=""))
3582 d.addCallback(_stash_root_and_create_file)
3583 def _stash_uri(fn, which):
3584 self.uris[which] = fn.get_uri()
3586 d.addCallback(_stash_uri, "good")
3587 d.addCallback(lambda ign:
3588 self.rootnode.add_file(u"small",
3589 upload.Data("literal",
3591 d.addCallback(_stash_uri, "small")
3592 d.addCallback(lambda ign:
3593 self.rootnode.add_file(u"sick",
3594 upload.Data(DATA+"1",
3596 d.addCallback(_stash_uri, "sick")
3598 # this tests that deep-check and stream-manifest will ignore
3599 # UnknownNode instances. Hopefully this will also cover deep-stats.
3600 future_write_uri = "x-tahoe-crazy://I_am_from_the_future."
3601 future_read_uri = "x-tahoe-crazy-readonly://I_am_from_the_future."
3602 future_node = UnknownNode(future_write_uri, future_read_uri)
3603 d.addCallback(lambda ign: self.rootnode.set_node(u"future", future_node))
3605 def _clobber_shares(ignored):
3606 self.delete_shares_numbered(self.uris["sick"], [0,1])
3607 d.addCallback(_clobber_shares)
3615 d.addCallback(self.CHECK, "root", "t=stream-deep-check")
3618 units = [simplejson.loads(line)
3619 for line in res.splitlines()
3622 print "response is:", res
3623 print "undecodeable line was '%s'" % line
3625 self.failUnlessEqual(len(units), 5+1)
3626 # should be parent-first
3628 self.failUnlessEqual(u0["path"], [])
3629 self.failUnlessEqual(u0["type"], "directory")
3630 self.failUnlessEqual(u0["cap"], self.rootnode.get_uri())
3631 u0cr = u0["check-results"]
3632 self.failUnlessEqual(u0cr["results"]["count-shares-good"], 10)
3634 ugood = [u for u in units
3635 if u["type"] == "file" and u["path"] == [u"good"]][0]
3636 self.failUnlessEqual(ugood["cap"], self.uris["good"])
3637 ugoodcr = ugood["check-results"]
3638 self.failUnlessEqual(ugoodcr["results"]["count-shares-good"], 10)
3641 self.failUnlessEqual(stats["type"], "stats")
3643 self.failUnlessEqual(s["count-immutable-files"], 2)
3644 self.failUnlessEqual(s["count-literal-files"], 1)
3645 self.failUnlessEqual(s["count-directories"], 1)
3646 self.failUnlessEqual(s["count-unknown"], 1)
3647 d.addCallback(_done)
3649 d.addCallback(self.CHECK, "root", "t=stream-manifest")
3650 def _check_manifest(res):
3651 self.failUnless(res.endswith("\n"))
3652 units = [simplejson.loads(t) for t in res[:-1].split("\n")]
3653 self.failUnlessEqual(len(units), 5+1)
3654 self.failUnlessEqual(units[-1]["type"], "stats")
3656 self.failUnlessEqual(first["path"], [])
3657 self.failUnlessEqual(first["cap"], self.rootnode.get_uri())
3658 self.failUnlessEqual(first["type"], "directory")
3659 stats = units[-1]["stats"]
3660 self.failUnlessEqual(stats["count-immutable-files"], 2)
3661 self.failUnlessEqual(stats["count-literal-files"], 1)
3662 self.failUnlessEqual(stats["count-mutable-files"], 0)
3663 self.failUnlessEqual(stats["count-immutable-files"], 2)
3664 self.failUnlessEqual(stats["count-unknown"], 1)
3665 d.addCallback(_check_manifest)
3667 # now add root/subdir and root/subdir/grandchild, then make subdir
3668 # unrecoverable, then see what happens
3670 d.addCallback(lambda ign:
3671 self.rootnode.create_subdirectory(u"subdir"))
3672 d.addCallback(_stash_uri, "subdir")
3673 d.addCallback(lambda subdir_node:
3674 subdir_node.add_file(u"grandchild",
3675 upload.Data(DATA+"2",
3677 d.addCallback(_stash_uri, "grandchild")
3679 d.addCallback(lambda ign:
3680 self.delete_shares_numbered(self.uris["subdir"],
3688 # root/subdir [unrecoverable]
3689 # root/subdir/grandchild
3691 # how should a streaming-JSON API indicate fatal error?
3692 # answer: emit ERROR: instead of a JSON string
3694 d.addCallback(self.CHECK, "root", "t=stream-manifest")
3695 def _check_broken_manifest(res):
3696 lines = res.splitlines()
3698 for (i,line) in enumerate(lines)
3699 if line.startswith("ERROR:")]
3701 self.fail("no ERROR: in output: %s" % (res,))
3702 first_error = error_lines[0]
3703 error_line = lines[first_error]
3704 error_msg = lines[first_error+1:]
3705 error_msg_s = "\n".join(error_msg) + "\n"
3706 self.failUnlessIn("ERROR: UnrecoverableFileError(no recoverable versions)",
3708 self.failUnless(len(error_msg) > 2, error_msg_s) # some traceback
3709 units = [simplejson.loads(line) for line in lines[:first_error]]
3710 self.failUnlessEqual(len(units), 6) # includes subdir
3711 last_unit = units[-1]
3712 self.failUnlessEqual(last_unit["path"], ["subdir"])
3713 d.addCallback(_check_broken_manifest)
3715 d.addCallback(self.CHECK, "root", "t=stream-deep-check")
3716 def _check_broken_deepcheck(res):
3717 lines = res.splitlines()
3719 for (i,line) in enumerate(lines)
3720 if line.startswith("ERROR:")]
3722 self.fail("no ERROR: in output: %s" % (res,))
3723 first_error = error_lines[0]
3724 error_line = lines[first_error]
3725 error_msg = lines[first_error+1:]
3726 error_msg_s = "\n".join(error_msg) + "\n"
3727 self.failUnlessIn("ERROR: UnrecoverableFileError(no recoverable versions)",
3729 self.failUnless(len(error_msg) > 2, error_msg_s) # some traceback
3730 units = [simplejson.loads(line) for line in lines[:first_error]]
3731 self.failUnlessEqual(len(units), 6) # includes subdir
3732 last_unit = units[-1]
3733 self.failUnlessEqual(last_unit["path"], ["subdir"])
3734 r = last_unit["check-results"]["results"]
3735 self.failUnlessEqual(r["count-recoverable-versions"], 0)
3736 self.failUnlessEqual(r["count-shares-good"], 1)
3737 self.failUnlessEqual(r["recoverable"], False)
3738 d.addCallback(_check_broken_deepcheck)
3740 d.addErrback(self.explain_web_error)
3743 def test_deep_check_and_repair(self):
3744 self.basedir = "web/Grid/deep_check_and_repair"
3746 c0 = self.g.clients[0]
3750 d = c0.create_dirnode()
3751 def _stash_root_and_create_file(n):
3753 self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri()) + "/"
3754 return n.add_file(u"good", upload.Data(DATA, convergence=""))
3755 d.addCallback(_stash_root_and_create_file)
3756 def _stash_uri(fn, which):
3757 self.uris[which] = fn.get_uri()
3758 d.addCallback(_stash_uri, "good")
3759 d.addCallback(lambda ign:
3760 self.rootnode.add_file(u"small",
3761 upload.Data("literal",
3763 d.addCallback(_stash_uri, "small")
3764 d.addCallback(lambda ign:
3765 self.rootnode.add_file(u"sick",
3766 upload.Data(DATA+"1",
3768 d.addCallback(_stash_uri, "sick")
3769 #d.addCallback(lambda ign:
3770 # self.rootnode.add_file(u"dead",
3771 # upload.Data(DATA+"2",
3773 #d.addCallback(_stash_uri, "dead")
3775 #d.addCallback(lambda ign: c0.create_mutable_file("mutable"))
3776 #d.addCallback(lambda fn: self.rootnode.set_node(u"corrupt", fn))
3777 #d.addCallback(_stash_uri, "corrupt")
3779 def _clobber_shares(ignored):
3780 good_shares = self.find_shares(self.uris["good"])
3781 self.failUnlessEqual(len(good_shares), 10)
3782 sick_shares = self.find_shares(self.uris["sick"])
3783 os.unlink(sick_shares[0][2])
3784 #dead_shares = self.find_shares(self.uris["dead"])
3785 #for i in range(1, 10):
3786 # os.unlink(dead_shares[i][2])
3788 #c_shares = self.find_shares(self.uris["corrupt"])
3789 #cso = CorruptShareOptions()
3790 #cso.stdout = StringIO()
3791 #cso.parseOptions([c_shares[0][2]])
3793 d.addCallback(_clobber_shares)
3796 # root/good CHK, 10 shares
3798 # root/sick CHK, 9 shares
3800 d.addCallback(self.CHECK, "root", "t=stream-deep-check&repair=true")
3802 units = [simplejson.loads(line)
3803 for line in res.splitlines()
3805 self.failUnlessEqual(len(units), 4+1)
3806 # should be parent-first
3808 self.failUnlessEqual(u0["path"], [])
3809 self.failUnlessEqual(u0["type"], "directory")
3810 self.failUnlessEqual(u0["cap"], self.rootnode.get_uri())
3811 u0crr = u0["check-and-repair-results"]
3812 self.failUnlessEqual(u0crr["repair-attempted"], False)
3813 self.failUnlessEqual(u0crr["pre-repair-results"]["results"]["count-shares-good"], 10)
3815 ugood = [u for u in units
3816 if u["type"] == "file" and u["path"] == [u"good"]][0]
3817 self.failUnlessEqual(ugood["cap"], self.uris["good"])
3818 ugoodcrr = ugood["check-and-repair-results"]
3819 self.failUnlessEqual(ugoodcrr["repair-attempted"], False)
3820 self.failUnlessEqual(ugoodcrr["pre-repair-results"]["results"]["count-shares-good"], 10)
3822 usick = [u for u in units
3823 if u["type"] == "file" and u["path"] == [u"sick"]][0]
3824 self.failUnlessEqual(usick["cap"], self.uris["sick"])
3825 usickcrr = usick["check-and-repair-results"]
3826 self.failUnlessEqual(usickcrr["repair-attempted"], True)
3827 self.failUnlessEqual(usickcrr["repair-successful"], True)
3828 self.failUnlessEqual(usickcrr["pre-repair-results"]["results"]["count-shares-good"], 9)
3829 self.failUnlessEqual(usickcrr["post-repair-results"]["results"]["count-shares-good"], 10)
3832 self.failUnlessEqual(stats["type"], "stats")
3834 self.failUnlessEqual(s["count-immutable-files"], 2)
3835 self.failUnlessEqual(s["count-literal-files"], 1)
3836 self.failUnlessEqual(s["count-directories"], 1)
3837 d.addCallback(_done)
3839 d.addErrback(self.explain_web_error)
3842 def _count_leases(self, ignored, which):
3843 u = self.uris[which]
3844 shares = self.find_shares(u)
3846 for shnum, serverid, fn in shares:
3847 sf = get_share_file(fn)
3848 num_leases = len(list(sf.get_leases()))
3849 lease_counts.append( (fn, num_leases) )
3852 def _assert_leasecount(self, lease_counts, expected):
3853 for (fn, num_leases) in lease_counts:
3854 if num_leases != expected:
3855 self.fail("expected %d leases, have %d, on %s" %
3856 (expected, num_leases, fn))
3858 def test_add_lease(self):
3859 self.basedir = "web/Grid/add_lease"
3860 self.set_up_grid(num_clients=2)
3861 c0 = self.g.clients[0]
3864 d = c0.upload(upload.Data(DATA, convergence=""))
3865 def _stash_uri(ur, which):
3866 self.uris[which] = ur.uri
3867 d.addCallback(_stash_uri, "one")
3868 d.addCallback(lambda ign:
3869 c0.upload(upload.Data(DATA+"1", convergence="")))
3870 d.addCallback(_stash_uri, "two")
3871 def _stash_mutable_uri(n, which):
3872 self.uris[which] = n.get_uri()
3873 assert isinstance(self.uris[which], str)
3874 d.addCallback(lambda ign: c0.create_mutable_file(DATA+"2"))
3875 d.addCallback(_stash_mutable_uri, "mutable")
3877 def _compute_fileurls(ignored):
3879 for which in self.uris:
3880 self.fileurls[which] = "uri/" + urllib.quote(self.uris[which])
3881 d.addCallback(_compute_fileurls)
3883 d.addCallback(self._count_leases, "one")
3884 d.addCallback(self._assert_leasecount, 1)
3885 d.addCallback(self._count_leases, "two")
3886 d.addCallback(self._assert_leasecount, 1)
3887 d.addCallback(self._count_leases, "mutable")
3888 d.addCallback(self._assert_leasecount, 1)
3890 d.addCallback(self.CHECK, "one", "t=check") # no add-lease
3891 def _got_html_good(res):
3892 self.failUnless("Healthy" in res, res)
3893 self.failIf("Not Healthy" in res, res)
3894 d.addCallback(_got_html_good)
3896 d.addCallback(self._count_leases, "one")
3897 d.addCallback(self._assert_leasecount, 1)
3898 d.addCallback(self._count_leases, "two")
3899 d.addCallback(self._assert_leasecount, 1)
3900 d.addCallback(self._count_leases, "mutable")
3901 d.addCallback(self._assert_leasecount, 1)
3903 # this CHECK uses the original client, which uses the same
3904 # lease-secrets, so it will just renew the original lease
3905 d.addCallback(self.CHECK, "one", "t=check&add-lease=true")
3906 d.addCallback(_got_html_good)
3908 d.addCallback(self._count_leases, "one")
3909 d.addCallback(self._assert_leasecount, 1)
3910 d.addCallback(self._count_leases, "two")
3911 d.addCallback(self._assert_leasecount, 1)
3912 d.addCallback(self._count_leases, "mutable")
3913 d.addCallback(self._assert_leasecount, 1)
3915 # this CHECK uses an alternate client, which adds a second lease
3916 d.addCallback(self.CHECK, "one", "t=check&add-lease=true", clientnum=1)
3917 d.addCallback(_got_html_good)
3919 d.addCallback(self._count_leases, "one")
3920 d.addCallback(self._assert_leasecount, 2)
3921 d.addCallback(self._count_leases, "two")
3922 d.addCallback(self._assert_leasecount, 1)
3923 d.addCallback(self._count_leases, "mutable")
3924 d.addCallback(self._assert_leasecount, 1)
3926 d.addCallback(self.CHECK, "mutable", "t=check&add-lease=true")
3927 d.addCallback(_got_html_good)
3929 d.addCallback(self._count_leases, "one")
3930 d.addCallback(self._assert_leasecount, 2)
3931 d.addCallback(self._count_leases, "two")
3932 d.addCallback(self._assert_leasecount, 1)
3933 d.addCallback(self._count_leases, "mutable")
3934 d.addCallback(self._assert_leasecount, 1)
3936 d.addCallback(self.CHECK, "mutable", "t=check&add-lease=true",
3938 d.addCallback(_got_html_good)
3940 d.addCallback(self._count_leases, "one")
3941 d.addCallback(self._assert_leasecount, 2)
3942 d.addCallback(self._count_leases, "two")
3943 d.addCallback(self._assert_leasecount, 1)
3944 d.addCallback(self._count_leases, "mutable")
3945 d.addCallback(self._assert_leasecount, 2)
3947 d.addErrback(self.explain_web_error)
3950 def test_deep_add_lease(self):
3951 self.basedir = "web/Grid/deep_add_lease"
3952 self.set_up_grid(num_clients=2)
3953 c0 = self.g.clients[0]
3957 d = c0.create_dirnode()
3958 def _stash_root_and_create_file(n):
3960 self.uris["root"] = n.get_uri()
3961 self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri()) + "/"
3962 return n.add_file(u"one", upload.Data(DATA, convergence=""))
3963 d.addCallback(_stash_root_and_create_file)
3964 def _stash_uri(fn, which):
3965 self.uris[which] = fn.get_uri()
3966 d.addCallback(_stash_uri, "one")
3967 d.addCallback(lambda ign:
3968 self.rootnode.add_file(u"small",
3969 upload.Data("literal",
3971 d.addCallback(_stash_uri, "small")
3973 d.addCallback(lambda ign: c0.create_mutable_file("mutable"))
3974 d.addCallback(lambda fn: self.rootnode.set_node(u"mutable", fn))
3975 d.addCallback(_stash_uri, "mutable")
3977 d.addCallback(self.CHECK, "root", "t=stream-deep-check") # no add-lease
3979 units = [simplejson.loads(line)
3980 for line in res.splitlines()
3982 # root, one, small, mutable, stats
3983 self.failUnlessEqual(len(units), 4+1)
3984 d.addCallback(_done)
3986 d.addCallback(self._count_leases, "root")
3987 d.addCallback(self._assert_leasecount, 1)
3988 d.addCallback(self._count_leases, "one")
3989 d.addCallback(self._assert_leasecount, 1)
3990 d.addCallback(self._count_leases, "mutable")
3991 d.addCallback(self._assert_leasecount, 1)
3993 d.addCallback(self.CHECK, "root", "t=stream-deep-check&add-lease=true")
3994 d.addCallback(_done)
3996 d.addCallback(self._count_leases, "root")
3997 d.addCallback(self._assert_leasecount, 1)
3998 d.addCallback(self._count_leases, "one")
3999 d.addCallback(self._assert_leasecount, 1)
4000 d.addCallback(self._count_leases, "mutable")
4001 d.addCallback(self._assert_leasecount, 1)
4003 d.addCallback(self.CHECK, "root", "t=stream-deep-check&add-lease=true",
4005 d.addCallback(_done)
4007 d.addCallback(self._count_leases, "root")
4008 d.addCallback(self._assert_leasecount, 2)
4009 d.addCallback(self._count_leases, "one")
4010 d.addCallback(self._assert_leasecount, 2)
4011 d.addCallback(self._count_leases, "mutable")
4012 d.addCallback(self._assert_leasecount, 2)
4014 d.addErrback(self.explain_web_error)
4018 def test_exceptions(self):
4019 self.basedir = "web/Grid/exceptions"
4020 self.set_up_grid(num_clients=1, num_servers=2)
4021 c0 = self.g.clients[0]
4024 d = c0.create_dirnode()
4026 self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri()) + "/"
4027 self.fileurls["imaginary"] = self.fileurls["root"] + "imaginary"
4029 d.addCallback(_stash_root)
4030 d.addCallback(lambda ign: c0.upload(upload.Data(DATA, convergence="")))
4032 self.fileurls["1share"] = "uri/" + urllib.quote(ur.uri)
4033 self.delete_shares_numbered(ur.uri, range(1,10))
4035 u = uri.from_string(ur.uri)
4036 u.key = testutil.flip_bit(u.key, 0)
4037 baduri = u.to_string()
4038 self.fileurls["0shares"] = "uri/" + urllib.quote(baduri)
4039 d.addCallback(_stash_bad)
4040 d.addCallback(lambda ign: c0.create_dirnode())
4041 def _mangle_dirnode_1share(n):
4043 url = self.fileurls["dir-1share"] = "uri/" + urllib.quote(u) + "/"
4044 self.fileurls["dir-1share-json"] = url + "?t=json"
4045 self.delete_shares_numbered(u, range(1,10))
4046 d.addCallback(_mangle_dirnode_1share)
4047 d.addCallback(lambda ign: c0.create_dirnode())
4048 def _mangle_dirnode_0share(n):
4050 url = self.fileurls["dir-0share"] = "uri/" + urllib.quote(u) + "/"
4051 self.fileurls["dir-0share-json"] = url + "?t=json"
4052 self.delete_shares_numbered(u, range(0,10))
4053 d.addCallback(_mangle_dirnode_0share)
4055 # NotEnoughSharesError should be reported sensibly, with a
4056 # text/plain explanation of the problem, and perhaps some
4057 # information on which shares *could* be found.
4059 d.addCallback(lambda ignored:
4060 self.shouldHTTPError("GET unrecoverable",
4061 410, "Gone", "NoSharesError",
4062 self.GET, self.fileurls["0shares"]))
4063 def _check_zero_shares(body):
4064 self.failIf("<html>" in body, body)
4065 body = " ".join(body.strip().split())
4066 exp = ("NoSharesError: no shares could be found. "
4067 "Zero shares usually indicates a corrupt URI, or that "
4068 "no servers were connected, but it might also indicate "
4069 "severe corruption. You should perform a filecheck on "
4070 "this object to learn more. The full error message is: "
4071 "Failed to get enough shareholders: have 0, need 3")
4072 self.failUnlessEqual(exp, body)
4073 d.addCallback(_check_zero_shares)
4076 d.addCallback(lambda ignored:
4077 self.shouldHTTPError("GET 1share",
4078 410, "Gone", "NotEnoughSharesError",
4079 self.GET, self.fileurls["1share"]))
4080 def _check_one_share(body):
4081 self.failIf("<html>" in body, body)
4082 body = " ".join(body.strip().split())
4083 exp = ("NotEnoughSharesError: This indicates that some "
4084 "servers were unavailable, or that shares have been "
4085 "lost to server departure, hard drive failure, or disk "
4086 "corruption. You should perform a filecheck on "
4087 "this object to learn more. The full error message is:"
4088 " Failed to get enough shareholders: have 1, need 3")
4089 self.failUnlessEqual(exp, body)
4090 d.addCallback(_check_one_share)
4092 d.addCallback(lambda ignored:
4093 self.shouldHTTPError("GET imaginary",
4094 404, "Not Found", None,
4095 self.GET, self.fileurls["imaginary"]))
4096 def _missing_child(body):
4097 self.failUnless("No such child: imaginary" in body, body)
4098 d.addCallback(_missing_child)
4100 d.addCallback(lambda ignored: self.GET(self.fileurls["dir-0share"]))
4101 def _check_0shares_dir_html(body):
4102 self.failUnless("<html>" in body, body)
4103 # we should see the regular page, but without the child table or
4105 body = " ".join(body.strip().split())
4106 self.failUnlessIn('href="?t=info">More info on this directory',
4108 exp = ("UnrecoverableFileError: the directory (or mutable file) "
4109 "could not be retrieved, because there were insufficient "
4110 "good shares. This might indicate that no servers were "
4111 "connected, insufficient servers were connected, the URI "
4112 "was corrupt, or that shares have been lost due to server "
4113 "departure, hard drive failure, or disk corruption. You "
4114 "should perform a filecheck on this object to learn more.")
4115 self.failUnlessIn(exp, body)
4116 self.failUnlessIn("No upload forms: directory is unreadable", body)
4117 d.addCallback(_check_0shares_dir_html)
4119 d.addCallback(lambda ignored: self.GET(self.fileurls["dir-1share"]))
4120 def _check_1shares_dir_html(body):
4121 # at some point, we'll split UnrecoverableFileError into 0-shares
4122 # and some-shares like we did for immutable files (since there
4123 # are different sorts of advice to offer in each case). For now,
4124 # they present the same way.
4125 self.failUnless("<html>" in body, body)
4126 body = " ".join(body.strip().split())
4127 self.failUnlessIn('href="?t=info">More info on this directory',
4129 exp = ("UnrecoverableFileError: the directory (or mutable file) "
4130 "could not be retrieved, because there were insufficient "
4131 "good shares. This might indicate that no servers were "
4132 "connected, insufficient servers were connected, the URI "
4133 "was corrupt, or that shares have been lost due to server "
4134 "departure, hard drive failure, or disk corruption. You "
4135 "should perform a filecheck on this object to learn more.")
4136 self.failUnlessIn(exp, body)
4137 self.failUnlessIn("No upload forms: directory is unreadable", body)
4138 d.addCallback(_check_1shares_dir_html)
4140 d.addCallback(lambda ignored:
4141 self.shouldHTTPError("GET dir-0share-json",
4142 410, "Gone", "UnrecoverableFileError",
4144 self.fileurls["dir-0share-json"]))
4145 def _check_unrecoverable_file(body):
4146 self.failIf("<html>" in body, body)
4147 body = " ".join(body.strip().split())
4148 exp = ("UnrecoverableFileError: the directory (or mutable file) "
4149 "could not be retrieved, because there were insufficient "
4150 "good shares. This might indicate that no servers were "
4151 "connected, insufficient servers were connected, the URI "
4152 "was corrupt, or that shares have been lost due to server "
4153 "departure, hard drive failure, or disk corruption. You "
4154 "should perform a filecheck on this object to learn more.")
4155 self.failUnlessEqual(exp, body)
4156 d.addCallback(_check_unrecoverable_file)
4158 d.addCallback(lambda ignored:
4159 self.shouldHTTPError("GET dir-1share-json",
4160 410, "Gone", "UnrecoverableFileError",
4162 self.fileurls["dir-1share-json"]))
4163 d.addCallback(_check_unrecoverable_file)
4165 d.addCallback(lambda ignored:
4166 self.shouldHTTPError("GET imaginary",
4167 404, "Not Found", None,
4168 self.GET, self.fileurls["imaginary"]))
4170 # attach a webapi child that throws a random error, to test how it
4172 w = c0.getServiceNamed("webish")
4173 w.root.putChild("ERRORBOOM", ErrorBoom())
4175 # "Accept: */*" : should get a text/html stack trace
4176 # "Accept: text/plain" : should get a text/plain stack trace
4177 # "Accept: text/plain, application/octet-stream" : text/plain (CLI)
4178 # no Accept header: should get a text/html stack trace
4180 d.addCallback(lambda ignored:
4181 self.shouldHTTPError("GET errorboom_html",
4182 500, "Internal Server Error", None,
4183 self.GET, "ERRORBOOM",
4184 headers={"accept": ["*/*"]}))
4185 def _internal_error_html1(body):
4186 self.failUnless("<html>" in body, "expected HTML, not '%s'" % body)
4187 d.addCallback(_internal_error_html1)
4189 d.addCallback(lambda ignored:
4190 self.shouldHTTPError("GET errorboom_text",
4191 500, "Internal Server Error", None,
4192 self.GET, "ERRORBOOM",
4193 headers={"accept": ["text/plain"]}))
4194 def _internal_error_text2(body):
4195 self.failIf("<html>" in body, body)
4196 self.failUnless(body.startswith("Traceback "), body)
4197 d.addCallback(_internal_error_text2)
4199 CLI_accepts = "text/plain, application/octet-stream"
4200 d.addCallback(lambda ignored:
4201 self.shouldHTTPError("GET errorboom_text",
4202 500, "Internal Server Error", None,
4203 self.GET, "ERRORBOOM",
4204 headers={"accept": [CLI_accepts]}))
4205 def _internal_error_text3(body):
4206 self.failIf("<html>" in body, body)
4207 self.failUnless(body.startswith("Traceback "), body)
4208 d.addCallback(_internal_error_text3)
4210 d.addCallback(lambda ignored:
4211 self.shouldHTTPError("GET errorboom_text",
4212 500, "Internal Server Error", None,
4213 self.GET, "ERRORBOOM"))
4214 def _internal_error_html4(body):
4215 self.failUnless("<html>" in body, "expected HTML, not '%s'" % body)
4216 d.addCallback(_internal_error_html4)
4218 def _flush_errors(res):
4219 # Trial: please ignore the CompletelyUnhandledError in the logs
4220 self.flushLoggedErrors(CompletelyUnhandledError)
4222 d.addBoth(_flush_errors)
4226 class CompletelyUnhandledError(Exception):
4228 class ErrorBoom(rend.Page):
4229 def beforeRender(self, ctx):
4230 raise CompletelyUnhandledError("whoops")