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.internet.task import Clock
8 from twisted.web import client, error, http
9 from twisted.python import failure, log
10 from nevow import rend
11 from allmydata import interfaces, uri, webish, dirnode
12 from allmydata.storage.shares import get_share_file
13 from allmydata.storage_client import StorageFarmBroker
14 from allmydata.immutable import upload, download
15 from allmydata.dirnode import DirectoryNode
16 from allmydata.nodemaker import NodeMaker
17 from allmydata.unknown import UnknownNode
18 from allmydata.web import status, common
19 from allmydata.scripts.debug import CorruptShareOptions, corrupt_share
20 from allmydata.util import fileutil, base32
21 from allmydata.util.consumer import download_to_data
22 from allmydata.util.netstring import split_netstring
23 from allmydata.test.common import FakeCHKFileNode, FakeMutableFileNode, \
24 create_chk_filenode, WebErrorMixin, ShouldFailMixin, make_mutable_file_uri
25 from allmydata.interfaces import IMutableFileNode
26 from allmydata.mutable import servermap, publish, retrieve
27 import allmydata.test.common_util as testutil
28 from allmydata.test.no_network import GridTestMixin
29 from allmydata.test.common_web import HTTPClientGETFactory, \
31 from allmydata.client import Client, SecretHolder
33 # create a fake uploader/downloader, and a couple of fake dirnodes, then
34 # create a webserver that works against them
36 timeout = 480 # Most of these take longer than 240 seconds on Francois's arm box.
38 unknown_rwcap = "lafs://from_the_future"
39 unknown_rocap = "ro.lafs://readonly_from_the_future"
40 unknown_immcap = "imm.lafs://immutable_from_the_future"
42 class FakeStatsProvider:
44 stats = {'stats': {}, 'counters': {}}
47 class FakeNodeMaker(NodeMaker):
48 def _create_lit(self, cap):
49 return FakeCHKFileNode(cap)
50 def _create_immutable(self, cap):
51 return FakeCHKFileNode(cap)
52 def _create_mutable(self, cap):
53 return FakeMutableFileNode(None, None, None, None).init_from_cap(cap)
54 def create_mutable_file(self, contents="", keysize=None):
55 n = FakeMutableFileNode(None, None, None, None)
56 return n.create(contents)
58 class FakeUploader(service.Service):
60 def upload(self, uploadable, history=None):
61 d = uploadable.get_size()
62 d.addCallback(lambda size: uploadable.read(size))
65 n = create_chk_filenode(data)
66 results = upload.UploadResults()
67 results.uri = n.get_uri()
69 d.addCallback(_got_data)
71 def get_helper_info(self):
75 _all_upload_status = [upload.UploadStatus()]
76 _all_download_status = [download.DownloadStatus()]
77 _all_mapupdate_statuses = [servermap.UpdateStatus()]
78 _all_publish_statuses = [publish.PublishStatus()]
79 _all_retrieve_statuses = [retrieve.RetrieveStatus()]
81 def list_all_upload_statuses(self):
82 return self._all_upload_status
83 def list_all_download_statuses(self):
84 return self._all_download_status
85 def list_all_mapupdate_statuses(self):
86 return self._all_mapupdate_statuses
87 def list_all_publish_statuses(self):
88 return self._all_publish_statuses
89 def list_all_retrieve_statuses(self):
90 return self._all_retrieve_statuses
91 def list_all_helper_statuses(self):
94 class FakeClient(Client):
96 # don't upcall to Client.__init__, since we only want to initialize a
98 service.MultiService.__init__(self)
99 self.nodeid = "fake_nodeid"
100 self.nickname = "fake_nickname"
101 self.introducer_furl = "None"
102 self.stats_provider = FakeStatsProvider()
103 self._secret_holder = SecretHolder("lease secret", "convergence secret")
105 self.convergence = "some random string"
106 self.storage_broker = StorageFarmBroker(None, permute_peers=True)
107 self.introducer_client = None
108 self.history = FakeHistory()
109 self.uploader = FakeUploader()
110 self.uploader.setServiceParent(self)
111 self.nodemaker = FakeNodeMaker(None, self._secret_holder, None,
112 self.uploader, None, None,
115 def startService(self):
116 return service.MultiService.startService(self)
117 def stopService(self):
118 return service.MultiService.stopService(self)
120 MUTABLE_SIZELIMIT = FakeMutableFileNode.MUTABLE_SIZELIMIT
122 class WebMixin(object):
124 self.s = FakeClient()
125 self.s.startService()
126 self.staticdir = self.mktemp()
128 self.ws = webish.WebishServer(self.s, "0", staticdir=self.staticdir,
130 self.ws.setServiceParent(self.s)
131 self.webish_port = port = self.ws.listener._port.getHost().port
132 self.webish_url = "http://localhost:%d" % port
134 l = [ self.s.create_dirnode() for x in range(6) ]
135 d = defer.DeferredList(l)
137 self.public_root = res[0][1]
138 assert interfaces.IDirectoryNode.providedBy(self.public_root), res
139 self.public_url = "/uri/" + self.public_root.get_uri()
140 self.private_root = res[1][1]
144 self._foo_uri = foo.get_uri()
145 self._foo_readonly_uri = foo.get_readonly_uri()
146 self._foo_verifycap = foo.get_verify_cap().to_string()
147 # NOTE: we ignore the deferred on all set_uri() calls, because we
148 # know the fake nodes do these synchronously
149 self.public_root.set_uri(u"foo", foo.get_uri(),
150 foo.get_readonly_uri())
152 self.BAR_CONTENTS, n, self._bar_txt_uri = self.makefile(0)
153 foo.set_uri(u"bar.txt", self._bar_txt_uri, self._bar_txt_uri)
154 self._bar_txt_verifycap = n.get_verify_cap().to_string()
156 foo.set_uri(u"empty", res[3][1].get_uri(),
157 res[3][1].get_readonly_uri())
158 sub_uri = res[4][1].get_uri()
159 self._sub_uri = sub_uri
160 foo.set_uri(u"sub", sub_uri, sub_uri)
161 sub = self.s.create_node_from_uri(sub_uri)
163 _ign, n, blocking_uri = self.makefile(1)
164 foo.set_uri(u"blockingfile", blocking_uri, blocking_uri)
166 unicode_filename = u"n\u00fc.txt" # n u-umlaut . t x t
167 # ok, unicode calls it LATIN SMALL LETTER U WITH DIAERESIS but I
168 # still think of it as an umlaut
169 foo.set_uri(unicode_filename, self._bar_txt_uri, self._bar_txt_uri)
171 _ign, n, baz_file = self.makefile(2)
172 self._baz_file_uri = baz_file
173 sub.set_uri(u"baz.txt", baz_file, baz_file)
175 _ign, n, self._bad_file_uri = self.makefile(3)
176 # this uri should not be downloadable
177 del FakeCHKFileNode.all_contents[self._bad_file_uri]
180 self.public_root.set_uri(u"reedownlee", rodir.get_readonly_uri(),
181 rodir.get_readonly_uri())
182 rodir.set_uri(u"nor", baz_file, baz_file)
187 # public/foo/blockingfile
190 # public/foo/sub/baz.txt
192 # public/reedownlee/nor
193 self.NEWFILE_CONTENTS = "newfile contents\n"
195 return foo.get_metadata_for(u"bar.txt")
197 def _got_metadata(metadata):
198 self._bar_txt_metadata = metadata
199 d.addCallback(_got_metadata)
202 def makefile(self, number):
203 contents = "contents of file %s\n" % number
204 n = create_chk_filenode(contents)
205 return contents, n, n.get_uri()
208 return self.s.stopService()
210 def failUnlessIsBarDotTxt(self, res):
211 self.failUnlessEqual(res, self.BAR_CONTENTS, res)
213 def failUnlessIsBarJSON(self, res):
214 data = simplejson.loads(res)
215 self.failUnless(isinstance(data, list))
216 self.failUnlessEqual(data[0], u"filenode")
217 self.failUnless(isinstance(data[1], dict))
218 self.failIf(data[1]["mutable"])
219 self.failIf("rw_uri" in data[1]) # immutable
220 self.failUnlessEqual(data[1]["ro_uri"], self._bar_txt_uri)
221 self.failUnlessEqual(data[1]["verify_uri"], self._bar_txt_verifycap)
222 self.failUnlessEqual(data[1]["size"], len(self.BAR_CONTENTS))
224 def failUnlessIsFooJSON(self, res):
225 data = simplejson.loads(res)
226 self.failUnless(isinstance(data, list))
227 self.failUnlessEqual(data[0], "dirnode", res)
228 self.failUnless(isinstance(data[1], dict))
229 self.failUnless(data[1]["mutable"])
230 self.failUnless("rw_uri" in data[1]) # mutable
231 self.failUnlessEqual(data[1]["rw_uri"], self._foo_uri)
232 self.failUnlessEqual(data[1]["ro_uri"], self._foo_readonly_uri)
233 self.failUnlessEqual(data[1]["verify_uri"], self._foo_verifycap)
235 kidnames = sorted([unicode(n) for n in data[1]["children"]])
236 self.failUnlessEqual(kidnames,
237 [u"bar.txt", u"blockingfile", u"empty",
238 u"n\u00fc.txt", u"sub"])
239 kids = dict( [(unicode(name),value)
241 in data[1]["children"].iteritems()] )
242 self.failUnlessEqual(kids[u"sub"][0], "dirnode")
243 self.failUnlessIn("metadata", kids[u"sub"][1])
244 self.failUnlessIn("tahoe", kids[u"sub"][1]["metadata"])
245 tahoe_md = kids[u"sub"][1]["metadata"]["tahoe"]
246 self.failUnlessIn("linkcrtime", tahoe_md)
247 self.failUnlessIn("linkmotime", tahoe_md)
248 self.failUnlessEqual(kids[u"bar.txt"][0], "filenode")
249 self.failUnlessEqual(kids[u"bar.txt"][1]["size"], len(self.BAR_CONTENTS))
250 self.failUnlessEqual(kids[u"bar.txt"][1]["ro_uri"], self._bar_txt_uri)
251 self.failUnlessEqual(kids[u"bar.txt"][1]["verify_uri"],
252 self._bar_txt_verifycap)
253 self.failUnlessIn("metadata", kids[u"bar.txt"][1])
254 self.failUnlessIn("tahoe", kids[u"bar.txt"][1]["metadata"])
255 self.failUnlessEqual(kids[u"bar.txt"][1]["metadata"]["tahoe"]["linkcrtime"],
256 self._bar_txt_metadata["tahoe"]["linkcrtime"])
257 self.failUnlessEqual(kids[u"n\u00fc.txt"][1]["ro_uri"],
260 def GET(self, urlpath, followRedirect=False, return_response=False,
262 # if return_response=True, this fires with (data, statuscode,
263 # respheaders) instead of just data.
264 assert not isinstance(urlpath, unicode)
265 url = self.webish_url + urlpath
266 factory = HTTPClientGETFactory(url, method="GET",
267 followRedirect=followRedirect, **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 HEAD(self, urlpath, return_response=False, **kwargs):
277 # this requires some surgery, because twisted.web.client doesn't want
278 # to give us back the response headers.
279 factory = HTTPClientHEADFactory(urlpath, method="HEAD", **kwargs)
280 reactor.connectTCP("localhost", self.webish_port, factory)
283 return (data, factory.status, factory.response_headers)
285 d.addCallback(_got_data)
286 return factory.deferred
288 def PUT(self, urlpath, data, **kwargs):
289 url = self.webish_url + urlpath
290 return client.getPage(url, method="PUT", postdata=data, **kwargs)
292 def DELETE(self, urlpath):
293 url = self.webish_url + urlpath
294 return client.getPage(url, method="DELETE")
296 def POST(self, urlpath, followRedirect=False, **fields):
297 sepbase = "boogabooga"
301 form.append('Content-Disposition: form-data; name="_charset"')
305 for name, value in fields.iteritems():
306 if isinstance(value, tuple):
307 filename, value = value
308 form.append('Content-Disposition: form-data; name="%s"; '
309 'filename="%s"' % (name, filename.encode("utf-8")))
311 form.append('Content-Disposition: form-data; name="%s"' % name)
313 if isinstance(value, unicode):
314 value = value.encode("utf-8")
317 assert isinstance(value, str)
324 body = "\r\n".join(form) + "\r\n"
325 headers["content-type"] = "multipart/form-data; boundary=%s" % sepbase
326 return self.POST2(urlpath, body, headers, followRedirect)
328 def POST2(self, urlpath, body="", headers={}, followRedirect=False):
329 url = self.webish_url + urlpath
330 return client.getPage(url, method="POST", postdata=body,
331 headers=headers, followRedirect=followRedirect)
333 def shouldFail(self, res, expected_failure, which,
334 substring=None, response_substring=None):
335 if isinstance(res, failure.Failure):
336 res.trap(expected_failure)
338 self.failUnless(substring in str(res),
339 "substring '%s' not in '%s'"
340 % (substring, str(res)))
341 if response_substring:
342 self.failUnless(response_substring in res.value.response,
343 "response substring '%s' not in '%s'"
344 % (response_substring, res.value.response))
346 self.fail("%s was supposed to raise %s, not get '%s'" %
347 (which, expected_failure, res))
349 def shouldFail2(self, expected_failure, which, substring,
351 callable, *args, **kwargs):
352 assert substring is None or isinstance(substring, str)
353 assert response_substring is None or isinstance(response_substring, str)
354 d = defer.maybeDeferred(callable, *args, **kwargs)
356 if isinstance(res, failure.Failure):
357 res.trap(expected_failure)
359 self.failUnless(substring in str(res),
360 "%s: substring '%s' not in '%s'"
361 % (which, substring, str(res)))
362 if response_substring:
363 self.failUnless(response_substring in res.value.response,
364 "%s: response substring '%s' not in '%s'"
366 response_substring, res.value.response))
368 self.fail("%s was supposed to raise %s, not get '%s'" %
369 (which, expected_failure, res))
373 def should404(self, res, which):
374 if isinstance(res, failure.Failure):
375 res.trap(error.Error)
376 self.failUnlessEqual(res.value.status, "404")
378 self.fail("%s was supposed to Error(404), not get '%s'" %
381 def should302(self, res, which):
382 if isinstance(res, failure.Failure):
383 res.trap(error.Error)
384 self.failUnlessEqual(res.value.status, "302")
386 self.fail("%s was supposed to Error(302), not get '%s'" %
390 class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase):
391 def test_create(self):
394 def test_welcome(self):
397 self.failUnless('Welcome To Tahoe-LAFS' in res, res)
399 self.s.basedir = 'web/test_welcome'
400 fileutil.make_dirs("web/test_welcome")
401 fileutil.make_dirs("web/test_welcome/private")
403 d.addCallback(_check)
406 def test_provisioning(self):
407 d = self.GET("/provisioning/")
409 self.failUnless('Provisioning Tool' in res)
410 fields = {'filled': True,
411 "num_users": int(50e3),
412 "files_per_user": 1000,
413 "space_per_user": int(1e9),
414 "sharing_ratio": 1.0,
415 "encoding_parameters": "3-of-10-5",
417 "ownership_mode": "A",
418 "download_rate": 100,
423 return self.POST("/provisioning/", **fields)
425 d.addCallback(_check)
427 self.failUnless('Provisioning Tool' in res)
428 self.failUnless("Share space consumed: 167.01TB" in res)
430 fields = {'filled': True,
431 "num_users": int(50e6),
432 "files_per_user": 1000,
433 "space_per_user": int(5e9),
434 "sharing_ratio": 1.0,
435 "encoding_parameters": "25-of-100-50",
436 "num_servers": 30000,
437 "ownership_mode": "E",
438 "drive_failure_model": "U",
440 "download_rate": 1000,
445 return self.POST("/provisioning/", **fields)
446 d.addCallback(_check2)
448 self.failUnless("Share space consumed: huge!" in res)
449 fields = {'filled': True}
450 return self.POST("/provisioning/", **fields)
451 d.addCallback(_check3)
453 self.failUnless("Share space consumed:" in res)
454 d.addCallback(_check4)
457 def test_reliability_tool(self):
459 from allmydata import reliability
460 _hush_pyflakes = reliability
463 raise unittest.SkipTest("reliability tool requires NumPy")
465 d = self.GET("/reliability/")
467 self.failUnless('Reliability Tool' in res)
468 fields = {'drive_lifetime': "8Y",
473 "check_period": "1M",
474 "report_period": "3M",
477 return self.POST("/reliability/", **fields)
479 d.addCallback(_check)
481 self.failUnless('Reliability Tool' in res)
482 r = r'Probability of loss \(no maintenance\):\s+<span>0.033591'
483 self.failUnless(re.search(r, res), res)
484 d.addCallback(_check2)
487 def test_status(self):
488 h = self.s.get_history()
489 dl_num = h.list_all_download_statuses()[0].get_counter()
490 ul_num = h.list_all_upload_statuses()[0].get_counter()
491 mu_num = h.list_all_mapupdate_statuses()[0].get_counter()
492 pub_num = h.list_all_publish_statuses()[0].get_counter()
493 ret_num = h.list_all_retrieve_statuses()[0].get_counter()
494 d = self.GET("/status", followRedirect=True)
496 self.failUnless('Upload and Download Status' in res, res)
497 self.failUnless('"down-%d"' % dl_num in res, res)
498 self.failUnless('"up-%d"' % ul_num in res, res)
499 self.failUnless('"mapupdate-%d"' % mu_num in res, res)
500 self.failUnless('"publish-%d"' % pub_num in res, res)
501 self.failUnless('"retrieve-%d"' % ret_num in res, res)
502 d.addCallback(_check)
503 d.addCallback(lambda res: self.GET("/status/?t=json"))
504 def _check_json(res):
505 data = simplejson.loads(res)
506 self.failUnless(isinstance(data, dict))
507 #active = data["active"]
508 # TODO: test more. We need a way to fake an active operation
510 d.addCallback(_check_json)
512 d.addCallback(lambda res: self.GET("/status/down-%d" % dl_num))
514 self.failUnless("File Download Status" in res, res)
515 d.addCallback(_check_dl)
516 d.addCallback(lambda res: self.GET("/status/up-%d" % ul_num))
518 self.failUnless("File Upload Status" in res, res)
519 d.addCallback(_check_ul)
520 d.addCallback(lambda res: self.GET("/status/mapupdate-%d" % mu_num))
521 def _check_mapupdate(res):
522 self.failUnless("Mutable File Servermap Update Status" in res, res)
523 d.addCallback(_check_mapupdate)
524 d.addCallback(lambda res: self.GET("/status/publish-%d" % pub_num))
525 def _check_publish(res):
526 self.failUnless("Mutable File Publish Status" in res, res)
527 d.addCallback(_check_publish)
528 d.addCallback(lambda res: self.GET("/status/retrieve-%d" % ret_num))
529 def _check_retrieve(res):
530 self.failUnless("Mutable File Retrieve Status" in res, res)
531 d.addCallback(_check_retrieve)
535 def test_status_numbers(self):
536 drrm = status.DownloadResultsRendererMixin()
537 self.failUnlessEqual(drrm.render_time(None, None), "")
538 self.failUnlessEqual(drrm.render_time(None, 2.5), "2.50s")
539 self.failUnlessEqual(drrm.render_time(None, 0.25), "250ms")
540 self.failUnlessEqual(drrm.render_time(None, 0.0021), "2.1ms")
541 self.failUnlessEqual(drrm.render_time(None, 0.000123), "123us")
542 self.failUnlessEqual(drrm.render_rate(None, None), "")
543 self.failUnlessEqual(drrm.render_rate(None, 2500000), "2.50MBps")
544 self.failUnlessEqual(drrm.render_rate(None, 30100), "30.1kBps")
545 self.failUnlessEqual(drrm.render_rate(None, 123), "123Bps")
547 urrm = status.UploadResultsRendererMixin()
548 self.failUnlessEqual(urrm.render_time(None, None), "")
549 self.failUnlessEqual(urrm.render_time(None, 2.5), "2.50s")
550 self.failUnlessEqual(urrm.render_time(None, 0.25), "250ms")
551 self.failUnlessEqual(urrm.render_time(None, 0.0021), "2.1ms")
552 self.failUnlessEqual(urrm.render_time(None, 0.000123), "123us")
553 self.failUnlessEqual(urrm.render_rate(None, None), "")
554 self.failUnlessEqual(urrm.render_rate(None, 2500000), "2.50MBps")
555 self.failUnlessEqual(urrm.render_rate(None, 30100), "30.1kBps")
556 self.failUnlessEqual(urrm.render_rate(None, 123), "123Bps")
558 def test_GET_FILEURL(self):
559 d = self.GET(self.public_url + "/foo/bar.txt")
560 d.addCallback(self.failUnlessIsBarDotTxt)
563 def test_GET_FILEURL_range(self):
564 headers = {"range": "bytes=1-10"}
565 d = self.GET(self.public_url + "/foo/bar.txt", headers=headers,
566 return_response=True)
567 def _got((res, status, headers)):
568 self.failUnlessEqual(int(status), 206)
569 self.failUnless(headers.has_key("content-range"))
570 self.failUnlessEqual(headers["content-range"][0],
571 "bytes 1-10/%d" % len(self.BAR_CONTENTS))
572 self.failUnlessEqual(res, self.BAR_CONTENTS[1:11])
576 def test_GET_FILEURL_partial_range(self):
577 headers = {"range": "bytes=5-"}
578 length = len(self.BAR_CONTENTS)
579 d = self.GET(self.public_url + "/foo/bar.txt", headers=headers,
580 return_response=True)
581 def _got((res, status, headers)):
582 self.failUnlessEqual(int(status), 206)
583 self.failUnless(headers.has_key("content-range"))
584 self.failUnlessEqual(headers["content-range"][0],
585 "bytes 5-%d/%d" % (length-1, length))
586 self.failUnlessEqual(res, self.BAR_CONTENTS[5:])
590 def test_GET_FILEURL_partial_end_range(self):
591 headers = {"range": "bytes=-5"}
592 length = len(self.BAR_CONTENTS)
593 d = self.GET(self.public_url + "/foo/bar.txt", headers=headers,
594 return_response=True)
595 def _got((res, status, headers)):
596 self.failUnlessEqual(int(status), 206)
597 self.failUnless(headers.has_key("content-range"))
598 self.failUnlessEqual(headers["content-range"][0],
599 "bytes %d-%d/%d" % (length-5, length-1, length))
600 self.failUnlessEqual(res, self.BAR_CONTENTS[-5:])
604 def test_GET_FILEURL_partial_range_overrun(self):
605 headers = {"range": "bytes=100-200"}
606 d = self.shouldFail2(error.Error, "test_GET_FILEURL_range_overrun",
607 "416 Requested Range not satisfiable",
608 "First beyond end of file",
609 self.GET, self.public_url + "/foo/bar.txt",
613 def test_HEAD_FILEURL_range(self):
614 headers = {"range": "bytes=1-10"}
615 d = self.HEAD(self.public_url + "/foo/bar.txt", headers=headers,
616 return_response=True)
617 def _got((res, status, headers)):
618 self.failUnlessEqual(res, "")
619 self.failUnlessEqual(int(status), 206)
620 self.failUnless(headers.has_key("content-range"))
621 self.failUnlessEqual(headers["content-range"][0],
622 "bytes 1-10/%d" % len(self.BAR_CONTENTS))
626 def test_HEAD_FILEURL_partial_range(self):
627 headers = {"range": "bytes=5-"}
628 length = len(self.BAR_CONTENTS)
629 d = self.HEAD(self.public_url + "/foo/bar.txt", headers=headers,
630 return_response=True)
631 def _got((res, status, headers)):
632 self.failUnlessEqual(int(status), 206)
633 self.failUnless(headers.has_key("content-range"))
634 self.failUnlessEqual(headers["content-range"][0],
635 "bytes 5-%d/%d" % (length-1, length))
639 def test_HEAD_FILEURL_partial_end_range(self):
640 headers = {"range": "bytes=-5"}
641 length = len(self.BAR_CONTENTS)
642 d = self.HEAD(self.public_url + "/foo/bar.txt", headers=headers,
643 return_response=True)
644 def _got((res, status, headers)):
645 self.failUnlessEqual(int(status), 206)
646 self.failUnless(headers.has_key("content-range"))
647 self.failUnlessEqual(headers["content-range"][0],
648 "bytes %d-%d/%d" % (length-5, length-1, length))
652 def test_HEAD_FILEURL_partial_range_overrun(self):
653 headers = {"range": "bytes=100-200"}
654 d = self.shouldFail2(error.Error, "test_HEAD_FILEURL_range_overrun",
655 "416 Requested Range not satisfiable",
657 self.HEAD, self.public_url + "/foo/bar.txt",
661 def test_GET_FILEURL_range_bad(self):
662 headers = {"range": "BOGUS=fizbop-quarnak"}
663 d = self.GET(self.public_url + "/foo/bar.txt", headers=headers,
664 return_response=True)
665 def _got((res, status, headers)):
666 self.failUnlessEqual(int(status), 200)
667 self.failUnless(not headers.has_key("content-range"))
668 self.failUnlessEqual(res, self.BAR_CONTENTS)
672 def test_HEAD_FILEURL(self):
673 d = self.HEAD(self.public_url + "/foo/bar.txt", return_response=True)
674 def _got((res, status, headers)):
675 self.failUnlessEqual(res, "")
676 self.failUnlessEqual(headers["content-length"][0],
677 str(len(self.BAR_CONTENTS)))
678 self.failUnlessEqual(headers["content-type"], ["text/plain"])
682 def test_GET_FILEURL_named(self):
683 base = "/file/%s" % urllib.quote(self._bar_txt_uri)
684 base2 = "/named/%s" % urllib.quote(self._bar_txt_uri)
685 d = self.GET(base + "/@@name=/blah.txt")
686 d.addCallback(self.failUnlessIsBarDotTxt)
687 d.addCallback(lambda res: self.GET(base + "/blah.txt"))
688 d.addCallback(self.failUnlessIsBarDotTxt)
689 d.addCallback(lambda res: self.GET(base + "/ignore/lots/blah.txt"))
690 d.addCallback(self.failUnlessIsBarDotTxt)
691 d.addCallback(lambda res: self.GET(base2 + "/@@name=/blah.txt"))
692 d.addCallback(self.failUnlessIsBarDotTxt)
693 save_url = base + "?save=true&filename=blah.txt"
694 d.addCallback(lambda res: self.GET(save_url))
695 d.addCallback(self.failUnlessIsBarDotTxt) # TODO: check headers
696 u_filename = u"n\u00e9wer.txt" # n e-acute w e r . t x t
697 u_fn_e = urllib.quote(u_filename.encode("utf-8"))
698 u_url = base + "?save=true&filename=" + u_fn_e
699 d.addCallback(lambda res: self.GET(u_url))
700 d.addCallback(self.failUnlessIsBarDotTxt) # TODO: check headers
703 def test_PUT_FILEURL_named_bad(self):
704 base = "/file/%s" % urllib.quote(self._bar_txt_uri)
705 d = self.shouldFail2(error.Error, "test_PUT_FILEURL_named_bad",
707 "/file can only be used with GET or HEAD",
708 self.PUT, base + "/@@name=/blah.txt", "")
711 def test_GET_DIRURL_named_bad(self):
712 base = "/file/%s" % urllib.quote(self._foo_uri)
713 d = self.shouldFail2(error.Error, "test_PUT_DIRURL_named_bad",
716 self.GET, base + "/@@name=/blah.txt")
719 def test_GET_slash_file_bad(self):
720 d = self.shouldFail2(error.Error, "test_GET_slash_file_bad",
722 "/file must be followed by a file-cap and a name",
726 def test_GET_unhandled_URI_named(self):
727 contents, n, newuri = self.makefile(12)
728 verifier_cap = n.get_verify_cap().to_string()
729 base = "/file/%s" % urllib.quote(verifier_cap)
730 # client.create_node_from_uri() can't handle verify-caps
731 d = self.shouldFail2(error.Error, "GET_unhandled_URI_named",
732 "400 Bad Request", "is not a file-cap",
736 def test_GET_unhandled_URI(self):
737 contents, n, newuri = self.makefile(12)
738 verifier_cap = n.get_verify_cap().to_string()
739 base = "/uri/%s" % urllib.quote(verifier_cap)
740 # client.create_node_from_uri() can't handle verify-caps
741 d = self.shouldFail2(error.Error, "test_GET_unhandled_URI",
743 "GET unknown URI type: can only do t=info",
747 def test_GET_FILE_URI(self):
748 base = "/uri/%s" % urllib.quote(self._bar_txt_uri)
750 d.addCallback(self.failUnlessIsBarDotTxt)
753 def test_GET_FILE_URI_badchild(self):
754 base = "/uri/%s/boguschild" % urllib.quote(self._bar_txt_uri)
755 errmsg = "Files have no children, certainly not named 'boguschild'"
756 d = self.shouldFail2(error.Error, "test_GET_FILE_URI_badchild",
757 "400 Bad Request", errmsg,
761 def test_PUT_FILE_URI_badchild(self):
762 base = "/uri/%s/boguschild" % urllib.quote(self._bar_txt_uri)
763 errmsg = "Cannot create directory 'boguschild', because its parent is a file, not a directory"
764 d = self.shouldFail2(error.Error, "test_GET_FILE_URI_badchild",
765 "400 Bad Request", errmsg,
769 # TODO: version of this with a Unicode filename
770 def test_GET_FILEURL_save(self):
771 d = self.GET(self.public_url + "/foo/bar.txt?filename=bar.txt&save=true",
772 return_response=True)
773 def _got((res, statuscode, headers)):
774 content_disposition = headers["content-disposition"][0]
775 self.failUnless(content_disposition == 'attachment; filename="bar.txt"', content_disposition)
776 self.failUnlessIsBarDotTxt(res)
780 def test_GET_FILEURL_missing(self):
781 d = self.GET(self.public_url + "/foo/missing")
782 d.addBoth(self.should404, "test_GET_FILEURL_missing")
785 def test_PUT_overwrite_only_files(self):
786 # create a directory, put a file in that directory.
787 contents, n, filecap = self.makefile(8)
788 d = self.PUT(self.public_url + "/foo/dir?t=mkdir", "")
789 d.addCallback(lambda res:
790 self.PUT(self.public_url + "/foo/dir/file1.txt",
791 self.NEWFILE_CONTENTS))
792 # try to overwrite the file with replace=only-files
794 d.addCallback(lambda res:
795 self.PUT(self.public_url + "/foo/dir/file1.txt?t=uri&replace=only-files",
797 d.addCallback(lambda res:
798 self.shouldFail2(error.Error, "PUT_bad_t", "409 Conflict",
799 "There was already a child by that name, and you asked me "
801 self.PUT, self.public_url + "/foo/dir?t=uri&replace=only-files",
805 def test_PUT_NEWFILEURL(self):
806 d = self.PUT(self.public_url + "/foo/new.txt", self.NEWFILE_CONTENTS)
807 # TODO: we lose the response code, so we can't check this
808 #self.failUnlessEqual(responsecode, 201)
809 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"new.txt")
810 d.addCallback(lambda res:
811 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
812 self.NEWFILE_CONTENTS))
815 def test_PUT_NEWFILEURL_not_mutable(self):
816 d = self.PUT(self.public_url + "/foo/new.txt?mutable=false",
817 self.NEWFILE_CONTENTS)
818 # TODO: we lose the response code, so we can't check this
819 #self.failUnlessEqual(responsecode, 201)
820 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"new.txt")
821 d.addCallback(lambda res:
822 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
823 self.NEWFILE_CONTENTS))
826 def test_PUT_NEWFILEURL_range_bad(self):
827 headers = {"content-range": "bytes 1-10/%d" % len(self.NEWFILE_CONTENTS)}
828 target = self.public_url + "/foo/new.txt"
829 d = self.shouldFail2(error.Error, "test_PUT_NEWFILEURL_range_bad",
830 "501 Not Implemented",
831 "Content-Range in PUT not yet supported",
832 # (and certainly not for immutable files)
833 self.PUT, target, self.NEWFILE_CONTENTS[1:11],
835 d.addCallback(lambda res:
836 self.failIfNodeHasChild(self._foo_node, u"new.txt"))
839 def test_PUT_NEWFILEURL_mutable(self):
840 d = self.PUT(self.public_url + "/foo/new.txt?mutable=true",
841 self.NEWFILE_CONTENTS)
842 # TODO: we lose the response code, so we can't check this
843 #self.failUnlessEqual(responsecode, 201)
845 u = uri.from_string_mutable_filenode(res)
846 self.failUnless(u.is_mutable())
847 self.failIf(u.is_readonly())
849 d.addCallback(_check_uri)
850 d.addCallback(self.failUnlessURIMatchesRWChild, self._foo_node, u"new.txt")
851 d.addCallback(lambda res:
852 self.failUnlessMutableChildContentsAre(self._foo_node,
854 self.NEWFILE_CONTENTS))
857 def test_PUT_NEWFILEURL_mutable_toobig(self):
858 d = self.shouldFail2(error.Error, "test_PUT_NEWFILEURL_mutable_toobig",
859 "413 Request Entity Too Large",
860 "SDMF is limited to one segment, and 10001 > 10000",
862 self.public_url + "/foo/new.txt?mutable=true",
863 "b" * (self.s.MUTABLE_SIZELIMIT+1))
866 def test_PUT_NEWFILEURL_replace(self):
867 d = self.PUT(self.public_url + "/foo/bar.txt", self.NEWFILE_CONTENTS)
868 # TODO: we lose the response code, so we can't check this
869 #self.failUnlessEqual(responsecode, 200)
870 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"bar.txt")
871 d.addCallback(lambda res:
872 self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
873 self.NEWFILE_CONTENTS))
876 def test_PUT_NEWFILEURL_bad_t(self):
877 d = self.shouldFail2(error.Error, "PUT_bad_t", "400 Bad Request",
878 "PUT to a file: bad t=bogus",
879 self.PUT, self.public_url + "/foo/bar.txt?t=bogus",
883 def test_PUT_NEWFILEURL_no_replace(self):
884 d = self.PUT(self.public_url + "/foo/bar.txt?replace=false",
885 self.NEWFILE_CONTENTS)
886 d.addBoth(self.shouldFail, error.Error, "PUT_NEWFILEURL_no_replace",
888 "There was already a child by that name, and you asked me "
892 def test_PUT_NEWFILEURL_mkdirs(self):
893 d = self.PUT(self.public_url + "/foo/newdir/new.txt", self.NEWFILE_CONTENTS)
895 d.addCallback(self.failUnlessURIMatchesROChild, fn, u"newdir/new.txt")
896 d.addCallback(lambda res: self.failIfNodeHasChild(fn, u"new.txt"))
897 d.addCallback(lambda res: self.failUnlessNodeHasChild(fn, u"newdir"))
898 d.addCallback(lambda res:
899 self.failUnlessChildContentsAre(fn, u"newdir/new.txt",
900 self.NEWFILE_CONTENTS))
903 def test_PUT_NEWFILEURL_blocked(self):
904 d = self.PUT(self.public_url + "/foo/blockingfile/new.txt",
905 self.NEWFILE_CONTENTS)
906 d.addBoth(self.shouldFail, error.Error, "PUT_NEWFILEURL_blocked",
908 "Unable to create directory 'blockingfile': a file was in the way")
911 def test_PUT_NEWFILEURL_emptyname(self):
912 # an empty pathname component (i.e. a double-slash) is disallowed
913 d = self.shouldFail2(error.Error, "test_PUT_NEWFILEURL_emptyname",
915 "The webapi does not allow empty pathname components",
916 self.PUT, self.public_url + "/foo//new.txt", "")
919 def test_DELETE_FILEURL(self):
920 d = self.DELETE(self.public_url + "/foo/bar.txt")
921 d.addCallback(lambda res:
922 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
925 def test_DELETE_FILEURL_missing(self):
926 d = self.DELETE(self.public_url + "/foo/missing")
927 d.addBoth(self.should404, "test_DELETE_FILEURL_missing")
930 def test_DELETE_FILEURL_missing2(self):
931 d = self.DELETE(self.public_url + "/missing/missing")
932 d.addBoth(self.should404, "test_DELETE_FILEURL_missing2")
935 def failUnlessHasBarDotTxtMetadata(self, res):
936 data = simplejson.loads(res)
937 self.failUnless(isinstance(data, list))
938 self.failUnlessIn("metadata", data[1])
939 self.failUnlessIn("tahoe", data[1]["metadata"])
940 self.failUnlessIn("linkcrtime", data[1]["metadata"]["tahoe"])
941 self.failUnlessIn("linkmotime", data[1]["metadata"]["tahoe"])
942 self.failUnlessEqual(data[1]["metadata"]["tahoe"]["linkcrtime"],
943 self._bar_txt_metadata["tahoe"]["linkcrtime"])
945 def test_GET_FILEURL_json(self):
946 # twisted.web.http.parse_qs ignores any query args without an '=', so
947 # I can't do "GET /path?json", I have to do "GET /path/t=json"
948 # instead. This may make it tricky to emulate the S3 interface
950 d = self.GET(self.public_url + "/foo/bar.txt?t=json")
952 self.failUnlessIsBarJSON(data)
953 self.failUnlessHasBarDotTxtMetadata(data)
955 d.addCallback(_check1)
958 def test_GET_FILEURL_json_missing(self):
959 d = self.GET(self.public_url + "/foo/missing?json")
960 d.addBoth(self.should404, "test_GET_FILEURL_json_missing")
963 def test_GET_FILEURL_uri(self):
964 d = self.GET(self.public_url + "/foo/bar.txt?t=uri")
966 self.failUnlessEqual(res, self._bar_txt_uri)
967 d.addCallback(_check)
968 d.addCallback(lambda res:
969 self.GET(self.public_url + "/foo/bar.txt?t=readonly-uri"))
971 # for now, for files, uris and readonly-uris are the same
972 self.failUnlessEqual(res, self._bar_txt_uri)
973 d.addCallback(_check2)
976 def test_GET_FILEURL_badtype(self):
977 d = self.shouldHTTPError("GET t=bogus", 400, "Bad Request",
980 self.public_url + "/foo/bar.txt?t=bogus")
983 def test_CSS_FILE(self):
984 d = self.GET("/tahoe_css", followRedirect=True)
986 CSS_STYLE=re.compile('toolbar\s{.+text-align:\scenter.+toolbar-item.+display:\sinline',re.DOTALL)
987 self.failUnless(CSS_STYLE.search(res), res)
988 d.addCallback(_check)
991 def test_GET_FILEURL_uri_missing(self):
992 d = self.GET(self.public_url + "/foo/missing?t=uri")
993 d.addBoth(self.should404, "test_GET_FILEURL_uri_missing")
996 def test_GET_DIRECTORY_html_banner(self):
997 d = self.GET(self.public_url + "/foo", followRedirect=True)
999 self.failUnlessIn('<div class="toolbar-item"><a href="../../..">Return to Welcome page</a></div>',res)
1000 d.addCallback(_check)
1003 def test_GET_DIRURL(self):
1004 # the addSlash means we get a redirect here
1005 # from /uri/$URI/foo/ , we need ../../../ to get back to the root
1007 d = self.GET(self.public_url + "/foo", followRedirect=True)
1009 self.failUnless(('<a href="%s">Return to Welcome page' % ROOT)
1011 # the FILE reference points to a URI, but it should end in bar.txt
1012 bar_url = ("%s/file/%s/@@named=/bar.txt" %
1013 (ROOT, urllib.quote(self._bar_txt_uri)))
1014 get_bar = "".join([r'<td>FILE</td>',
1016 r'<a href="%s">bar.txt</a>' % bar_url,
1018 r'\s+<td>%d</td>' % len(self.BAR_CONTENTS),
1020 self.failUnless(re.search(get_bar, res), res)
1021 for line in res.split("\n"):
1022 # find the line that contains the delete button for bar.txt
1023 if ("form action" in line and
1024 'value="delete"' in line and
1025 'value="bar.txt"' in line):
1026 # the form target should use a relative URL
1027 foo_url = urllib.quote("%s/uri/%s/" % (ROOT, self._foo_uri))
1028 self.failUnless(('action="%s"' % foo_url) in line, line)
1029 # and the when_done= should too
1030 #done_url = urllib.quote(???)
1031 #self.failUnless(('name="when_done" value="%s"' % done_url)
1035 self.fail("unable to find delete-bar.txt line", res)
1037 # the DIR reference just points to a URI
1038 sub_url = ("%s/uri/%s/" % (ROOT, urllib.quote(self._sub_uri)))
1039 get_sub = ((r'<td>DIR</td>')
1040 +r'\s+<td><a href="%s">sub</a></td>' % sub_url)
1041 self.failUnless(re.search(get_sub, res), res)
1042 d.addCallback(_check)
1044 # look at a readonly directory
1045 d.addCallback(lambda res:
1046 self.GET(self.public_url + "/reedownlee", followRedirect=True))
1048 self.failUnless("(read-only)" in res, res)
1049 self.failIf("Upload a file" in res, res)
1050 d.addCallback(_check2)
1052 # and at a directory that contains a readonly directory
1053 d.addCallback(lambda res:
1054 self.GET(self.public_url, followRedirect=True))
1056 self.failUnless(re.search('<td>DIR-RO</td>'
1057 r'\s+<td><a href="[\.\/]+/uri/URI%3ADIR2-RO%3A[^"]+">reedownlee</a></td>', res), res)
1058 d.addCallback(_check3)
1060 # and an empty directory
1061 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty/"))
1063 self.failUnless("directory is empty" in res, res)
1064 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)
1065 self.failUnless(MKDIR_BUTTON_RE.search(res), res)
1066 d.addCallback(_check4)
1068 # and at a literal directory
1069 tiny_litdir_uri = "URI:DIR2-LIT:gqytunj2onug64tufqzdcosvkjetutcjkq5gw4tvm5vwszdgnz5hgyzufqydulbshj5x2lbm" # contains one child which is itself also LIT
1070 d.addCallback(lambda res:
1071 self.GET("/uri/" + tiny_litdir_uri + "/", followRedirect=True))
1073 self.failUnless('(immutable)' in res, res)
1074 self.failUnless(re.search('<td>FILE</td>'
1075 r'\s+<td><a href="[\.\/]+/file/URI%3ALIT%3Akrugkidfnzsc4/@@named=/short">short</a></td>', res), res)
1076 d.addCallback(_check5)
1079 def test_GET_DIRURL_badtype(self):
1080 d = self.shouldHTTPError("test_GET_DIRURL_badtype",
1084 self.public_url + "/foo?t=bogus")
1087 def test_GET_DIRURL_json(self):
1088 d = self.GET(self.public_url + "/foo?t=json")
1089 d.addCallback(self.failUnlessIsFooJSON)
1093 def test_POST_DIRURL_manifest_no_ophandle(self):
1094 d = self.shouldFail2(error.Error,
1095 "test_POST_DIRURL_manifest_no_ophandle",
1097 "slow operation requires ophandle=",
1098 self.POST, self.public_url, t="start-manifest")
1101 def test_POST_DIRURL_manifest(self):
1102 d = defer.succeed(None)
1103 def getman(ignored, output):
1104 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=125",
1105 followRedirect=True)
1106 d.addCallback(self.wait_for_operation, "125")
1107 d.addCallback(self.get_operation_results, "125", output)
1109 d.addCallback(getman, None)
1110 def _got_html(manifest):
1111 self.failUnless("Manifest of SI=" in manifest)
1112 self.failUnless("<td>sub</td>" in manifest)
1113 self.failUnless(self._sub_uri in manifest)
1114 self.failUnless("<td>sub/baz.txt</td>" in manifest)
1115 d.addCallback(_got_html)
1117 # both t=status and unadorned GET should be identical
1118 d.addCallback(lambda res: self.GET("/operations/125"))
1119 d.addCallback(_got_html)
1121 d.addCallback(getman, "html")
1122 d.addCallback(_got_html)
1123 d.addCallback(getman, "text")
1124 def _got_text(manifest):
1125 self.failUnless("\nsub " + self._sub_uri + "\n" in manifest)
1126 self.failUnless("\nsub/baz.txt URI:CHK:" in manifest)
1127 d.addCallback(_got_text)
1128 d.addCallback(getman, "JSON")
1130 data = res["manifest"]
1132 for (path_list, cap) in data:
1133 got[tuple(path_list)] = cap
1134 self.failUnlessEqual(got[(u"sub",)], self._sub_uri)
1135 self.failUnless((u"sub",u"baz.txt") in got)
1136 self.failUnless("finished" in res)
1137 self.failUnless("origin" in res)
1138 self.failUnless("storage-index" in res)
1139 self.failUnless("verifycaps" in res)
1140 self.failUnless("stats" in res)
1141 d.addCallback(_got_json)
1144 def test_POST_DIRURL_deepsize_no_ophandle(self):
1145 d = self.shouldFail2(error.Error,
1146 "test_POST_DIRURL_deepsize_no_ophandle",
1148 "slow operation requires ophandle=",
1149 self.POST, self.public_url, t="start-deep-size")
1152 def test_POST_DIRURL_deepsize(self):
1153 d = self.POST(self.public_url + "/foo/?t=start-deep-size&ophandle=126",
1154 followRedirect=True)
1155 d.addCallback(self.wait_for_operation, "126")
1156 d.addCallback(self.get_operation_results, "126", "json")
1157 def _got_json(data):
1158 self.failUnlessEqual(data["finished"], True)
1160 self.failUnless(size > 1000)
1161 d.addCallback(_got_json)
1162 d.addCallback(self.get_operation_results, "126", "text")
1164 mo = re.search(r'^size: (\d+)$', res, re.M)
1165 self.failUnless(mo, res)
1166 size = int(mo.group(1))
1167 # with directories, the size varies.
1168 self.failUnless(size > 1000)
1169 d.addCallback(_got_text)
1172 def test_POST_DIRURL_deepstats_no_ophandle(self):
1173 d = self.shouldFail2(error.Error,
1174 "test_POST_DIRURL_deepstats_no_ophandle",
1176 "slow operation requires ophandle=",
1177 self.POST, self.public_url, t="start-deep-stats")
1180 def test_POST_DIRURL_deepstats(self):
1181 d = self.POST(self.public_url + "/foo/?t=start-deep-stats&ophandle=127",
1182 followRedirect=True)
1183 d.addCallback(self.wait_for_operation, "127")
1184 d.addCallback(self.get_operation_results, "127", "json")
1185 def _got_json(stats):
1186 expected = {"count-immutable-files": 3,
1187 "count-mutable-files": 0,
1188 "count-literal-files": 0,
1190 "count-directories": 3,
1191 "size-immutable-files": 57,
1192 "size-literal-files": 0,
1193 #"size-directories": 1912, # varies
1194 #"largest-directory": 1590,
1195 "largest-directory-children": 5,
1196 "largest-immutable-file": 19,
1198 for k,v in expected.iteritems():
1199 self.failUnlessEqual(stats[k], v,
1200 "stats[%s] was %s, not %s" %
1202 self.failUnlessEqual(stats["size-files-histogram"],
1204 d.addCallback(_got_json)
1207 def test_POST_DIRURL_stream_manifest(self):
1208 d = self.POST(self.public_url + "/foo/?t=stream-manifest")
1210 self.failUnless(res.endswith("\n"))
1211 units = [simplejson.loads(t) for t in res[:-1].split("\n")]
1212 self.failUnlessEqual(len(units), 7)
1213 self.failUnlessEqual(units[-1]["type"], "stats")
1215 self.failUnlessEqual(first["path"], [])
1216 self.failUnlessEqual(first["cap"], self._foo_uri)
1217 self.failUnlessEqual(first["type"], "directory")
1218 baz = [u for u in units[:-1] if u["cap"] == self._baz_file_uri][0]
1219 self.failUnlessEqual(baz["path"], ["sub", "baz.txt"])
1220 self.failIfEqual(baz["storage-index"], None)
1221 self.failIfEqual(baz["verifycap"], None)
1222 self.failIfEqual(baz["repaircap"], None)
1224 d.addCallback(_check)
1227 def test_GET_DIRURL_uri(self):
1228 d = self.GET(self.public_url + "/foo?t=uri")
1230 self.failUnlessEqual(res, self._foo_uri)
1231 d.addCallback(_check)
1234 def test_GET_DIRURL_readonly_uri(self):
1235 d = self.GET(self.public_url + "/foo?t=readonly-uri")
1237 self.failUnlessEqual(res, self._foo_readonly_uri)
1238 d.addCallback(_check)
1241 def test_PUT_NEWDIRURL(self):
1242 d = self.PUT(self.public_url + "/foo/newdir?t=mkdir", "")
1243 d.addCallback(lambda res:
1244 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1245 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1246 d.addCallback(self.failUnlessNodeKeysAre, [])
1249 def test_POST_NEWDIRURL(self):
1250 d = self.POST2(self.public_url + "/foo/newdir?t=mkdir", "")
1251 d.addCallback(lambda res:
1252 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1253 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1254 d.addCallback(self.failUnlessNodeKeysAre, [])
1257 def test_POST_NEWDIRURL_emptyname(self):
1258 # an empty pathname component (i.e. a double-slash) is disallowed
1259 d = self.shouldFail2(error.Error, "test_POST_NEWDIRURL_emptyname",
1261 "The webapi does not allow empty pathname components, i.e. a double slash",
1262 self.POST, self.public_url + "//?t=mkdir")
1265 def test_POST_NEWDIRURL_initial_children(self):
1266 (newkids, caps) = self._create_initial_children()
1267 d = self.POST2(self.public_url + "/foo/newdir?t=mkdir-with-children",
1268 simplejson.dumps(newkids))
1270 n = self.s.create_node_from_uri(uri.strip())
1271 d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
1272 d2.addCallback(lambda ign:
1273 self.failUnlessROChildURIIs(n, u"child-imm",
1275 d2.addCallback(lambda ign:
1276 self.failUnlessRWChildURIIs(n, u"child-mutable",
1278 d2.addCallback(lambda ign:
1279 self.failUnlessROChildURIIs(n, u"child-mutable-ro",
1281 d2.addCallback(lambda ign:
1282 self.failUnlessROChildURIIs(n, u"unknownchild-ro",
1283 caps['unknown_rocap']))
1284 d2.addCallback(lambda ign:
1285 self.failUnlessRWChildURIIs(n, u"unknownchild-rw",
1286 caps['unknown_rwcap']))
1287 d2.addCallback(lambda ign:
1288 self.failUnlessROChildURIIs(n, u"unknownchild-imm",
1289 caps['unknown_immcap']))
1290 d2.addCallback(lambda ign:
1291 self.failUnlessRWChildURIIs(n, u"dirchild",
1293 d2.addCallback(lambda ign:
1294 self.failUnlessROChildURIIs(n, u"dirchild-lit",
1296 d2.addCallback(lambda ign:
1297 self.failUnlessROChildURIIs(n, u"dirchild-empty",
1298 caps['emptydircap']))
1300 d.addCallback(_check)
1301 d.addCallback(lambda res:
1302 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1303 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1304 d.addCallback(self.failUnlessNodeKeysAre, newkids.keys())
1305 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1306 d.addCallback(self.failUnlessROChildURIIs, u"child-imm", caps['filecap1'])
1309 def test_POST_NEWDIRURL_immutable(self):
1310 (newkids, caps) = self._create_immutable_children()
1311 d = self.POST2(self.public_url + "/foo/newdir?t=mkdir-immutable",
1312 simplejson.dumps(newkids))
1314 n = self.s.create_node_from_uri(uri.strip())
1315 d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
1316 d2.addCallback(lambda ign:
1317 self.failUnlessROChildURIIs(n, u"child-imm",
1319 d2.addCallback(lambda ign:
1320 self.failUnlessROChildURIIs(n, u"unknownchild-imm",
1321 caps['unknown_immcap']))
1322 d2.addCallback(lambda ign:
1323 self.failUnlessROChildURIIs(n, u"dirchild-imm",
1325 d2.addCallback(lambda ign:
1326 self.failUnlessROChildURIIs(n, u"dirchild-lit",
1328 d2.addCallback(lambda ign:
1329 self.failUnlessROChildURIIs(n, u"dirchild-empty",
1330 caps['emptydircap']))
1332 d.addCallback(_check)
1333 d.addCallback(lambda res:
1334 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1335 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1336 d.addCallback(self.failUnlessNodeKeysAre, newkids.keys())
1337 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1338 d.addCallback(self.failUnlessROChildURIIs, u"child-imm", caps['filecap1'])
1339 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1340 d.addCallback(self.failUnlessROChildURIIs, u"unknownchild-imm", caps['unknown_immcap'])
1341 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1342 d.addCallback(self.failUnlessROChildURIIs, u"dirchild-imm", caps['immdircap'])
1343 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1344 d.addCallback(self.failUnlessROChildURIIs, u"dirchild-lit", caps['litdircap'])
1345 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1346 d.addCallback(self.failUnlessROChildURIIs, u"dirchild-empty", caps['emptydircap'])
1347 d.addErrback(self.explain_web_error)
1350 def test_POST_NEWDIRURL_immutable_bad(self):
1351 (newkids, caps) = self._create_initial_children()
1352 d = self.shouldFail2(error.Error, "test_POST_NEWDIRURL_immutable_bad",
1354 "needed to be immutable but was not",
1356 self.public_url + "/foo/newdir?t=mkdir-immutable",
1357 simplejson.dumps(newkids))
1360 def test_PUT_NEWDIRURL_exists(self):
1361 d = self.PUT(self.public_url + "/foo/sub?t=mkdir", "")
1362 d.addCallback(lambda res:
1363 self.failUnlessNodeHasChild(self._foo_node, u"sub"))
1364 d.addCallback(lambda res: self._foo_node.get(u"sub"))
1365 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
1368 def test_PUT_NEWDIRURL_blocked(self):
1369 d = self.shouldFail2(error.Error, "PUT_NEWDIRURL_blocked",
1370 "409 Conflict", "Unable to create directory 'bar.txt': a file was in the way",
1372 self.public_url + "/foo/bar.txt/sub?t=mkdir", "")
1373 d.addCallback(lambda res:
1374 self.failUnlessNodeHasChild(self._foo_node, u"sub"))
1375 d.addCallback(lambda res: self._foo_node.get(u"sub"))
1376 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
1379 def test_PUT_NEWDIRURL_mkdir_p(self):
1380 d = defer.succeed(None)
1381 d.addCallback(lambda res: self.POST(self.public_url + "/foo", t='mkdir', name='mkp'))
1382 d.addCallback(lambda res: self.failUnlessNodeHasChild(self._foo_node, u"mkp"))
1383 d.addCallback(lambda res: self._foo_node.get(u"mkp"))
1384 def mkdir_p(mkpnode):
1385 url = '/uri/%s?t=mkdir-p&path=/sub1/sub2' % urllib.quote(mkpnode.get_uri())
1387 def made_subsub(ssuri):
1388 d = self._foo_node.get_child_at_path(u"mkp/sub1/sub2")
1389 d.addCallback(lambda ssnode: self.failUnlessEqual(ssnode.get_uri(), ssuri))
1391 d.addCallback(lambda uri2: self.failUnlessEqual(uri2, ssuri))
1393 d.addCallback(made_subsub)
1395 d.addCallback(mkdir_p)
1398 def test_PUT_NEWDIRURL_mkdirs(self):
1399 d = self.PUT(self.public_url + "/foo/subdir/newdir?t=mkdir", "")
1400 d.addCallback(lambda res:
1401 self.failIfNodeHasChild(self._foo_node, u"newdir"))
1402 d.addCallback(lambda res:
1403 self.failUnlessNodeHasChild(self._foo_node, u"subdir"))
1404 d.addCallback(lambda res:
1405 self._foo_node.get_child_at_path(u"subdir/newdir"))
1406 d.addCallback(self.failUnlessNodeKeysAre, [])
1409 def test_DELETE_DIRURL(self):
1410 d = self.DELETE(self.public_url + "/foo")
1411 d.addCallback(lambda res:
1412 self.failIfNodeHasChild(self.public_root, u"foo"))
1415 def test_DELETE_DIRURL_missing(self):
1416 d = self.DELETE(self.public_url + "/foo/missing")
1417 d.addBoth(self.should404, "test_DELETE_DIRURL_missing")
1418 d.addCallback(lambda res:
1419 self.failUnlessNodeHasChild(self.public_root, u"foo"))
1422 def test_DELETE_DIRURL_missing2(self):
1423 d = self.DELETE(self.public_url + "/missing")
1424 d.addBoth(self.should404, "test_DELETE_DIRURL_missing2")
1427 def dump_root(self):
1429 w = webish.DirnodeWalkerMixin()
1430 def visitor(childpath, childnode, metadata):
1432 d = w.walk(self.public_root, visitor)
1435 def failUnlessNodeKeysAre(self, node, expected_keys):
1436 for k in expected_keys:
1437 assert isinstance(k, unicode)
1439 def _check(children):
1440 self.failUnlessEqual(sorted(children.keys()), sorted(expected_keys))
1441 d.addCallback(_check)
1443 def failUnlessNodeHasChild(self, node, name):
1444 assert isinstance(name, unicode)
1446 def _check(children):
1447 self.failUnless(name in children)
1448 d.addCallback(_check)
1450 def failIfNodeHasChild(self, node, name):
1451 assert isinstance(name, unicode)
1453 def _check(children):
1454 self.failIf(name in children)
1455 d.addCallback(_check)
1458 def failUnlessChildContentsAre(self, node, name, expected_contents):
1459 assert isinstance(name, unicode)
1460 d = node.get_child_at_path(name)
1461 d.addCallback(lambda node: download_to_data(node))
1462 def _check(contents):
1463 self.failUnlessEqual(contents, expected_contents)
1464 d.addCallback(_check)
1467 def failUnlessMutableChildContentsAre(self, node, name, expected_contents):
1468 assert isinstance(name, unicode)
1469 d = node.get_child_at_path(name)
1470 d.addCallback(lambda node: node.download_best_version())
1471 def _check(contents):
1472 self.failUnlessEqual(contents, expected_contents)
1473 d.addCallback(_check)
1476 def failUnlessRWChildURIIs(self, node, name, expected_uri):
1477 assert isinstance(name, unicode)
1478 d = node.get_child_at_path(name)
1480 self.failUnless(child.is_unknown() or not child.is_readonly())
1481 self.failUnlessEqual(child.get_uri(), expected_uri.strip())
1482 self.failUnlessEqual(child.get_write_uri(), expected_uri.strip())
1483 expected_ro_uri = self._make_readonly(expected_uri)
1485 self.failUnlessEqual(child.get_readonly_uri(), expected_ro_uri.strip())
1486 d.addCallback(_check)
1489 def failUnlessROChildURIIs(self, node, name, expected_uri):
1490 assert isinstance(name, unicode)
1491 d = node.get_child_at_path(name)
1493 self.failUnless(child.is_unknown() or child.is_readonly())
1494 self.failUnlessEqual(child.get_write_uri(), None)
1495 self.failUnlessEqual(child.get_uri(), expected_uri.strip())
1496 self.failUnlessEqual(child.get_readonly_uri(), expected_uri.strip())
1497 d.addCallback(_check)
1500 def failUnlessURIMatchesRWChild(self, got_uri, node, name):
1501 assert isinstance(name, unicode)
1502 d = node.get_child_at_path(name)
1504 self.failUnless(child.is_unknown() or not child.is_readonly())
1505 self.failUnlessEqual(child.get_uri(), got_uri.strip())
1506 self.failUnlessEqual(child.get_write_uri(), got_uri.strip())
1507 expected_ro_uri = self._make_readonly(got_uri)
1509 self.failUnlessEqual(child.get_readonly_uri(), expected_ro_uri.strip())
1510 d.addCallback(_check)
1513 def failUnlessURIMatchesROChild(self, got_uri, node, name):
1514 assert isinstance(name, unicode)
1515 d = node.get_child_at_path(name)
1517 self.failUnless(child.is_unknown() or child.is_readonly())
1518 self.failUnlessEqual(child.get_write_uri(), None)
1519 self.failUnlessEqual(got_uri.strip(), child.get_uri())
1520 self.failUnlessEqual(got_uri.strip(), child.get_readonly_uri())
1521 d.addCallback(_check)
1524 def failUnlessCHKURIHasContents(self, got_uri, contents):
1525 self.failUnless(FakeCHKFileNode.all_contents[got_uri] == contents)
1527 def test_POST_upload(self):
1528 d = self.POST(self.public_url + "/foo", t="upload",
1529 file=("new.txt", self.NEWFILE_CONTENTS))
1531 d.addCallback(self.failUnlessURIMatchesROChild, fn, u"new.txt")
1532 d.addCallback(lambda res:
1533 self.failUnlessChildContentsAre(fn, u"new.txt",
1534 self.NEWFILE_CONTENTS))
1537 def test_POST_upload_unicode(self):
1538 filename = u"n\u00e9wer.txt" # n e-acute w e r . t x t
1539 d = self.POST(self.public_url + "/foo", t="upload",
1540 file=(filename, self.NEWFILE_CONTENTS))
1542 d.addCallback(self.failUnlessURIMatchesROChild, fn, filename)
1543 d.addCallback(lambda res:
1544 self.failUnlessChildContentsAre(fn, filename,
1545 self.NEWFILE_CONTENTS))
1546 target_url = self.public_url + "/foo/" + filename.encode("utf-8")
1547 d.addCallback(lambda res: self.GET(target_url))
1548 d.addCallback(lambda contents: self.failUnlessEqual(contents,
1549 self.NEWFILE_CONTENTS,
1553 def test_POST_upload_unicode_named(self):
1554 filename = u"n\u00e9wer.txt" # n e-acute w e r . t x t
1555 d = self.POST(self.public_url + "/foo", t="upload",
1557 file=("overridden", self.NEWFILE_CONTENTS))
1559 d.addCallback(self.failUnlessURIMatchesROChild, fn, filename)
1560 d.addCallback(lambda res:
1561 self.failUnlessChildContentsAre(fn, filename,
1562 self.NEWFILE_CONTENTS))
1563 target_url = self.public_url + "/foo/" + filename.encode("utf-8")
1564 d.addCallback(lambda res: self.GET(target_url))
1565 d.addCallback(lambda contents: self.failUnlessEqual(contents,
1566 self.NEWFILE_CONTENTS,
1570 def test_POST_upload_no_link(self):
1571 d = self.POST("/uri", t="upload",
1572 file=("new.txt", self.NEWFILE_CONTENTS))
1573 def _check_upload_results(page):
1574 # this should be a page which describes the results of the upload
1575 # that just finished.
1576 self.failUnless("Upload Results:" in page)
1577 self.failUnless("URI:" in page)
1578 uri_re = re.compile("URI: <tt><span>(.*)</span>")
1579 mo = uri_re.search(page)
1580 self.failUnless(mo, page)
1581 new_uri = mo.group(1)
1583 d.addCallback(_check_upload_results)
1584 d.addCallback(self.failUnlessCHKURIHasContents, self.NEWFILE_CONTENTS)
1587 def test_POST_upload_no_link_whendone(self):
1588 d = self.POST("/uri", t="upload", when_done="/",
1589 file=("new.txt", self.NEWFILE_CONTENTS))
1590 d.addBoth(self.shouldRedirect, "/")
1593 def shouldRedirect2(self, which, checker, callable, *args, **kwargs):
1594 d = defer.maybeDeferred(callable, *args, **kwargs)
1596 if isinstance(res, failure.Failure):
1597 res.trap(error.PageRedirect)
1598 statuscode = res.value.status
1599 target = res.value.location
1600 return checker(statuscode, target)
1601 self.fail("%s: callable was supposed to redirect, not return '%s'"
1606 def test_POST_upload_no_link_whendone_results(self):
1607 def check(statuscode, target):
1608 self.failUnlessEqual(statuscode, str(http.FOUND))
1609 self.failUnless(target.startswith(self.webish_url), target)
1610 return client.getPage(target, method="GET")
1611 d = self.shouldRedirect2("test_POST_upload_no_link_whendone_results",
1613 self.POST, "/uri", t="upload",
1614 when_done="/uri/%(uri)s",
1615 file=("new.txt", self.NEWFILE_CONTENTS))
1616 d.addCallback(lambda res:
1617 self.failUnlessEqual(res, self.NEWFILE_CONTENTS))
1620 def test_POST_upload_no_link_mutable(self):
1621 d = self.POST("/uri", t="upload", mutable="true",
1622 file=("new.txt", self.NEWFILE_CONTENTS))
1623 def _check(filecap):
1624 filecap = filecap.strip()
1625 self.failUnless(filecap.startswith("URI:SSK:"), filecap)
1626 self.filecap = filecap
1627 u = uri.WriteableSSKFileURI.init_from_string(filecap)
1628 self.failUnless(u.get_storage_index() in FakeMutableFileNode.all_contents)
1629 n = self.s.create_node_from_uri(filecap)
1630 return n.download_best_version()
1631 d.addCallback(_check)
1633 self.failUnlessEqual(data, self.NEWFILE_CONTENTS)
1634 return self.GET("/uri/%s" % urllib.quote(self.filecap))
1635 d.addCallback(_check2)
1637 self.failUnlessEqual(data, self.NEWFILE_CONTENTS)
1638 return self.GET("/file/%s" % urllib.quote(self.filecap))
1639 d.addCallback(_check3)
1641 self.failUnlessEqual(data, self.NEWFILE_CONTENTS)
1642 d.addCallback(_check4)
1645 def test_POST_upload_no_link_mutable_toobig(self):
1646 d = self.shouldFail2(error.Error,
1647 "test_POST_upload_no_link_mutable_toobig",
1648 "413 Request Entity Too Large",
1649 "SDMF is limited to one segment, and 10001 > 10000",
1651 "/uri", t="upload", mutable="true",
1653 "b" * (self.s.MUTABLE_SIZELIMIT+1)) )
1656 def test_POST_upload_mutable(self):
1657 # this creates a mutable file
1658 d = self.POST(self.public_url + "/foo", t="upload", mutable="true",
1659 file=("new.txt", self.NEWFILE_CONTENTS))
1661 d.addCallback(self.failUnlessURIMatchesRWChild, fn, u"new.txt")
1662 d.addCallback(lambda res:
1663 self.failUnlessMutableChildContentsAre(fn, u"new.txt",
1664 self.NEWFILE_CONTENTS))
1665 d.addCallback(lambda res: self._foo_node.get(u"new.txt"))
1667 self.failUnless(IMutableFileNode.providedBy(newnode))
1668 self.failUnless(newnode.is_mutable())
1669 self.failIf(newnode.is_readonly())
1670 self._mutable_node = newnode
1671 self._mutable_uri = newnode.get_uri()
1674 # now upload it again and make sure that the URI doesn't change
1675 NEWER_CONTENTS = self.NEWFILE_CONTENTS + "newer\n"
1676 d.addCallback(lambda res:
1677 self.POST(self.public_url + "/foo", t="upload",
1679 file=("new.txt", NEWER_CONTENTS)))
1680 d.addCallback(self.failUnlessURIMatchesRWChild, fn, u"new.txt")
1681 d.addCallback(lambda res:
1682 self.failUnlessMutableChildContentsAre(fn, u"new.txt",
1684 d.addCallback(lambda res: self._foo_node.get(u"new.txt"))
1686 self.failUnless(IMutableFileNode.providedBy(newnode))
1687 self.failUnless(newnode.is_mutable())
1688 self.failIf(newnode.is_readonly())
1689 self.failUnlessEqual(self._mutable_uri, newnode.get_uri())
1690 d.addCallback(_got2)
1692 # upload a second time, using PUT instead of POST
1693 NEW2_CONTENTS = NEWER_CONTENTS + "overwrite with PUT\n"
1694 d.addCallback(lambda res:
1695 self.PUT(self.public_url + "/foo/new.txt", NEW2_CONTENTS))
1696 d.addCallback(self.failUnlessURIMatchesRWChild, fn, u"new.txt")
1697 d.addCallback(lambda res:
1698 self.failUnlessMutableChildContentsAre(fn, u"new.txt",
1701 # finally list the directory, since mutable files are displayed
1702 # slightly differently
1704 d.addCallback(lambda res:
1705 self.GET(self.public_url + "/foo/",
1706 followRedirect=True))
1707 def _check_page(res):
1708 # TODO: assert more about the contents
1709 self.failUnless("SSK" in res)
1711 d.addCallback(_check_page)
1713 d.addCallback(lambda res: self._foo_node.get(u"new.txt"))
1715 self.failUnless(IMutableFileNode.providedBy(newnode))
1716 self.failUnless(newnode.is_mutable())
1717 self.failIf(newnode.is_readonly())
1718 self.failUnlessEqual(self._mutable_uri, newnode.get_uri())
1719 d.addCallback(_got3)
1721 # look at the JSON form of the enclosing directory
1722 d.addCallback(lambda res:
1723 self.GET(self.public_url + "/foo/?t=json",
1724 followRedirect=True))
1725 def _check_page_json(res):
1726 parsed = simplejson.loads(res)
1727 self.failUnlessEqual(parsed[0], "dirnode")
1728 children = dict( [(unicode(name),value)
1730 in parsed[1]["children"].iteritems()] )
1731 self.failUnless("new.txt" in children)
1732 new_json = children["new.txt"]
1733 self.failUnlessEqual(new_json[0], "filenode")
1734 self.failUnless(new_json[1]["mutable"])
1735 self.failUnlessEqual(new_json[1]["rw_uri"], self._mutable_uri)
1736 ro_uri = unicode(self._mutable_node.get_readonly().to_string())
1737 self.failUnlessEqual(new_json[1]["ro_uri"], ro_uri)
1738 d.addCallback(_check_page_json)
1740 # and the JSON form of the file
1741 d.addCallback(lambda res:
1742 self.GET(self.public_url + "/foo/new.txt?t=json"))
1743 def _check_file_json(res):
1744 parsed = simplejson.loads(res)
1745 self.failUnlessEqual(parsed[0], "filenode")
1746 self.failUnless(parsed[1]["mutable"])
1747 self.failUnlessEqual(parsed[1]["rw_uri"], self._mutable_uri)
1748 ro_uri = unicode(self._mutable_node.get_readonly().to_string())
1749 self.failUnlessEqual(parsed[1]["ro_uri"], ro_uri)
1750 d.addCallback(_check_file_json)
1752 # and look at t=uri and t=readonly-uri
1753 d.addCallback(lambda res:
1754 self.GET(self.public_url + "/foo/new.txt?t=uri"))
1755 d.addCallback(lambda res: self.failUnlessEqual(res, self._mutable_uri))
1756 d.addCallback(lambda res:
1757 self.GET(self.public_url + "/foo/new.txt?t=readonly-uri"))
1758 def _check_ro_uri(res):
1759 ro_uri = unicode(self._mutable_node.get_readonly().to_string())
1760 self.failUnlessEqual(res, ro_uri)
1761 d.addCallback(_check_ro_uri)
1763 # make sure we can get to it from /uri/URI
1764 d.addCallback(lambda res:
1765 self.GET("/uri/%s" % urllib.quote(self._mutable_uri)))
1766 d.addCallback(lambda res:
1767 self.failUnlessEqual(res, NEW2_CONTENTS))
1769 # and that HEAD computes the size correctly
1770 d.addCallback(lambda res:
1771 self.HEAD(self.public_url + "/foo/new.txt",
1772 return_response=True))
1773 def _got_headers((res, status, headers)):
1774 self.failUnlessEqual(res, "")
1775 self.failUnlessEqual(headers["content-length"][0],
1776 str(len(NEW2_CONTENTS)))
1777 self.failUnlessEqual(headers["content-type"], ["text/plain"])
1778 d.addCallback(_got_headers)
1780 # make sure that size errors are displayed correctly for overwrite
1781 d.addCallback(lambda res:
1782 self.shouldFail2(error.Error,
1783 "test_POST_upload_mutable-toobig",
1784 "413 Request Entity Too Large",
1785 "SDMF is limited to one segment, and 10001 > 10000",
1787 self.public_url + "/foo", t="upload",
1790 "b" * (self.s.MUTABLE_SIZELIMIT+1)),
1793 d.addErrback(self.dump_error)
1796 def test_POST_upload_mutable_toobig(self):
1797 d = self.shouldFail2(error.Error,
1798 "test_POST_upload_mutable_toobig",
1799 "413 Request Entity Too Large",
1800 "SDMF is limited to one segment, and 10001 > 10000",
1802 self.public_url + "/foo",
1803 t="upload", mutable="true",
1805 "b" * (self.s.MUTABLE_SIZELIMIT+1)) )
1808 def dump_error(self, f):
1809 # if the web server returns an error code (like 400 Bad Request),
1810 # web.client.getPage puts the HTTP response body into the .response
1811 # attribute of the exception object that it gives back. It does not
1812 # appear in the Failure's repr(), so the ERROR that trial displays
1813 # will be rather terse and unhelpful. addErrback this method to the
1814 # end of your chain to get more information out of these errors.
1815 if f.check(error.Error):
1816 print "web.error.Error:"
1818 print f.value.response
1821 def test_POST_upload_replace(self):
1822 d = self.POST(self.public_url + "/foo", t="upload",
1823 file=("bar.txt", self.NEWFILE_CONTENTS))
1825 d.addCallback(self.failUnlessURIMatchesROChild, fn, u"bar.txt")
1826 d.addCallback(lambda res:
1827 self.failUnlessChildContentsAre(fn, u"bar.txt",
1828 self.NEWFILE_CONTENTS))
1831 def test_POST_upload_no_replace_ok(self):
1832 d = self.POST(self.public_url + "/foo?replace=false", t="upload",
1833 file=("new.txt", self.NEWFILE_CONTENTS))
1834 d.addCallback(lambda res: self.GET(self.public_url + "/foo/new.txt"))
1835 d.addCallback(lambda res: self.failUnlessEqual(res,
1836 self.NEWFILE_CONTENTS))
1839 def test_POST_upload_no_replace_queryarg(self):
1840 d = self.POST(self.public_url + "/foo?replace=false", t="upload",
1841 file=("bar.txt", self.NEWFILE_CONTENTS))
1842 d.addBoth(self.shouldFail, error.Error,
1843 "POST_upload_no_replace_queryarg",
1845 "There was already a child by that name, and you asked me "
1846 "to not replace it")
1847 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
1848 d.addCallback(self.failUnlessIsBarDotTxt)
1851 def test_POST_upload_no_replace_field(self):
1852 d = self.POST(self.public_url + "/foo", t="upload", replace="false",
1853 file=("bar.txt", self.NEWFILE_CONTENTS))
1854 d.addBoth(self.shouldFail, error.Error, "POST_upload_no_replace_field",
1856 "There was already a child by that name, and you asked me "
1857 "to not replace it")
1858 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
1859 d.addCallback(self.failUnlessIsBarDotTxt)
1862 def test_POST_upload_whendone(self):
1863 d = self.POST(self.public_url + "/foo", t="upload", when_done="/THERE",
1864 file=("new.txt", self.NEWFILE_CONTENTS))
1865 d.addBoth(self.shouldRedirect, "/THERE")
1867 d.addCallback(lambda res:
1868 self.failUnlessChildContentsAre(fn, u"new.txt",
1869 self.NEWFILE_CONTENTS))
1872 def test_POST_upload_named(self):
1874 d = self.POST(self.public_url + "/foo", t="upload",
1875 name="new.txt", file=self.NEWFILE_CONTENTS)
1876 d.addCallback(self.failUnlessURIMatchesROChild, fn, u"new.txt")
1877 d.addCallback(lambda res:
1878 self.failUnlessChildContentsAre(fn, u"new.txt",
1879 self.NEWFILE_CONTENTS))
1882 def test_POST_upload_named_badfilename(self):
1883 d = self.POST(self.public_url + "/foo", t="upload",
1884 name="slashes/are/bad.txt", file=self.NEWFILE_CONTENTS)
1885 d.addBoth(self.shouldFail, error.Error,
1886 "test_POST_upload_named_badfilename",
1888 "name= may not contain a slash",
1890 # make sure that nothing was added
1891 d.addCallback(lambda res:
1892 self.failUnlessNodeKeysAre(self._foo_node,
1893 [u"bar.txt", u"blockingfile",
1894 u"empty", u"n\u00fc.txt",
1898 def test_POST_FILEURL_check(self):
1899 bar_url = self.public_url + "/foo/bar.txt"
1900 d = self.POST(bar_url, t="check")
1902 self.failUnless("Healthy :" in res)
1903 d.addCallback(_check)
1904 redir_url = "http://allmydata.org/TARGET"
1905 def _check2(statuscode, target):
1906 self.failUnlessEqual(statuscode, str(http.FOUND))
1907 self.failUnlessEqual(target, redir_url)
1908 d.addCallback(lambda res:
1909 self.shouldRedirect2("test_POST_FILEURL_check",
1913 when_done=redir_url))
1914 d.addCallback(lambda res:
1915 self.POST(bar_url, t="check", return_to=redir_url))
1917 self.failUnless("Healthy :" in res)
1918 self.failUnless("Return to file" in res)
1919 self.failUnless(redir_url in res)
1920 d.addCallback(_check3)
1922 d.addCallback(lambda res:
1923 self.POST(bar_url, t="check", output="JSON"))
1924 def _check_json(res):
1925 data = simplejson.loads(res)
1926 self.failUnless("storage-index" in data)
1927 self.failUnless(data["results"]["healthy"])
1928 d.addCallback(_check_json)
1932 def test_POST_FILEURL_check_and_repair(self):
1933 bar_url = self.public_url + "/foo/bar.txt"
1934 d = self.POST(bar_url, t="check", repair="true")
1936 self.failUnless("Healthy :" in res)
1937 d.addCallback(_check)
1938 redir_url = "http://allmydata.org/TARGET"
1939 def _check2(statuscode, target):
1940 self.failUnlessEqual(statuscode, str(http.FOUND))
1941 self.failUnlessEqual(target, redir_url)
1942 d.addCallback(lambda res:
1943 self.shouldRedirect2("test_POST_FILEURL_check_and_repair",
1946 t="check", repair="true",
1947 when_done=redir_url))
1948 d.addCallback(lambda res:
1949 self.POST(bar_url, t="check", return_to=redir_url))
1951 self.failUnless("Healthy :" in res)
1952 self.failUnless("Return to file" in res)
1953 self.failUnless(redir_url in res)
1954 d.addCallback(_check3)
1957 def test_POST_DIRURL_check(self):
1958 foo_url = self.public_url + "/foo/"
1959 d = self.POST(foo_url, t="check")
1961 self.failUnless("Healthy :" in res, res)
1962 d.addCallback(_check)
1963 redir_url = "http://allmydata.org/TARGET"
1964 def _check2(statuscode, target):
1965 self.failUnlessEqual(statuscode, str(http.FOUND))
1966 self.failUnlessEqual(target, redir_url)
1967 d.addCallback(lambda res:
1968 self.shouldRedirect2("test_POST_DIRURL_check",
1972 when_done=redir_url))
1973 d.addCallback(lambda res:
1974 self.POST(foo_url, t="check", return_to=redir_url))
1976 self.failUnless("Healthy :" in res, res)
1977 self.failUnless("Return to file/directory" in res)
1978 self.failUnless(redir_url in res)
1979 d.addCallback(_check3)
1981 d.addCallback(lambda res:
1982 self.POST(foo_url, t="check", output="JSON"))
1983 def _check_json(res):
1984 data = simplejson.loads(res)
1985 self.failUnless("storage-index" in data)
1986 self.failUnless(data["results"]["healthy"])
1987 d.addCallback(_check_json)
1991 def test_POST_DIRURL_check_and_repair(self):
1992 foo_url = self.public_url + "/foo/"
1993 d = self.POST(foo_url, t="check", repair="true")
1995 self.failUnless("Healthy :" in res, res)
1996 d.addCallback(_check)
1997 redir_url = "http://allmydata.org/TARGET"
1998 def _check2(statuscode, target):
1999 self.failUnlessEqual(statuscode, str(http.FOUND))
2000 self.failUnlessEqual(target, redir_url)
2001 d.addCallback(lambda res:
2002 self.shouldRedirect2("test_POST_DIRURL_check_and_repair",
2005 t="check", repair="true",
2006 when_done=redir_url))
2007 d.addCallback(lambda res:
2008 self.POST(foo_url, t="check", return_to=redir_url))
2010 self.failUnless("Healthy :" in res)
2011 self.failUnless("Return to file/directory" in res)
2012 self.failUnless(redir_url in res)
2013 d.addCallback(_check3)
2016 def wait_for_operation(self, ignored, ophandle):
2017 url = "/operations/" + ophandle
2018 url += "?t=status&output=JSON"
2021 data = simplejson.loads(res)
2022 if not data["finished"]:
2023 d = self.stall(delay=1.0)
2024 d.addCallback(self.wait_for_operation, ophandle)
2030 def get_operation_results(self, ignored, ophandle, output=None):
2031 url = "/operations/" + ophandle
2034 url += "&output=" + output
2037 if output and output.lower() == "json":
2038 return simplejson.loads(res)
2043 def test_POST_DIRURL_deepcheck_no_ophandle(self):
2044 d = self.shouldFail2(error.Error,
2045 "test_POST_DIRURL_deepcheck_no_ophandle",
2047 "slow operation requires ophandle=",
2048 self.POST, self.public_url, t="start-deep-check")
2051 def test_POST_DIRURL_deepcheck(self):
2052 def _check_redirect(statuscode, target):
2053 self.failUnlessEqual(statuscode, str(http.FOUND))
2054 self.failUnless(target.endswith("/operations/123"))
2055 d = self.shouldRedirect2("test_POST_DIRURL_deepcheck", _check_redirect,
2056 self.POST, self.public_url,
2057 t="start-deep-check", ophandle="123")
2058 d.addCallback(self.wait_for_operation, "123")
2059 def _check_json(data):
2060 self.failUnlessEqual(data["finished"], True)
2061 self.failUnlessEqual(data["count-objects-checked"], 8)
2062 self.failUnlessEqual(data["count-objects-healthy"], 8)
2063 d.addCallback(_check_json)
2064 d.addCallback(self.get_operation_results, "123", "html")
2065 def _check_html(res):
2066 self.failUnless("Objects Checked: <span>8</span>" in res)
2067 self.failUnless("Objects Healthy: <span>8</span>" in res)
2068 d.addCallback(_check_html)
2070 d.addCallback(lambda res:
2071 self.GET("/operations/123/"))
2072 d.addCallback(_check_html) # should be the same as without the slash
2074 d.addCallback(lambda res:
2075 self.shouldFail2(error.Error, "one", "404 Not Found",
2076 "No detailed results for SI bogus",
2077 self.GET, "/operations/123/bogus"))
2079 foo_si = self._foo_node.get_storage_index()
2080 foo_si_s = base32.b2a(foo_si)
2081 d.addCallback(lambda res:
2082 self.GET("/operations/123/%s?output=JSON" % foo_si_s))
2083 def _check_foo_json(res):
2084 data = simplejson.loads(res)
2085 self.failUnlessEqual(data["storage-index"], foo_si_s)
2086 self.failUnless(data["results"]["healthy"])
2087 d.addCallback(_check_foo_json)
2090 def test_POST_DIRURL_deepcheck_and_repair(self):
2091 d = self.POST(self.public_url, t="start-deep-check", repair="true",
2092 ophandle="124", output="json", followRedirect=True)
2093 d.addCallback(self.wait_for_operation, "124")
2094 def _check_json(data):
2095 self.failUnlessEqual(data["finished"], True)
2096 self.failUnlessEqual(data["count-objects-checked"], 8)
2097 self.failUnlessEqual(data["count-objects-healthy-pre-repair"], 8)
2098 self.failUnlessEqual(data["count-objects-unhealthy-pre-repair"], 0)
2099 self.failUnlessEqual(data["count-corrupt-shares-pre-repair"], 0)
2100 self.failUnlessEqual(data["count-repairs-attempted"], 0)
2101 self.failUnlessEqual(data["count-repairs-successful"], 0)
2102 self.failUnlessEqual(data["count-repairs-unsuccessful"], 0)
2103 self.failUnlessEqual(data["count-objects-healthy-post-repair"], 8)
2104 self.failUnlessEqual(data["count-objects-unhealthy-post-repair"], 0)
2105 self.failUnlessEqual(data["count-corrupt-shares-post-repair"], 0)
2106 d.addCallback(_check_json)
2107 d.addCallback(self.get_operation_results, "124", "html")
2108 def _check_html(res):
2109 self.failUnless("Objects Checked: <span>8</span>" in res)
2111 self.failUnless("Objects Healthy (before repair): <span>8</span>" in res)
2112 self.failUnless("Objects Unhealthy (before repair): <span>0</span>" in res)
2113 self.failUnless("Corrupt Shares (before repair): <span>0</span>" in res)
2115 self.failUnless("Repairs Attempted: <span>0</span>" in res)
2116 self.failUnless("Repairs Successful: <span>0</span>" in res)
2117 self.failUnless("Repairs Unsuccessful: <span>0</span>" in res)
2119 self.failUnless("Objects Healthy (after repair): <span>8</span>" in res)
2120 self.failUnless("Objects Unhealthy (after repair): <span>0</span>" in res)
2121 self.failUnless("Corrupt Shares (after repair): <span>0</span>" in res)
2122 d.addCallback(_check_html)
2125 def test_POST_FILEURL_bad_t(self):
2126 d = self.shouldFail2(error.Error, "POST_bad_t", "400 Bad Request",
2127 "POST to file: bad t=bogus",
2128 self.POST, self.public_url + "/foo/bar.txt",
2132 def test_POST_mkdir(self): # return value?
2133 d = self.POST(self.public_url + "/foo", t="mkdir", name="newdir")
2134 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2135 d.addCallback(self.failUnlessNodeKeysAre, [])
2138 def test_POST_mkdir_initial_children(self):
2139 (newkids, caps) = self._create_initial_children()
2140 d = self.POST2(self.public_url +
2141 "/foo?t=mkdir-with-children&name=newdir",
2142 simplejson.dumps(newkids))
2143 d.addCallback(lambda res:
2144 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
2145 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2146 d.addCallback(self.failUnlessNodeKeysAre, newkids.keys())
2147 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2148 d.addCallback(self.failUnlessROChildURIIs, u"child-imm", caps['filecap1'])
2151 def test_POST_mkdir_immutable(self):
2152 (newkids, caps) = self._create_immutable_children()
2153 d = self.POST2(self.public_url +
2154 "/foo?t=mkdir-immutable&name=newdir",
2155 simplejson.dumps(newkids))
2156 d.addCallback(lambda res:
2157 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
2158 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2159 d.addCallback(self.failUnlessNodeKeysAre, newkids.keys())
2160 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2161 d.addCallback(self.failUnlessROChildURIIs, u"child-imm", caps['filecap1'])
2162 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2163 d.addCallback(self.failUnlessROChildURIIs, u"unknownchild-imm", caps['unknown_immcap'])
2164 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2165 d.addCallback(self.failUnlessROChildURIIs, u"dirchild-imm", caps['immdircap'])
2166 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2167 d.addCallback(self.failUnlessROChildURIIs, u"dirchild-lit", caps['litdircap'])
2168 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2169 d.addCallback(self.failUnlessROChildURIIs, u"dirchild-empty", caps['emptydircap'])
2172 def test_POST_mkdir_immutable_bad(self):
2173 (newkids, caps) = self._create_initial_children()
2174 d = self.shouldFail2(error.Error, "test_POST_mkdir_immutable_bad",
2176 "needed to be immutable but was not",
2179 "/foo?t=mkdir-immutable&name=newdir",
2180 simplejson.dumps(newkids))
2183 def test_POST_mkdir_2(self):
2184 d = self.POST(self.public_url + "/foo/newdir?t=mkdir", "")
2185 d.addCallback(lambda res:
2186 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
2187 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2188 d.addCallback(self.failUnlessNodeKeysAre, [])
2191 def test_POST_mkdirs_2(self):
2192 d = self.POST(self.public_url + "/foo/bardir/newdir?t=mkdir", "")
2193 d.addCallback(lambda res:
2194 self.failUnlessNodeHasChild(self._foo_node, u"bardir"))
2195 d.addCallback(lambda res: self._foo_node.get(u"bardir"))
2196 d.addCallback(lambda bardirnode: bardirnode.get(u"newdir"))
2197 d.addCallback(self.failUnlessNodeKeysAre, [])
2200 def test_POST_mkdir_no_parentdir_noredirect(self):
2201 d = self.POST("/uri?t=mkdir")
2202 def _after_mkdir(res):
2203 uri.DirectoryURI.init_from_string(res)
2204 d.addCallback(_after_mkdir)
2207 def test_POST_mkdir_no_parentdir_noredirect2(self):
2208 # make sure form-based arguments (as on the welcome page) still work
2209 d = self.POST("/uri", t="mkdir")
2210 def _after_mkdir(res):
2211 uri.DirectoryURI.init_from_string(res)
2212 d.addCallback(_after_mkdir)
2213 d.addErrback(self.explain_web_error)
2216 def test_POST_mkdir_no_parentdir_redirect(self):
2217 d = self.POST("/uri?t=mkdir&redirect_to_result=true")
2218 d.addBoth(self.shouldRedirect, None, statuscode='303')
2219 def _check_target(target):
2220 target = urllib.unquote(target)
2221 self.failUnless(target.startswith("uri/URI:DIR2:"), target)
2222 d.addCallback(_check_target)
2225 def test_POST_mkdir_no_parentdir_redirect2(self):
2226 d = self.POST("/uri", t="mkdir", redirect_to_result="true")
2227 d.addBoth(self.shouldRedirect, None, statuscode='303')
2228 def _check_target(target):
2229 target = urllib.unquote(target)
2230 self.failUnless(target.startswith("uri/URI:DIR2:"), target)
2231 d.addCallback(_check_target)
2232 d.addErrback(self.explain_web_error)
2235 def _make_readonly(self, u):
2236 ro_uri = uri.from_string(u).get_readonly()
2239 return ro_uri.to_string()
2241 def _create_initial_children(self):
2242 contents, n, filecap1 = self.makefile(12)
2243 md1 = {"metakey1": "metavalue1"}
2244 filecap2 = make_mutable_file_uri()
2245 node3 = self.s.create_node_from_uri(make_mutable_file_uri())
2246 filecap3 = node3.get_readonly_uri()
2247 node4 = self.s.create_node_from_uri(make_mutable_file_uri())
2248 dircap = DirectoryNode(node4, None, None).get_uri()
2249 litdircap = "URI:DIR2-LIT:ge3dumj2mewdcotyfqydulbshj5x2lbm"
2250 emptydircap = "URI:DIR2-LIT:"
2251 newkids = {u"child-imm": ["filenode", {"rw_uri": filecap1,
2252 "ro_uri": self._make_readonly(filecap1),
2253 "metadata": md1, }],
2254 u"child-mutable": ["filenode", {"rw_uri": filecap2,
2255 "ro_uri": self._make_readonly(filecap2)}],
2256 u"child-mutable-ro": ["filenode", {"ro_uri": filecap3}],
2257 u"unknownchild-rw": ["unknown", {"rw_uri": unknown_rwcap,
2258 "ro_uri": unknown_rocap}],
2259 u"unknownchild-ro": ["unknown", {"ro_uri": unknown_rocap}],
2260 u"unknownchild-imm": ["unknown", {"ro_uri": unknown_immcap}],
2261 u"dirchild": ["dirnode", {"rw_uri": dircap,
2262 "ro_uri": self._make_readonly(dircap)}],
2263 u"dirchild-lit": ["dirnode", {"ro_uri": litdircap}],
2264 u"dirchild-empty": ["dirnode", {"ro_uri": emptydircap}],
2266 return newkids, {'filecap1': filecap1,
2267 'filecap2': filecap2,
2268 'filecap3': filecap3,
2269 'unknown_rwcap': unknown_rwcap,
2270 'unknown_rocap': unknown_rocap,
2271 'unknown_immcap': unknown_immcap,
2273 'litdircap': litdircap,
2274 'emptydircap': emptydircap}
2276 def _create_immutable_children(self):
2277 contents, n, filecap1 = self.makefile(12)
2278 md1 = {"metakey1": "metavalue1"}
2279 tnode = create_chk_filenode("immutable directory contents\n"*10)
2280 dnode = DirectoryNode(tnode, None, None)
2281 assert not dnode.is_mutable()
2282 immdircap = dnode.get_uri()
2283 litdircap = "URI:DIR2-LIT:ge3dumj2mewdcotyfqydulbshj5x2lbm"
2284 emptydircap = "URI:DIR2-LIT:"
2285 newkids = {u"child-imm": ["filenode", {"ro_uri": filecap1,
2286 "metadata": md1, }],
2287 u"unknownchild-imm": ["unknown", {"ro_uri": unknown_immcap}],
2288 u"dirchild-imm": ["dirnode", {"ro_uri": immdircap}],
2289 u"dirchild-lit": ["dirnode", {"ro_uri": litdircap}],
2290 u"dirchild-empty": ["dirnode", {"ro_uri": emptydircap}],
2292 return newkids, {'filecap1': filecap1,
2293 'unknown_immcap': unknown_immcap,
2294 'immdircap': immdircap,
2295 'litdircap': litdircap,
2296 'emptydircap': emptydircap}
2298 def test_POST_mkdir_no_parentdir_initial_children(self):
2299 (newkids, caps) = self._create_initial_children()
2300 d = self.POST2("/uri?t=mkdir-with-children", simplejson.dumps(newkids))
2301 def _after_mkdir(res):
2302 self.failUnless(res.startswith("URI:DIR"), res)
2303 n = self.s.create_node_from_uri(res)
2304 d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
2305 d2.addCallback(lambda ign:
2306 self.failUnlessROChildURIIs(n, u"child-imm",
2308 d2.addCallback(lambda ign:
2309 self.failUnlessRWChildURIIs(n, u"child-mutable",
2311 d2.addCallback(lambda ign:
2312 self.failUnlessROChildURIIs(n, u"child-mutable-ro",
2314 d2.addCallback(lambda ign:
2315 self.failUnlessRWChildURIIs(n, u"unknownchild-rw",
2316 caps['unknown_rwcap']))
2317 d2.addCallback(lambda ign:
2318 self.failUnlessROChildURIIs(n, u"unknownchild-ro",
2319 caps['unknown_rocap']))
2320 d2.addCallback(lambda ign:
2321 self.failUnlessROChildURIIs(n, u"unknownchild-imm",
2322 caps['unknown_immcap']))
2323 d2.addCallback(lambda ign:
2324 self.failUnlessRWChildURIIs(n, u"dirchild",
2327 d.addCallback(_after_mkdir)
2330 def test_POST_mkdir_no_parentdir_unexpected_children(self):
2331 # the regular /uri?t=mkdir operation is specified to ignore its body.
2332 # Only t=mkdir-with-children pays attention to it.
2333 (newkids, caps) = self._create_initial_children()
2334 d = self.shouldHTTPError("POST t=mkdir unexpected children",
2336 "t=mkdir does not accept children=, "
2337 "try t=mkdir-with-children instead",
2338 self.POST2, "/uri?t=mkdir", # without children
2339 simplejson.dumps(newkids))
2342 def test_POST_noparent_bad(self):
2343 d = self.shouldHTTPError("POST /uri?t=bogus", 400, "Bad Request",
2344 "/uri accepts only PUT, PUT?t=mkdir, "
2345 "POST?t=upload, and POST?t=mkdir",
2346 self.POST, "/uri?t=bogus")
2349 def test_POST_mkdir_no_parentdir_immutable(self):
2350 (newkids, caps) = self._create_immutable_children()
2351 d = self.POST2("/uri?t=mkdir-immutable", simplejson.dumps(newkids))
2352 def _after_mkdir(res):
2353 self.failUnless(res.startswith("URI:DIR"), res)
2354 n = self.s.create_node_from_uri(res)
2355 d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
2356 d2.addCallback(lambda ign:
2357 self.failUnlessROChildURIIs(n, u"child-imm",
2359 d2.addCallback(lambda ign:
2360 self.failUnlessROChildURIIs(n, u"unknownchild-imm",
2361 caps['unknown_immcap']))
2362 d2.addCallback(lambda ign:
2363 self.failUnlessROChildURIIs(n, u"dirchild-imm",
2365 d2.addCallback(lambda ign:
2366 self.failUnlessROChildURIIs(n, u"dirchild-lit",
2368 d2.addCallback(lambda ign:
2369 self.failUnlessROChildURIIs(n, u"dirchild-empty",
2370 caps['emptydircap']))
2372 d.addCallback(_after_mkdir)
2375 def test_POST_mkdir_no_parentdir_immutable_bad(self):
2376 (newkids, caps) = self._create_initial_children()
2377 d = self.shouldFail2(error.Error,
2378 "test_POST_mkdir_no_parentdir_immutable_bad",
2380 "needed to be immutable but was not",
2382 "/uri?t=mkdir-immutable",
2383 simplejson.dumps(newkids))
2386 def test_welcome_page_mkdir_button(self):
2387 # Fetch the welcome page.
2389 def _after_get_welcome_page(res):
2390 MKDIR_BUTTON_RE = re.compile(
2391 '<form action="([^"]*)" method="post".*?'
2392 '<input type="hidden" name="t" value="([^"]*)" />'
2393 '<input type="hidden" name="([^"]*)" value="([^"]*)" />'
2394 '<input type="submit" value="Create a directory" />',
2396 mo = MKDIR_BUTTON_RE.search(res)
2397 formaction = mo.group(1)
2399 formaname = mo.group(3)
2400 formavalue = mo.group(4)
2401 return (formaction, formt, formaname, formavalue)
2402 d.addCallback(_after_get_welcome_page)
2403 def _after_parse_form(res):
2404 (formaction, formt, formaname, formavalue) = res
2405 return self.POST("/%s?t=%s&%s=%s" % (formaction, formt, formaname, formavalue))
2406 d.addCallback(_after_parse_form)
2407 d.addBoth(self.shouldRedirect, None, statuscode='303')
2410 def test_POST_mkdir_replace(self): # return value?
2411 d = self.POST(self.public_url + "/foo", t="mkdir", name="sub")
2412 d.addCallback(lambda res: self._foo_node.get(u"sub"))
2413 d.addCallback(self.failUnlessNodeKeysAre, [])
2416 def test_POST_mkdir_no_replace_queryarg(self): # return value?
2417 d = self.POST(self.public_url + "/foo?replace=false", t="mkdir", name="sub")
2418 d.addBoth(self.shouldFail, error.Error,
2419 "POST_mkdir_no_replace_queryarg",
2421 "There was already a child by that name, and you asked me "
2422 "to not replace it")
2423 d.addCallback(lambda res: self._foo_node.get(u"sub"))
2424 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
2427 def test_POST_mkdir_no_replace_field(self): # return value?
2428 d = self.POST(self.public_url + "/foo", t="mkdir", name="sub",
2430 d.addBoth(self.shouldFail, error.Error, "POST_mkdir_no_replace_field",
2432 "There was already a child by that name, and you asked me "
2433 "to not replace it")
2434 d.addCallback(lambda res: self._foo_node.get(u"sub"))
2435 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
2438 def test_POST_mkdir_whendone_field(self):
2439 d = self.POST(self.public_url + "/foo",
2440 t="mkdir", name="newdir", when_done="/THERE")
2441 d.addBoth(self.shouldRedirect, "/THERE")
2442 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2443 d.addCallback(self.failUnlessNodeKeysAre, [])
2446 def test_POST_mkdir_whendone_queryarg(self):
2447 d = self.POST(self.public_url + "/foo?when_done=/THERE",
2448 t="mkdir", name="newdir")
2449 d.addBoth(self.shouldRedirect, "/THERE")
2450 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2451 d.addCallback(self.failUnlessNodeKeysAre, [])
2454 def test_POST_bad_t(self):
2455 d = self.shouldFail2(error.Error, "POST_bad_t", "400 Bad Request",
2456 "POST to a directory with bad t=BOGUS",
2457 self.POST, self.public_url + "/foo", t="BOGUS")
2460 def test_POST_set_children(self, command_name="set_children"):
2461 contents9, n9, newuri9 = self.makefile(9)
2462 contents10, n10, newuri10 = self.makefile(10)
2463 contents11, n11, newuri11 = self.makefile(11)
2466 "atomic_added_1": [ "filenode", { "rw_uri": "%s",
2469 "ctime": 1002777696.7564139,
2470 "mtime": 1002777696.7564139
2473 "atomic_added_2": [ "filenode", { "rw_uri": "%s",
2476 "ctime": 1002777696.7564139,
2477 "mtime": 1002777696.7564139
2480 "atomic_added_3": [ "filenode", { "rw_uri": "%s",
2483 "ctime": 1002777696.7564139,
2484 "mtime": 1002777696.7564139
2487 }""" % (newuri9, newuri10, newuri11)
2489 url = self.webish_url + self.public_url + "/foo" + "?t=" + command_name
2491 d = client.getPage(url, method="POST", postdata=reqbody)
2493 self.failUnlessURIMatchesROChild(newuri9, self._foo_node, u"atomic_added_1")
2494 self.failUnlessURIMatchesROChild(newuri10, self._foo_node, u"atomic_added_2")
2495 self.failUnlessURIMatchesROChild(newuri11, self._foo_node, u"atomic_added_3")
2497 d.addCallback(_then)
2498 d.addErrback(self.dump_error)
2501 def test_POST_set_children_with_hyphen(self):
2502 return self.test_POST_set_children(command_name="set-children")
2504 def test_POST_link_uri(self):
2505 contents, n, newuri = self.makefile(8)
2506 d = self.POST(self.public_url + "/foo", t="uri", name="new.txt", uri=newuri)
2507 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"new.txt")
2508 d.addCallback(lambda res:
2509 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
2513 def test_POST_link_uri_replace(self):
2514 contents, n, newuri = self.makefile(8)
2515 d = self.POST(self.public_url + "/foo", t="uri", name="bar.txt", uri=newuri)
2516 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"bar.txt")
2517 d.addCallback(lambda res:
2518 self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
2522 def test_POST_link_uri_unknown_bad(self):
2523 d = self.POST(self.public_url + "/foo", t="uri", name="future.txt", uri=unknown_rwcap)
2524 d.addBoth(self.shouldFail, error.Error,
2525 "POST_link_uri_unknown_bad",
2527 "unknown cap in a write slot")
2530 def test_POST_link_uri_unknown_ro_good(self):
2531 d = self.POST(self.public_url + "/foo", t="uri", name="future-ro.txt", uri=unknown_rocap)
2532 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"future-ro.txt")
2535 def test_POST_link_uri_unknown_imm_good(self):
2536 d = self.POST(self.public_url + "/foo", t="uri", name="future-imm.txt", uri=unknown_immcap)
2537 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"future-imm.txt")
2540 def test_POST_link_uri_no_replace_queryarg(self):
2541 contents, n, newuri = self.makefile(8)
2542 d = self.POST(self.public_url + "/foo?replace=false", t="uri",
2543 name="bar.txt", uri=newuri)
2544 d.addBoth(self.shouldFail, error.Error,
2545 "POST_link_uri_no_replace_queryarg",
2547 "There was already a child by that name, and you asked me "
2548 "to not replace it")
2549 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
2550 d.addCallback(self.failUnlessIsBarDotTxt)
2553 def test_POST_link_uri_no_replace_field(self):
2554 contents, n, newuri = self.makefile(8)
2555 d = self.POST(self.public_url + "/foo", t="uri", replace="false",
2556 name="bar.txt", uri=newuri)
2557 d.addBoth(self.shouldFail, error.Error,
2558 "POST_link_uri_no_replace_field",
2560 "There was already a child by that name, and you asked me "
2561 "to not replace it")
2562 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
2563 d.addCallback(self.failUnlessIsBarDotTxt)
2566 def test_POST_delete(self):
2567 d = self.POST(self.public_url + "/foo", t="delete", name="bar.txt")
2568 d.addCallback(lambda res: self._foo_node.list())
2569 def _check(children):
2570 self.failIf(u"bar.txt" in children)
2571 d.addCallback(_check)
2574 def test_POST_rename_file(self):
2575 d = self.POST(self.public_url + "/foo", t="rename",
2576 from_name="bar.txt", to_name='wibble.txt')
2577 d.addCallback(lambda res:
2578 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
2579 d.addCallback(lambda res:
2580 self.failUnlessNodeHasChild(self._foo_node, u"wibble.txt"))
2581 d.addCallback(lambda res: self.GET(self.public_url + "/foo/wibble.txt"))
2582 d.addCallback(self.failUnlessIsBarDotTxt)
2583 d.addCallback(lambda res: self.GET(self.public_url + "/foo/wibble.txt?t=json"))
2584 d.addCallback(self.failUnlessIsBarJSON)
2587 def test_POST_rename_file_redundant(self):
2588 d = self.POST(self.public_url + "/foo", t="rename",
2589 from_name="bar.txt", to_name='bar.txt')
2590 d.addCallback(lambda res:
2591 self.failUnlessNodeHasChild(self._foo_node, u"bar.txt"))
2592 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
2593 d.addCallback(self.failUnlessIsBarDotTxt)
2594 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt?t=json"))
2595 d.addCallback(self.failUnlessIsBarJSON)
2598 def test_POST_rename_file_replace(self):
2599 # rename a file and replace a directory with it
2600 d = self.POST(self.public_url + "/foo", t="rename",
2601 from_name="bar.txt", to_name='empty')
2602 d.addCallback(lambda res:
2603 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
2604 d.addCallback(lambda res:
2605 self.failUnlessNodeHasChild(self._foo_node, u"empty"))
2606 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty"))
2607 d.addCallback(self.failUnlessIsBarDotTxt)
2608 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
2609 d.addCallback(self.failUnlessIsBarJSON)
2612 def test_POST_rename_file_no_replace_queryarg(self):
2613 # rename a file and replace a directory with it
2614 d = self.POST(self.public_url + "/foo?replace=false", t="rename",
2615 from_name="bar.txt", to_name='empty')
2616 d.addBoth(self.shouldFail, error.Error,
2617 "POST_rename_file_no_replace_queryarg",
2619 "There was already a child by that name, and you asked me "
2620 "to not replace it")
2621 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
2622 d.addCallback(self.failUnlessIsEmptyJSON)
2625 def test_POST_rename_file_no_replace_field(self):
2626 # rename a file and replace a directory with it
2627 d = self.POST(self.public_url + "/foo", t="rename", replace="false",
2628 from_name="bar.txt", to_name='empty')
2629 d.addBoth(self.shouldFail, error.Error,
2630 "POST_rename_file_no_replace_field",
2632 "There was already a child by that name, and you asked me "
2633 "to not replace it")
2634 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
2635 d.addCallback(self.failUnlessIsEmptyJSON)
2638 def failUnlessIsEmptyJSON(self, res):
2639 data = simplejson.loads(res)
2640 self.failUnlessEqual(data[0], "dirnode", data)
2641 self.failUnlessEqual(len(data[1]["children"]), 0)
2643 def test_POST_rename_file_slash_fail(self):
2644 d = self.POST(self.public_url + "/foo", t="rename",
2645 from_name="bar.txt", to_name='kirk/spock.txt')
2646 d.addBoth(self.shouldFail, error.Error,
2647 "test_POST_rename_file_slash_fail",
2649 "to_name= may not contain a slash",
2651 d.addCallback(lambda res:
2652 self.failUnlessNodeHasChild(self._foo_node, u"bar.txt"))
2655 def test_POST_rename_dir(self):
2656 d = self.POST(self.public_url, t="rename",
2657 from_name="foo", to_name='plunk')
2658 d.addCallback(lambda res:
2659 self.failIfNodeHasChild(self.public_root, u"foo"))
2660 d.addCallback(lambda res:
2661 self.failUnlessNodeHasChild(self.public_root, u"plunk"))
2662 d.addCallback(lambda res: self.GET(self.public_url + "/plunk?t=json"))
2663 d.addCallback(self.failUnlessIsFooJSON)
2666 def shouldRedirect(self, res, target=None, statuscode=None, which=""):
2667 """ If target is not None then the redirection has to go to target. If
2668 statuscode is not None then the redirection has to be accomplished with
2669 that HTTP status code."""
2670 if not isinstance(res, failure.Failure):
2671 to_where = (target is None) and "somewhere" or ("to " + target)
2672 self.fail("%s: we were expecting to get redirected %s, not get an"
2673 " actual page: %s" % (which, to_where, res))
2674 res.trap(error.PageRedirect)
2675 if statuscode is not None:
2676 self.failUnlessEqual(res.value.status, statuscode,
2677 "%s: not a redirect" % which)
2678 if target is not None:
2679 # the PageRedirect does not seem to capture the uri= query arg
2680 # properly, so we can't check for it.
2681 realtarget = self.webish_url + target
2682 self.failUnlessEqual(res.value.location, realtarget,
2683 "%s: wrong target" % which)
2684 return res.value.location
2686 def test_GET_URI_form(self):
2687 base = "/uri?uri=%s" % self._bar_txt_uri
2688 # this is supposed to give us a redirect to /uri/$URI, plus arguments
2689 targetbase = "/uri/%s" % urllib.quote(self._bar_txt_uri)
2691 d.addBoth(self.shouldRedirect, targetbase)
2692 d.addCallback(lambda res: self.GET(base+"&filename=bar.txt"))
2693 d.addBoth(self.shouldRedirect, targetbase+"?filename=bar.txt")
2694 d.addCallback(lambda res: self.GET(base+"&t=json"))
2695 d.addBoth(self.shouldRedirect, targetbase+"?t=json")
2696 d.addCallback(self.log, "about to get file by uri")
2697 d.addCallback(lambda res: self.GET(base, followRedirect=True))
2698 d.addCallback(self.failUnlessIsBarDotTxt)
2699 d.addCallback(self.log, "got file by uri, about to get dir by uri")
2700 d.addCallback(lambda res: self.GET("/uri?uri=%s&t=json" % self._foo_uri,
2701 followRedirect=True))
2702 d.addCallback(self.failUnlessIsFooJSON)
2703 d.addCallback(self.log, "got dir by uri")
2707 def test_GET_URI_form_bad(self):
2708 d = self.shouldFail2(error.Error, "test_GET_URI_form_bad",
2709 "400 Bad Request", "GET /uri requires uri=",
2713 def test_GET_rename_form(self):
2714 d = self.GET(self.public_url + "/foo?t=rename-form&name=bar.txt",
2715 followRedirect=True)
2717 self.failUnless('name="when_done" value="."' in res, res)
2718 self.failUnless(re.search(r'name="from_name" value="bar\.txt"', res))
2719 d.addCallback(_check)
2722 def log(self, res, msg):
2723 #print "MSG: %s RES: %s" % (msg, res)
2727 def test_GET_URI_URL(self):
2728 base = "/uri/%s" % self._bar_txt_uri
2730 d.addCallback(self.failUnlessIsBarDotTxt)
2731 d.addCallback(lambda res: self.GET(base+"?filename=bar.txt"))
2732 d.addCallback(self.failUnlessIsBarDotTxt)
2733 d.addCallback(lambda res: self.GET(base+"?filename=bar.txt&save=true"))
2734 d.addCallback(self.failUnlessIsBarDotTxt)
2737 def test_GET_URI_URL_dir(self):
2738 base = "/uri/%s?t=json" % self._foo_uri
2740 d.addCallback(self.failUnlessIsFooJSON)
2743 def test_GET_URI_URL_missing(self):
2744 base = "/uri/%s" % self._bad_file_uri
2745 d = self.shouldHTTPError("test_GET_URI_URL_missing",
2746 http.GONE, None, "NotEnoughSharesError",
2748 # TODO: how can we exercise both sides of WebDownloadTarget.fail
2749 # here? we must arrange for a download to fail after target.open()
2750 # has been called, and then inspect the response to see that it is
2751 # shorter than we expected.
2754 def test_PUT_DIRURL_uri(self):
2755 d = self.s.create_dirnode()
2757 new_uri = dn.get_uri()
2758 # replace /foo with a new (empty) directory
2759 d = self.PUT(self.public_url + "/foo?t=uri", new_uri)
2760 d.addCallback(lambda res:
2761 self.failUnlessEqual(res.strip(), new_uri))
2762 d.addCallback(lambda res:
2763 self.failUnlessRWChildURIIs(self.public_root,
2767 d.addCallback(_made_dir)
2770 def test_PUT_DIRURL_uri_noreplace(self):
2771 d = self.s.create_dirnode()
2773 new_uri = dn.get_uri()
2774 # replace /foo with a new (empty) directory, but ask that
2775 # replace=false, so it should fail
2776 d = self.shouldFail2(error.Error, "test_PUT_DIRURL_uri_noreplace",
2777 "409 Conflict", "There was already a child by that name, and you asked me to not replace it",
2779 self.public_url + "/foo?t=uri&replace=false",
2781 d.addCallback(lambda res:
2782 self.failUnlessRWChildURIIs(self.public_root,
2786 d.addCallback(_made_dir)
2789 def test_PUT_DIRURL_bad_t(self):
2790 d = self.shouldFail2(error.Error, "test_PUT_DIRURL_bad_t",
2791 "400 Bad Request", "PUT to a directory",
2792 self.PUT, self.public_url + "/foo?t=BOGUS", "")
2793 d.addCallback(lambda res:
2794 self.failUnlessRWChildURIIs(self.public_root,
2799 def test_PUT_NEWFILEURL_uri(self):
2800 contents, n, new_uri = self.makefile(8)
2801 d = self.PUT(self.public_url + "/foo/new.txt?t=uri", new_uri)
2802 d.addCallback(lambda res: self.failUnlessEqual(res.strip(), new_uri))
2803 d.addCallback(lambda res:
2804 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
2808 def test_PUT_NEWFILEURL_uri_replace(self):
2809 contents, n, new_uri = self.makefile(8)
2810 d = self.PUT(self.public_url + "/foo/bar.txt?t=uri", new_uri)
2811 d.addCallback(lambda res: self.failUnlessEqual(res.strip(), new_uri))
2812 d.addCallback(lambda res:
2813 self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
2817 def test_PUT_NEWFILEURL_uri_no_replace(self):
2818 contents, n, new_uri = self.makefile(8)
2819 d = self.PUT(self.public_url + "/foo/bar.txt?t=uri&replace=false", new_uri)
2820 d.addBoth(self.shouldFail, error.Error, "PUT_NEWFILEURL_uri_no_replace",
2822 "There was already a child by that name, and you asked me "
2823 "to not replace it")
2826 def test_PUT_NEWFILEURL_uri_unknown_bad(self):
2827 d = self.PUT(self.public_url + "/foo/put-future.txt?t=uri", unknown_rwcap)
2828 d.addBoth(self.shouldFail, error.Error,
2829 "POST_put_uri_unknown_bad",
2831 "unknown cap in a write slot")
2834 def test_PUT_NEWFILEURL_uri_unknown_ro_good(self):
2835 d = self.PUT(self.public_url + "/foo/put-future-ro.txt?t=uri", unknown_rocap)
2836 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node,
2837 u"put-future-ro.txt")
2840 def test_PUT_NEWFILEURL_uri_unknown_imm_good(self):
2841 d = self.PUT(self.public_url + "/foo/put-future-imm.txt?t=uri", unknown_immcap)
2842 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node,
2843 u"put-future-imm.txt")
2846 def test_PUT_NEWFILE_URI(self):
2847 file_contents = "New file contents here\n"
2848 d = self.PUT("/uri", file_contents)
2850 assert isinstance(uri, str), uri
2851 self.failUnless(uri in FakeCHKFileNode.all_contents)
2852 self.failUnlessEqual(FakeCHKFileNode.all_contents[uri],
2854 return self.GET("/uri/%s" % uri)
2855 d.addCallback(_check)
2857 self.failUnlessEqual(res, file_contents)
2858 d.addCallback(_check2)
2861 def test_PUT_NEWFILE_URI_not_mutable(self):
2862 file_contents = "New file contents here\n"
2863 d = self.PUT("/uri?mutable=false", file_contents)
2865 assert isinstance(uri, str), uri
2866 self.failUnless(uri in FakeCHKFileNode.all_contents)
2867 self.failUnlessEqual(FakeCHKFileNode.all_contents[uri],
2869 return self.GET("/uri/%s" % uri)
2870 d.addCallback(_check)
2872 self.failUnlessEqual(res, file_contents)
2873 d.addCallback(_check2)
2876 def test_PUT_NEWFILE_URI_only_PUT(self):
2877 d = self.PUT("/uri?t=bogus", "")
2878 d.addBoth(self.shouldFail, error.Error,
2879 "PUT_NEWFILE_URI_only_PUT",
2881 "/uri accepts only PUT, PUT?t=mkdir, POST?t=upload, and POST?t=mkdir")
2884 def test_PUT_NEWFILE_URI_mutable(self):
2885 file_contents = "New file contents here\n"
2886 d = self.PUT("/uri?mutable=true", file_contents)
2887 def _check1(filecap):
2888 filecap = filecap.strip()
2889 self.failUnless(filecap.startswith("URI:SSK:"), filecap)
2890 self.filecap = filecap
2891 u = uri.WriteableSSKFileURI.init_from_string(filecap)
2892 self.failUnless(u.get_storage_index() in FakeMutableFileNode.all_contents)
2893 n = self.s.create_node_from_uri(filecap)
2894 return n.download_best_version()
2895 d.addCallback(_check1)
2897 self.failUnlessEqual(data, file_contents)
2898 return self.GET("/uri/%s" % urllib.quote(self.filecap))
2899 d.addCallback(_check2)
2901 self.failUnlessEqual(res, file_contents)
2902 d.addCallback(_check3)
2905 def test_PUT_mkdir(self):
2906 d = self.PUT("/uri?t=mkdir", "")
2908 n = self.s.create_node_from_uri(uri.strip())
2909 d2 = self.failUnlessNodeKeysAre(n, [])
2910 d2.addCallback(lambda res:
2911 self.GET("/uri/%s?t=json" % uri))
2913 d.addCallback(_check)
2914 d.addCallback(self.failUnlessIsEmptyJSON)
2917 def test_POST_check(self):
2918 d = self.POST(self.public_url + "/foo", t="check", name="bar.txt")
2920 # this returns a string form of the results, which are probably
2921 # None since we're using fake filenodes.
2922 # TODO: verify that the check actually happened, by changing
2923 # FakeCHKFileNode to count how many times .check() has been
2926 d.addCallback(_done)
2929 def test_bad_method(self):
2930 url = self.webish_url + self.public_url + "/foo/bar.txt"
2931 d = self.shouldHTTPError("test_bad_method",
2932 501, "Not Implemented",
2933 "I don't know how to treat a BOGUS request.",
2934 client.getPage, url, method="BOGUS")
2937 def test_short_url(self):
2938 url = self.webish_url + "/uri"
2939 d = self.shouldHTTPError("test_short_url", 501, "Not Implemented",
2940 "I don't know how to treat a DELETE request.",
2941 client.getPage, url, method="DELETE")
2944 def test_ophandle_bad(self):
2945 url = self.webish_url + "/operations/bogus?t=status"
2946 d = self.shouldHTTPError("test_ophandle_bad", 404, "404 Not Found",
2947 "unknown/expired handle 'bogus'",
2948 client.getPage, url)
2951 def test_ophandle_cancel(self):
2952 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=128",
2953 followRedirect=True)
2954 d.addCallback(lambda ignored:
2955 self.GET("/operations/128?t=status&output=JSON"))
2957 data = simplejson.loads(res)
2958 self.failUnless("finished" in data, res)
2959 monitor = self.ws.root.child_operations.handles["128"][0]
2960 d = self.POST("/operations/128?t=cancel&output=JSON")
2962 data = simplejson.loads(res)
2963 self.failUnless("finished" in data, res)
2964 # t=cancel causes the handle to be forgotten
2965 self.failUnless(monitor.is_cancelled())
2966 d.addCallback(_check2)
2968 d.addCallback(_check1)
2969 d.addCallback(lambda ignored:
2970 self.shouldHTTPError("test_ophandle_cancel",
2971 404, "404 Not Found",
2972 "unknown/expired handle '128'",
2974 "/operations/128?t=status&output=JSON"))
2977 def test_ophandle_retainfor(self):
2978 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=129&retain-for=60",
2979 followRedirect=True)
2980 d.addCallback(lambda ignored:
2981 self.GET("/operations/129?t=status&output=JSON&retain-for=0"))
2983 data = simplejson.loads(res)
2984 self.failUnless("finished" in data, res)
2985 d.addCallback(_check1)
2986 # the retain-for=0 will cause the handle to be expired very soon
2987 d.addCallback(lambda ign:
2988 self.clock.advance(2.0))
2989 d.addCallback(lambda ignored:
2990 self.shouldHTTPError("test_ophandle_retainfor",
2991 404, "404 Not Found",
2992 "unknown/expired handle '129'",
2994 "/operations/129?t=status&output=JSON"))
2997 def test_ophandle_release_after_complete(self):
2998 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=130",
2999 followRedirect=True)
3000 d.addCallback(self.wait_for_operation, "130")
3001 d.addCallback(lambda ignored:
3002 self.GET("/operations/130?t=status&output=JSON&release-after-complete=true"))
3003 # the release-after-complete=true will cause the handle to be expired
3004 d.addCallback(lambda ignored:
3005 self.shouldHTTPError("test_ophandle_release_after_complete",
3006 404, "404 Not Found",
3007 "unknown/expired handle '130'",
3009 "/operations/130?t=status&output=JSON"))
3012 def test_uncollected_ophandle_expiration(self):
3013 # uncollected ophandles should expire after 4 days
3014 def _make_uncollected_ophandle(ophandle):
3015 d = self.POST(self.public_url +
3016 "/foo/?t=start-manifest&ophandle=%d" % ophandle,
3017 followRedirect=False)
3018 # When we start the operation, the webapi server will want
3019 # to redirect us to the page for the ophandle, so we get
3020 # confirmation that the operation has started. If the
3021 # manifest operation has finished by the time we get there,
3022 # following that redirect (by setting followRedirect=True
3023 # above) has the side effect of collecting the ophandle that
3024 # we've just created, which means that we can't use the
3025 # ophandle to test the uncollected timeout anymore. So,
3026 # instead, catch the 302 here and don't follow it.
3027 d.addBoth(self.should302, "uncollected_ophandle_creation")
3029 # Create an ophandle, don't collect it, then advance the clock by
3030 # 4 days - 1 second and make sure that the ophandle is still there.
3031 d = _make_uncollected_ophandle(131)
3032 d.addCallback(lambda ign:
3033 self.clock.advance((96*60*60) - 1)) # 96 hours = 4 days
3034 d.addCallback(lambda ign:
3035 self.GET("/operations/131?t=status&output=JSON"))
3037 data = simplejson.loads(res)
3038 self.failUnless("finished" in data, res)
3039 d.addCallback(_check1)
3040 # Create an ophandle, don't collect it, then try to collect it
3041 # after 4 days. It should be gone.
3042 d.addCallback(lambda ign:
3043 _make_uncollected_ophandle(132))
3044 d.addCallback(lambda ign:
3045 self.clock.advance(96*60*60))
3046 d.addCallback(lambda ign:
3047 self.shouldHTTPError("test_uncollected_ophandle_expired_after_100_hours",
3048 404, "404 Not Found",
3049 "unknown/expired handle '132'",
3051 "/operations/132?t=status&output=JSON"))
3054 def test_collected_ophandle_expiration(self):
3055 # collected ophandles should expire after 1 day
3056 def _make_collected_ophandle(ophandle):
3057 d = self.POST(self.public_url +
3058 "/foo/?t=start-manifest&ophandle=%d" % ophandle,
3059 followRedirect=True)
3060 # By following the initial redirect, we collect the ophandle
3061 # we've just created.
3063 # Create a collected ophandle, then collect it after 23 hours
3064 # and 59 seconds to make sure that it is still there.
3065 d = _make_collected_ophandle(133)
3066 d.addCallback(lambda ign:
3067 self.clock.advance((24*60*60) - 1))
3068 d.addCallback(lambda ign:
3069 self.GET("/operations/133?t=status&output=JSON"))
3071 data = simplejson.loads(res)
3072 self.failUnless("finished" in data, res)
3073 d.addCallback(_check1)
3074 # Create another uncollected ophandle, then try to collect it
3075 # after 24 hours to make sure that it is gone.
3076 d.addCallback(lambda ign:
3077 _make_collected_ophandle(134))
3078 d.addCallback(lambda ign:
3079 self.clock.advance(24*60*60))
3080 d.addCallback(lambda ign:
3081 self.shouldHTTPError("test_collected_ophandle_expired_after_1000_minutes",
3082 404, "404 Not Found",
3083 "unknown/expired handle '134'",
3085 "/operations/134?t=status&output=JSON"))
3088 def test_incident(self):
3089 d = self.POST("/report_incident", details="eek")
3091 self.failUnless("Thank you for your report!" in res, res)
3092 d.addCallback(_done)
3095 def test_static(self):
3096 webdir = os.path.join(self.staticdir, "subdir")
3097 fileutil.make_dirs(webdir)
3098 f = open(os.path.join(webdir, "hello.txt"), "wb")
3102 d = self.GET("/static/subdir/hello.txt")
3104 self.failUnlessEqual(res, "hello")
3105 d.addCallback(_check)
3109 class Util(unittest.TestCase, ShouldFailMixin):
3110 def test_load_file(self):
3111 # This will raise an exception unless a well-formed XML file is found under that name.
3112 common.getxmlfile('directory.xhtml').load()
3114 def test_parse_replace_arg(self):
3115 self.failUnlessEqual(common.parse_replace_arg("true"), True)
3116 self.failUnlessEqual(common.parse_replace_arg("false"), False)
3117 self.failUnlessEqual(common.parse_replace_arg("only-files"),
3119 self.shouldFail(AssertionError, "test_parse_replace_arg", "",
3120 common.parse_replace_arg, "only_fles")
3122 def test_abbreviate_time(self):
3123 self.failUnlessEqual(common.abbreviate_time(None), "")
3124 self.failUnlessEqual(common.abbreviate_time(1.234), "1.23s")
3125 self.failUnlessEqual(common.abbreviate_time(0.123), "123ms")
3126 self.failUnlessEqual(common.abbreviate_time(0.00123), "1.2ms")
3127 self.failUnlessEqual(common.abbreviate_time(0.000123), "123us")
3129 def test_abbreviate_rate(self):
3130 self.failUnlessEqual(common.abbreviate_rate(None), "")
3131 self.failUnlessEqual(common.abbreviate_rate(1234000), "1.23MBps")
3132 self.failUnlessEqual(common.abbreviate_rate(12340), "12.3kBps")
3133 self.failUnlessEqual(common.abbreviate_rate(123), "123Bps")
3135 def test_abbreviate_size(self):
3136 self.failUnlessEqual(common.abbreviate_size(None), "")
3137 self.failUnlessEqual(common.abbreviate_size(1.23*1000*1000*1000), "1.23GB")
3138 self.failUnlessEqual(common.abbreviate_size(1.23*1000*1000), "1.23MB")
3139 self.failUnlessEqual(common.abbreviate_size(1230), "1.2kB")
3140 self.failUnlessEqual(common.abbreviate_size(123), "123B")
3142 def test_plural(self):
3144 return "%d second%s" % (s, status.plural(s))
3145 self.failUnlessEqual(convert(0), "0 seconds")
3146 self.failUnlessEqual(convert(1), "1 second")
3147 self.failUnlessEqual(convert(2), "2 seconds")
3149 return "has share%s: %s" % (status.plural(s), ",".join(s))
3150 self.failUnlessEqual(convert2([]), "has shares: ")
3151 self.failUnlessEqual(convert2(["1"]), "has share: 1")
3152 self.failUnlessEqual(convert2(["1","2"]), "has shares: 1,2")
3155 class Grid(GridTestMixin, WebErrorMixin, unittest.TestCase, ShouldFailMixin):
3157 def CHECK(self, ign, which, args, clientnum=0):
3158 fileurl = self.fileurls[which]
3159 url = fileurl + "?" + args
3160 return self.GET(url, method="POST", clientnum=clientnum)
3162 def test_filecheck(self):
3163 self.basedir = "web/Grid/filecheck"
3165 c0 = self.g.clients[0]
3168 d = c0.upload(upload.Data(DATA, convergence=""))
3169 def _stash_uri(ur, which):
3170 self.uris[which] = ur.uri
3171 d.addCallback(_stash_uri, "good")
3172 d.addCallback(lambda ign:
3173 c0.upload(upload.Data(DATA+"1", convergence="")))
3174 d.addCallback(_stash_uri, "sick")
3175 d.addCallback(lambda ign:
3176 c0.upload(upload.Data(DATA+"2", convergence="")))
3177 d.addCallback(_stash_uri, "dead")
3178 def _stash_mutable_uri(n, which):
3179 self.uris[which] = n.get_uri()
3180 assert isinstance(self.uris[which], str)
3181 d.addCallback(lambda ign: c0.create_mutable_file(DATA+"3"))
3182 d.addCallback(_stash_mutable_uri, "corrupt")
3183 d.addCallback(lambda ign:
3184 c0.upload(upload.Data("literal", convergence="")))
3185 d.addCallback(_stash_uri, "small")
3186 d.addCallback(lambda ign: c0.create_immutable_dirnode({}))
3187 d.addCallback(_stash_mutable_uri, "smalldir")
3189 def _compute_fileurls(ignored):
3191 for which in self.uris:
3192 self.fileurls[which] = "uri/" + urllib.quote(self.uris[which])
3193 d.addCallback(_compute_fileurls)
3195 def _clobber_shares(ignored):
3196 good_shares = self.find_shares(self.uris["good"])
3197 self.failUnlessEqual(len(good_shares), 10)
3198 sick_shares = self.find_shares(self.uris["sick"])
3199 os.unlink(sick_shares[0][2])
3200 dead_shares = self.find_shares(self.uris["dead"])
3201 for i in range(1, 10):
3202 os.unlink(dead_shares[i][2])
3203 c_shares = self.find_shares(self.uris["corrupt"])
3204 cso = CorruptShareOptions()
3205 cso.stdout = StringIO()
3206 cso.parseOptions([c_shares[0][2]])
3208 d.addCallback(_clobber_shares)
3210 d.addCallback(self.CHECK, "good", "t=check")
3211 def _got_html_good(res):
3212 self.failUnless("Healthy" in res, res)
3213 self.failIf("Not Healthy" in res, res)
3214 d.addCallback(_got_html_good)
3215 d.addCallback(self.CHECK, "good", "t=check&return_to=somewhere")
3216 def _got_html_good_return_to(res):
3217 self.failUnless("Healthy" in res, res)
3218 self.failIf("Not Healthy" in res, res)
3219 self.failUnless('<a href="somewhere">Return to file'
3221 d.addCallback(_got_html_good_return_to)
3222 d.addCallback(self.CHECK, "good", "t=check&output=json")
3223 def _got_json_good(res):
3224 r = simplejson.loads(res)
3225 self.failUnlessEqual(r["summary"], "Healthy")
3226 self.failUnless(r["results"]["healthy"])
3227 self.failIf(r["results"]["needs-rebalancing"])
3228 self.failUnless(r["results"]["recoverable"])
3229 d.addCallback(_got_json_good)
3231 d.addCallback(self.CHECK, "small", "t=check")
3232 def _got_html_small(res):
3233 self.failUnless("Literal files are always healthy" in res, res)
3234 self.failIf("Not Healthy" in res, res)
3235 d.addCallback(_got_html_small)
3236 d.addCallback(self.CHECK, "small", "t=check&return_to=somewhere")
3237 def _got_html_small_return_to(res):
3238 self.failUnless("Literal files are always healthy" in res, res)
3239 self.failIf("Not Healthy" in res, res)
3240 self.failUnless('<a href="somewhere">Return to file'
3242 d.addCallback(_got_html_small_return_to)
3243 d.addCallback(self.CHECK, "small", "t=check&output=json")
3244 def _got_json_small(res):
3245 r = simplejson.loads(res)
3246 self.failUnlessEqual(r["storage-index"], "")
3247 self.failUnless(r["results"]["healthy"])
3248 d.addCallback(_got_json_small)
3250 d.addCallback(self.CHECK, "smalldir", "t=check")
3251 def _got_html_smalldir(res):
3252 self.failUnless("Literal files are always healthy" in res, res)
3253 self.failIf("Not Healthy" in res, res)
3254 d.addCallback(_got_html_smalldir)
3255 d.addCallback(self.CHECK, "smalldir", "t=check&output=json")
3256 def _got_json_smalldir(res):
3257 r = simplejson.loads(res)
3258 self.failUnlessEqual(r["storage-index"], "")
3259 self.failUnless(r["results"]["healthy"])
3260 d.addCallback(_got_json_smalldir)
3262 d.addCallback(self.CHECK, "sick", "t=check")
3263 def _got_html_sick(res):
3264 self.failUnless("Not Healthy" in res, res)
3265 d.addCallback(_got_html_sick)
3266 d.addCallback(self.CHECK, "sick", "t=check&output=json")
3267 def _got_json_sick(res):
3268 r = simplejson.loads(res)
3269 self.failUnlessEqual(r["summary"],
3270 "Not Healthy: 9 shares (enc 3-of-10)")
3271 self.failIf(r["results"]["healthy"])
3272 self.failIf(r["results"]["needs-rebalancing"])
3273 self.failUnless(r["results"]["recoverable"])
3274 d.addCallback(_got_json_sick)
3276 d.addCallback(self.CHECK, "dead", "t=check")
3277 def _got_html_dead(res):
3278 self.failUnless("Not Healthy" in res, res)
3279 d.addCallback(_got_html_dead)
3280 d.addCallback(self.CHECK, "dead", "t=check&output=json")
3281 def _got_json_dead(res):
3282 r = simplejson.loads(res)
3283 self.failUnlessEqual(r["summary"],
3284 "Not Healthy: 1 shares (enc 3-of-10)")
3285 self.failIf(r["results"]["healthy"])
3286 self.failIf(r["results"]["needs-rebalancing"])
3287 self.failIf(r["results"]["recoverable"])
3288 d.addCallback(_got_json_dead)
3290 d.addCallback(self.CHECK, "corrupt", "t=check&verify=true")
3291 def _got_html_corrupt(res):
3292 self.failUnless("Not Healthy! : Unhealthy" in res, res)
3293 d.addCallback(_got_html_corrupt)
3294 d.addCallback(self.CHECK, "corrupt", "t=check&verify=true&output=json")
3295 def _got_json_corrupt(res):
3296 r = simplejson.loads(res)
3297 self.failUnless("Unhealthy: 9 shares (enc 3-of-10)" in r["summary"],
3299 self.failIf(r["results"]["healthy"])
3300 self.failUnless(r["results"]["recoverable"])
3301 self.failUnlessEqual(r["results"]["count-shares-good"], 9)
3302 self.failUnlessEqual(r["results"]["count-corrupt-shares"], 1)
3303 d.addCallback(_got_json_corrupt)
3305 d.addErrback(self.explain_web_error)
3308 def test_repair_html(self):
3309 self.basedir = "web/Grid/repair_html"
3311 c0 = self.g.clients[0]
3314 d = c0.upload(upload.Data(DATA, convergence=""))
3315 def _stash_uri(ur, which):
3316 self.uris[which] = ur.uri
3317 d.addCallback(_stash_uri, "good")
3318 d.addCallback(lambda ign:
3319 c0.upload(upload.Data(DATA+"1", convergence="")))
3320 d.addCallback(_stash_uri, "sick")
3321 d.addCallback(lambda ign:
3322 c0.upload(upload.Data(DATA+"2", convergence="")))
3323 d.addCallback(_stash_uri, "dead")
3324 def _stash_mutable_uri(n, which):
3325 self.uris[which] = n.get_uri()
3326 assert isinstance(self.uris[which], str)
3327 d.addCallback(lambda ign: c0.create_mutable_file(DATA+"3"))
3328 d.addCallback(_stash_mutable_uri, "corrupt")
3330 def _compute_fileurls(ignored):
3332 for which in self.uris:
3333 self.fileurls[which] = "uri/" + urllib.quote(self.uris[which])
3334 d.addCallback(_compute_fileurls)
3336 def _clobber_shares(ignored):
3337 good_shares = self.find_shares(self.uris["good"])
3338 self.failUnlessEqual(len(good_shares), 10)
3339 sick_shares = self.find_shares(self.uris["sick"])
3340 os.unlink(sick_shares[0][2])
3341 dead_shares = self.find_shares(self.uris["dead"])
3342 for i in range(1, 10):
3343 os.unlink(dead_shares[i][2])
3344 c_shares = self.find_shares(self.uris["corrupt"])
3345 cso = CorruptShareOptions()
3346 cso.stdout = StringIO()
3347 cso.parseOptions([c_shares[0][2]])
3349 d.addCallback(_clobber_shares)
3351 d.addCallback(self.CHECK, "good", "t=check&repair=true")
3352 def _got_html_good(res):
3353 self.failUnless("Healthy" in res, res)
3354 self.failIf("Not Healthy" in res, res)
3355 self.failUnless("No repair necessary" in res, res)
3356 d.addCallback(_got_html_good)
3358 d.addCallback(self.CHECK, "sick", "t=check&repair=true")
3359 def _got_html_sick(res):
3360 self.failUnless("Healthy : healthy" in res, res)
3361 self.failIf("Not Healthy" in res, res)
3362 self.failUnless("Repair successful" in res, res)
3363 d.addCallback(_got_html_sick)
3365 # repair of a dead file will fail, of course, but it isn't yet
3366 # clear how this should be reported. Right now it shows up as
3369 #d.addCallback(self.CHECK, "dead", "t=check&repair=true")
3370 #def _got_html_dead(res):
3372 # self.failUnless("Healthy : healthy" in res, res)
3373 # self.failIf("Not Healthy" in res, res)
3374 # self.failUnless("No repair necessary" in res, res)
3375 #d.addCallback(_got_html_dead)
3377 d.addCallback(self.CHECK, "corrupt", "t=check&verify=true&repair=true")
3378 def _got_html_corrupt(res):
3379 self.failUnless("Healthy : Healthy" in res, res)
3380 self.failIf("Not Healthy" in res, res)
3381 self.failUnless("Repair successful" in res, res)
3382 d.addCallback(_got_html_corrupt)
3384 d.addErrback(self.explain_web_error)
3387 def test_repair_json(self):
3388 self.basedir = "web/Grid/repair_json"
3390 c0 = self.g.clients[0]
3393 d = c0.upload(upload.Data(DATA+"1", convergence=""))
3394 def _stash_uri(ur, which):
3395 self.uris[which] = ur.uri
3396 d.addCallback(_stash_uri, "sick")
3398 def _compute_fileurls(ignored):
3400 for which in self.uris:
3401 self.fileurls[which] = "uri/" + urllib.quote(self.uris[which])
3402 d.addCallback(_compute_fileurls)
3404 def _clobber_shares(ignored):
3405 sick_shares = self.find_shares(self.uris["sick"])
3406 os.unlink(sick_shares[0][2])
3407 d.addCallback(_clobber_shares)
3409 d.addCallback(self.CHECK, "sick", "t=check&repair=true&output=json")
3410 def _got_json_sick(res):
3411 r = simplejson.loads(res)
3412 self.failUnlessEqual(r["repair-attempted"], True)
3413 self.failUnlessEqual(r["repair-successful"], True)
3414 self.failUnlessEqual(r["pre-repair-results"]["summary"],
3415 "Not Healthy: 9 shares (enc 3-of-10)")
3416 self.failIf(r["pre-repair-results"]["results"]["healthy"])
3417 self.failUnlessEqual(r["post-repair-results"]["summary"], "healthy")
3418 self.failUnless(r["post-repair-results"]["results"]["healthy"])
3419 d.addCallback(_got_json_sick)
3421 d.addErrback(self.explain_web_error)
3424 def test_unknown(self, immutable=False):
3425 self.basedir = "web/Grid/unknown"
3427 self.basedir = "web/Grid/unknown-immutable"
3430 c0 = self.g.clients[0]
3434 # the future cap format may contain slashes, which must be tolerated
3435 expected_info_url = "uri/%s?t=info" % urllib.quote(unknown_rwcap,
3439 name = u"future-imm"
3440 future_node = UnknownNode(None, unknown_immcap, deep_immutable=True)
3441 d = c0.create_immutable_dirnode({name: (future_node, {})})
3444 future_node = UnknownNode(unknown_rwcap, unknown_rocap)
3445 d = c0.create_dirnode()
3447 def _stash_root_and_create_file(n):
3449 self.rooturl = "uri/" + urllib.quote(n.get_uri()) + "/"
3450 self.rourl = "uri/" + urllib.quote(n.get_readonly_uri()) + "/"
3452 return self.rootnode.set_node(name, future_node)
3453 d.addCallback(_stash_root_and_create_file)
3455 # make sure directory listing tolerates unknown nodes
3456 d.addCallback(lambda ign: self.GET(self.rooturl))
3457 def _check_directory_html(res, expected_type_suffix):
3458 pattern = re.compile(r'<td>\?%s</td>[ \t\n\r]*'
3459 '<td>%s</td>' % (expected_type_suffix, str(name)),
3461 self.failUnless(re.search(pattern, res), res)
3462 # find the More Info link for name, should be relative
3463 mo = re.search(r'<a href="([^"]+)">More Info</a>', res)
3464 info_url = mo.group(1)
3465 self.failUnlessEqual(info_url, "%s?t=info" % (str(name),))
3467 d.addCallback(_check_directory_html, "-IMM")
3469 d.addCallback(_check_directory_html, "")
3471 d.addCallback(lambda ign: self.GET(self.rooturl+"?t=json"))
3472 def _check_directory_json(res, expect_rw_uri):
3473 data = simplejson.loads(res)
3474 self.failUnlessEqual(data[0], "dirnode")
3475 f = data[1]["children"][name]
3476 self.failUnlessEqual(f[0], "unknown")
3478 self.failUnlessEqual(f[1]["rw_uri"], unknown_rwcap)
3480 self.failIfIn("rw_uri", f[1])
3482 self.failUnlessEqual(f[1]["ro_uri"], unknown_immcap, data)
3484 self.failUnlessEqual(f[1]["ro_uri"], unknown_rocap)
3485 self.failUnless("metadata" in f[1])
3486 d.addCallback(_check_directory_json, expect_rw_uri=not immutable)
3488 def _check_info(res, expect_rw_uri, expect_ro_uri):
3489 self.failUnlessIn("Object Type: <span>unknown</span>", res)
3491 self.failUnlessIn(unknown_rwcap, res)
3494 self.failUnlessIn(unknown_immcap, res)
3496 self.failUnlessIn(unknown_rocap, res)
3498 self.failIfIn(unknown_rocap, res)
3499 self.failIfIn("Raw data as", res)
3500 self.failIfIn("Directory writecap", res)
3501 self.failIfIn("Checker Operations", res)
3502 self.failIfIn("Mutable File Operations", res)
3503 self.failIfIn("Directory Operations", res)
3505 # FIXME: these should have expect_rw_uri=not immutable; I don't know
3506 # why they fail. Possibly related to ticket #922.
3508 d.addCallback(lambda ign: self.GET(expected_info_url))
3509 d.addCallback(_check_info, expect_rw_uri=False, expect_ro_uri=False)
3510 d.addCallback(lambda ign: self.GET("%s%s?t=info" % (self.rooturl, str(name))))
3511 d.addCallback(_check_info, expect_rw_uri=False, expect_ro_uri=True)
3513 def _check_json(res, expect_rw_uri):
3514 data = simplejson.loads(res)
3515 self.failUnlessEqual(data[0], "unknown")
3517 self.failUnlessEqual(data[1]["rw_uri"], unknown_rwcap)
3519 self.failIfIn("rw_uri", data[1])
3522 self.failUnlessEqual(data[1]["ro_uri"], unknown_immcap)
3523 self.failUnlessEqual(data[1]["mutable"], False)
3525 self.failUnlessEqual(data[1]["ro_uri"], unknown_rocap)
3526 self.failUnlessEqual(data[1]["mutable"], True)
3528 self.failUnlessEqual(data[1]["ro_uri"], unknown_rocap)
3529 self.failIf("mutable" in data[1], data[1])
3531 # TODO: check metadata contents
3532 self.failUnless("metadata" in data[1])
3534 d.addCallback(lambda ign: self.GET("%s%s?t=json" % (self.rooturl, str(name))))
3535 d.addCallback(_check_json, expect_rw_uri=not immutable)
3537 # and make sure that a read-only version of the directory can be
3538 # rendered too. This version will not have unknown_rwcap, whether
3539 # or not future_node was immutable.
3540 d.addCallback(lambda ign: self.GET(self.rourl))
3542 d.addCallback(_check_directory_html, "-IMM")
3544 d.addCallback(_check_directory_html, "-RO")
3546 d.addCallback(lambda ign: self.GET(self.rourl+"?t=json"))
3547 d.addCallback(_check_directory_json, expect_rw_uri=False)
3549 d.addCallback(lambda ign: self.GET("%s%s?t=json" % (self.rourl, str(name))))
3550 d.addCallback(_check_json, expect_rw_uri=False)
3552 # TODO: check that getting t=info from the Info link in the ro directory
3553 # works, and does not include the writecap URI.
3556 def test_immutable_unknown(self):
3557 return self.test_unknown(immutable=True)
3559 def test_mutant_dirnodes_are_omitted(self):
3560 self.basedir = "web/Grid/mutant_dirnodes_are_omitted"
3563 c = self.g.clients[0]
3568 lonely_uri = "URI:LIT:n5xgk" # LIT for "one"
3569 mut_write_uri = "URI:SSK:vfvcbdfbszyrsaxchgevhmmlii:euw4iw7bbnkrrwpzuburbhppuxhc3gwxv26f6imekhz7zyw2ojnq"
3570 mut_read_uri = "URI:SSK-RO:e3mdrzfwhoq42hy5ubcz6rp3o4:ybyibhnp3vvwuq2vaw2ckjmesgkklfs6ghxleztqidihjyofgw7q"
3572 # This method tests mainly dirnode, but we'd have to duplicate code in order to
3573 # test the dirnode and web layers separately.
3575 # 'lonely' is a valid LIT child, 'ro' is a mutant child with an SSK-RO readcap,
3576 # and 'write-in-ro' is a mutant child with an SSK writecap in the ro_uri field.
3577 # When the directory is read, the mutants should be silently disposed of, leaving
3578 # their lonely sibling.
3579 # We don't test the case of a retrieving a cap from the encrypted rw_uri field,
3580 # because immutable directories don't have a writecap and therefore that field
3581 # isn't (and can't be) decrypted.
3582 # TODO: The field still exists in the netstring. Technically we should check what
3583 # happens if something is put there (_unpack_contents should raise ValueError),
3584 # but that can wait.
3586 lonely_child = nm.create_from_cap(lonely_uri)
3587 mutant_ro_child = nm.create_from_cap(mut_read_uri)
3588 mutant_write_in_ro_child = nm.create_from_cap(mut_write_uri)
3590 def _by_hook_or_by_crook():
3592 for n in [mutant_ro_child, mutant_write_in_ro_child]:
3593 n.is_allowed_in_immutable_directory = _by_hook_or_by_crook
3595 mutant_write_in_ro_child.get_write_uri = lambda: None
3596 mutant_write_in_ro_child.get_readonly_uri = lambda: mut_write_uri
3598 kids = {u"lonely": (lonely_child, {}),
3599 u"ro": (mutant_ro_child, {}),
3600 u"write-in-ro": (mutant_write_in_ro_child, {}),
3602 d = c.create_immutable_dirnode(kids)
3605 self.failUnless(isinstance(dn, dirnode.DirectoryNode))
3606 self.failIf(dn.is_mutable())
3607 self.failUnless(dn.is_readonly())
3608 # This checks that if we somehow ended up calling dn._decrypt_rwcapdata, it would fail.
3609 self.failIf(hasattr(dn._node, 'get_writekey'))
3611 self.failUnless("RO-IMM" in rep)
3613 self.failUnlessIn("CHK", cap.to_string())
3616 self.rooturl = "uri/" + urllib.quote(dn.get_uri()) + "/"
3617 return download_to_data(dn._node)
3618 d.addCallback(_created)
3620 def _check_data(data):
3621 # Decode the netstring representation of the directory to check that all children
3622 # are present. This is a bit of an abstraction violation, but there's not really
3623 # any other way to do it given that the real DirectoryNode._unpack_contents would
3624 # strip the mutant children out (which is what we're trying to test, later).
3627 while position < len(data):
3628 entries, position = split_netstring(data, 1, position)
3630 (name_utf8, ro_uri, rwcapdata, metadata_s), subpos = split_netstring(entry, 4)
3631 name = name_utf8.decode("utf-8")
3632 self.failUnless(rwcapdata == "")
3633 self.failUnless(name in kids)
3634 (expected_child, ign) = kids[name]
3635 self.failUnlessEqual(ro_uri, expected_child.get_readonly_uri())
3638 self.failUnlessEqual(numkids, 3)
3639 return self.rootnode.list()
3640 d.addCallback(_check_data)
3642 # Now when we use the real directory listing code, the mutants should be absent.
3643 def _check_kids(children):
3644 self.failUnlessEqual(sorted(children.keys()), [u"lonely"])
3645 lonely_node, lonely_metadata = children[u"lonely"]
3647 self.failUnlessEqual(lonely_node.get_write_uri(), None)
3648 self.failUnlessEqual(lonely_node.get_readonly_uri(), lonely_uri)
3649 d.addCallback(_check_kids)
3651 d.addCallback(lambda ign: nm.create_from_cap(self.cap.to_string()))
3652 d.addCallback(lambda n: n.list())
3653 d.addCallback(_check_kids) # again with dirnode recreated from cap
3655 # Make sure the lonely child can be listed in HTML...
3656 d.addCallback(lambda ign: self.GET(self.rooturl))
3657 def _check_html(res):
3658 self.failIfIn("URI:SSK", res)
3659 get_lonely = "".join([r'<td>FILE</td>',
3661 r'<a href="[^"]+%s[^"]+">lonely</a>' % (urllib.quote(lonely_uri),),
3663 r'\s+<td>%d</td>' % len("one"),
3665 self.failUnless(re.search(get_lonely, res), res)
3667 # find the More Info link for name, should be relative
3668 mo = re.search(r'<a href="([^"]+)">More Info</a>', res)
3669 info_url = mo.group(1)
3670 self.failUnless(info_url.endswith(urllib.quote(lonely_uri) + "?t=info"), info_url)
3671 d.addCallback(_check_html)
3674 d.addCallback(lambda ign: self.GET(self.rooturl+"?t=json"))
3675 def _check_json(res):
3676 data = simplejson.loads(res)
3677 self.failUnlessEqual(data[0], "dirnode")
3678 listed_children = data[1]["children"]
3679 self.failUnlessEqual(sorted(listed_children.keys()), [u"lonely"])
3680 ll_type, ll_data = listed_children[u"lonely"]
3681 self.failUnlessEqual(ll_type, "filenode")
3682 self.failIf("rw_uri" in ll_data)
3683 self.failUnlessEqual(ll_data["ro_uri"], lonely_uri)
3684 d.addCallback(_check_json)
3687 def test_deep_check(self):
3688 self.basedir = "web/Grid/deep_check"
3690 c0 = self.g.clients[0]
3694 d = c0.create_dirnode()
3695 def _stash_root_and_create_file(n):
3697 self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri()) + "/"
3698 return n.add_file(u"good", upload.Data(DATA, convergence=""))
3699 d.addCallback(_stash_root_and_create_file)
3700 def _stash_uri(fn, which):
3701 self.uris[which] = fn.get_uri()
3703 d.addCallback(_stash_uri, "good")
3704 d.addCallback(lambda ign:
3705 self.rootnode.add_file(u"small",
3706 upload.Data("literal",
3708 d.addCallback(_stash_uri, "small")
3709 d.addCallback(lambda ign:
3710 self.rootnode.add_file(u"sick",
3711 upload.Data(DATA+"1",
3713 d.addCallback(_stash_uri, "sick")
3715 # this tests that deep-check and stream-manifest will ignore
3716 # UnknownNode instances. Hopefully this will also cover deep-stats.
3717 future_node = UnknownNode(unknown_rwcap, unknown_rocap)
3718 d.addCallback(lambda ign: self.rootnode.set_node(u"future", future_node))
3720 def _clobber_shares(ignored):
3721 self.delete_shares_numbered(self.uris["sick"], [0,1])
3722 d.addCallback(_clobber_shares)
3730 d.addCallback(self.CHECK, "root", "t=stream-deep-check")
3733 units = [simplejson.loads(line)
3734 for line in res.splitlines()
3737 print "response is:", res
3738 print "undecodeable line was '%s'" % line
3740 self.failUnlessEqual(len(units), 5+1)
3741 # should be parent-first
3743 self.failUnlessEqual(u0["path"], [])
3744 self.failUnlessEqual(u0["type"], "directory")
3745 self.failUnlessEqual(u0["cap"], self.rootnode.get_uri())
3746 u0cr = u0["check-results"]
3747 self.failUnlessEqual(u0cr["results"]["count-shares-good"], 10)
3749 ugood = [u for u in units
3750 if u["type"] == "file" and u["path"] == [u"good"]][0]
3751 self.failUnlessEqual(ugood["cap"], self.uris["good"])
3752 ugoodcr = ugood["check-results"]
3753 self.failUnlessEqual(ugoodcr["results"]["count-shares-good"], 10)
3756 self.failUnlessEqual(stats["type"], "stats")
3758 self.failUnlessEqual(s["count-immutable-files"], 2)
3759 self.failUnlessEqual(s["count-literal-files"], 1)
3760 self.failUnlessEqual(s["count-directories"], 1)
3761 self.failUnlessEqual(s["count-unknown"], 1)
3762 d.addCallback(_done)
3764 d.addCallback(self.CHECK, "root", "t=stream-manifest")
3765 def _check_manifest(res):
3766 self.failUnless(res.endswith("\n"))
3767 units = [simplejson.loads(t) for t in res[:-1].split("\n")]
3768 self.failUnlessEqual(len(units), 5+1)
3769 self.failUnlessEqual(units[-1]["type"], "stats")
3771 self.failUnlessEqual(first["path"], [])
3772 self.failUnlessEqual(first["cap"], self.rootnode.get_uri())
3773 self.failUnlessEqual(first["type"], "directory")
3774 stats = units[-1]["stats"]
3775 self.failUnlessEqual(stats["count-immutable-files"], 2)
3776 self.failUnlessEqual(stats["count-literal-files"], 1)
3777 self.failUnlessEqual(stats["count-mutable-files"], 0)
3778 self.failUnlessEqual(stats["count-immutable-files"], 2)
3779 self.failUnlessEqual(stats["count-unknown"], 1)
3780 d.addCallback(_check_manifest)
3782 # now add root/subdir and root/subdir/grandchild, then make subdir
3783 # unrecoverable, then see what happens
3785 d.addCallback(lambda ign:
3786 self.rootnode.create_subdirectory(u"subdir"))
3787 d.addCallback(_stash_uri, "subdir")
3788 d.addCallback(lambda subdir_node:
3789 subdir_node.add_file(u"grandchild",
3790 upload.Data(DATA+"2",
3792 d.addCallback(_stash_uri, "grandchild")
3794 d.addCallback(lambda ign:
3795 self.delete_shares_numbered(self.uris["subdir"],
3803 # root/subdir [unrecoverable]
3804 # root/subdir/grandchild
3806 # how should a streaming-JSON API indicate fatal error?
3807 # answer: emit ERROR: instead of a JSON string
3809 d.addCallback(self.CHECK, "root", "t=stream-manifest")
3810 def _check_broken_manifest(res):
3811 lines = res.splitlines()
3813 for (i,line) in enumerate(lines)
3814 if line.startswith("ERROR:")]
3816 self.fail("no ERROR: in output: %s" % (res,))
3817 first_error = error_lines[0]
3818 error_line = lines[first_error]
3819 error_msg = lines[first_error+1:]
3820 error_msg_s = "\n".join(error_msg) + "\n"
3821 self.failUnlessIn("ERROR: UnrecoverableFileError(no recoverable versions)",
3823 self.failUnless(len(error_msg) > 2, error_msg_s) # some traceback
3824 units = [simplejson.loads(line) for line in lines[:first_error]]
3825 self.failUnlessEqual(len(units), 6) # includes subdir
3826 last_unit = units[-1]
3827 self.failUnlessEqual(last_unit["path"], ["subdir"])
3828 d.addCallback(_check_broken_manifest)
3830 d.addCallback(self.CHECK, "root", "t=stream-deep-check")
3831 def _check_broken_deepcheck(res):
3832 lines = res.splitlines()
3834 for (i,line) in enumerate(lines)
3835 if line.startswith("ERROR:")]
3837 self.fail("no ERROR: in output: %s" % (res,))
3838 first_error = error_lines[0]
3839 error_line = lines[first_error]
3840 error_msg = lines[first_error+1:]
3841 error_msg_s = "\n".join(error_msg) + "\n"
3842 self.failUnlessIn("ERROR: UnrecoverableFileError(no recoverable versions)",
3844 self.failUnless(len(error_msg) > 2, error_msg_s) # some traceback
3845 units = [simplejson.loads(line) for line in lines[:first_error]]
3846 self.failUnlessEqual(len(units), 6) # includes subdir
3847 last_unit = units[-1]
3848 self.failUnlessEqual(last_unit["path"], ["subdir"])
3849 r = last_unit["check-results"]["results"]
3850 self.failUnlessEqual(r["count-recoverable-versions"], 0)
3851 self.failUnlessEqual(r["count-shares-good"], 1)
3852 self.failUnlessEqual(r["recoverable"], False)
3853 d.addCallback(_check_broken_deepcheck)
3855 d.addErrback(self.explain_web_error)
3858 def test_deep_check_and_repair(self):
3859 self.basedir = "web/Grid/deep_check_and_repair"
3861 c0 = self.g.clients[0]
3865 d = c0.create_dirnode()
3866 def _stash_root_and_create_file(n):
3868 self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri()) + "/"
3869 return n.add_file(u"good", upload.Data(DATA, convergence=""))
3870 d.addCallback(_stash_root_and_create_file)
3871 def _stash_uri(fn, which):
3872 self.uris[which] = fn.get_uri()
3873 d.addCallback(_stash_uri, "good")
3874 d.addCallback(lambda ign:
3875 self.rootnode.add_file(u"small",
3876 upload.Data("literal",
3878 d.addCallback(_stash_uri, "small")
3879 d.addCallback(lambda ign:
3880 self.rootnode.add_file(u"sick",
3881 upload.Data(DATA+"1",
3883 d.addCallback(_stash_uri, "sick")
3884 #d.addCallback(lambda ign:
3885 # self.rootnode.add_file(u"dead",
3886 # upload.Data(DATA+"2",
3888 #d.addCallback(_stash_uri, "dead")
3890 #d.addCallback(lambda ign: c0.create_mutable_file("mutable"))
3891 #d.addCallback(lambda fn: self.rootnode.set_node(u"corrupt", fn))
3892 #d.addCallback(_stash_uri, "corrupt")
3894 def _clobber_shares(ignored):
3895 good_shares = self.find_shares(self.uris["good"])
3896 self.failUnlessEqual(len(good_shares), 10)
3897 sick_shares = self.find_shares(self.uris["sick"])
3898 os.unlink(sick_shares[0][2])
3899 #dead_shares = self.find_shares(self.uris["dead"])
3900 #for i in range(1, 10):
3901 # os.unlink(dead_shares[i][2])
3903 #c_shares = self.find_shares(self.uris["corrupt"])
3904 #cso = CorruptShareOptions()
3905 #cso.stdout = StringIO()
3906 #cso.parseOptions([c_shares[0][2]])
3908 d.addCallback(_clobber_shares)
3911 # root/good CHK, 10 shares
3913 # root/sick CHK, 9 shares
3915 d.addCallback(self.CHECK, "root", "t=stream-deep-check&repair=true")
3917 units = [simplejson.loads(line)
3918 for line in res.splitlines()
3920 self.failUnlessEqual(len(units), 4+1)
3921 # should be parent-first
3923 self.failUnlessEqual(u0["path"], [])
3924 self.failUnlessEqual(u0["type"], "directory")
3925 self.failUnlessEqual(u0["cap"], self.rootnode.get_uri())
3926 u0crr = u0["check-and-repair-results"]
3927 self.failUnlessEqual(u0crr["repair-attempted"], False)
3928 self.failUnlessEqual(u0crr["pre-repair-results"]["results"]["count-shares-good"], 10)
3930 ugood = [u for u in units
3931 if u["type"] == "file" and u["path"] == [u"good"]][0]
3932 self.failUnlessEqual(ugood["cap"], self.uris["good"])
3933 ugoodcrr = ugood["check-and-repair-results"]
3934 self.failUnlessEqual(ugoodcrr["repair-attempted"], False)
3935 self.failUnlessEqual(ugoodcrr["pre-repair-results"]["results"]["count-shares-good"], 10)
3937 usick = [u for u in units
3938 if u["type"] == "file" and u["path"] == [u"sick"]][0]
3939 self.failUnlessEqual(usick["cap"], self.uris["sick"])
3940 usickcrr = usick["check-and-repair-results"]
3941 self.failUnlessEqual(usickcrr["repair-attempted"], True)
3942 self.failUnlessEqual(usickcrr["repair-successful"], True)
3943 self.failUnlessEqual(usickcrr["pre-repair-results"]["results"]["count-shares-good"], 9)
3944 self.failUnlessEqual(usickcrr["post-repair-results"]["results"]["count-shares-good"], 10)
3947 self.failUnlessEqual(stats["type"], "stats")
3949 self.failUnlessEqual(s["count-immutable-files"], 2)
3950 self.failUnlessEqual(s["count-literal-files"], 1)
3951 self.failUnlessEqual(s["count-directories"], 1)
3952 d.addCallback(_done)
3954 d.addErrback(self.explain_web_error)
3957 def _count_leases(self, ignored, which):
3958 u = self.uris[which]
3959 shares = self.find_shares(u)
3961 for shnum, serverid, fn in shares:
3962 sf = get_share_file(fn)
3963 num_leases = len(list(sf.get_leases()))
3964 lease_counts.append( (fn, num_leases) )
3967 def _assert_leasecount(self, lease_counts, expected):
3968 for (fn, num_leases) in lease_counts:
3969 if num_leases != expected:
3970 self.fail("expected %d leases, have %d, on %s" %
3971 (expected, num_leases, fn))
3973 def test_add_lease(self):
3974 self.basedir = "web/Grid/add_lease"
3975 self.set_up_grid(num_clients=2)
3976 c0 = self.g.clients[0]
3979 d = c0.upload(upload.Data(DATA, convergence=""))
3980 def _stash_uri(ur, which):
3981 self.uris[which] = ur.uri
3982 d.addCallback(_stash_uri, "one")
3983 d.addCallback(lambda ign:
3984 c0.upload(upload.Data(DATA+"1", convergence="")))
3985 d.addCallback(_stash_uri, "two")
3986 def _stash_mutable_uri(n, which):
3987 self.uris[which] = n.get_uri()
3988 assert isinstance(self.uris[which], str)
3989 d.addCallback(lambda ign: c0.create_mutable_file(DATA+"2"))
3990 d.addCallback(_stash_mutable_uri, "mutable")
3992 def _compute_fileurls(ignored):
3994 for which in self.uris:
3995 self.fileurls[which] = "uri/" + urllib.quote(self.uris[which])
3996 d.addCallback(_compute_fileurls)
3998 d.addCallback(self._count_leases, "one")
3999 d.addCallback(self._assert_leasecount, 1)
4000 d.addCallback(self._count_leases, "two")
4001 d.addCallback(self._assert_leasecount, 1)
4002 d.addCallback(self._count_leases, "mutable")
4003 d.addCallback(self._assert_leasecount, 1)
4005 d.addCallback(self.CHECK, "one", "t=check") # no add-lease
4006 def _got_html_good(res):
4007 self.failUnless("Healthy" in res, res)
4008 self.failIf("Not Healthy" in res, res)
4009 d.addCallback(_got_html_good)
4011 d.addCallback(self._count_leases, "one")
4012 d.addCallback(self._assert_leasecount, 1)
4013 d.addCallback(self._count_leases, "two")
4014 d.addCallback(self._assert_leasecount, 1)
4015 d.addCallback(self._count_leases, "mutable")
4016 d.addCallback(self._assert_leasecount, 1)
4018 # this CHECK uses the original client, which uses the same
4019 # lease-secrets, so it will just renew the original lease
4020 d.addCallback(self.CHECK, "one", "t=check&add-lease=true")
4021 d.addCallback(_got_html_good)
4023 d.addCallback(self._count_leases, "one")
4024 d.addCallback(self._assert_leasecount, 1)
4025 d.addCallback(self._count_leases, "two")
4026 d.addCallback(self._assert_leasecount, 1)
4027 d.addCallback(self._count_leases, "mutable")
4028 d.addCallback(self._assert_leasecount, 1)
4030 # this CHECK uses an alternate client, which adds a second lease
4031 d.addCallback(self.CHECK, "one", "t=check&add-lease=true", clientnum=1)
4032 d.addCallback(_got_html_good)
4034 d.addCallback(self._count_leases, "one")
4035 d.addCallback(self._assert_leasecount, 2)
4036 d.addCallback(self._count_leases, "two")
4037 d.addCallback(self._assert_leasecount, 1)
4038 d.addCallback(self._count_leases, "mutable")
4039 d.addCallback(self._assert_leasecount, 1)
4041 d.addCallback(self.CHECK, "mutable", "t=check&add-lease=true")
4042 d.addCallback(_got_html_good)
4044 d.addCallback(self._count_leases, "one")
4045 d.addCallback(self._assert_leasecount, 2)
4046 d.addCallback(self._count_leases, "two")
4047 d.addCallback(self._assert_leasecount, 1)
4048 d.addCallback(self._count_leases, "mutable")
4049 d.addCallback(self._assert_leasecount, 1)
4051 d.addCallback(self.CHECK, "mutable", "t=check&add-lease=true",
4053 d.addCallback(_got_html_good)
4055 d.addCallback(self._count_leases, "one")
4056 d.addCallback(self._assert_leasecount, 2)
4057 d.addCallback(self._count_leases, "two")
4058 d.addCallback(self._assert_leasecount, 1)
4059 d.addCallback(self._count_leases, "mutable")
4060 d.addCallback(self._assert_leasecount, 2)
4062 d.addErrback(self.explain_web_error)
4065 def test_deep_add_lease(self):
4066 self.basedir = "web/Grid/deep_add_lease"
4067 self.set_up_grid(num_clients=2)
4068 c0 = self.g.clients[0]
4072 d = c0.create_dirnode()
4073 def _stash_root_and_create_file(n):
4075 self.uris["root"] = n.get_uri()
4076 self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri()) + "/"
4077 return n.add_file(u"one", upload.Data(DATA, convergence=""))
4078 d.addCallback(_stash_root_and_create_file)
4079 def _stash_uri(fn, which):
4080 self.uris[which] = fn.get_uri()
4081 d.addCallback(_stash_uri, "one")
4082 d.addCallback(lambda ign:
4083 self.rootnode.add_file(u"small",
4084 upload.Data("literal",
4086 d.addCallback(_stash_uri, "small")
4088 d.addCallback(lambda ign: c0.create_mutable_file("mutable"))
4089 d.addCallback(lambda fn: self.rootnode.set_node(u"mutable", fn))
4090 d.addCallback(_stash_uri, "mutable")
4092 d.addCallback(self.CHECK, "root", "t=stream-deep-check") # no add-lease
4094 units = [simplejson.loads(line)
4095 for line in res.splitlines()
4097 # root, one, small, mutable, stats
4098 self.failUnlessEqual(len(units), 4+1)
4099 d.addCallback(_done)
4101 d.addCallback(self._count_leases, "root")
4102 d.addCallback(self._assert_leasecount, 1)
4103 d.addCallback(self._count_leases, "one")
4104 d.addCallback(self._assert_leasecount, 1)
4105 d.addCallback(self._count_leases, "mutable")
4106 d.addCallback(self._assert_leasecount, 1)
4108 d.addCallback(self.CHECK, "root", "t=stream-deep-check&add-lease=true")
4109 d.addCallback(_done)
4111 d.addCallback(self._count_leases, "root")
4112 d.addCallback(self._assert_leasecount, 1)
4113 d.addCallback(self._count_leases, "one")
4114 d.addCallback(self._assert_leasecount, 1)
4115 d.addCallback(self._count_leases, "mutable")
4116 d.addCallback(self._assert_leasecount, 1)
4118 d.addCallback(self.CHECK, "root", "t=stream-deep-check&add-lease=true",
4120 d.addCallback(_done)
4122 d.addCallback(self._count_leases, "root")
4123 d.addCallback(self._assert_leasecount, 2)
4124 d.addCallback(self._count_leases, "one")
4125 d.addCallback(self._assert_leasecount, 2)
4126 d.addCallback(self._count_leases, "mutable")
4127 d.addCallback(self._assert_leasecount, 2)
4129 d.addErrback(self.explain_web_error)
4133 def test_exceptions(self):
4134 self.basedir = "web/Grid/exceptions"
4135 self.set_up_grid(num_clients=1, num_servers=2)
4136 c0 = self.g.clients[0]
4137 c0.DEFAULT_ENCODING_PARAMETERS['happy'] = 2
4140 d = c0.create_dirnode()
4142 self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri()) + "/"
4143 self.fileurls["imaginary"] = self.fileurls["root"] + "imaginary"
4145 d.addCallback(_stash_root)
4146 d.addCallback(lambda ign: c0.upload(upload.Data(DATA, convergence="")))
4148 self.fileurls["1share"] = "uri/" + urllib.quote(ur.uri)
4149 self.delete_shares_numbered(ur.uri, range(1,10))
4151 u = uri.from_string(ur.uri)
4152 u.key = testutil.flip_bit(u.key, 0)
4153 baduri = u.to_string()
4154 self.fileurls["0shares"] = "uri/" + urllib.quote(baduri)
4155 d.addCallback(_stash_bad)
4156 d.addCallback(lambda ign: c0.create_dirnode())
4157 def _mangle_dirnode_1share(n):
4159 url = self.fileurls["dir-1share"] = "uri/" + urllib.quote(u) + "/"
4160 self.fileurls["dir-1share-json"] = url + "?t=json"
4161 self.delete_shares_numbered(u, range(1,10))
4162 d.addCallback(_mangle_dirnode_1share)
4163 d.addCallback(lambda ign: c0.create_dirnode())
4164 def _mangle_dirnode_0share(n):
4166 url = self.fileurls["dir-0share"] = "uri/" + urllib.quote(u) + "/"
4167 self.fileurls["dir-0share-json"] = url + "?t=json"
4168 self.delete_shares_numbered(u, range(0,10))
4169 d.addCallback(_mangle_dirnode_0share)
4171 # NotEnoughSharesError should be reported sensibly, with a
4172 # text/plain explanation of the problem, and perhaps some
4173 # information on which shares *could* be found.
4175 d.addCallback(lambda ignored:
4176 self.shouldHTTPError("GET unrecoverable",
4177 410, "Gone", "NoSharesError",
4178 self.GET, self.fileurls["0shares"]))
4179 def _check_zero_shares(body):
4180 self.failIf("<html>" in body, body)
4181 body = " ".join(body.strip().split())
4182 exp = ("NoSharesError: no shares could be found. "
4183 "Zero shares usually indicates a corrupt URI, or that "
4184 "no servers were connected, but it might also indicate "
4185 "severe corruption. You should perform a filecheck on "
4186 "this object to learn more. The full error message is: "
4187 "Failed to get enough shareholders: have 0, need 3")
4188 self.failUnlessEqual(exp, body)
4189 d.addCallback(_check_zero_shares)
4192 d.addCallback(lambda ignored:
4193 self.shouldHTTPError("GET 1share",
4194 410, "Gone", "NotEnoughSharesError",
4195 self.GET, self.fileurls["1share"]))
4196 def _check_one_share(body):
4197 self.failIf("<html>" in body, body)
4198 body = " ".join(body.strip().split())
4199 exp = ("NotEnoughSharesError: This indicates that some "
4200 "servers were unavailable, or that shares have been "
4201 "lost to server departure, hard drive failure, or disk "
4202 "corruption. You should perform a filecheck on "
4203 "this object to learn more. The full error message is:"
4204 " Failed to get enough shareholders: have 1, need 3")
4205 self.failUnlessEqual(exp, body)
4206 d.addCallback(_check_one_share)
4208 d.addCallback(lambda ignored:
4209 self.shouldHTTPError("GET imaginary",
4210 404, "Not Found", None,
4211 self.GET, self.fileurls["imaginary"]))
4212 def _missing_child(body):
4213 self.failUnless("No such child: imaginary" in body, body)
4214 d.addCallback(_missing_child)
4216 d.addCallback(lambda ignored: self.GET(self.fileurls["dir-0share"]))
4217 def _check_0shares_dir_html(body):
4218 self.failUnless("<html>" in body, body)
4219 # we should see the regular page, but without the child table or
4221 body = " ".join(body.strip().split())
4222 self.failUnlessIn('href="?t=info">More info on this directory',
4224 exp = ("UnrecoverableFileError: the directory (or mutable file) "
4225 "could not be retrieved, because there were insufficient "
4226 "good shares. This might indicate that no servers were "
4227 "connected, insufficient servers were connected, the URI "
4228 "was corrupt, or that shares have been lost due to server "
4229 "departure, hard drive failure, or disk corruption. You "
4230 "should perform a filecheck on this object to learn more.")
4231 self.failUnlessIn(exp, body)
4232 self.failUnlessIn("No upload forms: directory is unreadable", body)
4233 d.addCallback(_check_0shares_dir_html)
4235 d.addCallback(lambda ignored: self.GET(self.fileurls["dir-1share"]))
4236 def _check_1shares_dir_html(body):
4237 # at some point, we'll split UnrecoverableFileError into 0-shares
4238 # and some-shares like we did for immutable files (since there
4239 # are different sorts of advice to offer in each case). For now,
4240 # they present the same way.
4241 self.failUnless("<html>" in body, body)
4242 body = " ".join(body.strip().split())
4243 self.failUnlessIn('href="?t=info">More info on this directory',
4245 exp = ("UnrecoverableFileError: the directory (or mutable file) "
4246 "could not be retrieved, because there were insufficient "
4247 "good shares. This might indicate that no servers were "
4248 "connected, insufficient servers were connected, the URI "
4249 "was corrupt, or that shares have been lost due to server "
4250 "departure, hard drive failure, or disk corruption. You "
4251 "should perform a filecheck on this object to learn more.")
4252 self.failUnlessIn(exp, body)
4253 self.failUnlessIn("No upload forms: directory is unreadable", body)
4254 d.addCallback(_check_1shares_dir_html)
4256 d.addCallback(lambda ignored:
4257 self.shouldHTTPError("GET dir-0share-json",
4258 410, "Gone", "UnrecoverableFileError",
4260 self.fileurls["dir-0share-json"]))
4261 def _check_unrecoverable_file(body):
4262 self.failIf("<html>" in body, body)
4263 body = " ".join(body.strip().split())
4264 exp = ("UnrecoverableFileError: the directory (or mutable file) "
4265 "could not be retrieved, because there were insufficient "
4266 "good shares. This might indicate that no servers were "
4267 "connected, insufficient servers were connected, the URI "
4268 "was corrupt, or that shares have been lost due to server "
4269 "departure, hard drive failure, or disk corruption. You "
4270 "should perform a filecheck on this object to learn more.")
4271 self.failUnlessEqual(exp, body)
4272 d.addCallback(_check_unrecoverable_file)
4274 d.addCallback(lambda ignored:
4275 self.shouldHTTPError("GET dir-1share-json",
4276 410, "Gone", "UnrecoverableFileError",
4278 self.fileurls["dir-1share-json"]))
4279 d.addCallback(_check_unrecoverable_file)
4281 d.addCallback(lambda ignored:
4282 self.shouldHTTPError("GET imaginary",
4283 404, "Not Found", None,
4284 self.GET, self.fileurls["imaginary"]))
4286 # attach a webapi child that throws a random error, to test how it
4288 w = c0.getServiceNamed("webish")
4289 w.root.putChild("ERRORBOOM", ErrorBoom())
4291 # "Accept: */*" : should get a text/html stack trace
4292 # "Accept: text/plain" : should get a text/plain stack trace
4293 # "Accept: text/plain, application/octet-stream" : text/plain (CLI)
4294 # no Accept header: should get a text/html stack trace
4296 d.addCallback(lambda ignored:
4297 self.shouldHTTPError("GET errorboom_html",
4298 500, "Internal Server Error", None,
4299 self.GET, "ERRORBOOM",
4300 headers={"accept": ["*/*"]}))
4301 def _internal_error_html1(body):
4302 self.failUnless("<html>" in body, "expected HTML, not '%s'" % body)
4303 d.addCallback(_internal_error_html1)
4305 d.addCallback(lambda ignored:
4306 self.shouldHTTPError("GET errorboom_text",
4307 500, "Internal Server Error", None,
4308 self.GET, "ERRORBOOM",
4309 headers={"accept": ["text/plain"]}))
4310 def _internal_error_text2(body):
4311 self.failIf("<html>" in body, body)
4312 self.failUnless(body.startswith("Traceback "), body)
4313 d.addCallback(_internal_error_text2)
4315 CLI_accepts = "text/plain, application/octet-stream"
4316 d.addCallback(lambda ignored:
4317 self.shouldHTTPError("GET errorboom_text",
4318 500, "Internal Server Error", None,
4319 self.GET, "ERRORBOOM",
4320 headers={"accept": [CLI_accepts]}))
4321 def _internal_error_text3(body):
4322 self.failIf("<html>" in body, body)
4323 self.failUnless(body.startswith("Traceback "), body)
4324 d.addCallback(_internal_error_text3)
4326 d.addCallback(lambda ignored:
4327 self.shouldHTTPError("GET errorboom_text",
4328 500, "Internal Server Error", None,
4329 self.GET, "ERRORBOOM"))
4330 def _internal_error_html4(body):
4331 self.failUnless("<html>" in body, "expected HTML, not '%s'" % body)
4332 d.addCallback(_internal_error_html4)
4334 def _flush_errors(res):
4335 # Trial: please ignore the CompletelyUnhandledError in the logs
4336 self.flushLoggedErrors(CompletelyUnhandledError)
4338 d.addBoth(_flush_errors)
4342 class CompletelyUnhandledError(Exception):
4344 class ErrorBoom(rend.Page):
4345 def beforeRender(self, ctx):
4346 raise CompletelyUnhandledError("whoops")