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
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.test.common import FakeCHKFileNode, FakeMutableFileNode, \
22 create_chk_filenode, WebErrorMixin, ShouldFailMixin, make_mutable_file_uri
23 from allmydata.interfaces import IMutableFileNode
24 from allmydata.mutable import servermap, publish, retrieve
25 import common_util as testutil
26 from allmydata.test.no_network import GridTestMixin
27 from allmydata.test.common_web import HTTPClientGETFactory, \
29 from allmydata.client import Client, SecretHolder
31 # create a fake uploader/downloader, and a couple of fake dirnodes, then
32 # create a webserver that works against them
34 timeout = 480 # Most of these take longer than 240 seconds on Francois's arm box.
36 class FakeStatsProvider:
38 stats = {'stats': {}, 'counters': {}}
41 class FakeNodeMaker(NodeMaker):
42 def _create_lit(self, cap):
43 return FakeCHKFileNode(cap)
44 def _create_immutable(self, cap):
45 return FakeCHKFileNode(cap)
46 def _create_mutable(self, cap):
47 return FakeMutableFileNode(None, None, None, None).init_from_cap(cap)
48 def create_mutable_file(self, contents="", keysize=None):
49 n = FakeMutableFileNode(None, None, None, None)
50 return n.create(contents)
52 class FakeUploader(service.Service):
54 def upload(self, uploadable, history=None):
55 d = uploadable.get_size()
56 d.addCallback(lambda size: uploadable.read(size))
59 n = create_chk_filenode(data)
60 results = upload.UploadResults()
61 results.uri = n.get_uri()
63 d.addCallback(_got_data)
65 def get_helper_info(self):
69 _all_upload_status = [upload.UploadStatus()]
70 _all_download_status = [download.DownloadStatus()]
71 _all_mapupdate_statuses = [servermap.UpdateStatus()]
72 _all_publish_statuses = [publish.PublishStatus()]
73 _all_retrieve_statuses = [retrieve.RetrieveStatus()]
75 def list_all_upload_statuses(self):
76 return self._all_upload_status
77 def list_all_download_statuses(self):
78 return self._all_download_status
79 def list_all_mapupdate_statuses(self):
80 return self._all_mapupdate_statuses
81 def list_all_publish_statuses(self):
82 return self._all_publish_statuses
83 def list_all_retrieve_statuses(self):
84 return self._all_retrieve_statuses
85 def list_all_helper_statuses(self):
88 class FakeClient(Client):
90 # don't upcall to Client.__init__, since we only want to initialize a
92 service.MultiService.__init__(self)
93 self.nodeid = "fake_nodeid"
94 self.nickname = "fake_nickname"
95 self.introducer_furl = "None"
96 self.stats_provider = FakeStatsProvider()
97 self._secret_holder = SecretHolder("lease secret", "convergence secret")
99 self.convergence = "some random string"
100 self.storage_broker = StorageFarmBroker(None, permute_peers=True)
101 self.introducer_client = None
102 self.history = FakeHistory()
103 self.uploader = FakeUploader()
104 self.uploader.setServiceParent(self)
105 self.nodemaker = FakeNodeMaker(None, self._secret_holder, None,
106 self.uploader, None, None,
109 def startService(self):
110 return service.MultiService.startService(self)
111 def stopService(self):
112 return service.MultiService.stopService(self)
114 MUTABLE_SIZELIMIT = FakeMutableFileNode.MUTABLE_SIZELIMIT
116 class WebMixin(object):
118 self.s = FakeClient()
119 self.s.startService()
120 self.staticdir = self.mktemp()
121 self.ws = webish.WebishServer(self.s, "0", staticdir=self.staticdir)
122 self.ws.setServiceParent(self.s)
123 self.webish_port = port = self.ws.listener._port.getHost().port
124 self.webish_url = "http://localhost:%d" % port
126 l = [ self.s.create_dirnode() for x in range(6) ]
127 d = defer.DeferredList(l)
129 self.public_root = res[0][1]
130 assert interfaces.IDirectoryNode.providedBy(self.public_root), res
131 self.public_url = "/uri/" + self.public_root.get_uri()
132 self.private_root = res[1][1]
136 self._foo_uri = foo.get_uri()
137 self._foo_readonly_uri = foo.get_readonly_uri()
138 self._foo_verifycap = foo.get_verify_cap().to_string()
139 # NOTE: we ignore the deferred on all set_uri() calls, because we
140 # know the fake nodes do these synchronously
141 self.public_root.set_uri(u"foo", foo.get_uri(),
142 foo.get_readonly_uri())
144 self.BAR_CONTENTS, n, self._bar_txt_uri = self.makefile(0)
145 foo.set_uri(u"bar.txt", self._bar_txt_uri, self._bar_txt_uri)
146 self._bar_txt_verifycap = n.get_verify_cap().to_string()
148 foo.set_uri(u"empty", res[3][1].get_uri(),
149 res[3][1].get_readonly_uri())
150 sub_uri = res[4][1].get_uri()
151 self._sub_uri = sub_uri
152 foo.set_uri(u"sub", sub_uri, sub_uri)
153 sub = self.s.create_node_from_uri(sub_uri)
155 _ign, n, blocking_uri = self.makefile(1)
156 foo.set_uri(u"blockingfile", blocking_uri, blocking_uri)
158 unicode_filename = u"n\u00fc.txt" # n u-umlaut . t x t
159 # ok, unicode calls it LATIN SMALL LETTER U WITH DIAERESIS but I
160 # still think of it as an umlaut
161 foo.set_uri(unicode_filename, self._bar_txt_uri, self._bar_txt_uri)
163 _ign, n, baz_file = self.makefile(2)
164 self._baz_file_uri = baz_file
165 sub.set_uri(u"baz.txt", baz_file, baz_file)
167 _ign, n, self._bad_file_uri = self.makefile(3)
168 # this uri should not be downloadable
169 del FakeCHKFileNode.all_contents[self._bad_file_uri]
172 self.public_root.set_uri(u"reedownlee", rodir.get_readonly_uri(),
173 rodir.get_readonly_uri())
174 rodir.set_uri(u"nor", baz_file, baz_file)
179 # public/foo/blockingfile
182 # public/foo/sub/baz.txt
184 # public/reedownlee/nor
185 self.NEWFILE_CONTENTS = "newfile contents\n"
187 return foo.get_metadata_for(u"bar.txt")
189 def _got_metadata(metadata):
190 self._bar_txt_metadata = metadata
191 d.addCallback(_got_metadata)
194 def makefile(self, number):
195 contents = "contents of file %s\n" % number
196 n = create_chk_filenode(contents)
197 return contents, n, n.get_uri()
200 return self.s.stopService()
202 def failUnlessIsBarDotTxt(self, res):
203 self.failUnlessEqual(res, self.BAR_CONTENTS, res)
205 def failUnlessIsBarJSON(self, res):
206 data = simplejson.loads(res)
207 self.failUnless(isinstance(data, list))
208 self.failUnlessEqual(data[0], u"filenode")
209 self.failUnless(isinstance(data[1], dict))
210 self.failIf(data[1]["mutable"])
211 self.failIf("rw_uri" in data[1]) # immutable
212 self.failUnlessEqual(data[1]["ro_uri"], self._bar_txt_uri)
213 self.failUnlessEqual(data[1]["verify_uri"], self._bar_txt_verifycap)
214 self.failUnlessEqual(data[1]["size"], len(self.BAR_CONTENTS))
216 def failUnlessIsFooJSON(self, res):
217 data = simplejson.loads(res)
218 self.failUnless(isinstance(data, list))
219 self.failUnlessEqual(data[0], "dirnode", res)
220 self.failUnless(isinstance(data[1], dict))
221 self.failUnless(data[1]["mutable"])
222 self.failUnless("rw_uri" in data[1]) # mutable
223 self.failUnlessEqual(data[1]["rw_uri"], self._foo_uri)
224 self.failUnlessEqual(data[1]["ro_uri"], self._foo_readonly_uri)
225 self.failUnlessEqual(data[1]["verify_uri"], self._foo_verifycap)
227 kidnames = sorted([unicode(n) for n in data[1]["children"]])
228 self.failUnlessEqual(kidnames,
229 [u"bar.txt", u"blockingfile", u"empty",
230 u"n\u00fc.txt", u"sub"])
231 kids = dict( [(unicode(name),value)
233 in data[1]["children"].iteritems()] )
234 self.failUnlessEqual(kids[u"sub"][0], "dirnode")
235 self.failUnless("metadata" in kids[u"sub"][1])
236 self.failUnless("ctime" in kids[u"sub"][1]["metadata"])
237 self.failUnless("mtime" in kids[u"sub"][1]["metadata"])
238 self.failUnlessEqual(kids[u"bar.txt"][0], "filenode")
239 self.failUnlessEqual(kids[u"bar.txt"][1]["size"], len(self.BAR_CONTENTS))
240 self.failUnlessEqual(kids[u"bar.txt"][1]["ro_uri"], self._bar_txt_uri)
241 self.failUnlessEqual(kids[u"bar.txt"][1]["verify_uri"],
242 self._bar_txt_verifycap)
243 self.failUnlessEqual(kids[u"bar.txt"][1]["metadata"]["ctime"],
244 self._bar_txt_metadata["ctime"])
245 self.failUnlessEqual(kids[u"n\u00fc.txt"][1]["ro_uri"],
248 def GET(self, urlpath, followRedirect=False, return_response=False,
250 # if return_response=True, this fires with (data, statuscode,
251 # respheaders) instead of just data.
252 assert not isinstance(urlpath, unicode)
253 url = self.webish_url + urlpath
254 factory = HTTPClientGETFactory(url, method="GET",
255 followRedirect=followRedirect, **kwargs)
256 reactor.connectTCP("localhost", self.webish_port, factory)
259 return (data, factory.status, factory.response_headers)
261 d.addCallback(_got_data)
262 return factory.deferred
264 def HEAD(self, urlpath, return_response=False, **kwargs):
265 # this requires some surgery, because twisted.web.client doesn't want
266 # to give us back the response headers.
267 factory = HTTPClientHEADFactory(urlpath, method="HEAD", **kwargs)
268 reactor.connectTCP("localhost", self.webish_port, factory)
271 return (data, factory.status, factory.response_headers)
273 d.addCallback(_got_data)
274 return factory.deferred
276 def PUT(self, urlpath, data, **kwargs):
277 url = self.webish_url + urlpath
278 return client.getPage(url, method="PUT", postdata=data, **kwargs)
280 def DELETE(self, urlpath):
281 url = self.webish_url + urlpath
282 return client.getPage(url, method="DELETE")
284 def POST(self, urlpath, followRedirect=False, **fields):
285 sepbase = "boogabooga"
289 form.append('Content-Disposition: form-data; name="_charset"')
293 for name, value in fields.iteritems():
294 if isinstance(value, tuple):
295 filename, value = value
296 form.append('Content-Disposition: form-data; name="%s"; '
297 'filename="%s"' % (name, filename.encode("utf-8")))
299 form.append('Content-Disposition: form-data; name="%s"' % name)
301 if isinstance(value, unicode):
302 value = value.encode("utf-8")
305 assert isinstance(value, str)
312 body = "\r\n".join(form) + "\r\n"
313 headers["content-type"] = "multipart/form-data; boundary=%s" % sepbase
314 return self.POST2(urlpath, body, headers, followRedirect)
316 def POST2(self, urlpath, body="", headers={}, followRedirect=False):
317 url = self.webish_url + urlpath
318 return client.getPage(url, method="POST", postdata=body,
319 headers=headers, followRedirect=followRedirect)
321 def shouldFail(self, res, expected_failure, which,
322 substring=None, response_substring=None):
323 if isinstance(res, failure.Failure):
324 res.trap(expected_failure)
326 self.failUnless(substring in str(res),
327 "substring '%s' not in '%s'"
328 % (substring, str(res)))
329 if response_substring:
330 self.failUnless(response_substring in res.value.response,
331 "response substring '%s' not in '%s'"
332 % (response_substring, res.value.response))
334 self.fail("%s was supposed to raise %s, not get '%s'" %
335 (which, expected_failure, res))
337 def shouldFail2(self, expected_failure, which, substring,
339 callable, *args, **kwargs):
340 assert substring is None or isinstance(substring, str)
341 assert response_substring is None or isinstance(response_substring, str)
342 d = defer.maybeDeferred(callable, *args, **kwargs)
344 if isinstance(res, failure.Failure):
345 res.trap(expected_failure)
347 self.failUnless(substring in str(res),
348 "%s: substring '%s' not in '%s'"
349 % (which, substring, str(res)))
350 if response_substring:
351 self.failUnless(response_substring in res.value.response,
352 "%s: response substring '%s' not in '%s'"
354 response_substring, res.value.response))
356 self.fail("%s was supposed to raise %s, not get '%s'" %
357 (which, expected_failure, res))
361 def should404(self, res, which):
362 if isinstance(res, failure.Failure):
363 res.trap(error.Error)
364 self.failUnlessEqual(res.value.status, "404")
366 self.fail("%s was supposed to Error(404), not get '%s'" %
370 class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase):
371 def test_create(self):
374 def test_welcome(self):
377 self.failUnless('Welcome To Tahoe-LAFS' in res, res)
379 self.s.basedir = 'web/test_welcome'
380 fileutil.make_dirs("web/test_welcome")
381 fileutil.make_dirs("web/test_welcome/private")
383 d.addCallback(_check)
386 def test_provisioning(self):
387 d = self.GET("/provisioning/")
389 self.failUnless('Tahoe Provisioning Tool' in res)
390 fields = {'filled': True,
391 "num_users": int(50e3),
392 "files_per_user": 1000,
393 "space_per_user": int(1e9),
394 "sharing_ratio": 1.0,
395 "encoding_parameters": "3-of-10-5",
397 "ownership_mode": "A",
398 "download_rate": 100,
403 return self.POST("/provisioning/", **fields)
405 d.addCallback(_check)
407 self.failUnless('Tahoe Provisioning Tool' in res)
408 self.failUnless("Share space consumed: 167.01TB" in res)
410 fields = {'filled': True,
411 "num_users": int(50e6),
412 "files_per_user": 1000,
413 "space_per_user": int(5e9),
414 "sharing_ratio": 1.0,
415 "encoding_parameters": "25-of-100-50",
416 "num_servers": 30000,
417 "ownership_mode": "E",
418 "drive_failure_model": "U",
420 "download_rate": 1000,
425 return self.POST("/provisioning/", **fields)
426 d.addCallback(_check2)
428 self.failUnless("Share space consumed: huge!" in res)
429 fields = {'filled': True}
430 return self.POST("/provisioning/", **fields)
431 d.addCallback(_check3)
433 self.failUnless("Share space consumed:" in res)
434 d.addCallback(_check4)
437 def test_reliability_tool(self):
439 from allmydata import reliability
440 _hush_pyflakes = reliability
443 raise unittest.SkipTest("reliability tool requires NumPy")
445 d = self.GET("/reliability/")
447 self.failUnless('Tahoe Reliability Tool' in res)
448 fields = {'drive_lifetime': "8Y",
453 "check_period": "1M",
454 "report_period": "3M",
457 return self.POST("/reliability/", **fields)
459 d.addCallback(_check)
461 self.failUnless('Tahoe Reliability Tool' in res)
462 r = r'Probability of loss \(no maintenance\):\s+<span>0.033591'
463 self.failUnless(re.search(r, res), res)
464 d.addCallback(_check2)
467 def test_status(self):
468 h = self.s.get_history()
469 dl_num = h.list_all_download_statuses()[0].get_counter()
470 ul_num = h.list_all_upload_statuses()[0].get_counter()
471 mu_num = h.list_all_mapupdate_statuses()[0].get_counter()
472 pub_num = h.list_all_publish_statuses()[0].get_counter()
473 ret_num = h.list_all_retrieve_statuses()[0].get_counter()
474 d = self.GET("/status", followRedirect=True)
476 self.failUnless('Upload and Download Status' in res, res)
477 self.failUnless('"down-%d"' % dl_num in res, res)
478 self.failUnless('"up-%d"' % ul_num in res, res)
479 self.failUnless('"mapupdate-%d"' % mu_num in res, res)
480 self.failUnless('"publish-%d"' % pub_num in res, res)
481 self.failUnless('"retrieve-%d"' % ret_num in res, res)
482 d.addCallback(_check)
483 d.addCallback(lambda res: self.GET("/status/?t=json"))
484 def _check_json(res):
485 data = simplejson.loads(res)
486 self.failUnless(isinstance(data, dict))
487 #active = data["active"]
488 # TODO: test more. We need a way to fake an active operation
490 d.addCallback(_check_json)
492 d.addCallback(lambda res: self.GET("/status/down-%d" % dl_num))
494 self.failUnless("File Download Status" in res, res)
495 d.addCallback(_check_dl)
496 d.addCallback(lambda res: self.GET("/status/up-%d" % ul_num))
498 self.failUnless("File Upload Status" in res, res)
499 d.addCallback(_check_ul)
500 d.addCallback(lambda res: self.GET("/status/mapupdate-%d" % mu_num))
501 def _check_mapupdate(res):
502 self.failUnless("Mutable File Servermap Update Status" in res, res)
503 d.addCallback(_check_mapupdate)
504 d.addCallback(lambda res: self.GET("/status/publish-%d" % pub_num))
505 def _check_publish(res):
506 self.failUnless("Mutable File Publish Status" in res, res)
507 d.addCallback(_check_publish)
508 d.addCallback(lambda res: self.GET("/status/retrieve-%d" % ret_num))
509 def _check_retrieve(res):
510 self.failUnless("Mutable File Retrieve Status" in res, res)
511 d.addCallback(_check_retrieve)
515 def test_status_numbers(self):
516 drrm = status.DownloadResultsRendererMixin()
517 self.failUnlessEqual(drrm.render_time(None, None), "")
518 self.failUnlessEqual(drrm.render_time(None, 2.5), "2.50s")
519 self.failUnlessEqual(drrm.render_time(None, 0.25), "250ms")
520 self.failUnlessEqual(drrm.render_time(None, 0.0021), "2.1ms")
521 self.failUnlessEqual(drrm.render_time(None, 0.000123), "123us")
522 self.failUnlessEqual(drrm.render_rate(None, None), "")
523 self.failUnlessEqual(drrm.render_rate(None, 2500000), "2.50MBps")
524 self.failUnlessEqual(drrm.render_rate(None, 30100), "30.1kBps")
525 self.failUnlessEqual(drrm.render_rate(None, 123), "123Bps")
527 urrm = status.UploadResultsRendererMixin()
528 self.failUnlessEqual(urrm.render_time(None, None), "")
529 self.failUnlessEqual(urrm.render_time(None, 2.5), "2.50s")
530 self.failUnlessEqual(urrm.render_time(None, 0.25), "250ms")
531 self.failUnlessEqual(urrm.render_time(None, 0.0021), "2.1ms")
532 self.failUnlessEqual(urrm.render_time(None, 0.000123), "123us")
533 self.failUnlessEqual(urrm.render_rate(None, None), "")
534 self.failUnlessEqual(urrm.render_rate(None, 2500000), "2.50MBps")
535 self.failUnlessEqual(urrm.render_rate(None, 30100), "30.1kBps")
536 self.failUnlessEqual(urrm.render_rate(None, 123), "123Bps")
538 def test_GET_FILEURL(self):
539 d = self.GET(self.public_url + "/foo/bar.txt")
540 d.addCallback(self.failUnlessIsBarDotTxt)
543 def test_GET_FILEURL_range(self):
544 headers = {"range": "bytes=1-10"}
545 d = self.GET(self.public_url + "/foo/bar.txt", headers=headers,
546 return_response=True)
547 def _got((res, status, headers)):
548 self.failUnlessEqual(int(status), 206)
549 self.failUnless(headers.has_key("content-range"))
550 self.failUnlessEqual(headers["content-range"][0],
551 "bytes 1-10/%d" % len(self.BAR_CONTENTS))
552 self.failUnlessEqual(res, self.BAR_CONTENTS[1:11])
556 def test_GET_FILEURL_partial_range(self):
557 headers = {"range": "bytes=5-"}
558 length = len(self.BAR_CONTENTS)
559 d = self.GET(self.public_url + "/foo/bar.txt", headers=headers,
560 return_response=True)
561 def _got((res, status, headers)):
562 self.failUnlessEqual(int(status), 206)
563 self.failUnless(headers.has_key("content-range"))
564 self.failUnlessEqual(headers["content-range"][0],
565 "bytes 5-%d/%d" % (length-1, length))
566 self.failUnlessEqual(res, self.BAR_CONTENTS[5:])
570 def test_HEAD_FILEURL_range(self):
571 headers = {"range": "bytes=1-10"}
572 d = self.HEAD(self.public_url + "/foo/bar.txt", headers=headers,
573 return_response=True)
574 def _got((res, status, headers)):
575 self.failUnlessEqual(res, "")
576 self.failUnlessEqual(int(status), 206)
577 self.failUnless(headers.has_key("content-range"))
578 self.failUnlessEqual(headers["content-range"][0],
579 "bytes 1-10/%d" % len(self.BAR_CONTENTS))
583 def test_HEAD_FILEURL_partial_range(self):
584 headers = {"range": "bytes=5-"}
585 length = len(self.BAR_CONTENTS)
586 d = self.HEAD(self.public_url + "/foo/bar.txt", headers=headers,
587 return_response=True)
588 def _got((res, status, headers)):
589 self.failUnlessEqual(int(status), 206)
590 self.failUnless(headers.has_key("content-range"))
591 self.failUnlessEqual(headers["content-range"][0],
592 "bytes 5-%d/%d" % (length-1, length))
596 def test_GET_FILEURL_range_bad(self):
597 headers = {"range": "BOGUS=fizbop-quarnak"}
598 d = self.shouldFail2(error.Error, "test_GET_FILEURL_range_bad",
600 "Syntactically invalid http range header",
601 self.GET, self.public_url + "/foo/bar.txt",
605 def test_HEAD_FILEURL(self):
606 d = self.HEAD(self.public_url + "/foo/bar.txt", return_response=True)
607 def _got((res, status, headers)):
608 self.failUnlessEqual(res, "")
609 self.failUnlessEqual(headers["content-length"][0],
610 str(len(self.BAR_CONTENTS)))
611 self.failUnlessEqual(headers["content-type"], ["text/plain"])
615 def test_GET_FILEURL_named(self):
616 base = "/file/%s" % urllib.quote(self._bar_txt_uri)
617 base2 = "/named/%s" % urllib.quote(self._bar_txt_uri)
618 d = self.GET(base + "/@@name=/blah.txt")
619 d.addCallback(self.failUnlessIsBarDotTxt)
620 d.addCallback(lambda res: self.GET(base + "/blah.txt"))
621 d.addCallback(self.failUnlessIsBarDotTxt)
622 d.addCallback(lambda res: self.GET(base + "/ignore/lots/blah.txt"))
623 d.addCallback(self.failUnlessIsBarDotTxt)
624 d.addCallback(lambda res: self.GET(base2 + "/@@name=/blah.txt"))
625 d.addCallback(self.failUnlessIsBarDotTxt)
626 save_url = base + "?save=true&filename=blah.txt"
627 d.addCallback(lambda res: self.GET(save_url))
628 d.addCallback(self.failUnlessIsBarDotTxt) # TODO: check headers
629 u_filename = u"n\u00e9wer.txt" # n e-acute w e r . t x t
630 u_fn_e = urllib.quote(u_filename.encode("utf-8"))
631 u_url = base + "?save=true&filename=" + u_fn_e
632 d.addCallback(lambda res: self.GET(u_url))
633 d.addCallback(self.failUnlessIsBarDotTxt) # TODO: check headers
636 def test_PUT_FILEURL_named_bad(self):
637 base = "/file/%s" % urllib.quote(self._bar_txt_uri)
638 d = self.shouldFail2(error.Error, "test_PUT_FILEURL_named_bad",
640 "/file can only be used with GET or HEAD",
641 self.PUT, base + "/@@name=/blah.txt", "")
644 def test_GET_DIRURL_named_bad(self):
645 base = "/file/%s" % urllib.quote(self._foo_uri)
646 d = self.shouldFail2(error.Error, "test_PUT_DIRURL_named_bad",
649 self.GET, base + "/@@name=/blah.txt")
652 def test_GET_slash_file_bad(self):
653 d = self.shouldFail2(error.Error, "test_GET_slash_file_bad",
655 "/file must be followed by a file-cap and a name",
659 def test_GET_unhandled_URI_named(self):
660 contents, n, newuri = self.makefile(12)
661 verifier_cap = n.get_verify_cap().to_string()
662 base = "/file/%s" % urllib.quote(verifier_cap)
663 # client.create_node_from_uri() can't handle verify-caps
664 d = self.shouldFail2(error.Error, "GET_unhandled_URI_named",
665 "400 Bad Request", "is not a file-cap",
669 def test_GET_unhandled_URI(self):
670 contents, n, newuri = self.makefile(12)
671 verifier_cap = n.get_verify_cap().to_string()
672 base = "/uri/%s" % urllib.quote(verifier_cap)
673 # client.create_node_from_uri() can't handle verify-caps
674 d = self.shouldFail2(error.Error, "test_GET_unhandled_URI",
676 "GET unknown URI type: can only do t=info",
680 def test_GET_FILE_URI(self):
681 base = "/uri/%s" % urllib.quote(self._bar_txt_uri)
683 d.addCallback(self.failUnlessIsBarDotTxt)
686 def test_GET_FILE_URI_badchild(self):
687 base = "/uri/%s/boguschild" % urllib.quote(self._bar_txt_uri)
688 errmsg = "Files have no children, certainly not named 'boguschild'"
689 d = self.shouldFail2(error.Error, "test_GET_FILE_URI_badchild",
690 "400 Bad Request", errmsg,
694 def test_PUT_FILE_URI_badchild(self):
695 base = "/uri/%s/boguschild" % urllib.quote(self._bar_txt_uri)
696 errmsg = "Cannot create directory 'boguschild', because its parent is a file, not a directory"
697 d = self.shouldFail2(error.Error, "test_GET_FILE_URI_badchild",
698 "400 Bad Request", errmsg,
702 def test_GET_FILEURL_save(self):
703 d = self.GET(self.public_url + "/foo/bar.txt?filename=bar.txt&save=true")
704 # TODO: look at the headers, expect a Content-Disposition: attachment
706 d.addCallback(self.failUnlessIsBarDotTxt)
709 def test_GET_FILEURL_missing(self):
710 d = self.GET(self.public_url + "/foo/missing")
711 d.addBoth(self.should404, "test_GET_FILEURL_missing")
714 def test_PUT_overwrite_only_files(self):
715 # create a directory, put a file in that directory.
716 contents, n, filecap = self.makefile(8)
717 d = self.PUT(self.public_url + "/foo/dir?t=mkdir", "")
718 d.addCallback(lambda res:
719 self.PUT(self.public_url + "/foo/dir/file1.txt",
720 self.NEWFILE_CONTENTS))
721 # try to overwrite the file with replace=only-files
723 d.addCallback(lambda res:
724 self.PUT(self.public_url + "/foo/dir/file1.txt?t=uri&replace=only-files",
726 d.addCallback(lambda res:
727 self.shouldFail2(error.Error, "PUT_bad_t", "409 Conflict",
728 "There was already a child by that name, and you asked me "
730 self.PUT, self.public_url + "/foo/dir?t=uri&replace=only-files",
734 def test_PUT_NEWFILEURL(self):
735 d = self.PUT(self.public_url + "/foo/new.txt", self.NEWFILE_CONTENTS)
736 # TODO: we lose the response code, so we can't check this
737 #self.failUnlessEqual(responsecode, 201)
738 d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"new.txt")
739 d.addCallback(lambda res:
740 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
741 self.NEWFILE_CONTENTS))
744 def test_PUT_NEWFILEURL_not_mutable(self):
745 d = self.PUT(self.public_url + "/foo/new.txt?mutable=false",
746 self.NEWFILE_CONTENTS)
747 # TODO: we lose the response code, so we can't check this
748 #self.failUnlessEqual(responsecode, 201)
749 d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"new.txt")
750 d.addCallback(lambda res:
751 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
752 self.NEWFILE_CONTENTS))
755 def test_PUT_NEWFILEURL_range_bad(self):
756 headers = {"content-range": "bytes 1-10/%d" % len(self.NEWFILE_CONTENTS)}
757 target = self.public_url + "/foo/new.txt"
758 d = self.shouldFail2(error.Error, "test_PUT_NEWFILEURL_range_bad",
759 "501 Not Implemented",
760 "Content-Range in PUT not yet supported",
761 # (and certainly not for immutable files)
762 self.PUT, target, self.NEWFILE_CONTENTS[1:11],
764 d.addCallback(lambda res:
765 self.failIfNodeHasChild(self._foo_node, u"new.txt"))
768 def test_PUT_NEWFILEURL_mutable(self):
769 d = self.PUT(self.public_url + "/foo/new.txt?mutable=true",
770 self.NEWFILE_CONTENTS)
771 # TODO: we lose the response code, so we can't check this
772 #self.failUnlessEqual(responsecode, 201)
774 u = uri.from_string_mutable_filenode(res)
775 self.failUnless(u.is_mutable())
776 self.failIf(u.is_readonly())
778 d.addCallback(_check_uri)
779 d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"new.txt")
780 d.addCallback(lambda res:
781 self.failUnlessMutableChildContentsAre(self._foo_node,
783 self.NEWFILE_CONTENTS))
786 def test_PUT_NEWFILEURL_mutable_toobig(self):
787 d = self.shouldFail2(error.Error, "test_PUT_NEWFILEURL_mutable_toobig",
788 "413 Request Entity Too Large",
789 "SDMF is limited to one segment, and 10001 > 10000",
791 self.public_url + "/foo/new.txt?mutable=true",
792 "b" * (self.s.MUTABLE_SIZELIMIT+1))
795 def test_PUT_NEWFILEURL_replace(self):
796 d = self.PUT(self.public_url + "/foo/bar.txt", self.NEWFILE_CONTENTS)
797 # TODO: we lose the response code, so we can't check this
798 #self.failUnlessEqual(responsecode, 200)
799 d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"bar.txt")
800 d.addCallback(lambda res:
801 self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
802 self.NEWFILE_CONTENTS))
805 def test_PUT_NEWFILEURL_bad_t(self):
806 d = self.shouldFail2(error.Error, "PUT_bad_t", "400 Bad Request",
807 "PUT to a file: bad t=bogus",
808 self.PUT, self.public_url + "/foo/bar.txt?t=bogus",
812 def test_PUT_NEWFILEURL_no_replace(self):
813 d = self.PUT(self.public_url + "/foo/bar.txt?replace=false",
814 self.NEWFILE_CONTENTS)
815 d.addBoth(self.shouldFail, error.Error, "PUT_NEWFILEURL_no_replace",
817 "There was already a child by that name, and you asked me "
821 def test_PUT_NEWFILEURL_mkdirs(self):
822 d = self.PUT(self.public_url + "/foo/newdir/new.txt", self.NEWFILE_CONTENTS)
824 d.addCallback(self.failUnlessURIMatchesChild, fn, u"newdir/new.txt")
825 d.addCallback(lambda res: self.failIfNodeHasChild(fn, u"new.txt"))
826 d.addCallback(lambda res: self.failUnlessNodeHasChild(fn, u"newdir"))
827 d.addCallback(lambda res:
828 self.failUnlessChildContentsAre(fn, u"newdir/new.txt",
829 self.NEWFILE_CONTENTS))
832 def test_PUT_NEWFILEURL_blocked(self):
833 d = self.PUT(self.public_url + "/foo/blockingfile/new.txt",
834 self.NEWFILE_CONTENTS)
835 d.addBoth(self.shouldFail, error.Error, "PUT_NEWFILEURL_blocked",
837 "Unable to create directory 'blockingfile': a file was in the way")
840 def test_PUT_NEWFILEURL_emptyname(self):
841 # an empty pathname component (i.e. a double-slash) is disallowed
842 d = self.shouldFail2(error.Error, "test_PUT_NEWFILEURL_emptyname",
844 "The webapi does not allow empty pathname components",
845 self.PUT, self.public_url + "/foo//new.txt", "")
848 def test_DELETE_FILEURL(self):
849 d = self.DELETE(self.public_url + "/foo/bar.txt")
850 d.addCallback(lambda res:
851 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
854 def test_DELETE_FILEURL_missing(self):
855 d = self.DELETE(self.public_url + "/foo/missing")
856 d.addBoth(self.should404, "test_DELETE_FILEURL_missing")
859 def test_DELETE_FILEURL_missing2(self):
860 d = self.DELETE(self.public_url + "/missing/missing")
861 d.addBoth(self.should404, "test_DELETE_FILEURL_missing2")
864 def failUnlessHasBarDotTxtMetadata(self, res):
865 data = simplejson.loads(res)
866 self.failUnless(isinstance(data, list))
867 self.failUnless(data[1].has_key("metadata"))
868 self.failUnless(data[1]["metadata"].has_key("ctime"))
869 self.failUnless(data[1]["metadata"].has_key("mtime"))
870 self.failUnlessEqual(data[1]["metadata"]["ctime"],
871 self._bar_txt_metadata["ctime"])
873 def test_GET_FILEURL_json(self):
874 # twisted.web.http.parse_qs ignores any query args without an '=', so
875 # I can't do "GET /path?json", I have to do "GET /path/t=json"
876 # instead. This may make it tricky to emulate the S3 interface
878 d = self.GET(self.public_url + "/foo/bar.txt?t=json")
880 self.failUnlessIsBarJSON(data)
881 self.failUnlessHasBarDotTxtMetadata(data)
883 d.addCallback(_check1)
886 def test_GET_FILEURL_json_missing(self):
887 d = self.GET(self.public_url + "/foo/missing?json")
888 d.addBoth(self.should404, "test_GET_FILEURL_json_missing")
891 def test_GET_FILEURL_uri(self):
892 d = self.GET(self.public_url + "/foo/bar.txt?t=uri")
894 self.failUnlessEqual(res, self._bar_txt_uri)
895 d.addCallback(_check)
896 d.addCallback(lambda res:
897 self.GET(self.public_url + "/foo/bar.txt?t=readonly-uri"))
899 # for now, for files, uris and readonly-uris are the same
900 self.failUnlessEqual(res, self._bar_txt_uri)
901 d.addCallback(_check2)
904 def test_GET_FILEURL_badtype(self):
905 d = self.shouldHTTPError("GET t=bogus", 400, "Bad Request",
908 self.public_url + "/foo/bar.txt?t=bogus")
911 def test_GET_FILEURL_uri_missing(self):
912 d = self.GET(self.public_url + "/foo/missing?t=uri")
913 d.addBoth(self.should404, "test_GET_FILEURL_uri_missing")
916 def test_GET_DIRURL(self):
917 # the addSlash means we get a redirect here
918 # from /uri/$URI/foo/ , we need ../../../ to get back to the root
920 d = self.GET(self.public_url + "/foo", followRedirect=True)
922 self.failUnless(('<a href="%s">Return to Welcome page' % ROOT)
924 # the FILE reference points to a URI, but it should end in bar.txt
925 bar_url = ("%s/file/%s/@@named=/bar.txt" %
926 (ROOT, urllib.quote(self._bar_txt_uri)))
927 get_bar = "".join([r'<td>FILE</td>',
929 r'<a href="%s">bar.txt</a>' % bar_url,
931 r'\s+<td>%d</td>' % len(self.BAR_CONTENTS),
933 self.failUnless(re.search(get_bar, res), res)
934 for line in res.split("\n"):
935 # find the line that contains the delete button for bar.txt
936 if ("form action" in line and
937 'value="delete"' in line and
938 'value="bar.txt"' in line):
939 # the form target should use a relative URL
940 foo_url = urllib.quote("%s/uri/%s/" % (ROOT, self._foo_uri))
941 self.failUnless(('action="%s"' % foo_url) in line, line)
942 # and the when_done= should too
943 #done_url = urllib.quote(???)
944 #self.failUnless(('name="when_done" value="%s"' % done_url)
948 self.fail("unable to find delete-bar.txt line", res)
950 # the DIR reference just points to a URI
951 sub_url = ("%s/uri/%s/" % (ROOT, urllib.quote(self._sub_uri)))
952 get_sub = ((r'<td>DIR</td>')
953 +r'\s+<td><a href="%s">sub</a></td>' % sub_url)
954 self.failUnless(re.search(get_sub, res), res)
955 d.addCallback(_check)
957 # look at a directory which is readonly
958 d.addCallback(lambda res:
959 self.GET(self.public_url + "/reedownlee", followRedirect=True))
961 self.failUnless("(read-only)" in res, res)
962 self.failIf("Upload a file" in res, res)
963 d.addCallback(_check2)
965 # and at a directory that contains a readonly directory
966 d.addCallback(lambda res:
967 self.GET(self.public_url, followRedirect=True))
969 self.failUnless(re.search('<td>DIR-RO</td>'
970 r'\s+<td><a href="[\.\/]+/uri/URI%3ADIR2-RO%3A[^"]+">reedownlee</a></td>', res), res)
971 d.addCallback(_check3)
973 # and an empty directory
974 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty/"))
976 self.failUnless("directory is empty" in res, res)
977 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)
978 self.failUnless(MKDIR_BUTTON_RE.search(res), res)
979 d.addCallback(_check4)
983 def test_GET_DIRURL_badtype(self):
984 d = self.shouldHTTPError("test_GET_DIRURL_badtype",
988 self.public_url + "/foo?t=bogus")
991 def test_GET_DIRURL_json(self):
992 d = self.GET(self.public_url + "/foo?t=json")
993 d.addCallback(self.failUnlessIsFooJSON)
997 def test_POST_DIRURL_manifest_no_ophandle(self):
998 d = self.shouldFail2(error.Error,
999 "test_POST_DIRURL_manifest_no_ophandle",
1001 "slow operation requires ophandle=",
1002 self.POST, self.public_url, t="start-manifest")
1005 def test_POST_DIRURL_manifest(self):
1006 d = defer.succeed(None)
1007 def getman(ignored, output):
1008 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=125",
1009 followRedirect=True)
1010 d.addCallback(self.wait_for_operation, "125")
1011 d.addCallback(self.get_operation_results, "125", output)
1013 d.addCallback(getman, None)
1014 def _got_html(manifest):
1015 self.failUnless("Manifest of SI=" in manifest)
1016 self.failUnless("<td>sub</td>" in manifest)
1017 self.failUnless(self._sub_uri in manifest)
1018 self.failUnless("<td>sub/baz.txt</td>" in manifest)
1019 d.addCallback(_got_html)
1021 # both t=status and unadorned GET should be identical
1022 d.addCallback(lambda res: self.GET("/operations/125"))
1023 d.addCallback(_got_html)
1025 d.addCallback(getman, "html")
1026 d.addCallback(_got_html)
1027 d.addCallback(getman, "text")
1028 def _got_text(manifest):
1029 self.failUnless("\nsub " + self._sub_uri + "\n" in manifest)
1030 self.failUnless("\nsub/baz.txt URI:CHK:" in manifest)
1031 d.addCallback(_got_text)
1032 d.addCallback(getman, "JSON")
1034 data = res["manifest"]
1036 for (path_list, cap) in data:
1037 got[tuple(path_list)] = cap
1038 self.failUnlessEqual(got[(u"sub",)], self._sub_uri)
1039 self.failUnless((u"sub",u"baz.txt") in got)
1040 self.failUnless("finished" in res)
1041 self.failUnless("origin" in res)
1042 self.failUnless("storage-index" in res)
1043 self.failUnless("verifycaps" in res)
1044 self.failUnless("stats" in res)
1045 d.addCallback(_got_json)
1048 def test_POST_DIRURL_deepsize_no_ophandle(self):
1049 d = self.shouldFail2(error.Error,
1050 "test_POST_DIRURL_deepsize_no_ophandle",
1052 "slow operation requires ophandle=",
1053 self.POST, self.public_url, t="start-deep-size")
1056 def test_POST_DIRURL_deepsize(self):
1057 d = self.POST(self.public_url + "/foo/?t=start-deep-size&ophandle=126",
1058 followRedirect=True)
1059 d.addCallback(self.wait_for_operation, "126")
1060 d.addCallback(self.get_operation_results, "126", "json")
1061 def _got_json(data):
1062 self.failUnlessEqual(data["finished"], True)
1064 self.failUnless(size > 1000)
1065 d.addCallback(_got_json)
1066 d.addCallback(self.get_operation_results, "126", "text")
1068 mo = re.search(r'^size: (\d+)$', res, re.M)
1069 self.failUnless(mo, res)
1070 size = int(mo.group(1))
1071 # with directories, the size varies.
1072 self.failUnless(size > 1000)
1073 d.addCallback(_got_text)
1076 def test_POST_DIRURL_deepstats_no_ophandle(self):
1077 d = self.shouldFail2(error.Error,
1078 "test_POST_DIRURL_deepstats_no_ophandle",
1080 "slow operation requires ophandle=",
1081 self.POST, self.public_url, t="start-deep-stats")
1084 def test_POST_DIRURL_deepstats(self):
1085 d = self.POST(self.public_url + "/foo/?t=start-deep-stats&ophandle=127",
1086 followRedirect=True)
1087 d.addCallback(self.wait_for_operation, "127")
1088 d.addCallback(self.get_operation_results, "127", "json")
1089 def _got_json(stats):
1090 expected = {"count-immutable-files": 3,
1091 "count-mutable-files": 0,
1092 "count-literal-files": 0,
1094 "count-directories": 3,
1095 "size-immutable-files": 57,
1096 "size-literal-files": 0,
1097 #"size-directories": 1912, # varies
1098 #"largest-directory": 1590,
1099 "largest-directory-children": 5,
1100 "largest-immutable-file": 19,
1102 for k,v in expected.iteritems():
1103 self.failUnlessEqual(stats[k], v,
1104 "stats[%s] was %s, not %s" %
1106 self.failUnlessEqual(stats["size-files-histogram"],
1108 d.addCallback(_got_json)
1111 def test_POST_DIRURL_stream_manifest(self):
1112 d = self.POST(self.public_url + "/foo/?t=stream-manifest")
1114 self.failUnless(res.endswith("\n"))
1115 units = [simplejson.loads(t) for t in res[:-1].split("\n")]
1116 self.failUnlessEqual(len(units), 7)
1117 self.failUnlessEqual(units[-1]["type"], "stats")
1119 self.failUnlessEqual(first["path"], [])
1120 self.failUnlessEqual(first["cap"], self._foo_uri)
1121 self.failUnlessEqual(first["type"], "directory")
1122 baz = [u for u in units[:-1] if u["cap"] == self._baz_file_uri][0]
1123 self.failUnlessEqual(baz["path"], ["sub", "baz.txt"])
1124 self.failIfEqual(baz["storage-index"], None)
1125 self.failIfEqual(baz["verifycap"], None)
1126 self.failIfEqual(baz["repaircap"], None)
1128 d.addCallback(_check)
1131 def test_GET_DIRURL_uri(self):
1132 d = self.GET(self.public_url + "/foo?t=uri")
1134 self.failUnlessEqual(res, self._foo_uri)
1135 d.addCallback(_check)
1138 def test_GET_DIRURL_readonly_uri(self):
1139 d = self.GET(self.public_url + "/foo?t=readonly-uri")
1141 self.failUnlessEqual(res, self._foo_readonly_uri)
1142 d.addCallback(_check)
1145 def test_PUT_NEWDIRURL(self):
1146 d = self.PUT(self.public_url + "/foo/newdir?t=mkdir", "")
1147 d.addCallback(lambda res:
1148 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1149 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1150 d.addCallback(self.failUnlessNodeKeysAre, [])
1153 def test_POST_NEWDIRURL(self):
1154 d = self.POST2(self.public_url + "/foo/newdir?t=mkdir", "")
1155 d.addCallback(lambda res:
1156 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1157 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1158 d.addCallback(self.failUnlessNodeKeysAre, [])
1161 def test_POST_NEWDIRURL_emptyname(self):
1162 # an empty pathname component (i.e. a double-slash) is disallowed
1163 d = self.shouldFail2(error.Error, "test_POST_NEWDIRURL_emptyname",
1165 "The webapi does not allow empty pathname components, i.e. a double slash",
1166 self.POST, self.public_url + "//?t=mkdir")
1169 def test_POST_NEWDIRURL_initial_children(self):
1170 (newkids, filecap1, filecap2, filecap3,
1171 dircap) = self._create_initial_children()
1172 d = self.POST2(self.public_url + "/foo/newdir?t=mkdir-with-children",
1173 simplejson.dumps(newkids))
1175 n = self.s.create_node_from_uri(uri.strip())
1176 d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
1177 d2.addCallback(lambda ign:
1178 self.failUnlessChildURIIs(n, u"child-imm", filecap1))
1179 d2.addCallback(lambda ign:
1180 self.failUnlessChildURIIs(n, u"child-mutable",
1182 d2.addCallback(lambda ign:
1183 self.failUnlessChildURIIs(n, u"child-mutable-ro",
1185 d2.addCallback(lambda ign:
1186 self.failUnlessChildURIIs(n, u"dirchild", dircap))
1188 d.addCallback(_check)
1189 d.addCallback(lambda res:
1190 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1191 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1192 d.addCallback(self.failUnlessNodeKeysAre, newkids.keys())
1193 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1194 d.addCallback(self.failUnlessChildURIIs, u"child-imm", filecap1)
1197 def test_POST_NEWDIRURL_immutable(self):
1198 (newkids, filecap1, immdircap) = self._create_immutable_children()
1199 d = self.POST2(self.public_url + "/foo/newdir?t=mkdir-immutable",
1200 simplejson.dumps(newkids))
1202 n = self.s.create_node_from_uri(uri.strip())
1203 d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
1204 d2.addCallback(lambda ign:
1205 self.failUnlessChildURIIs(n, u"child-imm", filecap1))
1206 d2.addCallback(lambda ign:
1207 self.failUnlessChildURIIs(n, u"dirchild-imm",
1210 d.addCallback(_check)
1211 d.addCallback(lambda res:
1212 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1213 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1214 d.addCallback(self.failUnlessNodeKeysAre, newkids.keys())
1215 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1216 d.addCallback(self.failUnlessChildURIIs, u"child-imm", filecap1)
1217 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1218 d.addCallback(self.failUnlessChildURIIs, u"dirchild-imm", immdircap)
1219 d.addErrback(self.explain_web_error)
1222 def test_POST_NEWDIRURL_immutable_bad(self):
1223 (newkids, filecap1, filecap2, filecap3,
1224 dircap) = self._create_initial_children()
1225 d = self.shouldFail2(error.Error, "test_POST_NEWDIRURL_immutable_bad",
1227 "a mkdir-immutable operation was given a child that was not itself immutable",
1229 self.public_url + "/foo/newdir?t=mkdir-immutable",
1230 simplejson.dumps(newkids))
1233 def test_PUT_NEWDIRURL_exists(self):
1234 d = self.PUT(self.public_url + "/foo/sub?t=mkdir", "")
1235 d.addCallback(lambda res:
1236 self.failUnlessNodeHasChild(self._foo_node, u"sub"))
1237 d.addCallback(lambda res: self._foo_node.get(u"sub"))
1238 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
1241 def test_PUT_NEWDIRURL_blocked(self):
1242 d = self.shouldFail2(error.Error, "PUT_NEWDIRURL_blocked",
1243 "409 Conflict", "Unable to create directory 'bar.txt': a file was in the way",
1245 self.public_url + "/foo/bar.txt/sub?t=mkdir", "")
1246 d.addCallback(lambda res:
1247 self.failUnlessNodeHasChild(self._foo_node, u"sub"))
1248 d.addCallback(lambda res: self._foo_node.get(u"sub"))
1249 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
1252 def test_PUT_NEWDIRURL_mkdir_p(self):
1253 d = defer.succeed(None)
1254 d.addCallback(lambda res: self.POST(self.public_url + "/foo", t='mkdir', name='mkp'))
1255 d.addCallback(lambda res: self.failUnlessNodeHasChild(self._foo_node, u"mkp"))
1256 d.addCallback(lambda res: self._foo_node.get(u"mkp"))
1257 def mkdir_p(mkpnode):
1258 url = '/uri/%s?t=mkdir-p&path=/sub1/sub2' % urllib.quote(mkpnode.get_uri())
1260 def made_subsub(ssuri):
1261 d = self._foo_node.get_child_at_path(u"mkp/sub1/sub2")
1262 d.addCallback(lambda ssnode: self.failUnlessEqual(ssnode.get_uri(), ssuri))
1264 d.addCallback(lambda uri2: self.failUnlessEqual(uri2, ssuri))
1266 d.addCallback(made_subsub)
1268 d.addCallback(mkdir_p)
1271 def test_PUT_NEWDIRURL_mkdirs(self):
1272 d = self.PUT(self.public_url + "/foo/subdir/newdir?t=mkdir", "")
1273 d.addCallback(lambda res:
1274 self.failIfNodeHasChild(self._foo_node, u"newdir"))
1275 d.addCallback(lambda res:
1276 self.failUnlessNodeHasChild(self._foo_node, u"subdir"))
1277 d.addCallback(lambda res:
1278 self._foo_node.get_child_at_path(u"subdir/newdir"))
1279 d.addCallback(self.failUnlessNodeKeysAre, [])
1282 def test_DELETE_DIRURL(self):
1283 d = self.DELETE(self.public_url + "/foo")
1284 d.addCallback(lambda res:
1285 self.failIfNodeHasChild(self.public_root, u"foo"))
1288 def test_DELETE_DIRURL_missing(self):
1289 d = self.DELETE(self.public_url + "/foo/missing")
1290 d.addBoth(self.should404, "test_DELETE_DIRURL_missing")
1291 d.addCallback(lambda res:
1292 self.failUnlessNodeHasChild(self.public_root, u"foo"))
1295 def test_DELETE_DIRURL_missing2(self):
1296 d = self.DELETE(self.public_url + "/missing")
1297 d.addBoth(self.should404, "test_DELETE_DIRURL_missing2")
1300 def dump_root(self):
1302 w = webish.DirnodeWalkerMixin()
1303 def visitor(childpath, childnode, metadata):
1305 d = w.walk(self.public_root, visitor)
1308 def failUnlessNodeKeysAre(self, node, expected_keys):
1309 for k in expected_keys:
1310 assert isinstance(k, unicode)
1312 def _check(children):
1313 self.failUnlessEqual(sorted(children.keys()), sorted(expected_keys))
1314 d.addCallback(_check)
1316 def failUnlessNodeHasChild(self, node, name):
1317 assert isinstance(name, unicode)
1319 def _check(children):
1320 self.failUnless(name in children)
1321 d.addCallback(_check)
1323 def failIfNodeHasChild(self, node, name):
1324 assert isinstance(name, unicode)
1326 def _check(children):
1327 self.failIf(name in children)
1328 d.addCallback(_check)
1331 def failUnlessChildContentsAre(self, node, name, expected_contents):
1332 assert isinstance(name, unicode)
1333 d = node.get_child_at_path(name)
1334 d.addCallback(lambda node: download_to_data(node))
1335 def _check(contents):
1336 self.failUnlessEqual(contents, expected_contents)
1337 d.addCallback(_check)
1340 def failUnlessMutableChildContentsAre(self, node, name, expected_contents):
1341 assert isinstance(name, unicode)
1342 d = node.get_child_at_path(name)
1343 d.addCallback(lambda node: node.download_best_version())
1344 def _check(contents):
1345 self.failUnlessEqual(contents, expected_contents)
1346 d.addCallback(_check)
1349 def failUnlessChildURIIs(self, node, name, expected_uri):
1350 assert isinstance(name, unicode)
1351 d = node.get_child_at_path(name)
1353 self.failUnlessEqual(child.get_uri(), expected_uri.strip())
1354 d.addCallback(_check)
1357 def failUnlessURIMatchesChild(self, got_uri, node, name):
1358 assert isinstance(name, unicode)
1359 d = node.get_child_at_path(name)
1361 self.failUnlessEqual(got_uri.strip(), child.get_uri())
1362 d.addCallback(_check)
1365 def failUnlessCHKURIHasContents(self, got_uri, contents):
1366 self.failUnless(FakeCHKFileNode.all_contents[got_uri] == contents)
1368 def test_POST_upload(self):
1369 d = self.POST(self.public_url + "/foo", t="upload",
1370 file=("new.txt", self.NEWFILE_CONTENTS))
1372 d.addCallback(self.failUnlessURIMatchesChild, fn, u"new.txt")
1373 d.addCallback(lambda res:
1374 self.failUnlessChildContentsAre(fn, u"new.txt",
1375 self.NEWFILE_CONTENTS))
1378 def test_POST_upload_unicode(self):
1379 filename = u"n\u00e9wer.txt" # n e-acute w e r . t x t
1380 d = self.POST(self.public_url + "/foo", t="upload",
1381 file=(filename, self.NEWFILE_CONTENTS))
1383 d.addCallback(self.failUnlessURIMatchesChild, fn, filename)
1384 d.addCallback(lambda res:
1385 self.failUnlessChildContentsAre(fn, filename,
1386 self.NEWFILE_CONTENTS))
1387 target_url = self.public_url + "/foo/" + filename.encode("utf-8")
1388 d.addCallback(lambda res: self.GET(target_url))
1389 d.addCallback(lambda contents: self.failUnlessEqual(contents,
1390 self.NEWFILE_CONTENTS,
1394 def test_POST_upload_unicode_named(self):
1395 filename = u"n\u00e9wer.txt" # n e-acute w e r . t x t
1396 d = self.POST(self.public_url + "/foo", t="upload",
1398 file=("overridden", self.NEWFILE_CONTENTS))
1400 d.addCallback(self.failUnlessURIMatchesChild, fn, filename)
1401 d.addCallback(lambda res:
1402 self.failUnlessChildContentsAre(fn, filename,
1403 self.NEWFILE_CONTENTS))
1404 target_url = self.public_url + "/foo/" + filename.encode("utf-8")
1405 d.addCallback(lambda res: self.GET(target_url))
1406 d.addCallback(lambda contents: self.failUnlessEqual(contents,
1407 self.NEWFILE_CONTENTS,
1411 def test_POST_upload_no_link(self):
1412 d = self.POST("/uri", t="upload",
1413 file=("new.txt", self.NEWFILE_CONTENTS))
1414 def _check_upload_results(page):
1415 # this should be a page which describes the results of the upload
1416 # that just finished.
1417 self.failUnless("Upload Results:" in page)
1418 self.failUnless("URI:" in page)
1419 uri_re = re.compile("URI: <tt><span>(.*)</span>")
1420 mo = uri_re.search(page)
1421 self.failUnless(mo, page)
1422 new_uri = mo.group(1)
1424 d.addCallback(_check_upload_results)
1425 d.addCallback(self.failUnlessCHKURIHasContents, self.NEWFILE_CONTENTS)
1428 def test_POST_upload_no_link_whendone(self):
1429 d = self.POST("/uri", t="upload", when_done="/",
1430 file=("new.txt", self.NEWFILE_CONTENTS))
1431 d.addBoth(self.shouldRedirect, "/")
1434 def shouldRedirect2(self, which, checker, callable, *args, **kwargs):
1435 d = defer.maybeDeferred(callable, *args, **kwargs)
1437 if isinstance(res, failure.Failure):
1438 res.trap(error.PageRedirect)
1439 statuscode = res.value.status
1440 target = res.value.location
1441 return checker(statuscode, target)
1442 self.fail("%s: callable was supposed to redirect, not return '%s'"
1447 def test_POST_upload_no_link_whendone_results(self):
1448 def check(statuscode, target):
1449 self.failUnlessEqual(statuscode, str(http.FOUND))
1450 self.failUnless(target.startswith(self.webish_url), target)
1451 return client.getPage(target, method="GET")
1452 d = self.shouldRedirect2("test_POST_upload_no_link_whendone_results",
1454 self.POST, "/uri", t="upload",
1455 when_done="/uri/%(uri)s",
1456 file=("new.txt", self.NEWFILE_CONTENTS))
1457 d.addCallback(lambda res:
1458 self.failUnlessEqual(res, self.NEWFILE_CONTENTS))
1461 def test_POST_upload_no_link_mutable(self):
1462 d = self.POST("/uri", t="upload", mutable="true",
1463 file=("new.txt", self.NEWFILE_CONTENTS))
1464 def _check(filecap):
1465 filecap = filecap.strip()
1466 self.failUnless(filecap.startswith("URI:SSK:"), filecap)
1467 self.filecap = filecap
1468 u = uri.WriteableSSKFileURI.init_from_string(filecap)
1469 self.failUnless(u.storage_index in FakeMutableFileNode.all_contents)
1470 n = self.s.create_node_from_uri(filecap)
1471 return n.download_best_version()
1472 d.addCallback(_check)
1474 self.failUnlessEqual(data, self.NEWFILE_CONTENTS)
1475 return self.GET("/uri/%s" % urllib.quote(self.filecap))
1476 d.addCallback(_check2)
1478 self.failUnlessEqual(data, self.NEWFILE_CONTENTS)
1479 return self.GET("/file/%s" % urllib.quote(self.filecap))
1480 d.addCallback(_check3)
1482 self.failUnlessEqual(data, self.NEWFILE_CONTENTS)
1483 d.addCallback(_check4)
1486 def test_POST_upload_no_link_mutable_toobig(self):
1487 d = self.shouldFail2(error.Error,
1488 "test_POST_upload_no_link_mutable_toobig",
1489 "413 Request Entity Too Large",
1490 "SDMF is limited to one segment, and 10001 > 10000",
1492 "/uri", t="upload", mutable="true",
1494 "b" * (self.s.MUTABLE_SIZELIMIT+1)) )
1497 def test_POST_upload_mutable(self):
1498 # this creates a mutable file
1499 d = self.POST(self.public_url + "/foo", t="upload", mutable="true",
1500 file=("new.txt", self.NEWFILE_CONTENTS))
1502 d.addCallback(self.failUnlessURIMatchesChild, fn, u"new.txt")
1503 d.addCallback(lambda res:
1504 self.failUnlessMutableChildContentsAre(fn, u"new.txt",
1505 self.NEWFILE_CONTENTS))
1506 d.addCallback(lambda res: self._foo_node.get(u"new.txt"))
1508 self.failUnless(IMutableFileNode.providedBy(newnode))
1509 self.failUnless(newnode.is_mutable())
1510 self.failIf(newnode.is_readonly())
1511 self._mutable_node = newnode
1512 self._mutable_uri = newnode.get_uri()
1515 # now upload it again and make sure that the URI doesn't change
1516 NEWER_CONTENTS = self.NEWFILE_CONTENTS + "newer\n"
1517 d.addCallback(lambda res:
1518 self.POST(self.public_url + "/foo", t="upload",
1520 file=("new.txt", NEWER_CONTENTS)))
1521 d.addCallback(self.failUnlessURIMatchesChild, fn, u"new.txt")
1522 d.addCallback(lambda res:
1523 self.failUnlessMutableChildContentsAre(fn, u"new.txt",
1525 d.addCallback(lambda res: self._foo_node.get(u"new.txt"))
1527 self.failUnless(IMutableFileNode.providedBy(newnode))
1528 self.failUnless(newnode.is_mutable())
1529 self.failIf(newnode.is_readonly())
1530 self.failUnlessEqual(self._mutable_uri, newnode.get_uri())
1531 d.addCallback(_got2)
1533 # upload a second time, using PUT instead of POST
1534 NEW2_CONTENTS = NEWER_CONTENTS + "overwrite with PUT\n"
1535 d.addCallback(lambda res:
1536 self.PUT(self.public_url + "/foo/new.txt", NEW2_CONTENTS))
1537 d.addCallback(self.failUnlessURIMatchesChild, fn, u"new.txt")
1538 d.addCallback(lambda res:
1539 self.failUnlessMutableChildContentsAre(fn, u"new.txt",
1542 # finally list the directory, since mutable files are displayed
1543 # slightly differently
1545 d.addCallback(lambda res:
1546 self.GET(self.public_url + "/foo/",
1547 followRedirect=True))
1548 def _check_page(res):
1549 # TODO: assert more about the contents
1550 self.failUnless("SSK" in res)
1552 d.addCallback(_check_page)
1554 d.addCallback(lambda res: self._foo_node.get(u"new.txt"))
1556 self.failUnless(IMutableFileNode.providedBy(newnode))
1557 self.failUnless(newnode.is_mutable())
1558 self.failIf(newnode.is_readonly())
1559 self.failUnlessEqual(self._mutable_uri, newnode.get_uri())
1560 d.addCallback(_got3)
1562 # look at the JSON form of the enclosing directory
1563 d.addCallback(lambda res:
1564 self.GET(self.public_url + "/foo/?t=json",
1565 followRedirect=True))
1566 def _check_page_json(res):
1567 parsed = simplejson.loads(res)
1568 self.failUnlessEqual(parsed[0], "dirnode")
1569 children = dict( [(unicode(name),value)
1571 in parsed[1]["children"].iteritems()] )
1572 self.failUnless("new.txt" in children)
1573 new_json = children["new.txt"]
1574 self.failUnlessEqual(new_json[0], "filenode")
1575 self.failUnless(new_json[1]["mutable"])
1576 self.failUnlessEqual(new_json[1]["rw_uri"], self._mutable_uri)
1577 ro_uri = unicode(self._mutable_node.get_readonly().to_string())
1578 self.failUnlessEqual(new_json[1]["ro_uri"], ro_uri)
1579 d.addCallback(_check_page_json)
1581 # and the JSON form of the file
1582 d.addCallback(lambda res:
1583 self.GET(self.public_url + "/foo/new.txt?t=json"))
1584 def _check_file_json(res):
1585 parsed = simplejson.loads(res)
1586 self.failUnlessEqual(parsed[0], "filenode")
1587 self.failUnless(parsed[1]["mutable"])
1588 self.failUnlessEqual(parsed[1]["rw_uri"], self._mutable_uri)
1589 ro_uri = unicode(self._mutable_node.get_readonly().to_string())
1590 self.failUnlessEqual(parsed[1]["ro_uri"], ro_uri)
1591 d.addCallback(_check_file_json)
1593 # and look at t=uri and t=readonly-uri
1594 d.addCallback(lambda res:
1595 self.GET(self.public_url + "/foo/new.txt?t=uri"))
1596 d.addCallback(lambda res: self.failUnlessEqual(res, self._mutable_uri))
1597 d.addCallback(lambda res:
1598 self.GET(self.public_url + "/foo/new.txt?t=readonly-uri"))
1599 def _check_ro_uri(res):
1600 ro_uri = unicode(self._mutable_node.get_readonly().to_string())
1601 self.failUnlessEqual(res, ro_uri)
1602 d.addCallback(_check_ro_uri)
1604 # make sure we can get to it from /uri/URI
1605 d.addCallback(lambda res:
1606 self.GET("/uri/%s" % urllib.quote(self._mutable_uri)))
1607 d.addCallback(lambda res:
1608 self.failUnlessEqual(res, NEW2_CONTENTS))
1610 # and that HEAD computes the size correctly
1611 d.addCallback(lambda res:
1612 self.HEAD(self.public_url + "/foo/new.txt",
1613 return_response=True))
1614 def _got_headers((res, status, headers)):
1615 self.failUnlessEqual(res, "")
1616 self.failUnlessEqual(headers["content-length"][0],
1617 str(len(NEW2_CONTENTS)))
1618 self.failUnlessEqual(headers["content-type"], ["text/plain"])
1619 d.addCallback(_got_headers)
1621 # make sure that size errors are displayed correctly for overwrite
1622 d.addCallback(lambda res:
1623 self.shouldFail2(error.Error,
1624 "test_POST_upload_mutable-toobig",
1625 "413 Request Entity Too Large",
1626 "SDMF is limited to one segment, and 10001 > 10000",
1628 self.public_url + "/foo", t="upload",
1631 "b" * (self.s.MUTABLE_SIZELIMIT+1)),
1634 d.addErrback(self.dump_error)
1637 def test_POST_upload_mutable_toobig(self):
1638 d = self.shouldFail2(error.Error,
1639 "test_POST_upload_mutable_toobig",
1640 "413 Request Entity Too Large",
1641 "SDMF is limited to one segment, and 10001 > 10000",
1643 self.public_url + "/foo",
1644 t="upload", mutable="true",
1646 "b" * (self.s.MUTABLE_SIZELIMIT+1)) )
1649 def dump_error(self, f):
1650 # if the web server returns an error code (like 400 Bad Request),
1651 # web.client.getPage puts the HTTP response body into the .response
1652 # attribute of the exception object that it gives back. It does not
1653 # appear in the Failure's repr(), so the ERROR that trial displays
1654 # will be rather terse and unhelpful. addErrback this method to the
1655 # end of your chain to get more information out of these errors.
1656 if f.check(error.Error):
1657 print "web.error.Error:"
1659 print f.value.response
1662 def test_POST_upload_replace(self):
1663 d = self.POST(self.public_url + "/foo", t="upload",
1664 file=("bar.txt", self.NEWFILE_CONTENTS))
1666 d.addCallback(self.failUnlessURIMatchesChild, fn, u"bar.txt")
1667 d.addCallback(lambda res:
1668 self.failUnlessChildContentsAre(fn, u"bar.txt",
1669 self.NEWFILE_CONTENTS))
1672 def test_POST_upload_no_replace_ok(self):
1673 d = self.POST(self.public_url + "/foo?replace=false", t="upload",
1674 file=("new.txt", self.NEWFILE_CONTENTS))
1675 d.addCallback(lambda res: self.GET(self.public_url + "/foo/new.txt"))
1676 d.addCallback(lambda res: self.failUnlessEqual(res,
1677 self.NEWFILE_CONTENTS))
1680 def test_POST_upload_no_replace_queryarg(self):
1681 d = self.POST(self.public_url + "/foo?replace=false", t="upload",
1682 file=("bar.txt", self.NEWFILE_CONTENTS))
1683 d.addBoth(self.shouldFail, error.Error,
1684 "POST_upload_no_replace_queryarg",
1686 "There was already a child by that name, and you asked me "
1687 "to not replace it")
1688 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
1689 d.addCallback(self.failUnlessIsBarDotTxt)
1692 def test_POST_upload_no_replace_field(self):
1693 d = self.POST(self.public_url + "/foo", t="upload", replace="false",
1694 file=("bar.txt", self.NEWFILE_CONTENTS))
1695 d.addBoth(self.shouldFail, error.Error, "POST_upload_no_replace_field",
1697 "There was already a child by that name, and you asked me "
1698 "to not replace it")
1699 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
1700 d.addCallback(self.failUnlessIsBarDotTxt)
1703 def test_POST_upload_whendone(self):
1704 d = self.POST(self.public_url + "/foo", t="upload", when_done="/THERE",
1705 file=("new.txt", self.NEWFILE_CONTENTS))
1706 d.addBoth(self.shouldRedirect, "/THERE")
1708 d.addCallback(lambda res:
1709 self.failUnlessChildContentsAre(fn, u"new.txt",
1710 self.NEWFILE_CONTENTS))
1713 def test_POST_upload_named(self):
1715 d = self.POST(self.public_url + "/foo", t="upload",
1716 name="new.txt", file=self.NEWFILE_CONTENTS)
1717 d.addCallback(self.failUnlessURIMatchesChild, fn, u"new.txt")
1718 d.addCallback(lambda res:
1719 self.failUnlessChildContentsAre(fn, u"new.txt",
1720 self.NEWFILE_CONTENTS))
1723 def test_POST_upload_named_badfilename(self):
1724 d = self.POST(self.public_url + "/foo", t="upload",
1725 name="slashes/are/bad.txt", file=self.NEWFILE_CONTENTS)
1726 d.addBoth(self.shouldFail, error.Error,
1727 "test_POST_upload_named_badfilename",
1729 "name= may not contain a slash",
1731 # make sure that nothing was added
1732 d.addCallback(lambda res:
1733 self.failUnlessNodeKeysAre(self._foo_node,
1734 [u"bar.txt", u"blockingfile",
1735 u"empty", u"n\u00fc.txt",
1739 def test_POST_FILEURL_check(self):
1740 bar_url = self.public_url + "/foo/bar.txt"
1741 d = self.POST(bar_url, t="check")
1743 self.failUnless("Healthy :" in res)
1744 d.addCallback(_check)
1745 redir_url = "http://allmydata.org/TARGET"
1746 def _check2(statuscode, target):
1747 self.failUnlessEqual(statuscode, str(http.FOUND))
1748 self.failUnlessEqual(target, redir_url)
1749 d.addCallback(lambda res:
1750 self.shouldRedirect2("test_POST_FILEURL_check",
1754 when_done=redir_url))
1755 d.addCallback(lambda res:
1756 self.POST(bar_url, t="check", return_to=redir_url))
1758 self.failUnless("Healthy :" in res)
1759 self.failUnless("Return to file" in res)
1760 self.failUnless(redir_url in res)
1761 d.addCallback(_check3)
1763 d.addCallback(lambda res:
1764 self.POST(bar_url, t="check", output="JSON"))
1765 def _check_json(res):
1766 data = simplejson.loads(res)
1767 self.failUnless("storage-index" in data)
1768 self.failUnless(data["results"]["healthy"])
1769 d.addCallback(_check_json)
1773 def test_POST_FILEURL_check_and_repair(self):
1774 bar_url = self.public_url + "/foo/bar.txt"
1775 d = self.POST(bar_url, t="check", repair="true")
1777 self.failUnless("Healthy :" in res)
1778 d.addCallback(_check)
1779 redir_url = "http://allmydata.org/TARGET"
1780 def _check2(statuscode, target):
1781 self.failUnlessEqual(statuscode, str(http.FOUND))
1782 self.failUnlessEqual(target, redir_url)
1783 d.addCallback(lambda res:
1784 self.shouldRedirect2("test_POST_FILEURL_check_and_repair",
1787 t="check", repair="true",
1788 when_done=redir_url))
1789 d.addCallback(lambda res:
1790 self.POST(bar_url, t="check", return_to=redir_url))
1792 self.failUnless("Healthy :" in res)
1793 self.failUnless("Return to file" in res)
1794 self.failUnless(redir_url in res)
1795 d.addCallback(_check3)
1798 def test_POST_DIRURL_check(self):
1799 foo_url = self.public_url + "/foo/"
1800 d = self.POST(foo_url, t="check")
1802 self.failUnless("Healthy :" in res, res)
1803 d.addCallback(_check)
1804 redir_url = "http://allmydata.org/TARGET"
1805 def _check2(statuscode, target):
1806 self.failUnlessEqual(statuscode, str(http.FOUND))
1807 self.failUnlessEqual(target, redir_url)
1808 d.addCallback(lambda res:
1809 self.shouldRedirect2("test_POST_DIRURL_check",
1813 when_done=redir_url))
1814 d.addCallback(lambda res:
1815 self.POST(foo_url, t="check", return_to=redir_url))
1817 self.failUnless("Healthy :" in res, res)
1818 self.failUnless("Return to file/directory" in res)
1819 self.failUnless(redir_url in res)
1820 d.addCallback(_check3)
1822 d.addCallback(lambda res:
1823 self.POST(foo_url, t="check", output="JSON"))
1824 def _check_json(res):
1825 data = simplejson.loads(res)
1826 self.failUnless("storage-index" in data)
1827 self.failUnless(data["results"]["healthy"])
1828 d.addCallback(_check_json)
1832 def test_POST_DIRURL_check_and_repair(self):
1833 foo_url = self.public_url + "/foo/"
1834 d = self.POST(foo_url, t="check", repair="true")
1836 self.failUnless("Healthy :" in res, res)
1837 d.addCallback(_check)
1838 redir_url = "http://allmydata.org/TARGET"
1839 def _check2(statuscode, target):
1840 self.failUnlessEqual(statuscode, str(http.FOUND))
1841 self.failUnlessEqual(target, redir_url)
1842 d.addCallback(lambda res:
1843 self.shouldRedirect2("test_POST_DIRURL_check_and_repair",
1846 t="check", repair="true",
1847 when_done=redir_url))
1848 d.addCallback(lambda res:
1849 self.POST(foo_url, t="check", return_to=redir_url))
1851 self.failUnless("Healthy :" in res)
1852 self.failUnless("Return to file/directory" in res)
1853 self.failUnless(redir_url in res)
1854 d.addCallback(_check3)
1857 def wait_for_operation(self, ignored, ophandle):
1858 url = "/operations/" + ophandle
1859 url += "?t=status&output=JSON"
1862 data = simplejson.loads(res)
1863 if not data["finished"]:
1864 d = self.stall(delay=1.0)
1865 d.addCallback(self.wait_for_operation, ophandle)
1871 def get_operation_results(self, ignored, ophandle, output=None):
1872 url = "/operations/" + ophandle
1875 url += "&output=" + output
1878 if output and output.lower() == "json":
1879 return simplejson.loads(res)
1884 def test_POST_DIRURL_deepcheck_no_ophandle(self):
1885 d = self.shouldFail2(error.Error,
1886 "test_POST_DIRURL_deepcheck_no_ophandle",
1888 "slow operation requires ophandle=",
1889 self.POST, self.public_url, t="start-deep-check")
1892 def test_POST_DIRURL_deepcheck(self):
1893 def _check_redirect(statuscode, target):
1894 self.failUnlessEqual(statuscode, str(http.FOUND))
1895 self.failUnless(target.endswith("/operations/123"))
1896 d = self.shouldRedirect2("test_POST_DIRURL_deepcheck", _check_redirect,
1897 self.POST, self.public_url,
1898 t="start-deep-check", ophandle="123")
1899 d.addCallback(self.wait_for_operation, "123")
1900 def _check_json(data):
1901 self.failUnlessEqual(data["finished"], True)
1902 self.failUnlessEqual(data["count-objects-checked"], 8)
1903 self.failUnlessEqual(data["count-objects-healthy"], 8)
1904 d.addCallback(_check_json)
1905 d.addCallback(self.get_operation_results, "123", "html")
1906 def _check_html(res):
1907 self.failUnless("Objects Checked: <span>8</span>" in res)
1908 self.failUnless("Objects Healthy: <span>8</span>" in res)
1909 d.addCallback(_check_html)
1911 d.addCallback(lambda res:
1912 self.GET("/operations/123/"))
1913 d.addCallback(_check_html) # should be the same as without the slash
1915 d.addCallback(lambda res:
1916 self.shouldFail2(error.Error, "one", "404 Not Found",
1917 "No detailed results for SI bogus",
1918 self.GET, "/operations/123/bogus"))
1920 foo_si = self._foo_node.get_storage_index()
1921 foo_si_s = base32.b2a(foo_si)
1922 d.addCallback(lambda res:
1923 self.GET("/operations/123/%s?output=JSON" % foo_si_s))
1924 def _check_foo_json(res):
1925 data = simplejson.loads(res)
1926 self.failUnlessEqual(data["storage-index"], foo_si_s)
1927 self.failUnless(data["results"]["healthy"])
1928 d.addCallback(_check_foo_json)
1931 def test_POST_DIRURL_deepcheck_and_repair(self):
1932 d = self.POST(self.public_url, t="start-deep-check", repair="true",
1933 ophandle="124", output="json", followRedirect=True)
1934 d.addCallback(self.wait_for_operation, "124")
1935 def _check_json(data):
1936 self.failUnlessEqual(data["finished"], True)
1937 self.failUnlessEqual(data["count-objects-checked"], 8)
1938 self.failUnlessEqual(data["count-objects-healthy-pre-repair"], 8)
1939 self.failUnlessEqual(data["count-objects-unhealthy-pre-repair"], 0)
1940 self.failUnlessEqual(data["count-corrupt-shares-pre-repair"], 0)
1941 self.failUnlessEqual(data["count-repairs-attempted"], 0)
1942 self.failUnlessEqual(data["count-repairs-successful"], 0)
1943 self.failUnlessEqual(data["count-repairs-unsuccessful"], 0)
1944 self.failUnlessEqual(data["count-objects-healthy-post-repair"], 8)
1945 self.failUnlessEqual(data["count-objects-unhealthy-post-repair"], 0)
1946 self.failUnlessEqual(data["count-corrupt-shares-post-repair"], 0)
1947 d.addCallback(_check_json)
1948 d.addCallback(self.get_operation_results, "124", "html")
1949 def _check_html(res):
1950 self.failUnless("Objects Checked: <span>8</span>" in res)
1952 self.failUnless("Objects Healthy (before repair): <span>8</span>" in res)
1953 self.failUnless("Objects Unhealthy (before repair): <span>0</span>" in res)
1954 self.failUnless("Corrupt Shares (before repair): <span>0</span>" in res)
1956 self.failUnless("Repairs Attempted: <span>0</span>" in res)
1957 self.failUnless("Repairs Successful: <span>0</span>" in res)
1958 self.failUnless("Repairs Unsuccessful: <span>0</span>" in res)
1960 self.failUnless("Objects Healthy (after repair): <span>8</span>" in res)
1961 self.failUnless("Objects Unhealthy (after repair): <span>0</span>" in res)
1962 self.failUnless("Corrupt Shares (after repair): <span>0</span>" in res)
1963 d.addCallback(_check_html)
1966 def test_POST_FILEURL_bad_t(self):
1967 d = self.shouldFail2(error.Error, "POST_bad_t", "400 Bad Request",
1968 "POST to file: bad t=bogus",
1969 self.POST, self.public_url + "/foo/bar.txt",
1973 def test_POST_mkdir(self): # return value?
1974 d = self.POST(self.public_url + "/foo", t="mkdir", name="newdir")
1975 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1976 d.addCallback(self.failUnlessNodeKeysAre, [])
1979 def test_POST_mkdir_initial_children(self):
1980 newkids, filecap1, ign, ign, ign = self._create_initial_children()
1981 d = self.POST2(self.public_url +
1982 "/foo?t=mkdir-with-children&name=newdir",
1983 simplejson.dumps(newkids))
1984 d.addCallback(lambda res:
1985 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1986 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1987 d.addCallback(self.failUnlessNodeKeysAre, newkids.keys())
1988 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1989 d.addCallback(self.failUnlessChildURIIs, u"child-imm", filecap1)
1992 def test_POST_mkdir_immutable(self):
1993 (newkids, filecap1, immdircap) = self._create_immutable_children()
1994 d = self.POST2(self.public_url +
1995 "/foo?t=mkdir-immutable&name=newdir",
1996 simplejson.dumps(newkids))
1997 d.addCallback(lambda res:
1998 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1999 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2000 d.addCallback(self.failUnlessNodeKeysAre, newkids.keys())
2001 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2002 d.addCallback(self.failUnlessChildURIIs, u"child-imm", filecap1)
2003 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2004 d.addCallback(self.failUnlessChildURIIs, u"dirchild-imm", immdircap)
2007 def test_POST_mkdir_immutable_bad(self):
2008 (newkids, filecap1, filecap2, filecap3,
2009 dircap) = self._create_initial_children()
2010 d = self.shouldFail2(error.Error, "test_POST_mkdir_immutable_bad",
2012 "a mkdir-immutable operation was given a child that was not itself immutable",
2015 "/foo?t=mkdir-immutable&name=newdir",
2016 simplejson.dumps(newkids))
2019 def test_POST_mkdir_2(self):
2020 d = self.POST(self.public_url + "/foo/newdir?t=mkdir", "")
2021 d.addCallback(lambda res:
2022 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
2023 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2024 d.addCallback(self.failUnlessNodeKeysAre, [])
2027 def test_POST_mkdirs_2(self):
2028 d = self.POST(self.public_url + "/foo/bardir/newdir?t=mkdir", "")
2029 d.addCallback(lambda res:
2030 self.failUnlessNodeHasChild(self._foo_node, u"bardir"))
2031 d.addCallback(lambda res: self._foo_node.get(u"bardir"))
2032 d.addCallback(lambda bardirnode: bardirnode.get(u"newdir"))
2033 d.addCallback(self.failUnlessNodeKeysAre, [])
2036 def test_POST_mkdir_no_parentdir_noredirect(self):
2037 d = self.POST("/uri?t=mkdir")
2038 def _after_mkdir(res):
2039 uri.DirectoryURI.init_from_string(res)
2040 d.addCallback(_after_mkdir)
2043 def test_POST_mkdir_no_parentdir_noredirect2(self):
2044 # make sure form-based arguments (as on the welcome page) still work
2045 d = self.POST("/uri", t="mkdir")
2046 def _after_mkdir(res):
2047 uri.DirectoryURI.init_from_string(res)
2048 d.addCallback(_after_mkdir)
2049 d.addErrback(self.explain_web_error)
2052 def test_POST_mkdir_no_parentdir_redirect(self):
2053 d = self.POST("/uri?t=mkdir&redirect_to_result=true")
2054 d.addBoth(self.shouldRedirect, None, statuscode='303')
2055 def _check_target(target):
2056 target = urllib.unquote(target)
2057 self.failUnless(target.startswith("uri/URI:DIR2:"), target)
2058 d.addCallback(_check_target)
2061 def test_POST_mkdir_no_parentdir_redirect2(self):
2062 d = self.POST("/uri", t="mkdir", redirect_to_result="true")
2063 d.addBoth(self.shouldRedirect, None, statuscode='303')
2064 def _check_target(target):
2065 target = urllib.unquote(target)
2066 self.failUnless(target.startswith("uri/URI:DIR2:"), target)
2067 d.addCallback(_check_target)
2068 d.addErrback(self.explain_web_error)
2071 def _create_initial_children(self):
2072 contents, n, filecap1 = self.makefile(12)
2073 md1 = {"metakey1": "metavalue1"}
2074 filecap2 = make_mutable_file_uri()
2075 node3 = self.s.create_node_from_uri(make_mutable_file_uri())
2076 filecap3 = node3.get_readonly_uri()
2077 node4 = self.s.create_node_from_uri(make_mutable_file_uri())
2078 dircap = DirectoryNode(node4, None, None).get_uri()
2079 newkids = {u"child-imm": ["filenode", {"ro_uri": filecap1,
2080 "metadata": md1, }],
2081 u"child-mutable": ["filenode", {"rw_uri": filecap2}],
2082 u"child-mutable-ro": ["filenode", {"ro_uri": filecap3}],
2083 u"dirchild": ["dirnode", {"rw_uri": dircap}],
2085 return newkids, filecap1, filecap2, filecap3, dircap
2087 def _create_immutable_children(self):
2088 contents, n, filecap1 = self.makefile(12)
2089 md1 = {"metakey1": "metavalue1"}
2090 tnode = create_chk_filenode("immutable directory contents\n"*10)
2091 dnode = DirectoryNode(tnode, None, None)
2092 assert not dnode.is_mutable()
2093 immdircap = dnode.get_uri()
2094 newkids = {u"child-imm": ["filenode", {"ro_uri": filecap1,
2095 "metadata": md1, }],
2096 u"dirchild-imm": ["dirnode", {"ro_uri": immdircap}],
2098 return newkids, filecap1, immdircap
2100 def test_POST_mkdir_no_parentdir_initial_children(self):
2101 (newkids, filecap1, filecap2, filecap3,
2102 dircap) = self._create_initial_children()
2103 d = self.POST2("/uri?t=mkdir-with-children", simplejson.dumps(newkids))
2104 def _after_mkdir(res):
2105 self.failUnless(res.startswith("URI:DIR"), res)
2106 n = self.s.create_node_from_uri(res)
2107 d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
2108 d2.addCallback(lambda ign:
2109 self.failUnlessChildURIIs(n, u"child-imm", filecap1))
2110 d2.addCallback(lambda ign:
2111 self.failUnlessChildURIIs(n, u"child-mutable",
2113 d2.addCallback(lambda ign:
2114 self.failUnlessChildURIIs(n, u"child-mutable-ro",
2116 d2.addCallback(lambda ign:
2117 self.failUnlessChildURIIs(n, u"dirchild", dircap))
2119 d.addCallback(_after_mkdir)
2122 def test_POST_mkdir_no_parentdir_unexpected_children(self):
2123 # the regular /uri?t=mkdir operation is specified to ignore its body.
2124 # Only t=mkdir-with-children pays attention to it.
2125 (newkids, filecap1, filecap2, filecap3,
2126 dircap) = self._create_initial_children()
2127 d = self.shouldHTTPError("POST t=mkdir unexpected children",
2129 "t=mkdir does not accept children=, "
2130 "try t=mkdir-with-children instead",
2131 self.POST2, "/uri?t=mkdir", # without children
2132 simplejson.dumps(newkids))
2135 def test_POST_noparent_bad(self):
2136 d = self.shouldHTTPError("POST /uri?t=bogus", 400, "Bad Request",
2137 "/uri accepts only PUT, PUT?t=mkdir, "
2138 "POST?t=upload, and POST?t=mkdir",
2139 self.POST, "/uri?t=bogus")
2142 def test_POST_mkdir_no_parentdir_immutable(self):
2143 (newkids, filecap1, immdircap) = self._create_immutable_children()
2144 d = self.POST2("/uri?t=mkdir-immutable", simplejson.dumps(newkids))
2145 def _after_mkdir(res):
2146 self.failUnless(res.startswith("URI:DIR"), res)
2147 n = self.s.create_node_from_uri(res)
2148 d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
2149 d2.addCallback(lambda ign:
2150 self.failUnlessChildURIIs(n, u"child-imm", filecap1))
2151 d2.addCallback(lambda ign:
2152 self.failUnlessChildURIIs(n, u"dirchild-imm",
2155 d.addCallback(_after_mkdir)
2158 def test_POST_mkdir_no_parentdir_immutable_bad(self):
2159 (newkids, filecap1, filecap2, filecap3,
2160 dircap) = self._create_initial_children()
2161 d = self.shouldFail2(error.Error,
2162 "test_POST_mkdir_no_parentdir_immutable_bad",
2164 "a mkdir-immutable operation was given a child that was not itself immutable",
2166 "/uri?t=mkdir-immutable",
2167 simplejson.dumps(newkids))
2170 def test_welcome_page_mkdir_button(self):
2171 # Fetch the welcome page.
2173 def _after_get_welcome_page(res):
2174 MKDIR_BUTTON_RE=re.compile('<form action="([^"]*)" method="post".*?<input type="hidden" name="t" value="([^"]*)" /><input type="hidden" name="([^"]*)" value="([^"]*)" /><input type="submit" value="Create a directory" />', re.I)
2175 mo = MKDIR_BUTTON_RE.search(res)
2176 formaction = mo.group(1)
2178 formaname = mo.group(3)
2179 formavalue = mo.group(4)
2180 return (formaction, formt, formaname, formavalue)
2181 d.addCallback(_after_get_welcome_page)
2182 def _after_parse_form(res):
2183 (formaction, formt, formaname, formavalue) = res
2184 return self.POST("/%s?t=%s&%s=%s" % (formaction, formt, formaname, formavalue))
2185 d.addCallback(_after_parse_form)
2186 d.addBoth(self.shouldRedirect, None, statuscode='303')
2189 def test_POST_mkdir_replace(self): # return value?
2190 d = self.POST(self.public_url + "/foo", t="mkdir", name="sub")
2191 d.addCallback(lambda res: self._foo_node.get(u"sub"))
2192 d.addCallback(self.failUnlessNodeKeysAre, [])
2195 def test_POST_mkdir_no_replace_queryarg(self): # return value?
2196 d = self.POST(self.public_url + "/foo?replace=false", t="mkdir", name="sub")
2197 d.addBoth(self.shouldFail, error.Error,
2198 "POST_mkdir_no_replace_queryarg",
2200 "There was already a child by that name, and you asked me "
2201 "to not replace it")
2202 d.addCallback(lambda res: self._foo_node.get(u"sub"))
2203 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
2206 def test_POST_mkdir_no_replace_field(self): # return value?
2207 d = self.POST(self.public_url + "/foo", t="mkdir", name="sub",
2209 d.addBoth(self.shouldFail, error.Error, "POST_mkdir_no_replace_field",
2211 "There was already a child by that name, and you asked me "
2212 "to not replace it")
2213 d.addCallback(lambda res: self._foo_node.get(u"sub"))
2214 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
2217 def test_POST_mkdir_whendone_field(self):
2218 d = self.POST(self.public_url + "/foo",
2219 t="mkdir", name="newdir", when_done="/THERE")
2220 d.addBoth(self.shouldRedirect, "/THERE")
2221 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2222 d.addCallback(self.failUnlessNodeKeysAre, [])
2225 def test_POST_mkdir_whendone_queryarg(self):
2226 d = self.POST(self.public_url + "/foo?when_done=/THERE",
2227 t="mkdir", name="newdir")
2228 d.addBoth(self.shouldRedirect, "/THERE")
2229 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2230 d.addCallback(self.failUnlessNodeKeysAre, [])
2233 def test_POST_bad_t(self):
2234 d = self.shouldFail2(error.Error, "POST_bad_t", "400 Bad Request",
2235 "POST to a directory with bad t=BOGUS",
2236 self.POST, self.public_url + "/foo", t="BOGUS")
2239 def test_POST_set_children(self):
2240 contents9, n9, newuri9 = self.makefile(9)
2241 contents10, n10, newuri10 = self.makefile(10)
2242 contents11, n11, newuri11 = self.makefile(11)
2245 "atomic_added_1": [ "filenode", { "rw_uri": "%s",
2248 "ctime": 1002777696.7564139,
2249 "mtime": 1002777696.7564139
2252 "atomic_added_2": [ "filenode", { "rw_uri": "%s",
2255 "ctime": 1002777696.7564139,
2256 "mtime": 1002777696.7564139
2259 "atomic_added_3": [ "filenode", { "rw_uri": "%s",
2262 "ctime": 1002777696.7564139,
2263 "mtime": 1002777696.7564139
2266 }""" % (newuri9, newuri10, newuri11)
2268 url = self.webish_url + self.public_url + "/foo" + "?t=set_children"
2270 d = client.getPage(url, method="POST", postdata=reqbody)
2272 self.failUnlessURIMatchesChild(newuri9, self._foo_node, u"atomic_added_1")
2273 self.failUnlessURIMatchesChild(newuri10, self._foo_node, u"atomic_added_2")
2274 self.failUnlessURIMatchesChild(newuri11, self._foo_node, u"atomic_added_3")
2276 d.addCallback(_then)
2277 d.addErrback(self.dump_error)
2280 def test_POST_put_uri(self):
2281 contents, n, newuri = self.makefile(8)
2282 d = self.POST(self.public_url + "/foo", t="uri", name="new.txt", uri=newuri)
2283 d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"new.txt")
2284 d.addCallback(lambda res:
2285 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
2289 def test_POST_put_uri_replace(self):
2290 contents, n, newuri = self.makefile(8)
2291 d = self.POST(self.public_url + "/foo", t="uri", name="bar.txt", uri=newuri)
2292 d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"bar.txt")
2293 d.addCallback(lambda res:
2294 self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
2298 def test_POST_put_uri_no_replace_queryarg(self):
2299 contents, n, newuri = self.makefile(8)
2300 d = self.POST(self.public_url + "/foo?replace=false", t="uri",
2301 name="bar.txt", uri=newuri)
2302 d.addBoth(self.shouldFail, error.Error,
2303 "POST_put_uri_no_replace_queryarg",
2305 "There was already a child by that name, and you asked me "
2306 "to not replace it")
2307 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
2308 d.addCallback(self.failUnlessIsBarDotTxt)
2311 def test_POST_put_uri_no_replace_field(self):
2312 contents, n, newuri = self.makefile(8)
2313 d = self.POST(self.public_url + "/foo", t="uri", replace="false",
2314 name="bar.txt", uri=newuri)
2315 d.addBoth(self.shouldFail, error.Error,
2316 "POST_put_uri_no_replace_field",
2318 "There was already a child by that name, and you asked me "
2319 "to not replace it")
2320 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
2321 d.addCallback(self.failUnlessIsBarDotTxt)
2324 def test_POST_delete(self):
2325 d = self.POST(self.public_url + "/foo", t="delete", name="bar.txt")
2326 d.addCallback(lambda res: self._foo_node.list())
2327 def _check(children):
2328 self.failIf(u"bar.txt" in children)
2329 d.addCallback(_check)
2332 def test_POST_rename_file(self):
2333 d = self.POST(self.public_url + "/foo", t="rename",
2334 from_name="bar.txt", to_name='wibble.txt')
2335 d.addCallback(lambda res:
2336 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
2337 d.addCallback(lambda res:
2338 self.failUnlessNodeHasChild(self._foo_node, u"wibble.txt"))
2339 d.addCallback(lambda res: self.GET(self.public_url + "/foo/wibble.txt"))
2340 d.addCallback(self.failUnlessIsBarDotTxt)
2341 d.addCallback(lambda res: self.GET(self.public_url + "/foo/wibble.txt?t=json"))
2342 d.addCallback(self.failUnlessIsBarJSON)
2345 def test_POST_rename_file_redundant(self):
2346 d = self.POST(self.public_url + "/foo", t="rename",
2347 from_name="bar.txt", to_name='bar.txt')
2348 d.addCallback(lambda res:
2349 self.failUnlessNodeHasChild(self._foo_node, u"bar.txt"))
2350 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
2351 d.addCallback(self.failUnlessIsBarDotTxt)
2352 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt?t=json"))
2353 d.addCallback(self.failUnlessIsBarJSON)
2356 def test_POST_rename_file_replace(self):
2357 # rename a file and replace a directory with it
2358 d = self.POST(self.public_url + "/foo", t="rename",
2359 from_name="bar.txt", to_name='empty')
2360 d.addCallback(lambda res:
2361 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
2362 d.addCallback(lambda res:
2363 self.failUnlessNodeHasChild(self._foo_node, u"empty"))
2364 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty"))
2365 d.addCallback(self.failUnlessIsBarDotTxt)
2366 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
2367 d.addCallback(self.failUnlessIsBarJSON)
2370 def test_POST_rename_file_no_replace_queryarg(self):
2371 # rename a file and replace a directory with it
2372 d = self.POST(self.public_url + "/foo?replace=false", t="rename",
2373 from_name="bar.txt", to_name='empty')
2374 d.addBoth(self.shouldFail, error.Error,
2375 "POST_rename_file_no_replace_queryarg",
2377 "There was already a child by that name, and you asked me "
2378 "to not replace it")
2379 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
2380 d.addCallback(self.failUnlessIsEmptyJSON)
2383 def test_POST_rename_file_no_replace_field(self):
2384 # rename a file and replace a directory with it
2385 d = self.POST(self.public_url + "/foo", t="rename", replace="false",
2386 from_name="bar.txt", to_name='empty')
2387 d.addBoth(self.shouldFail, error.Error,
2388 "POST_rename_file_no_replace_field",
2390 "There was already a child by that name, and you asked me "
2391 "to not replace it")
2392 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
2393 d.addCallback(self.failUnlessIsEmptyJSON)
2396 def failUnlessIsEmptyJSON(self, res):
2397 data = simplejson.loads(res)
2398 self.failUnlessEqual(data[0], "dirnode", data)
2399 self.failUnlessEqual(len(data[1]["children"]), 0)
2401 def test_POST_rename_file_slash_fail(self):
2402 d = self.POST(self.public_url + "/foo", t="rename",
2403 from_name="bar.txt", to_name='kirk/spock.txt')
2404 d.addBoth(self.shouldFail, error.Error,
2405 "test_POST_rename_file_slash_fail",
2407 "to_name= may not contain a slash",
2409 d.addCallback(lambda res:
2410 self.failUnlessNodeHasChild(self._foo_node, u"bar.txt"))
2413 def test_POST_rename_dir(self):
2414 d = self.POST(self.public_url, t="rename",
2415 from_name="foo", to_name='plunk')
2416 d.addCallback(lambda res:
2417 self.failIfNodeHasChild(self.public_root, u"foo"))
2418 d.addCallback(lambda res:
2419 self.failUnlessNodeHasChild(self.public_root, u"plunk"))
2420 d.addCallback(lambda res: self.GET(self.public_url + "/plunk?t=json"))
2421 d.addCallback(self.failUnlessIsFooJSON)
2424 def shouldRedirect(self, res, target=None, statuscode=None, which=""):
2425 """ If target is not None then the redirection has to go to target. If
2426 statuscode is not None then the redirection has to be accomplished with
2427 that HTTP status code."""
2428 if not isinstance(res, failure.Failure):
2429 to_where = (target is None) and "somewhere" or ("to " + target)
2430 self.fail("%s: we were expecting to get redirected %s, not get an"
2431 " actual page: %s" % (which, to_where, res))
2432 res.trap(error.PageRedirect)
2433 if statuscode is not None:
2434 self.failUnlessEqual(res.value.status, statuscode,
2435 "%s: not a redirect" % which)
2436 if target is not None:
2437 # the PageRedirect does not seem to capture the uri= query arg
2438 # properly, so we can't check for it.
2439 realtarget = self.webish_url + target
2440 self.failUnlessEqual(res.value.location, realtarget,
2441 "%s: wrong target" % which)
2442 return res.value.location
2444 def test_GET_URI_form(self):
2445 base = "/uri?uri=%s" % self._bar_txt_uri
2446 # this is supposed to give us a redirect to /uri/$URI, plus arguments
2447 targetbase = "/uri/%s" % urllib.quote(self._bar_txt_uri)
2449 d.addBoth(self.shouldRedirect, targetbase)
2450 d.addCallback(lambda res: self.GET(base+"&filename=bar.txt"))
2451 d.addBoth(self.shouldRedirect, targetbase+"?filename=bar.txt")
2452 d.addCallback(lambda res: self.GET(base+"&t=json"))
2453 d.addBoth(self.shouldRedirect, targetbase+"?t=json")
2454 d.addCallback(self.log, "about to get file by uri")
2455 d.addCallback(lambda res: self.GET(base, followRedirect=True))
2456 d.addCallback(self.failUnlessIsBarDotTxt)
2457 d.addCallback(self.log, "got file by uri, about to get dir by uri")
2458 d.addCallback(lambda res: self.GET("/uri?uri=%s&t=json" % self._foo_uri,
2459 followRedirect=True))
2460 d.addCallback(self.failUnlessIsFooJSON)
2461 d.addCallback(self.log, "got dir by uri")
2465 def test_GET_URI_form_bad(self):
2466 d = self.shouldFail2(error.Error, "test_GET_URI_form_bad",
2467 "400 Bad Request", "GET /uri requires uri=",
2471 def test_GET_rename_form(self):
2472 d = self.GET(self.public_url + "/foo?t=rename-form&name=bar.txt",
2473 followRedirect=True)
2475 self.failUnless('name="when_done" value="."' in res, res)
2476 self.failUnless(re.search(r'name="from_name" value="bar\.txt"', res))
2477 d.addCallback(_check)
2480 def log(self, res, msg):
2481 #print "MSG: %s RES: %s" % (msg, res)
2485 def test_GET_URI_URL(self):
2486 base = "/uri/%s" % self._bar_txt_uri
2488 d.addCallback(self.failUnlessIsBarDotTxt)
2489 d.addCallback(lambda res: self.GET(base+"?filename=bar.txt"))
2490 d.addCallback(self.failUnlessIsBarDotTxt)
2491 d.addCallback(lambda res: self.GET(base+"?filename=bar.txt&save=true"))
2492 d.addCallback(self.failUnlessIsBarDotTxt)
2495 def test_GET_URI_URL_dir(self):
2496 base = "/uri/%s?t=json" % self._foo_uri
2498 d.addCallback(self.failUnlessIsFooJSON)
2501 def test_GET_URI_URL_missing(self):
2502 base = "/uri/%s" % self._bad_file_uri
2503 d = self.shouldHTTPError("test_GET_URI_URL_missing",
2504 http.GONE, None, "NotEnoughSharesError",
2506 # TODO: how can we exercise both sides of WebDownloadTarget.fail
2507 # here? we must arrange for a download to fail after target.open()
2508 # has been called, and then inspect the response to see that it is
2509 # shorter than we expected.
2512 def test_PUT_DIRURL_uri(self):
2513 d = self.s.create_dirnode()
2515 new_uri = dn.get_uri()
2516 # replace /foo with a new (empty) directory
2517 d = self.PUT(self.public_url + "/foo?t=uri", new_uri)
2518 d.addCallback(lambda res:
2519 self.failUnlessEqual(res.strip(), new_uri))
2520 d.addCallback(lambda res:
2521 self.failUnlessChildURIIs(self.public_root,
2525 d.addCallback(_made_dir)
2528 def test_PUT_DIRURL_uri_noreplace(self):
2529 d = self.s.create_dirnode()
2531 new_uri = dn.get_uri()
2532 # replace /foo with a new (empty) directory, but ask that
2533 # replace=false, so it should fail
2534 d = self.shouldFail2(error.Error, "test_PUT_DIRURL_uri_noreplace",
2535 "409 Conflict", "There was already a child by that name, and you asked me to not replace it",
2537 self.public_url + "/foo?t=uri&replace=false",
2539 d.addCallback(lambda res:
2540 self.failUnlessChildURIIs(self.public_root,
2544 d.addCallback(_made_dir)
2547 def test_PUT_DIRURL_bad_t(self):
2548 d = self.shouldFail2(error.Error, "test_PUT_DIRURL_bad_t",
2549 "400 Bad Request", "PUT to a directory",
2550 self.PUT, self.public_url + "/foo?t=BOGUS", "")
2551 d.addCallback(lambda res:
2552 self.failUnlessChildURIIs(self.public_root,
2557 def test_PUT_NEWFILEURL_uri(self):
2558 contents, n, new_uri = self.makefile(8)
2559 d = self.PUT(self.public_url + "/foo/new.txt?t=uri", new_uri)
2560 d.addCallback(lambda res: self.failUnlessEqual(res.strip(), new_uri))
2561 d.addCallback(lambda res:
2562 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
2566 def test_PUT_NEWFILEURL_uri_replace(self):
2567 contents, n, new_uri = self.makefile(8)
2568 d = self.PUT(self.public_url + "/foo/bar.txt?t=uri", new_uri)
2569 d.addCallback(lambda res: self.failUnlessEqual(res.strip(), new_uri))
2570 d.addCallback(lambda res:
2571 self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
2575 def test_PUT_NEWFILEURL_uri_no_replace(self):
2576 contents, n, new_uri = self.makefile(8)
2577 d = self.PUT(self.public_url + "/foo/bar.txt?t=uri&replace=false", new_uri)
2578 d.addBoth(self.shouldFail, error.Error, "PUT_NEWFILEURL_uri_no_replace",
2580 "There was already a child by that name, and you asked me "
2581 "to not replace it")
2584 def test_PUT_NEWFILE_URI(self):
2585 file_contents = "New file contents here\n"
2586 d = self.PUT("/uri", file_contents)
2588 assert isinstance(uri, str), uri
2589 self.failUnless(uri in FakeCHKFileNode.all_contents)
2590 self.failUnlessEqual(FakeCHKFileNode.all_contents[uri],
2592 return self.GET("/uri/%s" % uri)
2593 d.addCallback(_check)
2595 self.failUnlessEqual(res, file_contents)
2596 d.addCallback(_check2)
2599 def test_PUT_NEWFILE_URI_not_mutable(self):
2600 file_contents = "New file contents here\n"
2601 d = self.PUT("/uri?mutable=false", file_contents)
2603 assert isinstance(uri, str), uri
2604 self.failUnless(uri in FakeCHKFileNode.all_contents)
2605 self.failUnlessEqual(FakeCHKFileNode.all_contents[uri],
2607 return self.GET("/uri/%s" % uri)
2608 d.addCallback(_check)
2610 self.failUnlessEqual(res, file_contents)
2611 d.addCallback(_check2)
2614 def test_PUT_NEWFILE_URI_only_PUT(self):
2615 d = self.PUT("/uri?t=bogus", "")
2616 d.addBoth(self.shouldFail, error.Error,
2617 "PUT_NEWFILE_URI_only_PUT",
2619 "/uri accepts only PUT, PUT?t=mkdir, POST?t=upload, and POST?t=mkdir")
2622 def test_PUT_NEWFILE_URI_mutable(self):
2623 file_contents = "New file contents here\n"
2624 d = self.PUT("/uri?mutable=true", file_contents)
2625 def _check1(filecap):
2626 filecap = filecap.strip()
2627 self.failUnless(filecap.startswith("URI:SSK:"), filecap)
2628 self.filecap = filecap
2629 u = uri.WriteableSSKFileURI.init_from_string(filecap)
2630 self.failUnless(u.storage_index in FakeMutableFileNode.all_contents)
2631 n = self.s.create_node_from_uri(filecap)
2632 return n.download_best_version()
2633 d.addCallback(_check1)
2635 self.failUnlessEqual(data, file_contents)
2636 return self.GET("/uri/%s" % urllib.quote(self.filecap))
2637 d.addCallback(_check2)
2639 self.failUnlessEqual(res, file_contents)
2640 d.addCallback(_check3)
2643 def test_PUT_mkdir(self):
2644 d = self.PUT("/uri?t=mkdir", "")
2646 n = self.s.create_node_from_uri(uri.strip())
2647 d2 = self.failUnlessNodeKeysAre(n, [])
2648 d2.addCallback(lambda res:
2649 self.GET("/uri/%s?t=json" % uri))
2651 d.addCallback(_check)
2652 d.addCallback(self.failUnlessIsEmptyJSON)
2655 def test_POST_check(self):
2656 d = self.POST(self.public_url + "/foo", t="check", name="bar.txt")
2658 # this returns a string form of the results, which are probably
2659 # None since we're using fake filenodes.
2660 # TODO: verify that the check actually happened, by changing
2661 # FakeCHKFileNode to count how many times .check() has been
2664 d.addCallback(_done)
2667 def test_bad_method(self):
2668 url = self.webish_url + self.public_url + "/foo/bar.txt"
2669 d = self.shouldHTTPError("test_bad_method",
2670 501, "Not Implemented",
2671 "I don't know how to treat a BOGUS request.",
2672 client.getPage, url, method="BOGUS")
2675 def test_short_url(self):
2676 url = self.webish_url + "/uri"
2677 d = self.shouldHTTPError("test_short_url", 501, "Not Implemented",
2678 "I don't know how to treat a DELETE request.",
2679 client.getPage, url, method="DELETE")
2682 def test_ophandle_bad(self):
2683 url = self.webish_url + "/operations/bogus?t=status"
2684 d = self.shouldHTTPError("test_ophandle_bad", 404, "404 Not Found",
2685 "unknown/expired handle 'bogus'",
2686 client.getPage, url)
2689 def test_ophandle_cancel(self):
2690 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=128",
2691 followRedirect=True)
2692 d.addCallback(lambda ignored:
2693 self.GET("/operations/128?t=status&output=JSON"))
2695 data = simplejson.loads(res)
2696 self.failUnless("finished" in data, res)
2697 monitor = self.ws.root.child_operations.handles["128"][0]
2698 d = self.POST("/operations/128?t=cancel&output=JSON")
2700 data = simplejson.loads(res)
2701 self.failUnless("finished" in data, res)
2702 # t=cancel causes the handle to be forgotten
2703 self.failUnless(monitor.is_cancelled())
2704 d.addCallback(_check2)
2706 d.addCallback(_check1)
2707 d.addCallback(lambda ignored:
2708 self.shouldHTTPError("test_ophandle_cancel",
2709 404, "404 Not Found",
2710 "unknown/expired handle '128'",
2712 "/operations/128?t=status&output=JSON"))
2715 def test_ophandle_retainfor(self):
2716 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=129&retain-for=60",
2717 followRedirect=True)
2718 d.addCallback(lambda ignored:
2719 self.GET("/operations/129?t=status&output=JSON&retain-for=0"))
2721 data = simplejson.loads(res)
2722 self.failUnless("finished" in data, res)
2723 d.addCallback(_check1)
2724 # the retain-for=0 will cause the handle to be expired very soon
2725 d.addCallback(self.stall, 2.0)
2726 d.addCallback(lambda ignored:
2727 self.shouldHTTPError("test_ophandle_retainfor",
2728 404, "404 Not Found",
2729 "unknown/expired handle '129'",
2731 "/operations/129?t=status&output=JSON"))
2734 def test_ophandle_release_after_complete(self):
2735 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=130",
2736 followRedirect=True)
2737 d.addCallback(self.wait_for_operation, "130")
2738 d.addCallback(lambda ignored:
2739 self.GET("/operations/130?t=status&output=JSON&release-after-complete=true"))
2740 # the release-after-complete=true will cause the handle to be expired
2741 d.addCallback(lambda ignored:
2742 self.shouldHTTPError("test_ophandle_release_after_complete",
2743 404, "404 Not Found",
2744 "unknown/expired handle '130'",
2746 "/operations/130?t=status&output=JSON"))
2749 def test_incident(self):
2750 d = self.POST("/report_incident", details="eek")
2752 self.failUnless("Thank you for your report!" in res, res)
2753 d.addCallback(_done)
2756 def test_static(self):
2757 webdir = os.path.join(self.staticdir, "subdir")
2758 fileutil.make_dirs(webdir)
2759 f = open(os.path.join(webdir, "hello.txt"), "wb")
2763 d = self.GET("/static/subdir/hello.txt")
2765 self.failUnlessEqual(res, "hello")
2766 d.addCallback(_check)
2770 class Util(unittest.TestCase, ShouldFailMixin):
2771 def test_parse_replace_arg(self):
2772 self.failUnlessEqual(common.parse_replace_arg("true"), True)
2773 self.failUnlessEqual(common.parse_replace_arg("false"), False)
2774 self.failUnlessEqual(common.parse_replace_arg("only-files"),
2776 self.shouldFail(AssertionError, "test_parse_replace_arg", "",
2777 common.parse_replace_arg, "only_fles")
2779 def test_abbreviate_time(self):
2780 self.failUnlessEqual(common.abbreviate_time(None), "")
2781 self.failUnlessEqual(common.abbreviate_time(1.234), "1.23s")
2782 self.failUnlessEqual(common.abbreviate_time(0.123), "123ms")
2783 self.failUnlessEqual(common.abbreviate_time(0.00123), "1.2ms")
2784 self.failUnlessEqual(common.abbreviate_time(0.000123), "123us")
2786 def test_abbreviate_rate(self):
2787 self.failUnlessEqual(common.abbreviate_rate(None), "")
2788 self.failUnlessEqual(common.abbreviate_rate(1234000), "1.23MBps")
2789 self.failUnlessEqual(common.abbreviate_rate(12340), "12.3kBps")
2790 self.failUnlessEqual(common.abbreviate_rate(123), "123Bps")
2792 def test_abbreviate_size(self):
2793 self.failUnlessEqual(common.abbreviate_size(None), "")
2794 self.failUnlessEqual(common.abbreviate_size(1.23*1000*1000*1000), "1.23GB")
2795 self.failUnlessEqual(common.abbreviate_size(1.23*1000*1000), "1.23MB")
2796 self.failUnlessEqual(common.abbreviate_size(1230), "1.2kB")
2797 self.failUnlessEqual(common.abbreviate_size(123), "123B")
2799 def test_plural(self):
2801 return "%d second%s" % (s, status.plural(s))
2802 self.failUnlessEqual(convert(0), "0 seconds")
2803 self.failUnlessEqual(convert(1), "1 second")
2804 self.failUnlessEqual(convert(2), "2 seconds")
2806 return "has share%s: %s" % (status.plural(s), ",".join(s))
2807 self.failUnlessEqual(convert2([]), "has shares: ")
2808 self.failUnlessEqual(convert2(["1"]), "has share: 1")
2809 self.failUnlessEqual(convert2(["1","2"]), "has shares: 1,2")
2812 class Grid(GridTestMixin, WebErrorMixin, unittest.TestCase, ShouldFailMixin):
2814 def CHECK(self, ign, which, args, clientnum=0):
2815 fileurl = self.fileurls[which]
2816 url = fileurl + "?" + args
2817 return self.GET(url, method="POST", clientnum=clientnum)
2819 def test_filecheck(self):
2820 self.basedir = "web/Grid/filecheck"
2822 c0 = self.g.clients[0]
2825 d = c0.upload(upload.Data(DATA, convergence=""))
2826 def _stash_uri(ur, which):
2827 self.uris[which] = ur.uri
2828 d.addCallback(_stash_uri, "good")
2829 d.addCallback(lambda ign:
2830 c0.upload(upload.Data(DATA+"1", convergence="")))
2831 d.addCallback(_stash_uri, "sick")
2832 d.addCallback(lambda ign:
2833 c0.upload(upload.Data(DATA+"2", convergence="")))
2834 d.addCallback(_stash_uri, "dead")
2835 def _stash_mutable_uri(n, which):
2836 self.uris[which] = n.get_uri()
2837 assert isinstance(self.uris[which], str)
2838 d.addCallback(lambda ign: c0.create_mutable_file(DATA+"3"))
2839 d.addCallback(_stash_mutable_uri, "corrupt")
2840 d.addCallback(lambda ign:
2841 c0.upload(upload.Data("literal", convergence="")))
2842 d.addCallback(_stash_uri, "small")
2843 d.addCallback(lambda ign: c0.create_immutable_dirnode({}))
2844 d.addCallback(_stash_mutable_uri, "smalldir")
2846 def _compute_fileurls(ignored):
2848 for which in self.uris:
2849 self.fileurls[which] = "uri/" + urllib.quote(self.uris[which])
2850 d.addCallback(_compute_fileurls)
2852 def _clobber_shares(ignored):
2853 good_shares = self.find_shares(self.uris["good"])
2854 self.failUnlessEqual(len(good_shares), 10)
2855 sick_shares = self.find_shares(self.uris["sick"])
2856 os.unlink(sick_shares[0][2])
2857 dead_shares = self.find_shares(self.uris["dead"])
2858 for i in range(1, 10):
2859 os.unlink(dead_shares[i][2])
2860 c_shares = self.find_shares(self.uris["corrupt"])
2861 cso = CorruptShareOptions()
2862 cso.stdout = StringIO()
2863 cso.parseOptions([c_shares[0][2]])
2865 d.addCallback(_clobber_shares)
2867 d.addCallback(self.CHECK, "good", "t=check")
2868 def _got_html_good(res):
2869 self.failUnless("Healthy" in res, res)
2870 self.failIf("Not Healthy" in res, res)
2871 d.addCallback(_got_html_good)
2872 d.addCallback(self.CHECK, "good", "t=check&return_to=somewhere")
2873 def _got_html_good_return_to(res):
2874 self.failUnless("Healthy" in res, res)
2875 self.failIf("Not Healthy" in res, res)
2876 self.failUnless('<a href="somewhere">Return to file'
2878 d.addCallback(_got_html_good_return_to)
2879 d.addCallback(self.CHECK, "good", "t=check&output=json")
2880 def _got_json_good(res):
2881 r = simplejson.loads(res)
2882 self.failUnlessEqual(r["summary"], "Healthy")
2883 self.failUnless(r["results"]["healthy"])
2884 self.failIf(r["results"]["needs-rebalancing"])
2885 self.failUnless(r["results"]["recoverable"])
2886 d.addCallback(_got_json_good)
2888 d.addCallback(self.CHECK, "small", "t=check")
2889 def _got_html_small(res):
2890 self.failUnless("Literal files are always healthy" in res, res)
2891 self.failIf("Not Healthy" in res, res)
2892 d.addCallback(_got_html_small)
2893 d.addCallback(self.CHECK, "small", "t=check&return_to=somewhere")
2894 def _got_html_small_return_to(res):
2895 self.failUnless("Literal files are always healthy" in res, res)
2896 self.failIf("Not Healthy" in res, res)
2897 self.failUnless('<a href="somewhere">Return to file'
2899 d.addCallback(_got_html_small_return_to)
2900 d.addCallback(self.CHECK, "small", "t=check&output=json")
2901 def _got_json_small(res):
2902 r = simplejson.loads(res)
2903 self.failUnlessEqual(r["storage-index"], "")
2904 self.failUnless(r["results"]["healthy"])
2905 d.addCallback(_got_json_small)
2907 d.addCallback(self.CHECK, "smalldir", "t=check")
2908 def _got_html_smalldir(res):
2909 self.failUnless("Literal files are always healthy" in res, res)
2910 self.failIf("Not Healthy" in res, res)
2911 d.addCallback(_got_html_smalldir)
2912 d.addCallback(self.CHECK, "smalldir", "t=check&output=json")
2913 def _got_json_smalldir(res):
2914 r = simplejson.loads(res)
2915 self.failUnlessEqual(r["storage-index"], "")
2916 self.failUnless(r["results"]["healthy"])
2917 d.addCallback(_got_json_smalldir)
2919 d.addCallback(self.CHECK, "sick", "t=check")
2920 def _got_html_sick(res):
2921 self.failUnless("Not Healthy" in res, res)
2922 d.addCallback(_got_html_sick)
2923 d.addCallback(self.CHECK, "sick", "t=check&output=json")
2924 def _got_json_sick(res):
2925 r = simplejson.loads(res)
2926 self.failUnlessEqual(r["summary"],
2927 "Not Healthy: 9 shares (enc 3-of-10)")
2928 self.failIf(r["results"]["healthy"])
2929 self.failIf(r["results"]["needs-rebalancing"])
2930 self.failUnless(r["results"]["recoverable"])
2931 d.addCallback(_got_json_sick)
2933 d.addCallback(self.CHECK, "dead", "t=check")
2934 def _got_html_dead(res):
2935 self.failUnless("Not Healthy" in res, res)
2936 d.addCallback(_got_html_dead)
2937 d.addCallback(self.CHECK, "dead", "t=check&output=json")
2938 def _got_json_dead(res):
2939 r = simplejson.loads(res)
2940 self.failUnlessEqual(r["summary"],
2941 "Not Healthy: 1 shares (enc 3-of-10)")
2942 self.failIf(r["results"]["healthy"])
2943 self.failIf(r["results"]["needs-rebalancing"])
2944 self.failIf(r["results"]["recoverable"])
2945 d.addCallback(_got_json_dead)
2947 d.addCallback(self.CHECK, "corrupt", "t=check&verify=true")
2948 def _got_html_corrupt(res):
2949 self.failUnless("Not Healthy! : Unhealthy" in res, res)
2950 d.addCallback(_got_html_corrupt)
2951 d.addCallback(self.CHECK, "corrupt", "t=check&verify=true&output=json")
2952 def _got_json_corrupt(res):
2953 r = simplejson.loads(res)
2954 self.failUnless("Unhealthy: 9 shares (enc 3-of-10)" in r["summary"],
2956 self.failIf(r["results"]["healthy"])
2957 self.failUnless(r["results"]["recoverable"])
2958 self.failUnlessEqual(r["results"]["count-shares-good"], 9)
2959 self.failUnlessEqual(r["results"]["count-corrupt-shares"], 1)
2960 d.addCallback(_got_json_corrupt)
2962 d.addErrback(self.explain_web_error)
2965 def test_repair_html(self):
2966 self.basedir = "web/Grid/repair_html"
2968 c0 = self.g.clients[0]
2971 d = c0.upload(upload.Data(DATA, convergence=""))
2972 def _stash_uri(ur, which):
2973 self.uris[which] = ur.uri
2974 d.addCallback(_stash_uri, "good")
2975 d.addCallback(lambda ign:
2976 c0.upload(upload.Data(DATA+"1", convergence="")))
2977 d.addCallback(_stash_uri, "sick")
2978 d.addCallback(lambda ign:
2979 c0.upload(upload.Data(DATA+"2", convergence="")))
2980 d.addCallback(_stash_uri, "dead")
2981 def _stash_mutable_uri(n, which):
2982 self.uris[which] = n.get_uri()
2983 assert isinstance(self.uris[which], str)
2984 d.addCallback(lambda ign: c0.create_mutable_file(DATA+"3"))
2985 d.addCallback(_stash_mutable_uri, "corrupt")
2987 def _compute_fileurls(ignored):
2989 for which in self.uris:
2990 self.fileurls[which] = "uri/" + urllib.quote(self.uris[which])
2991 d.addCallback(_compute_fileurls)
2993 def _clobber_shares(ignored):
2994 good_shares = self.find_shares(self.uris["good"])
2995 self.failUnlessEqual(len(good_shares), 10)
2996 sick_shares = self.find_shares(self.uris["sick"])
2997 os.unlink(sick_shares[0][2])
2998 dead_shares = self.find_shares(self.uris["dead"])
2999 for i in range(1, 10):
3000 os.unlink(dead_shares[i][2])
3001 c_shares = self.find_shares(self.uris["corrupt"])
3002 cso = CorruptShareOptions()
3003 cso.stdout = StringIO()
3004 cso.parseOptions([c_shares[0][2]])
3006 d.addCallback(_clobber_shares)
3008 d.addCallback(self.CHECK, "good", "t=check&repair=true")
3009 def _got_html_good(res):
3010 self.failUnless("Healthy" in res, res)
3011 self.failIf("Not Healthy" in res, res)
3012 self.failUnless("No repair necessary" in res, res)
3013 d.addCallback(_got_html_good)
3015 d.addCallback(self.CHECK, "sick", "t=check&repair=true")
3016 def _got_html_sick(res):
3017 self.failUnless("Healthy : healthy" in res, res)
3018 self.failIf("Not Healthy" in res, res)
3019 self.failUnless("Repair successful" in res, res)
3020 d.addCallback(_got_html_sick)
3022 # repair of a dead file will fail, of course, but it isn't yet
3023 # clear how this should be reported. Right now it shows up as
3026 #d.addCallback(self.CHECK, "dead", "t=check&repair=true")
3027 #def _got_html_dead(res):
3029 # self.failUnless("Healthy : healthy" in res, res)
3030 # self.failIf("Not Healthy" in res, res)
3031 # self.failUnless("No repair necessary" in res, res)
3032 #d.addCallback(_got_html_dead)
3034 d.addCallback(self.CHECK, "corrupt", "t=check&verify=true&repair=true")
3035 def _got_html_corrupt(res):
3036 self.failUnless("Healthy : Healthy" in res, res)
3037 self.failIf("Not Healthy" in res, res)
3038 self.failUnless("Repair successful" in res, res)
3039 d.addCallback(_got_html_corrupt)
3041 d.addErrback(self.explain_web_error)
3044 def test_repair_json(self):
3045 self.basedir = "web/Grid/repair_json"
3047 c0 = self.g.clients[0]
3050 d = c0.upload(upload.Data(DATA+"1", convergence=""))
3051 def _stash_uri(ur, which):
3052 self.uris[which] = ur.uri
3053 d.addCallback(_stash_uri, "sick")
3055 def _compute_fileurls(ignored):
3057 for which in self.uris:
3058 self.fileurls[which] = "uri/" + urllib.quote(self.uris[which])
3059 d.addCallback(_compute_fileurls)
3061 def _clobber_shares(ignored):
3062 sick_shares = self.find_shares(self.uris["sick"])
3063 os.unlink(sick_shares[0][2])
3064 d.addCallback(_clobber_shares)
3066 d.addCallback(self.CHECK, "sick", "t=check&repair=true&output=json")
3067 def _got_json_sick(res):
3068 r = simplejson.loads(res)
3069 self.failUnlessEqual(r["repair-attempted"], True)
3070 self.failUnlessEqual(r["repair-successful"], True)
3071 self.failUnlessEqual(r["pre-repair-results"]["summary"],
3072 "Not Healthy: 9 shares (enc 3-of-10)")
3073 self.failIf(r["pre-repair-results"]["results"]["healthy"])
3074 self.failUnlessEqual(r["post-repair-results"]["summary"], "healthy")
3075 self.failUnless(r["post-repair-results"]["results"]["healthy"])
3076 d.addCallback(_got_json_sick)
3078 d.addErrback(self.explain_web_error)
3081 def test_unknown(self):
3082 self.basedir = "web/Grid/unknown"
3084 c0 = self.g.clients[0]
3088 future_writecap = "x-tahoe-crazy://I_am_from_the_future."
3089 future_readcap = "x-tahoe-crazy-readonly://I_am_from_the_future."
3090 # the future cap format may contain slashes, which must be tolerated
3091 expected_info_url = "uri/%s?t=info" % urllib.quote(future_writecap,
3093 future_node = UnknownNode(future_writecap, future_readcap)
3095 d = c0.create_dirnode()
3096 def _stash_root_and_create_file(n):
3098 self.rooturl = "uri/" + urllib.quote(n.get_uri()) + "/"
3099 self.rourl = "uri/" + urllib.quote(n.get_readonly_uri()) + "/"
3100 return self.rootnode.set_node(u"future", future_node)
3101 d.addCallback(_stash_root_and_create_file)
3102 # make sure directory listing tolerates unknown nodes
3103 d.addCallback(lambda ign: self.GET(self.rooturl))
3104 def _check_html(res):
3105 self.failUnlessIn("<td>future</td>", res)
3106 # find the More Info link for "future", should be relative
3107 mo = re.search(r'<a href="([^"]+)">More Info</a>', res)
3108 info_url = mo.group(1)
3109 self.failUnlessEqual(info_url, "future?t=info")
3111 d.addCallback(_check_html)
3112 d.addCallback(lambda ign: self.GET(self.rooturl+"?t=json"))
3113 def _check_json(res, expect_writecap):
3114 data = simplejson.loads(res)
3115 self.failUnlessEqual(data[0], "dirnode")
3116 f = data[1]["children"]["future"]
3117 self.failUnlessEqual(f[0], "unknown")
3119 self.failUnlessEqual(f[1]["rw_uri"], future_writecap)
3121 self.failIfIn("rw_uri", f[1])
3122 self.failUnlessEqual(f[1]["ro_uri"], future_readcap)
3123 self.failUnless("metadata" in f[1])
3124 d.addCallback(_check_json, expect_writecap=True)
3125 d.addCallback(lambda ign: self.GET(expected_info_url))
3126 def _check_info(res, expect_readcap):
3127 self.failUnlessIn("Object Type: <span>unknown</span>", res)
3128 self.failUnlessIn(future_writecap, res)
3130 self.failUnlessIn(future_readcap, res)
3131 self.failIfIn("Raw data as", res)
3132 self.failIfIn("Directory writecap", res)
3133 self.failIfIn("Checker Operations", res)
3134 self.failIfIn("Mutable File Operations", res)
3135 self.failIfIn("Directory Operations", res)
3136 d.addCallback(_check_info, expect_readcap=False)
3137 d.addCallback(lambda ign: self.GET(self.rooturl+"future?t=info"))
3138 d.addCallback(_check_info, expect_readcap=True)
3140 # and make sure that a read-only version of the directory can be
3141 # rendered too. This version will not have future_writecap
3142 d.addCallback(lambda ign: self.GET(self.rourl))
3143 d.addCallback(_check_html)
3144 d.addCallback(lambda ign: self.GET(self.rourl+"?t=json"))
3145 d.addCallback(_check_json, expect_writecap=False)
3148 def test_deep_check(self):
3149 self.basedir = "web/Grid/deep_check"
3151 c0 = self.g.clients[0]
3155 d = c0.create_dirnode()
3156 def _stash_root_and_create_file(n):
3158 self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri()) + "/"
3159 return n.add_file(u"good", upload.Data(DATA, convergence=""))
3160 d.addCallback(_stash_root_and_create_file)
3161 def _stash_uri(fn, which):
3162 self.uris[which] = fn.get_uri()
3164 d.addCallback(_stash_uri, "good")
3165 d.addCallback(lambda ign:
3166 self.rootnode.add_file(u"small",
3167 upload.Data("literal",
3169 d.addCallback(_stash_uri, "small")
3170 d.addCallback(lambda ign:
3171 self.rootnode.add_file(u"sick",
3172 upload.Data(DATA+"1",
3174 d.addCallback(_stash_uri, "sick")
3176 # this tests that deep-check and stream-manifest will ignore
3177 # UnknownNode instances. Hopefully this will also cover deep-stats.
3178 future_writecap = "x-tahoe-crazy://I_am_from_the_future."
3179 future_readcap = "x-tahoe-crazy-readonly://I_am_from_the_future."
3180 future_node = UnknownNode(future_writecap, future_readcap)
3181 d.addCallback(lambda ign: self.rootnode.set_node(u"future",future_node))
3183 def _clobber_shares(ignored):
3184 self.delete_shares_numbered(self.uris["sick"], [0,1])
3185 d.addCallback(_clobber_shares)
3193 d.addCallback(self.CHECK, "root", "t=stream-deep-check")
3196 units = [simplejson.loads(line)
3197 for line in res.splitlines()
3200 print "response is:", res
3201 print "undecodeable line was '%s'" % line
3203 self.failUnlessEqual(len(units), 5+1)
3204 # should be parent-first
3206 self.failUnlessEqual(u0["path"], [])
3207 self.failUnlessEqual(u0["type"], "directory")
3208 self.failUnlessEqual(u0["cap"], self.rootnode.get_uri())
3209 u0cr = u0["check-results"]
3210 self.failUnlessEqual(u0cr["results"]["count-shares-good"], 10)
3212 ugood = [u for u in units
3213 if u["type"] == "file" and u["path"] == [u"good"]][0]
3214 self.failUnlessEqual(ugood["cap"], self.uris["good"])
3215 ugoodcr = ugood["check-results"]
3216 self.failUnlessEqual(ugoodcr["results"]["count-shares-good"], 10)
3219 self.failUnlessEqual(stats["type"], "stats")
3221 self.failUnlessEqual(s["count-immutable-files"], 2)
3222 self.failUnlessEqual(s["count-literal-files"], 1)
3223 self.failUnlessEqual(s["count-directories"], 1)
3224 self.failUnlessEqual(s["count-unknown"], 1)
3225 d.addCallback(_done)
3227 d.addCallback(self.CHECK, "root", "t=stream-manifest")
3228 def _check_manifest(res):
3229 self.failUnless(res.endswith("\n"))
3230 units = [simplejson.loads(t) for t in res[:-1].split("\n")]
3231 self.failUnlessEqual(len(units), 5+1)
3232 self.failUnlessEqual(units[-1]["type"], "stats")
3234 self.failUnlessEqual(first["path"], [])
3235 self.failUnlessEqual(first["cap"], self.rootnode.get_uri())
3236 self.failUnlessEqual(first["type"], "directory")
3237 stats = units[-1]["stats"]
3238 self.failUnlessEqual(stats["count-immutable-files"], 2)
3239 self.failUnlessEqual(stats["count-literal-files"], 1)
3240 self.failUnlessEqual(stats["count-mutable-files"], 0)
3241 self.failUnlessEqual(stats["count-immutable-files"], 2)
3242 self.failUnlessEqual(stats["count-unknown"], 1)
3243 d.addCallback(_check_manifest)
3245 # now add root/subdir and root/subdir/grandchild, then make subdir
3246 # unrecoverable, then see what happens
3248 d.addCallback(lambda ign:
3249 self.rootnode.create_subdirectory(u"subdir"))
3250 d.addCallback(_stash_uri, "subdir")
3251 d.addCallback(lambda subdir_node:
3252 subdir_node.add_file(u"grandchild",
3253 upload.Data(DATA+"2",
3255 d.addCallback(_stash_uri, "grandchild")
3257 d.addCallback(lambda ign:
3258 self.delete_shares_numbered(self.uris["subdir"],
3266 # root/subdir [unrecoverable]
3267 # root/subdir/grandchild
3269 # how should a streaming-JSON API indicate fatal error?
3270 # answer: emit ERROR: instead of a JSON string
3272 d.addCallback(self.CHECK, "root", "t=stream-manifest")
3273 def _check_broken_manifest(res):
3274 lines = res.splitlines()
3276 for (i,line) in enumerate(lines)
3277 if line.startswith("ERROR:")]
3279 self.fail("no ERROR: in output: %s" % (res,))
3280 first_error = error_lines[0]
3281 error_line = lines[first_error]
3282 error_msg = lines[first_error+1:]
3283 error_msg_s = "\n".join(error_msg) + "\n"
3284 self.failUnlessIn("ERROR: UnrecoverableFileError(no recoverable versions)",
3286 self.failUnless(len(error_msg) > 2, error_msg_s) # some traceback
3287 units = [simplejson.loads(line) for line in lines[:first_error]]
3288 self.failUnlessEqual(len(units), 6) # includes subdir
3289 last_unit = units[-1]
3290 self.failUnlessEqual(last_unit["path"], ["subdir"])
3291 d.addCallback(_check_broken_manifest)
3293 d.addCallback(self.CHECK, "root", "t=stream-deep-check")
3294 def _check_broken_deepcheck(res):
3295 lines = res.splitlines()
3297 for (i,line) in enumerate(lines)
3298 if line.startswith("ERROR:")]
3300 self.fail("no ERROR: in output: %s" % (res,))
3301 first_error = error_lines[0]
3302 error_line = lines[first_error]
3303 error_msg = lines[first_error+1:]
3304 error_msg_s = "\n".join(error_msg) + "\n"
3305 self.failUnlessIn("ERROR: UnrecoverableFileError(no recoverable versions)",
3307 self.failUnless(len(error_msg) > 2, error_msg_s) # some traceback
3308 units = [simplejson.loads(line) for line in lines[:first_error]]
3309 self.failUnlessEqual(len(units), 6) # includes subdir
3310 last_unit = units[-1]
3311 self.failUnlessEqual(last_unit["path"], ["subdir"])
3312 r = last_unit["check-results"]["results"]
3313 self.failUnlessEqual(r["count-recoverable-versions"], 0)
3314 self.failUnlessEqual(r["count-shares-good"], 1)
3315 self.failUnlessEqual(r["recoverable"], False)
3316 d.addCallback(_check_broken_deepcheck)
3318 d.addErrback(self.explain_web_error)
3321 def test_deep_check_and_repair(self):
3322 self.basedir = "web/Grid/deep_check_and_repair"
3324 c0 = self.g.clients[0]
3328 d = c0.create_dirnode()
3329 def _stash_root_and_create_file(n):
3331 self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri()) + "/"
3332 return n.add_file(u"good", upload.Data(DATA, convergence=""))
3333 d.addCallback(_stash_root_and_create_file)
3334 def _stash_uri(fn, which):
3335 self.uris[which] = fn.get_uri()
3336 d.addCallback(_stash_uri, "good")
3337 d.addCallback(lambda ign:
3338 self.rootnode.add_file(u"small",
3339 upload.Data("literal",
3341 d.addCallback(_stash_uri, "small")
3342 d.addCallback(lambda ign:
3343 self.rootnode.add_file(u"sick",
3344 upload.Data(DATA+"1",
3346 d.addCallback(_stash_uri, "sick")
3347 #d.addCallback(lambda ign:
3348 # self.rootnode.add_file(u"dead",
3349 # upload.Data(DATA+"2",
3351 #d.addCallback(_stash_uri, "dead")
3353 #d.addCallback(lambda ign: c0.create_mutable_file("mutable"))
3354 #d.addCallback(lambda fn: self.rootnode.set_node(u"corrupt", fn))
3355 #d.addCallback(_stash_uri, "corrupt")
3357 def _clobber_shares(ignored):
3358 good_shares = self.find_shares(self.uris["good"])
3359 self.failUnlessEqual(len(good_shares), 10)
3360 sick_shares = self.find_shares(self.uris["sick"])
3361 os.unlink(sick_shares[0][2])
3362 #dead_shares = self.find_shares(self.uris["dead"])
3363 #for i in range(1, 10):
3364 # os.unlink(dead_shares[i][2])
3366 #c_shares = self.find_shares(self.uris["corrupt"])
3367 #cso = CorruptShareOptions()
3368 #cso.stdout = StringIO()
3369 #cso.parseOptions([c_shares[0][2]])
3371 d.addCallback(_clobber_shares)
3374 # root/good CHK, 10 shares
3376 # root/sick CHK, 9 shares
3378 d.addCallback(self.CHECK, "root", "t=stream-deep-check&repair=true")
3380 units = [simplejson.loads(line)
3381 for line in res.splitlines()
3383 self.failUnlessEqual(len(units), 4+1)
3384 # should be parent-first
3386 self.failUnlessEqual(u0["path"], [])
3387 self.failUnlessEqual(u0["type"], "directory")
3388 self.failUnlessEqual(u0["cap"], self.rootnode.get_uri())
3389 u0crr = u0["check-and-repair-results"]
3390 self.failUnlessEqual(u0crr["repair-attempted"], False)
3391 self.failUnlessEqual(u0crr["pre-repair-results"]["results"]["count-shares-good"], 10)
3393 ugood = [u for u in units
3394 if u["type"] == "file" and u["path"] == [u"good"]][0]
3395 self.failUnlessEqual(ugood["cap"], self.uris["good"])
3396 ugoodcrr = ugood["check-and-repair-results"]
3397 self.failUnlessEqual(ugoodcrr["repair-attempted"], False)
3398 self.failUnlessEqual(ugoodcrr["pre-repair-results"]["results"]["count-shares-good"], 10)
3400 usick = [u for u in units
3401 if u["type"] == "file" and u["path"] == [u"sick"]][0]
3402 self.failUnlessEqual(usick["cap"], self.uris["sick"])
3403 usickcrr = usick["check-and-repair-results"]
3404 self.failUnlessEqual(usickcrr["repair-attempted"], True)
3405 self.failUnlessEqual(usickcrr["repair-successful"], True)
3406 self.failUnlessEqual(usickcrr["pre-repair-results"]["results"]["count-shares-good"], 9)
3407 self.failUnlessEqual(usickcrr["post-repair-results"]["results"]["count-shares-good"], 10)
3410 self.failUnlessEqual(stats["type"], "stats")
3412 self.failUnlessEqual(s["count-immutable-files"], 2)
3413 self.failUnlessEqual(s["count-literal-files"], 1)
3414 self.failUnlessEqual(s["count-directories"], 1)
3415 d.addCallback(_done)
3417 d.addErrback(self.explain_web_error)
3420 def _count_leases(self, ignored, which):
3421 u = self.uris[which]
3422 shares = self.find_shares(u)
3424 for shnum, serverid, fn in shares:
3425 sf = get_share_file(fn)
3426 num_leases = len(list(sf.get_leases()))
3427 lease_counts.append( (fn, num_leases) )
3430 def _assert_leasecount(self, lease_counts, expected):
3431 for (fn, num_leases) in lease_counts:
3432 if num_leases != expected:
3433 self.fail("expected %d leases, have %d, on %s" %
3434 (expected, num_leases, fn))
3436 def test_add_lease(self):
3437 self.basedir = "web/Grid/add_lease"
3438 self.set_up_grid(num_clients=2)
3439 c0 = self.g.clients[0]
3442 d = c0.upload(upload.Data(DATA, convergence=""))
3443 def _stash_uri(ur, which):
3444 self.uris[which] = ur.uri
3445 d.addCallback(_stash_uri, "one")
3446 d.addCallback(lambda ign:
3447 c0.upload(upload.Data(DATA+"1", convergence="")))
3448 d.addCallback(_stash_uri, "two")
3449 def _stash_mutable_uri(n, which):
3450 self.uris[which] = n.get_uri()
3451 assert isinstance(self.uris[which], str)
3452 d.addCallback(lambda ign: c0.create_mutable_file(DATA+"2"))
3453 d.addCallback(_stash_mutable_uri, "mutable")
3455 def _compute_fileurls(ignored):
3457 for which in self.uris:
3458 self.fileurls[which] = "uri/" + urllib.quote(self.uris[which])
3459 d.addCallback(_compute_fileurls)
3461 d.addCallback(self._count_leases, "one")
3462 d.addCallback(self._assert_leasecount, 1)
3463 d.addCallback(self._count_leases, "two")
3464 d.addCallback(self._assert_leasecount, 1)
3465 d.addCallback(self._count_leases, "mutable")
3466 d.addCallback(self._assert_leasecount, 1)
3468 d.addCallback(self.CHECK, "one", "t=check") # no add-lease
3469 def _got_html_good(res):
3470 self.failUnless("Healthy" in res, res)
3471 self.failIf("Not Healthy" in res, res)
3472 d.addCallback(_got_html_good)
3474 d.addCallback(self._count_leases, "one")
3475 d.addCallback(self._assert_leasecount, 1)
3476 d.addCallback(self._count_leases, "two")
3477 d.addCallback(self._assert_leasecount, 1)
3478 d.addCallback(self._count_leases, "mutable")
3479 d.addCallback(self._assert_leasecount, 1)
3481 # this CHECK uses the original client, which uses the same
3482 # lease-secrets, so it will just renew the original lease
3483 d.addCallback(self.CHECK, "one", "t=check&add-lease=true")
3484 d.addCallback(_got_html_good)
3486 d.addCallback(self._count_leases, "one")
3487 d.addCallback(self._assert_leasecount, 1)
3488 d.addCallback(self._count_leases, "two")
3489 d.addCallback(self._assert_leasecount, 1)
3490 d.addCallback(self._count_leases, "mutable")
3491 d.addCallback(self._assert_leasecount, 1)
3493 # this CHECK uses an alternate client, which adds a second lease
3494 d.addCallback(self.CHECK, "one", "t=check&add-lease=true", clientnum=1)
3495 d.addCallback(_got_html_good)
3497 d.addCallback(self._count_leases, "one")
3498 d.addCallback(self._assert_leasecount, 2)
3499 d.addCallback(self._count_leases, "two")
3500 d.addCallback(self._assert_leasecount, 1)
3501 d.addCallback(self._count_leases, "mutable")
3502 d.addCallback(self._assert_leasecount, 1)
3504 d.addCallback(self.CHECK, "mutable", "t=check&add-lease=true")
3505 d.addCallback(_got_html_good)
3507 d.addCallback(self._count_leases, "one")
3508 d.addCallback(self._assert_leasecount, 2)
3509 d.addCallback(self._count_leases, "two")
3510 d.addCallback(self._assert_leasecount, 1)
3511 d.addCallback(self._count_leases, "mutable")
3512 d.addCallback(self._assert_leasecount, 1)
3514 d.addCallback(self.CHECK, "mutable", "t=check&add-lease=true",
3516 d.addCallback(_got_html_good)
3518 d.addCallback(self._count_leases, "one")
3519 d.addCallback(self._assert_leasecount, 2)
3520 d.addCallback(self._count_leases, "two")
3521 d.addCallback(self._assert_leasecount, 1)
3522 d.addCallback(self._count_leases, "mutable")
3523 d.addCallback(self._assert_leasecount, 2)
3525 d.addErrback(self.explain_web_error)
3528 def test_deep_add_lease(self):
3529 self.basedir = "web/Grid/deep_add_lease"
3530 self.set_up_grid(num_clients=2)
3531 c0 = self.g.clients[0]
3535 d = c0.create_dirnode()
3536 def _stash_root_and_create_file(n):
3538 self.uris["root"] = n.get_uri()
3539 self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri()) + "/"
3540 return n.add_file(u"one", upload.Data(DATA, convergence=""))
3541 d.addCallback(_stash_root_and_create_file)
3542 def _stash_uri(fn, which):
3543 self.uris[which] = fn.get_uri()
3544 d.addCallback(_stash_uri, "one")
3545 d.addCallback(lambda ign:
3546 self.rootnode.add_file(u"small",
3547 upload.Data("literal",
3549 d.addCallback(_stash_uri, "small")
3551 d.addCallback(lambda ign: c0.create_mutable_file("mutable"))
3552 d.addCallback(lambda fn: self.rootnode.set_node(u"mutable", fn))
3553 d.addCallback(_stash_uri, "mutable")
3555 d.addCallback(self.CHECK, "root", "t=stream-deep-check") # no add-lease
3557 units = [simplejson.loads(line)
3558 for line in res.splitlines()
3560 # root, one, small, mutable, stats
3561 self.failUnlessEqual(len(units), 4+1)
3562 d.addCallback(_done)
3564 d.addCallback(self._count_leases, "root")
3565 d.addCallback(self._assert_leasecount, 1)
3566 d.addCallback(self._count_leases, "one")
3567 d.addCallback(self._assert_leasecount, 1)
3568 d.addCallback(self._count_leases, "mutable")
3569 d.addCallback(self._assert_leasecount, 1)
3571 d.addCallback(self.CHECK, "root", "t=stream-deep-check&add-lease=true")
3572 d.addCallback(_done)
3574 d.addCallback(self._count_leases, "root")
3575 d.addCallback(self._assert_leasecount, 1)
3576 d.addCallback(self._count_leases, "one")
3577 d.addCallback(self._assert_leasecount, 1)
3578 d.addCallback(self._count_leases, "mutable")
3579 d.addCallback(self._assert_leasecount, 1)
3581 d.addCallback(self.CHECK, "root", "t=stream-deep-check&add-lease=true",
3583 d.addCallback(_done)
3585 d.addCallback(self._count_leases, "root")
3586 d.addCallback(self._assert_leasecount, 2)
3587 d.addCallback(self._count_leases, "one")
3588 d.addCallback(self._assert_leasecount, 2)
3589 d.addCallback(self._count_leases, "mutable")
3590 d.addCallback(self._assert_leasecount, 2)
3592 d.addErrback(self.explain_web_error)
3596 def test_exceptions(self):
3597 self.basedir = "web/Grid/exceptions"
3598 self.set_up_grid(num_clients=1, num_servers=2)
3599 c0 = self.g.clients[0]
3602 d = c0.create_dirnode()
3604 self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri()) + "/"
3605 self.fileurls["imaginary"] = self.fileurls["root"] + "imaginary"
3607 d.addCallback(_stash_root)
3608 d.addCallback(lambda ign: c0.upload(upload.Data(DATA, convergence="")))
3610 self.fileurls["1share"] = "uri/" + urllib.quote(ur.uri)
3611 self.delete_shares_numbered(ur.uri, range(1,10))
3613 u = uri.from_string(ur.uri)
3614 u.key = testutil.flip_bit(u.key, 0)
3615 baduri = u.to_string()
3616 self.fileurls["0shares"] = "uri/" + urllib.quote(baduri)
3617 d.addCallback(_stash_bad)
3618 d.addCallback(lambda ign: c0.create_dirnode())
3619 def _mangle_dirnode_1share(n):
3621 url = self.fileurls["dir-1share"] = "uri/" + urllib.quote(u) + "/"
3622 self.fileurls["dir-1share-json"] = url + "?t=json"
3623 self.delete_shares_numbered(u, range(1,10))
3624 d.addCallback(_mangle_dirnode_1share)
3625 d.addCallback(lambda ign: c0.create_dirnode())
3626 def _mangle_dirnode_0share(n):
3628 url = self.fileurls["dir-0share"] = "uri/" + urllib.quote(u) + "/"
3629 self.fileurls["dir-0share-json"] = url + "?t=json"
3630 self.delete_shares_numbered(u, range(0,10))
3631 d.addCallback(_mangle_dirnode_0share)
3633 # NotEnoughSharesError should be reported sensibly, with a
3634 # text/plain explanation of the problem, and perhaps some
3635 # information on which shares *could* be found.
3637 d.addCallback(lambda ignored:
3638 self.shouldHTTPError("GET unrecoverable",
3639 410, "Gone", "NoSharesError",
3640 self.GET, self.fileurls["0shares"]))
3641 def _check_zero_shares(body):
3642 self.failIf("<html>" in body, body)
3643 body = " ".join(body.strip().split())
3644 exp = ("NoSharesError: no shares could be found. "
3645 "Zero shares usually indicates a corrupt URI, or that "
3646 "no servers were connected, but it might also indicate "
3647 "severe corruption. You should perform a filecheck on "
3648 "this object to learn more. The full error message is: "
3649 "Failed to get enough shareholders: have 0, need 3")
3650 self.failUnlessEqual(exp, body)
3651 d.addCallback(_check_zero_shares)
3654 d.addCallback(lambda ignored:
3655 self.shouldHTTPError("GET 1share",
3656 410, "Gone", "NotEnoughSharesError",
3657 self.GET, self.fileurls["1share"]))
3658 def _check_one_share(body):
3659 self.failIf("<html>" in body, body)
3660 body = " ".join(body.strip().split())
3661 exp = ("NotEnoughSharesError: This indicates that some "
3662 "servers were unavailable, or that shares have been "
3663 "lost to server departure, hard drive failure, or disk "
3664 "corruption. You should perform a filecheck on "
3665 "this object to learn more. The full error message is:"
3666 " Failed to get enough shareholders: have 1, need 3")
3667 self.failUnlessEqual(exp, body)
3668 d.addCallback(_check_one_share)
3670 d.addCallback(lambda ignored:
3671 self.shouldHTTPError("GET imaginary",
3672 404, "Not Found", None,
3673 self.GET, self.fileurls["imaginary"]))
3674 def _missing_child(body):
3675 self.failUnless("No such child: imaginary" in body, body)
3676 d.addCallback(_missing_child)
3678 d.addCallback(lambda ignored: self.GET(self.fileurls["dir-0share"]))
3679 def _check_0shares_dir_html(body):
3680 self.failUnless("<html>" in body, body)
3681 # we should see the regular page, but without the child table or
3683 body = " ".join(body.strip().split())
3684 self.failUnlessIn('href="?t=info">More info on this directory',
3686 exp = ("UnrecoverableFileError: the directory (or mutable file) "
3687 "could not be retrieved, because there were insufficient "
3688 "good shares. This might indicate that no servers were "
3689 "connected, insufficient servers were connected, the URI "
3690 "was corrupt, or that shares have been lost due to server "
3691 "departure, hard drive failure, or disk corruption. You "
3692 "should perform a filecheck on this object to learn more.")
3693 self.failUnlessIn(exp, body)
3694 self.failUnlessIn("No upload forms: directory is unreadable", body)
3695 d.addCallback(_check_0shares_dir_html)
3697 d.addCallback(lambda ignored: self.GET(self.fileurls["dir-1share"]))
3698 def _check_1shares_dir_html(body):
3699 # at some point, we'll split UnrecoverableFileError into 0-shares
3700 # and some-shares like we did for immutable files (since there
3701 # are different sorts of advice to offer in each case). For now,
3702 # they present the same way.
3703 self.failUnless("<html>" in body, body)
3704 body = " ".join(body.strip().split())
3705 self.failUnlessIn('href="?t=info">More info on this directory',
3707 exp = ("UnrecoverableFileError: the directory (or mutable file) "
3708 "could not be retrieved, because there were insufficient "
3709 "good shares. This might indicate that no servers were "
3710 "connected, insufficient servers were connected, the URI "
3711 "was corrupt, or that shares have been lost due to server "
3712 "departure, hard drive failure, or disk corruption. You "
3713 "should perform a filecheck on this object to learn more.")
3714 self.failUnlessIn(exp, body)
3715 self.failUnlessIn("No upload forms: directory is unreadable", body)
3716 d.addCallback(_check_1shares_dir_html)
3718 d.addCallback(lambda ignored:
3719 self.shouldHTTPError("GET dir-0share-json",
3720 410, "Gone", "UnrecoverableFileError",
3722 self.fileurls["dir-0share-json"]))
3723 def _check_unrecoverable_file(body):
3724 self.failIf("<html>" in body, body)
3725 body = " ".join(body.strip().split())
3726 exp = ("UnrecoverableFileError: the directory (or mutable file) "
3727 "could not be retrieved, because there were insufficient "
3728 "good shares. This might indicate that no servers were "
3729 "connected, insufficient servers were connected, the URI "
3730 "was corrupt, or that shares have been lost due to server "
3731 "departure, hard drive failure, or disk corruption. You "
3732 "should perform a filecheck on this object to learn more.")
3733 self.failUnlessEqual(exp, body)
3734 d.addCallback(_check_unrecoverable_file)
3736 d.addCallback(lambda ignored:
3737 self.shouldHTTPError("GET dir-1share-json",
3738 410, "Gone", "UnrecoverableFileError",
3740 self.fileurls["dir-1share-json"]))
3741 d.addCallback(_check_unrecoverable_file)
3743 d.addCallback(lambda ignored:
3744 self.shouldHTTPError("GET imaginary",
3745 404, "Not Found", None,
3746 self.GET, self.fileurls["imaginary"]))
3748 # attach a webapi child that throws a random error, to test how it
3750 w = c0.getServiceNamed("webish")
3751 w.root.putChild("ERRORBOOM", ErrorBoom())
3753 # "Accept: */*" : should get a text/html stack trace
3754 # "Accept: text/plain" : should get a text/plain stack trace
3755 # "Accept: text/plain, application/octet-stream" : text/plain (CLI)
3756 # no Accept header: should get a text/html stack trace
3758 d.addCallback(lambda ignored:
3759 self.shouldHTTPError("GET errorboom_html",
3760 500, "Internal Server Error", None,
3761 self.GET, "ERRORBOOM",
3762 headers={"accept": ["*/*"]}))
3763 def _internal_error_html1(body):
3764 self.failUnless("<html>" in body, "expected HTML, not '%s'" % body)
3765 d.addCallback(_internal_error_html1)
3767 d.addCallback(lambda ignored:
3768 self.shouldHTTPError("GET errorboom_text",
3769 500, "Internal Server Error", None,
3770 self.GET, "ERRORBOOM",
3771 headers={"accept": ["text/plain"]}))
3772 def _internal_error_text2(body):
3773 self.failIf("<html>" in body, body)
3774 self.failUnless(body.startswith("Traceback "), body)
3775 d.addCallback(_internal_error_text2)
3777 CLI_accepts = "text/plain, application/octet-stream"
3778 d.addCallback(lambda ignored:
3779 self.shouldHTTPError("GET errorboom_text",
3780 500, "Internal Server Error", None,
3781 self.GET, "ERRORBOOM",
3782 headers={"accept": [CLI_accepts]}))
3783 def _internal_error_text3(body):
3784 self.failIf("<html>" in body, body)
3785 self.failUnless(body.startswith("Traceback "), body)
3786 d.addCallback(_internal_error_text3)
3788 d.addCallback(lambda ignored:
3789 self.shouldHTTPError("GET errorboom_text",
3790 500, "Internal Server Error", None,
3791 self.GET, "ERRORBOOM"))
3792 def _internal_error_html4(body):
3793 self.failUnless("<html>" in body, "expected HTML, not '%s'" % body)
3794 d.addCallback(_internal_error_html4)
3796 def _flush_errors(res):
3797 # Trial: please ignore the CompletelyUnhandledError in the logs
3798 self.flushLoggedErrors(CompletelyUnhandledError)
3800 d.addBoth(_flush_errors)
3804 class CompletelyUnhandledError(Exception):
3806 class ErrorBoom(rend.Page):
3807 def beforeRender(self, ctx):
3808 raise CompletelyUnhandledError("whoops")