1 import os.path, re, urllib, time
3 from StringIO import StringIO
5 from twisted.application import service
6 from twisted.trial import unittest
7 from twisted.internet import defer, reactor
8 from twisted.internet.task import Clock
9 from twisted.web import client, error, http, html
10 from twisted.python import failure, log
12 from foolscap.api import fireEventually, flushEventualQueue
14 from nevow.util import escapeToXML
15 from nevow import rend
17 from allmydata import interfaces, uri, webish, dirnode
18 from allmydata.storage.shares import get_share_file
19 from allmydata.storage_client import StorageFarmBroker, StubServer
20 from allmydata.immutable import upload
21 from allmydata.immutable.downloader.status import DownloadStatus
22 from allmydata.dirnode import DirectoryNode
23 from allmydata.nodemaker import NodeMaker
24 from allmydata.unknown import UnknownNode
25 from allmydata.web import status, common
26 from allmydata.scripts.debug import CorruptShareOptions, corrupt_share
27 from allmydata.util import fileutil, base32, hashutil
28 from allmydata.util.consumer import download_to_data
29 from allmydata.util.netstring import split_netstring
30 from allmydata.util.encodingutil import to_str
31 from allmydata.test.common import FakeCHKFileNode, FakeMutableFileNode, \
32 create_chk_filenode, WebErrorMixin, ShouldFailMixin, \
33 make_mutable_file_uri, create_mutable_filenode
34 from allmydata.interfaces import IMutableFileNode, SDMF_VERSION, MDMF_VERSION
35 from allmydata.mutable import servermap, publish, retrieve
36 import allmydata.test.common_util as testutil
37 from allmydata.test.no_network import GridTestMixin
38 from allmydata.test.common_web import HTTPClientGETFactory, \
40 from allmydata.client import Client, SecretHolder
41 from allmydata.introducer import IntroducerNode
43 # create a fake uploader/downloader, and a couple of fake dirnodes, then
44 # create a webserver that works against them
46 timeout = 480 # Most of these take longer than 240 seconds on Francois's arm box.
48 unknown_rwcap = u"lafs://from_the_future_rw_\u263A".encode('utf-8')
49 unknown_rocap = u"ro.lafs://readonly_from_the_future_ro_\u263A".encode('utf-8')
50 unknown_immcap = u"imm.lafs://immutable_from_the_future_imm_\u263A".encode('utf-8')
52 FAVICON_MARKUP = '<link href="/icon.png" rel="shortcut icon" />'
55 class FakeStatsProvider:
57 stats = {'stats': {}, 'counters': {}}
60 class FakeNodeMaker(NodeMaker):
65 'max_segment_size':128*1024 # 1024=KiB
67 def _create_lit(self, cap):
68 return FakeCHKFileNode(cap, self.all_contents)
69 def _create_immutable(self, cap):
70 return FakeCHKFileNode(cap, self.all_contents)
71 def _create_mutable(self, cap):
72 return FakeMutableFileNode(None, None,
73 self.encoding_params, None,
74 self.all_contents).init_from_cap(cap)
75 def create_mutable_file(self, contents="", keysize=None,
76 version=SDMF_VERSION):
77 n = FakeMutableFileNode(None, None, self.encoding_params, None,
79 return n.create(contents, version=version)
81 class FakeUploader(service.Service):
83 def upload(self, uploadable):
84 d = uploadable.get_size()
85 d.addCallback(lambda size: uploadable.read(size))
88 n = create_chk_filenode(data, self.all_contents)
89 ur = upload.UploadResults(file_size=len(data),
96 uri_extension_data={},
97 uri_extension_hash="fake",
98 verifycapstr="fakevcap")
99 ur.set_uri(n.get_uri())
101 d.addCallback(_got_data)
103 def get_helper_info(self):
107 ds = DownloadStatus("storage_index", 1234)
110 serverA = StubServer(hashutil.tagged_hash("foo", "serverid_a")[:20])
111 serverB = StubServer(hashutil.tagged_hash("foo", "serverid_b")[:20])
112 storage_index = hashutil.storage_index_hash("SI")
113 e0 = ds.add_segment_request(0, now)
115 e0.deliver(now+1, 0, 100, 0.5) # when, start,len, decodetime
116 e1 = ds.add_segment_request(1, now+2)
118 # two outstanding requests
119 e2 = ds.add_segment_request(2, now+4)
120 e3 = ds.add_segment_request(3, now+5)
121 del e2,e3 # hush pyflakes
123 # simulate a segment which gets delivered faster than a system clock tick (ticket #1166)
124 e = ds.add_segment_request(4, now)
126 e.deliver(now, 0, 140, 0.5)
128 e = ds.add_dyhb_request(serverA, now)
129 e.finished([1,2], now+1)
130 e = ds.add_dyhb_request(serverB, now+2) # left unfinished
132 e = ds.add_read_event(0, 120, now)
133 e.update(60, 0.5, 0.1) # bytes, decrypttime, pausetime
135 e = ds.add_read_event(120, 30, now+2) # left unfinished
137 e = ds.add_block_request(serverA, 1, 100, 20, now)
138 e.finished(20, now+1)
139 e = ds.add_block_request(serverB, 1, 120, 30, now+1) # left unfinished
141 # make sure that add_read_event() can come first too
142 ds1 = DownloadStatus(storage_index, 1234)
143 e = ds1.add_read_event(0, 120, now)
144 e.update(60, 0.5, 0.1) # bytes, decrypttime, pausetime
150 _all_upload_status = [upload.UploadStatus()]
151 _all_download_status = [build_one_ds()]
152 _all_mapupdate_statuses = [servermap.UpdateStatus()]
153 _all_publish_statuses = [publish.PublishStatus()]
154 _all_retrieve_statuses = [retrieve.RetrieveStatus()]
156 def list_all_upload_statuses(self):
157 return self._all_upload_status
158 def list_all_download_statuses(self):
159 return self._all_download_status
160 def list_all_mapupdate_statuses(self):
161 return self._all_mapupdate_statuses
162 def list_all_publish_statuses(self):
163 return self._all_publish_statuses
164 def list_all_retrieve_statuses(self):
165 return self._all_retrieve_statuses
166 def list_all_helper_statuses(self):
169 class FakeDisplayableServer(StubServer):
170 def __init__(self, serverid, nickname):
171 StubServer.__init__(self, serverid)
172 self.announcement = {"my-version": "allmydata-tahoe-fake",
173 "service-name": "storage",
174 "nickname": nickname}
175 def is_connected(self):
177 def get_permutation_seed(self):
179 def get_remote_host(self):
181 def get_last_loss_time(self):
183 def get_announcement_time(self):
185 def get_announcement(self):
186 return self.announcement
187 def get_nickname(self):
188 return self.announcement["nickname"]
190 class FakeBucketCounter(object):
192 return {"last-complete-bucket-count": 0}
193 def get_progress(self):
194 return {"estimated-time-per-cycle": 0,
195 "cycle-in-progress": False,
196 "remaining-wait-time": 0}
198 class FakeLeaseChecker(object):
200 self.expiration_enabled = False
202 self.override_lease_duration = None
203 self.sharetypes_to_expire = {}
205 return {"history": None}
206 def get_progress(self):
207 return {"estimated-time-per-cycle": 0,
208 "cycle-in-progress": False,
209 "remaining-wait-time": 0}
211 class FakeStorageServer(service.MultiService):
213 def __init__(self, nodeid, nickname):
214 service.MultiService.__init__(self)
215 self.my_nodeid = nodeid
216 self.nickname = nickname
217 self.bucket_counter = FakeBucketCounter()
218 self.lease_checker = FakeLeaseChecker()
220 return {"storage_server.accepting_immutable_shares": False}
222 class FakeClient(Client):
224 # don't upcall to Client.__init__, since we only want to initialize a
226 service.MultiService.__init__(self)
227 self.all_contents = {}
228 self.nodeid = "fake_nodeid"
229 self.nickname = u"fake_nickname \u263A"
230 self.introducer_furl = "None"
231 self.stats_provider = FakeStatsProvider()
232 self._secret_holder = SecretHolder("lease secret", "convergence secret")
234 self.convergence = "some random string"
235 self.storage_broker = StorageFarmBroker(None, permute_peers=True)
236 # fake knowledge of another server
237 self.storage_broker.test_add_server("other_nodeid",
238 FakeDisplayableServer("other_nodeid", u"other_nickname \u263B"))
239 self.introducer_client = None
240 self.history = FakeHistory()
241 self.uploader = FakeUploader()
242 self.uploader.all_contents = self.all_contents
243 self.uploader.setServiceParent(self)
244 self.blacklist = None
245 self.nodemaker = FakeNodeMaker(None, self._secret_holder, None,
248 self.nodemaker.all_contents = self.all_contents
249 self.mutable_file_default = SDMF_VERSION
250 self.addService(FakeStorageServer(self.nodeid, self.nickname))
252 def startService(self):
253 return service.MultiService.startService(self)
254 def stopService(self):
255 return service.MultiService.stopService(self)
257 MUTABLE_SIZELIMIT = FakeMutableFileNode.MUTABLE_SIZELIMIT
259 class WebMixin(object):
261 self.s = FakeClient()
262 self.s.startService()
263 self.staticdir = self.mktemp()
265 self.ws = webish.WebishServer(self.s, "0", staticdir=self.staticdir,
267 self.ws.setServiceParent(self.s)
268 self.webish_port = self.ws.getPortnum()
269 self.webish_url = self.ws.getURL()
270 assert self.webish_url.endswith("/")
271 self.webish_url = self.webish_url[:-1] # these tests add their own /
273 l = [ self.s.create_dirnode() for x in range(6) ]
274 d = defer.DeferredList(l)
276 self.public_root = res[0][1]
277 assert interfaces.IDirectoryNode.providedBy(self.public_root), res
278 self.public_url = "/uri/" + self.public_root.get_uri()
279 self.private_root = res[1][1]
283 self._foo_uri = foo.get_uri()
284 self._foo_readonly_uri = foo.get_readonly_uri()
285 self._foo_verifycap = foo.get_verify_cap().to_string()
286 # NOTE: we ignore the deferred on all set_uri() calls, because we
287 # know the fake nodes do these synchronously
288 self.public_root.set_uri(u"foo", foo.get_uri(),
289 foo.get_readonly_uri())
291 self.BAR_CONTENTS, n, self._bar_txt_uri = self.makefile(0)
292 foo.set_uri(u"bar.txt", self._bar_txt_uri, self._bar_txt_uri)
293 self._bar_txt_verifycap = n.get_verify_cap().to_string()
296 # XXX: Do we ever use this?
297 self.BAZ_CONTENTS, n, self._baz_txt_uri, self._baz_txt_readonly_uri = self.makefile_mutable(0)
299 foo.set_uri(u"baz.txt", self._baz_txt_uri, self._baz_txt_readonly_uri)
302 self.QUUX_CONTENTS, n, self._quux_txt_uri, self._quux_txt_readonly_uri = self.makefile_mutable(0, mdmf=True)
303 assert self._quux_txt_uri.startswith("URI:MDMF")
304 foo.set_uri(u"quux.txt", self._quux_txt_uri, self._quux_txt_readonly_uri)
306 foo.set_uri(u"empty", res[3][1].get_uri(),
307 res[3][1].get_readonly_uri())
308 sub_uri = res[4][1].get_uri()
309 self._sub_uri = sub_uri
310 foo.set_uri(u"sub", sub_uri, sub_uri)
311 sub = self.s.create_node_from_uri(sub_uri)
314 _ign, n, blocking_uri = self.makefile(1)
315 foo.set_uri(u"blockingfile", blocking_uri, blocking_uri)
317 # filenode to test for html encoding issues
318 self._htmlname_unicode = u"<&weirdly'named\"file>>>_<iframe />.txt"
319 self._htmlname_raw = self._htmlname_unicode.encode('utf-8')
320 self._htmlname_urlencoded = urllib.quote(self._htmlname_raw, '')
321 self._htmlname_escaped = escapeToXML(self._htmlname_raw)
322 self._htmlname_escaped_attr = html.escape(self._htmlname_raw)
323 self._htmlname_escaped_double = escapeToXML(html.escape(self._htmlname_raw))
324 self.HTMLNAME_CONTENTS, n, self._htmlname_txt_uri = self.makefile(0)
325 foo.set_uri(self._htmlname_unicode, self._htmlname_txt_uri, self._htmlname_txt_uri)
327 unicode_filename = u"n\u00fc.txt" # n u-umlaut . t x t
328 # ok, unicode calls it LATIN SMALL LETTER U WITH DIAERESIS but I
329 # still think of it as an umlaut
330 foo.set_uri(unicode_filename, self._bar_txt_uri, self._bar_txt_uri)
332 self.SUBBAZ_CONTENTS, n, baz_file = self.makefile(2)
333 self._baz_file_uri = baz_file
334 sub.set_uri(u"baz.txt", baz_file, baz_file)
336 _ign, n, self._bad_file_uri = self.makefile(3)
337 # this uri should not be downloadable
338 del self.s.all_contents[self._bad_file_uri]
341 self.public_root.set_uri(u"reedownlee", rodir.get_readonly_uri(),
342 rodir.get_readonly_uri())
343 rodir.set_uri(u"nor", baz_file, baz_file)
349 # public/foo/quux.txt
350 # public/foo/blockingfile
351 # public/foo/<&weirdly'named\"file>>>_<iframe />.txt
354 # public/foo/sub/baz.txt
356 # public/reedownlee/nor
357 self.NEWFILE_CONTENTS = "newfile contents\n"
359 return foo.get_metadata_for(u"bar.txt")
361 def _got_metadata(metadata):
362 self._bar_txt_metadata = metadata
363 d.addCallback(_got_metadata)
366 def get_all_contents(self):
367 return self.s.all_contents
369 def makefile(self, number):
370 contents = "contents of file %s\n" % number
371 n = create_chk_filenode(contents, self.get_all_contents())
372 return contents, n, n.get_uri()
374 def makefile_mutable(self, number, mdmf=False):
375 contents = "contents of mutable file %s\n" % number
376 n = create_mutable_filenode(contents, mdmf, self.s.all_contents)
377 return contents, n, n.get_uri(), n.get_readonly_uri()
380 return self.s.stopService()
382 def failUnlessIsBarDotTxt(self, res):
383 self.failUnlessReallyEqual(res, self.BAR_CONTENTS, res)
385 def failUnlessIsQuuxDotTxt(self, res):
386 self.failUnlessReallyEqual(res, self.QUUX_CONTENTS, res)
388 def failUnlessIsBazDotTxt(self, res):
389 self.failUnlessReallyEqual(res, self.BAZ_CONTENTS, res)
391 def failUnlessIsSubBazDotTxt(self, res):
392 self.failUnlessReallyEqual(res, self.SUBBAZ_CONTENTS, res)
394 def failUnlessIsBarJSON(self, res):
395 data = simplejson.loads(res)
396 self.failUnless(isinstance(data, list))
397 self.failUnlessEqual(data[0], "filenode")
398 self.failUnless(isinstance(data[1], dict))
399 self.failIf(data[1]["mutable"])
400 self.failIfIn("rw_uri", data[1]) # immutable
401 self.failUnlessReallyEqual(to_str(data[1]["ro_uri"]), self._bar_txt_uri)
402 self.failUnlessReallyEqual(to_str(data[1]["verify_uri"]), self._bar_txt_verifycap)
403 self.failUnlessReallyEqual(data[1]["size"], len(self.BAR_CONTENTS))
405 def failUnlessIsQuuxJSON(self, res, readonly=False):
406 data = simplejson.loads(res)
407 self.failUnless(isinstance(data, list))
408 self.failUnlessEqual(data[0], "filenode")
409 self.failUnless(isinstance(data[1], dict))
411 return self.failUnlessIsQuuxDotTxtMetadata(metadata, readonly)
413 def failUnlessIsQuuxDotTxtMetadata(self, metadata, readonly):
414 self.failUnless(metadata['mutable'])
416 self.failIfIn("rw_uri", metadata)
418 self.failUnlessIn("rw_uri", metadata)
419 self.failUnlessEqual(metadata['rw_uri'], self._quux_txt_uri)
420 self.failUnlessIn("ro_uri", metadata)
421 self.failUnlessEqual(metadata['ro_uri'], self._quux_txt_readonly_uri)
422 self.failUnlessReallyEqual(metadata['size'], len(self.QUUX_CONTENTS))
424 def failUnlessIsFooJSON(self, res):
425 data = simplejson.loads(res)
426 self.failUnless(isinstance(data, list))
427 self.failUnlessEqual(data[0], "dirnode", res)
428 self.failUnless(isinstance(data[1], dict))
429 self.failUnless(data[1]["mutable"])
430 self.failUnlessIn("rw_uri", data[1]) # mutable
431 self.failUnlessReallyEqual(to_str(data[1]["rw_uri"]), self._foo_uri)
432 self.failUnlessReallyEqual(to_str(data[1]["ro_uri"]), self._foo_readonly_uri)
433 self.failUnlessReallyEqual(to_str(data[1]["verify_uri"]), self._foo_verifycap)
435 kidnames = sorted([unicode(n) for n in data[1]["children"]])
436 self.failUnlessEqual(kidnames,
437 [self._htmlname_unicode, u"bar.txt", u"baz.txt",
438 u"blockingfile", u"empty", u"n\u00fc.txt", u"quux.txt", u"sub"])
439 kids = dict( [(unicode(name),value)
441 in data[1]["children"].iteritems()] )
442 self.failUnlessEqual(kids[u"sub"][0], "dirnode")
443 self.failUnlessIn("metadata", kids[u"sub"][1])
444 self.failUnlessIn("tahoe", kids[u"sub"][1]["metadata"])
445 tahoe_md = kids[u"sub"][1]["metadata"]["tahoe"]
446 self.failUnlessIn("linkcrtime", tahoe_md)
447 self.failUnlessIn("linkmotime", tahoe_md)
448 self.failUnlessEqual(kids[u"bar.txt"][0], "filenode")
449 self.failUnlessReallyEqual(kids[u"bar.txt"][1]["size"], len(self.BAR_CONTENTS))
450 self.failUnlessReallyEqual(to_str(kids[u"bar.txt"][1]["ro_uri"]), self._bar_txt_uri)
451 self.failUnlessReallyEqual(to_str(kids[u"bar.txt"][1]["verify_uri"]),
452 self._bar_txt_verifycap)
453 self.failUnlessIn("metadata", kids[u"bar.txt"][1])
454 self.failUnlessIn("tahoe", kids[u"bar.txt"][1]["metadata"])
455 self.failUnlessReallyEqual(kids[u"bar.txt"][1]["metadata"]["tahoe"]["linkcrtime"],
456 self._bar_txt_metadata["tahoe"]["linkcrtime"])
457 self.failUnlessReallyEqual(to_str(kids[u"n\u00fc.txt"][1]["ro_uri"]),
459 self.failUnlessIn("quux.txt", kids)
460 self.failUnlessReallyEqual(to_str(kids[u"quux.txt"][1]["rw_uri"]),
462 self.failUnlessReallyEqual(to_str(kids[u"quux.txt"][1]["ro_uri"]),
463 self._quux_txt_readonly_uri)
465 def GET(self, urlpath, followRedirect=False, return_response=False,
467 # if return_response=True, this fires with (data, statuscode,
468 # respheaders) instead of just data.
469 assert not isinstance(urlpath, unicode)
470 url = self.webish_url + urlpath
471 factory = HTTPClientGETFactory(url, method="GET",
472 followRedirect=followRedirect, **kwargs)
473 reactor.connectTCP("localhost", self.webish_port, factory)
476 return (data, factory.status, factory.response_headers)
478 d.addCallback(_got_data)
479 return factory.deferred
481 def HEAD(self, urlpath, return_response=False, **kwargs):
482 # this requires some surgery, because twisted.web.client doesn't want
483 # to give us back the response headers.
484 factory = HTTPClientHEADFactory(urlpath, method="HEAD", **kwargs)
485 reactor.connectTCP("localhost", self.webish_port, factory)
488 return (data, factory.status, factory.response_headers)
490 d.addCallback(_got_data)
491 return factory.deferred
493 def PUT(self, urlpath, data, **kwargs):
494 url = self.webish_url + urlpath
495 return client.getPage(url, method="PUT", postdata=data, **kwargs)
497 def DELETE(self, urlpath):
498 url = self.webish_url + urlpath
499 return client.getPage(url, method="DELETE")
501 def POST(self, urlpath, followRedirect=False, **fields):
502 sepbase = "boogabooga"
506 form.append('Content-Disposition: form-data; name="_charset"')
510 for name, value in fields.iteritems():
511 if isinstance(value, tuple):
512 filename, value = value
513 form.append('Content-Disposition: form-data; name="%s"; '
514 'filename="%s"' % (name, filename.encode("utf-8")))
516 form.append('Content-Disposition: form-data; name="%s"' % name)
518 if isinstance(value, unicode):
519 value = value.encode("utf-8")
522 assert isinstance(value, str)
529 body = "\r\n".join(form) + "\r\n"
530 headers["content-type"] = "multipart/form-data; boundary=%s" % sepbase
531 return self.POST2(urlpath, body, headers, followRedirect)
533 def POST2(self, urlpath, body="", headers={}, followRedirect=False):
534 url = self.webish_url + urlpath
535 return client.getPage(url, method="POST", postdata=body,
536 headers=headers, followRedirect=followRedirect)
538 def shouldFail(self, res, expected_failure, which,
539 substring=None, response_substring=None):
540 if isinstance(res, failure.Failure):
541 res.trap(expected_failure)
543 self.failUnlessIn(substring, str(res), which)
544 if response_substring:
545 self.failUnlessIn(response_substring, res.value.response, which)
547 self.fail("%s was supposed to raise %s, not get '%s'" %
548 (which, expected_failure, res))
550 def shouldFail2(self, expected_failure, which, substring,
552 callable, *args, **kwargs):
553 assert substring is None or isinstance(substring, str)
554 assert response_substring is None or isinstance(response_substring, str)
555 d = defer.maybeDeferred(callable, *args, **kwargs)
557 if isinstance(res, failure.Failure):
558 res.trap(expected_failure)
560 self.failUnlessIn(substring, str(res),
561 "'%s' not in '%s' for test '%s'" % \
562 (substring, str(res), which))
563 if response_substring:
564 self.failUnlessIn(response_substring, res.value.response,
565 "'%s' not in '%s' for test '%s'" % \
566 (response_substring, res.value.response,
569 self.fail("%s was supposed to raise %s, not get '%s'" %
570 (which, expected_failure, res))
574 def should404(self, res, which):
575 if isinstance(res, failure.Failure):
576 res.trap(error.Error)
577 self.failUnlessReallyEqual(res.value.status, "404")
579 self.fail("%s was supposed to Error(404), not get '%s'" %
582 def should302(self, res, which):
583 if isinstance(res, failure.Failure):
584 res.trap(error.Error)
585 self.failUnlessReallyEqual(res.value.status, "302")
587 self.fail("%s was supposed to Error(302), not get '%s'" %
591 class Web(WebMixin, WebErrorMixin, testutil.StallMixin, testutil.ReallyEqualMixin, unittest.TestCase):
592 def test_create(self):
595 def test_welcome(self):
598 self.failUnlessIn('Welcome to Tahoe-LAFS', res)
599 self.failUnlessIn(FAVICON_MARKUP, res)
600 self.failUnlessIn('href="https://tahoe-lafs.org/"', res)
601 res_u = res.decode('utf-8')
602 self.failUnlessIn(u'<th>My nickname:</th> <td class="nickname mine">fake_nickname \u263A</td></tr>', res_u)
603 self.failUnlessIn(u'<div class="nickname">other_nickname \u263B</div>', res_u)
605 self.s.basedir = 'web/test_welcome'
606 fileutil.make_dirs("web/test_welcome")
607 fileutil.make_dirs("web/test_welcome/private")
609 d.addCallback(_check)
612 def test_storage(self):
613 d = self.GET("/storage")
615 self.failUnlessIn('Storage Server Status', res)
616 self.failUnlessIn(FAVICON_MARKUP, res)
617 res_u = res.decode('utf-8')
618 self.failUnlessIn(u'<li>Server Nickname: <span class="nickname mine">fake_nickname \u263A</span></li>', res_u)
619 d.addCallback(_check)
622 def test_status(self):
623 h = self.s.get_history()
624 dl_num = h.list_all_download_statuses()[0].get_counter()
625 ul_num = h.list_all_upload_statuses()[0].get_counter()
626 mu_num = h.list_all_mapupdate_statuses()[0].get_counter()
627 pub_num = h.list_all_publish_statuses()[0].get_counter()
628 ret_num = h.list_all_retrieve_statuses()[0].get_counter()
629 d = self.GET("/status", followRedirect=True)
631 self.failUnlessIn('Upload and Download Status', res)
632 self.failUnlessIn('"down-%d"' % dl_num, res)
633 self.failUnlessIn('"up-%d"' % ul_num, res)
634 self.failUnlessIn('"mapupdate-%d"' % mu_num, res)
635 self.failUnlessIn('"publish-%d"' % pub_num, res)
636 self.failUnlessIn('"retrieve-%d"' % ret_num, res)
637 d.addCallback(_check)
638 d.addCallback(lambda res: self.GET("/status/?t=json"))
639 def _check_json(res):
640 data = simplejson.loads(res)
641 self.failUnless(isinstance(data, dict))
642 #active = data["active"]
643 # TODO: test more. We need a way to fake an active operation
645 d.addCallback(_check_json)
647 d.addCallback(lambda res: self.GET("/status/down-%d" % dl_num))
649 self.failUnlessIn("File Download Status", res)
650 d.addCallback(_check_dl)
651 d.addCallback(lambda res: self.GET("/status/down-%d/event_json" % dl_num))
652 def _check_dl_json(res):
653 data = simplejson.loads(res)
654 self.failUnless(isinstance(data, dict))
655 self.failUnlessIn("read", data)
656 self.failUnlessEqual(data["read"][0]["length"], 120)
657 self.failUnlessEqual(data["segment"][0]["segment_length"], 100)
658 self.failUnlessEqual(data["segment"][2]["segment_number"], 2)
659 self.failUnlessEqual(data["segment"][2]["finish_time"], None)
660 phwr_id = base32.b2a(hashutil.tagged_hash("foo", "serverid_a")[:20])
661 cmpu_id = base32.b2a(hashutil.tagged_hash("foo", "serverid_b")[:20])
662 # serverids[] keys are strings, since that's what JSON does, but
663 # we'd really like them to be ints
664 self.failUnlessEqual(data["serverids"]["0"], "phwrsjte")
665 self.failUnless(data["serverids"].has_key("1"),
666 str(data["serverids"]))
667 self.failUnlessEqual(data["serverids"]["1"], "cmpuvkjm",
668 str(data["serverids"]))
669 self.failUnlessEqual(data["server_info"][phwr_id]["short"],
671 self.failUnlessEqual(data["server_info"][cmpu_id]["short"],
673 self.failUnlessIn("dyhb", data)
674 self.failUnlessIn("misc", data)
675 d.addCallback(_check_dl_json)
676 d.addCallback(lambda res: self.GET("/status/up-%d" % ul_num))
678 self.failUnlessIn("File Upload Status", res)
679 d.addCallback(_check_ul)
680 d.addCallback(lambda res: self.GET("/status/mapupdate-%d" % mu_num))
681 def _check_mapupdate(res):
682 self.failUnlessIn("Mutable File Servermap Update Status", res)
683 d.addCallback(_check_mapupdate)
684 d.addCallback(lambda res: self.GET("/status/publish-%d" % pub_num))
685 def _check_publish(res):
686 self.failUnlessIn("Mutable File Publish Status", res)
687 d.addCallback(_check_publish)
688 d.addCallback(lambda res: self.GET("/status/retrieve-%d" % ret_num))
689 def _check_retrieve(res):
690 self.failUnlessIn("Mutable File Retrieve Status", res)
691 d.addCallback(_check_retrieve)
695 def test_status_numbers(self):
696 drrm = status.DownloadResultsRendererMixin()
697 self.failUnlessReallyEqual(drrm.render_time(None, None), "")
698 self.failUnlessReallyEqual(drrm.render_time(None, 2.5), "2.50s")
699 self.failUnlessReallyEqual(drrm.render_time(None, 0.25), "250ms")
700 self.failUnlessReallyEqual(drrm.render_time(None, 0.0021), "2.1ms")
701 self.failUnlessReallyEqual(drrm.render_time(None, 0.000123), "123us")
702 self.failUnlessReallyEqual(drrm.render_rate(None, None), "")
703 self.failUnlessReallyEqual(drrm.render_rate(None, 2500000), "2.50MBps")
704 self.failUnlessReallyEqual(drrm.render_rate(None, 30100), "30.1kBps")
705 self.failUnlessReallyEqual(drrm.render_rate(None, 123), "123Bps")
707 urrm = status.UploadResultsRendererMixin()
708 self.failUnlessReallyEqual(urrm.render_time(None, None), "")
709 self.failUnlessReallyEqual(urrm.render_time(None, 2.5), "2.50s")
710 self.failUnlessReallyEqual(urrm.render_time(None, 0.25), "250ms")
711 self.failUnlessReallyEqual(urrm.render_time(None, 0.0021), "2.1ms")
712 self.failUnlessReallyEqual(urrm.render_time(None, 0.000123), "123us")
713 self.failUnlessReallyEqual(urrm.render_rate(None, None), "")
714 self.failUnlessReallyEqual(urrm.render_rate(None, 2500000), "2.50MBps")
715 self.failUnlessReallyEqual(urrm.render_rate(None, 30100), "30.1kBps")
716 self.failUnlessReallyEqual(urrm.render_rate(None, 123), "123Bps")
718 def test_GET_FILEURL(self):
719 d = self.GET(self.public_url + "/foo/bar.txt")
720 d.addCallback(self.failUnlessIsBarDotTxt)
723 def test_GET_FILEURL_range(self):
724 headers = {"range": "bytes=1-10"}
725 d = self.GET(self.public_url + "/foo/bar.txt", headers=headers,
726 return_response=True)
727 def _got((res, status, headers)):
728 self.failUnlessReallyEqual(int(status), 206)
729 self.failUnless(headers.has_key("content-range"))
730 self.failUnlessReallyEqual(headers["content-range"][0],
731 "bytes 1-10/%d" % len(self.BAR_CONTENTS))
732 self.failUnlessReallyEqual(res, self.BAR_CONTENTS[1:11])
736 def test_GET_FILEURL_partial_range(self):
737 headers = {"range": "bytes=5-"}
738 length = len(self.BAR_CONTENTS)
739 d = self.GET(self.public_url + "/foo/bar.txt", headers=headers,
740 return_response=True)
741 def _got((res, status, headers)):
742 self.failUnlessReallyEqual(int(status), 206)
743 self.failUnless(headers.has_key("content-range"))
744 self.failUnlessReallyEqual(headers["content-range"][0],
745 "bytes 5-%d/%d" % (length-1, length))
746 self.failUnlessReallyEqual(res, self.BAR_CONTENTS[5:])
750 def test_GET_FILEURL_partial_end_range(self):
751 headers = {"range": "bytes=-5"}
752 length = len(self.BAR_CONTENTS)
753 d = self.GET(self.public_url + "/foo/bar.txt", headers=headers,
754 return_response=True)
755 def _got((res, status, headers)):
756 self.failUnlessReallyEqual(int(status), 206)
757 self.failUnless(headers.has_key("content-range"))
758 self.failUnlessReallyEqual(headers["content-range"][0],
759 "bytes %d-%d/%d" % (length-5, length-1, length))
760 self.failUnlessReallyEqual(res, self.BAR_CONTENTS[-5:])
764 def test_GET_FILEURL_partial_range_overrun(self):
765 headers = {"range": "bytes=100-200"}
766 d = self.shouldFail2(error.Error, "test_GET_FILEURL_range_overrun",
767 "416 Requested Range not satisfiable",
768 "First beyond end of file",
769 self.GET, self.public_url + "/foo/bar.txt",
773 def test_HEAD_FILEURL_range(self):
774 headers = {"range": "bytes=1-10"}
775 d = self.HEAD(self.public_url + "/foo/bar.txt", headers=headers,
776 return_response=True)
777 def _got((res, status, headers)):
778 self.failUnlessReallyEqual(res, "")
779 self.failUnlessReallyEqual(int(status), 206)
780 self.failUnless(headers.has_key("content-range"))
781 self.failUnlessReallyEqual(headers["content-range"][0],
782 "bytes 1-10/%d" % len(self.BAR_CONTENTS))
786 def test_HEAD_FILEURL_partial_range(self):
787 headers = {"range": "bytes=5-"}
788 length = len(self.BAR_CONTENTS)
789 d = self.HEAD(self.public_url + "/foo/bar.txt", headers=headers,
790 return_response=True)
791 def _got((res, status, headers)):
792 self.failUnlessReallyEqual(int(status), 206)
793 self.failUnless(headers.has_key("content-range"))
794 self.failUnlessReallyEqual(headers["content-range"][0],
795 "bytes 5-%d/%d" % (length-1, length))
799 def test_HEAD_FILEURL_partial_end_range(self):
800 headers = {"range": "bytes=-5"}
801 length = len(self.BAR_CONTENTS)
802 d = self.HEAD(self.public_url + "/foo/bar.txt", headers=headers,
803 return_response=True)
804 def _got((res, status, headers)):
805 self.failUnlessReallyEqual(int(status), 206)
806 self.failUnless(headers.has_key("content-range"))
807 self.failUnlessReallyEqual(headers["content-range"][0],
808 "bytes %d-%d/%d" % (length-5, length-1, length))
812 def test_HEAD_FILEURL_partial_range_overrun(self):
813 headers = {"range": "bytes=100-200"}
814 d = self.shouldFail2(error.Error, "test_HEAD_FILEURL_range_overrun",
815 "416 Requested Range not satisfiable",
817 self.HEAD, self.public_url + "/foo/bar.txt",
821 def test_GET_FILEURL_range_bad(self):
822 headers = {"range": "BOGUS=fizbop-quarnak"}
823 d = self.GET(self.public_url + "/foo/bar.txt", headers=headers,
824 return_response=True)
825 def _got((res, status, headers)):
826 self.failUnlessReallyEqual(int(status), 200)
827 self.failUnless(not headers.has_key("content-range"))
828 self.failUnlessReallyEqual(res, self.BAR_CONTENTS)
832 def test_HEAD_FILEURL(self):
833 d = self.HEAD(self.public_url + "/foo/bar.txt", return_response=True)
834 def _got((res, status, headers)):
835 self.failUnlessReallyEqual(res, "")
836 self.failUnlessReallyEqual(headers["content-length"][0],
837 str(len(self.BAR_CONTENTS)))
838 self.failUnlessReallyEqual(headers["content-type"], ["text/plain"])
842 def test_GET_FILEURL_named(self):
843 base = "/file/%s" % urllib.quote(self._bar_txt_uri)
844 base2 = "/named/%s" % urllib.quote(self._bar_txt_uri)
845 d = self.GET(base + "/@@name=/blah.txt")
846 d.addCallback(self.failUnlessIsBarDotTxt)
847 d.addCallback(lambda res: self.GET(base + "/blah.txt"))
848 d.addCallback(self.failUnlessIsBarDotTxt)
849 d.addCallback(lambda res: self.GET(base + "/ignore/lots/blah.txt"))
850 d.addCallback(self.failUnlessIsBarDotTxt)
851 d.addCallback(lambda res: self.GET(base2 + "/@@name=/blah.txt"))
852 d.addCallback(self.failUnlessIsBarDotTxt)
853 save_url = base + "?save=true&filename=blah.txt"
854 d.addCallback(lambda res: self.GET(save_url))
855 d.addCallback(self.failUnlessIsBarDotTxt) # TODO: check headers
856 u_filename = u"n\u00e9wer.txt" # n e-acute w e r . t x t
857 u_fn_e = urllib.quote(u_filename.encode("utf-8"))
858 u_url = base + "?save=true&filename=" + u_fn_e
859 d.addCallback(lambda res: self.GET(u_url))
860 d.addCallback(self.failUnlessIsBarDotTxt) # TODO: check headers
863 def test_PUT_FILEURL_named_bad(self):
864 base = "/file/%s" % urllib.quote(self._bar_txt_uri)
865 d = self.shouldFail2(error.Error, "test_PUT_FILEURL_named_bad",
867 "/file can only be used with GET or HEAD",
868 self.PUT, base + "/@@name=/blah.txt", "")
872 def test_GET_DIRURL_named_bad(self):
873 base = "/file/%s" % urllib.quote(self._foo_uri)
874 d = self.shouldFail2(error.Error, "test_PUT_DIRURL_named_bad",
877 self.GET, base + "/@@name=/blah.txt")
880 def test_GET_slash_file_bad(self):
881 d = self.shouldFail2(error.Error, "test_GET_slash_file_bad",
883 "/file must be followed by a file-cap and a name",
887 def test_GET_unhandled_URI_named(self):
888 contents, n, newuri = self.makefile(12)
889 verifier_cap = n.get_verify_cap().to_string()
890 base = "/file/%s" % urllib.quote(verifier_cap)
891 # client.create_node_from_uri() can't handle verify-caps
892 d = self.shouldFail2(error.Error, "GET_unhandled_URI_named",
893 "400 Bad Request", "is not a file-cap",
897 def test_GET_unhandled_URI(self):
898 contents, n, newuri = self.makefile(12)
899 verifier_cap = n.get_verify_cap().to_string()
900 base = "/uri/%s" % urllib.quote(verifier_cap)
901 # client.create_node_from_uri() can't handle verify-caps
902 d = self.shouldFail2(error.Error, "test_GET_unhandled_URI",
904 "GET unknown URI type: can only do t=info",
908 def test_GET_FILE_URI(self):
909 base = "/uri/%s" % urllib.quote(self._bar_txt_uri)
911 d.addCallback(self.failUnlessIsBarDotTxt)
914 def test_GET_FILE_URI_mdmf(self):
915 base = "/uri/%s" % urllib.quote(self._quux_txt_uri)
917 d.addCallback(self.failUnlessIsQuuxDotTxt)
920 def test_GET_FILE_URI_mdmf_extensions(self):
921 base = "/uri/%s" % urllib.quote("%s:RANDOMSTUFF" % self._quux_txt_uri)
923 d.addCallback(self.failUnlessIsQuuxDotTxt)
926 def test_GET_FILE_URI_mdmf_readonly(self):
927 base = "/uri/%s" % urllib.quote(self._quux_txt_readonly_uri)
929 d.addCallback(self.failUnlessIsQuuxDotTxt)
932 def test_GET_FILE_URI_badchild(self):
933 base = "/uri/%s/boguschild" % urllib.quote(self._bar_txt_uri)
934 errmsg = "Files have no children, certainly not named 'boguschild'"
935 d = self.shouldFail2(error.Error, "test_GET_FILE_URI_badchild",
936 "400 Bad Request", errmsg,
940 def test_PUT_FILE_URI_badchild(self):
941 base = "/uri/%s/boguschild" % urllib.quote(self._bar_txt_uri)
942 errmsg = "Cannot create directory 'boguschild', because its parent is a file, not a directory"
943 d = self.shouldFail2(error.Error, "test_GET_FILE_URI_badchild",
944 "400 Bad Request", errmsg,
948 def test_PUT_FILE_URI_mdmf(self):
949 base = "/uri/%s" % urllib.quote(self._quux_txt_uri)
950 self._quux_new_contents = "new_contents"
952 d.addCallback(lambda res:
953 self.failUnlessIsQuuxDotTxt(res))
954 d.addCallback(lambda ignored:
955 self.PUT(base, self._quux_new_contents))
956 d.addCallback(lambda ignored:
958 d.addCallback(lambda res:
959 self.failUnlessReallyEqual(res, self._quux_new_contents))
962 def test_PUT_FILE_URI_mdmf_extensions(self):
963 base = "/uri/%s" % urllib.quote("%s:EXTENSIONSTUFF" % self._quux_txt_uri)
964 self._quux_new_contents = "new_contents"
966 d.addCallback(lambda res: self.failUnlessIsQuuxDotTxt(res))
967 d.addCallback(lambda ignored: self.PUT(base, self._quux_new_contents))
968 d.addCallback(lambda ignored: self.GET(base))
969 d.addCallback(lambda res: self.failUnlessEqual(self._quux_new_contents,
973 def test_PUT_FILE_URI_mdmf_readonly(self):
974 # We're not allowed to PUT things to a readonly cap.
975 base = "/uri/%s" % self._quux_txt_readonly_uri
977 d.addCallback(lambda res:
978 self.failUnlessIsQuuxDotTxt(res))
979 # What should we get here? We get a 500 error now; that's not right.
980 d.addCallback(lambda ignored:
981 self.shouldFail2(error.Error, "test_PUT_FILE_URI_mdmf_readonly",
982 "400 Bad Request", "read-only cap",
983 self.PUT, base, "new data"))
986 def test_PUT_FILE_URI_sdmf_readonly(self):
987 # We're not allowed to put things to a readonly cap.
988 base = "/uri/%s" % self._baz_txt_readonly_uri
990 d.addCallback(lambda res:
991 self.failUnlessIsBazDotTxt(res))
992 d.addCallback(lambda ignored:
993 self.shouldFail2(error.Error, "test_PUT_FILE_URI_sdmf_readonly",
994 "400 Bad Request", "read-only cap",
995 self.PUT, base, "new_data"))
998 def test_GET_etags(self):
1000 def _check_etags(uri):
1002 d2 = _get_etag(uri, 'json')
1003 d = defer.DeferredList([d1, d2], consumeErrors=True)
1004 def _check(results):
1005 # All deferred must succeed
1006 self.failUnless(all([r[0] for r in results]))
1007 # the etag for the t=json form should be just like the etag
1008 # fo the default t='' form, but with a 'json' suffix
1009 self.failUnlessEqual(results[0][1] + 'json', results[1][1])
1010 d.addCallback(_check)
1013 def _get_etag(uri, t=''):
1014 targetbase = "/uri/%s?t=%s" % (urllib.quote(uri.strip()), t)
1015 d = self.GET(targetbase, return_response=True, followRedirect=True)
1016 def _just_the_etag(result):
1017 data, response, headers = result
1018 etag = headers['etag'][0]
1019 if uri.startswith('URI:DIR'):
1020 self.failUnless(etag.startswith('DIR:'), etag)
1022 return d.addCallback(_just_the_etag)
1024 # Check that etags work with immutable directories
1025 (newkids, caps) = self._create_immutable_children()
1026 d = self.POST2(self.public_url + "/foo/newdir?t=mkdir-immutable",
1027 simplejson.dumps(newkids))
1028 def _stash_immdir_uri(uri):
1029 self._immdir_uri = uri
1031 d.addCallback(_stash_immdir_uri)
1032 d.addCallback(_check_etags)
1034 # Check that etags work with immutable files
1035 d.addCallback(lambda _: _check_etags(self._bar_txt_uri))
1037 # use the ETag on GET
1038 def _check_match(ign):
1039 uri = "/uri/%s" % self._bar_txt_uri
1040 d = self.GET(uri, return_response=True)
1042 d.addCallback(lambda (data, code, headers):
1044 # do a GET that's supposed to match the ETag
1045 d.addCallback(lambda etag:
1046 self.GET(uri, return_response=True,
1047 headers={"If-None-Match": etag}))
1048 # make sure it short-circuited (304 instead of 200)
1049 d.addCallback(lambda (data, code, headers):
1050 self.failUnlessEqual(int(code), http.NOT_MODIFIED))
1052 d.addCallback(_check_match)
1054 def _no_etag(uri, t):
1055 target = "/uri/%s?t=%s" % (uri, t)
1056 d = self.GET(target, return_response=True, followRedirect=True)
1057 d.addCallback(lambda (data, code, headers):
1058 self.failIf("etag" in headers, target))
1060 def _yes_etag(uri, t):
1061 target = "/uri/%s?t=%s" % (uri, t)
1062 d = self.GET(target, return_response=True, followRedirect=True)
1063 d.addCallback(lambda (data, code, headers):
1064 self.failUnless("etag" in headers, target))
1067 d.addCallback(lambda ign: _yes_etag(self._bar_txt_uri, ""))
1068 d.addCallback(lambda ign: _yes_etag(self._bar_txt_uri, "json"))
1069 d.addCallback(lambda ign: _yes_etag(self._bar_txt_uri, "uri"))
1070 d.addCallback(lambda ign: _yes_etag(self._bar_txt_uri, "readonly-uri"))
1071 d.addCallback(lambda ign: _no_etag(self._bar_txt_uri, "info"))
1073 d.addCallback(lambda ign: _yes_etag(self._immdir_uri, ""))
1074 d.addCallback(lambda ign: _yes_etag(self._immdir_uri, "json"))
1075 d.addCallback(lambda ign: _yes_etag(self._immdir_uri, "uri"))
1076 d.addCallback(lambda ign: _yes_etag(self._immdir_uri, "readonly-uri"))
1077 d.addCallback(lambda ign: _no_etag(self._immdir_uri, "info"))
1078 d.addCallback(lambda ign: _no_etag(self._immdir_uri, "rename-form"))
1082 # TODO: version of this with a Unicode filename
1083 def test_GET_FILEURL_save(self):
1084 d = self.GET(self.public_url + "/foo/bar.txt?filename=bar.txt&save=true",
1085 return_response=True)
1086 def _got((res, statuscode, headers)):
1087 content_disposition = headers["content-disposition"][0]
1088 self.failUnless(content_disposition == 'attachment; filename="bar.txt"', content_disposition)
1089 self.failUnlessIsBarDotTxt(res)
1093 def test_GET_FILEURL_missing(self):
1094 d = self.GET(self.public_url + "/foo/missing")
1095 d.addBoth(self.should404, "test_GET_FILEURL_missing")
1098 def test_GET_FILEURL_info_mdmf(self):
1099 d = self.GET("/uri/%s?t=info" % self._quux_txt_uri)
1101 self.failUnlessIn("mutable file (mdmf)", res)
1102 self.failUnlessIn(self._quux_txt_uri, res)
1103 self.failUnlessIn(self._quux_txt_readonly_uri, res)
1107 def test_GET_FILEURL_info_mdmf_readonly(self):
1108 d = self.GET("/uri/%s?t=info" % self._quux_txt_readonly_uri)
1110 self.failUnlessIn("mutable file (mdmf)", res)
1111 self.failIfIn(self._quux_txt_uri, res)
1112 self.failUnlessIn(self._quux_txt_readonly_uri, res)
1116 def test_GET_FILEURL_info_sdmf(self):
1117 d = self.GET("/uri/%s?t=info" % self._baz_txt_uri)
1119 self.failUnlessIn("mutable file (sdmf)", res)
1120 self.failUnlessIn(self._baz_txt_uri, res)
1124 def test_GET_FILEURL_info_mdmf_extensions(self):
1125 d = self.GET("/uri/%s:STUFF?t=info" % self._quux_txt_uri)
1127 self.failUnlessIn("mutable file (mdmf)", res)
1128 self.failUnlessIn(self._quux_txt_uri, res)
1129 self.failUnlessIn(self._quux_txt_readonly_uri, res)
1133 def test_PUT_overwrite_only_files(self):
1134 # create a directory, put a file in that directory.
1135 contents, n, filecap = self.makefile(8)
1136 d = self.PUT(self.public_url + "/foo/dir?t=mkdir", "")
1137 d.addCallback(lambda res:
1138 self.PUT(self.public_url + "/foo/dir/file1.txt",
1139 self.NEWFILE_CONTENTS))
1140 # try to overwrite the file with replace=only-files
1141 # (this should work)
1142 d.addCallback(lambda res:
1143 self.PUT(self.public_url + "/foo/dir/file1.txt?t=uri&replace=only-files",
1145 d.addCallback(lambda res:
1146 self.shouldFail2(error.Error, "PUT_bad_t", "409 Conflict",
1147 "There was already a child by that name, and you asked me "
1148 "to not replace it",
1149 self.PUT, self.public_url + "/foo/dir?t=uri&replace=only-files",
1153 def test_PUT_NEWFILEURL(self):
1154 d = self.PUT(self.public_url + "/foo/new.txt", self.NEWFILE_CONTENTS)
1155 # TODO: we lose the response code, so we can't check this
1156 #self.failUnlessReallyEqual(responsecode, 201)
1157 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"new.txt")
1158 d.addCallback(lambda res:
1159 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
1160 self.NEWFILE_CONTENTS))
1163 def test_PUT_NEWFILEURL_not_mutable(self):
1164 d = self.PUT(self.public_url + "/foo/new.txt?mutable=false",
1165 self.NEWFILE_CONTENTS)
1166 # TODO: we lose the response code, so we can't check this
1167 #self.failUnlessReallyEqual(responsecode, 201)
1168 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"new.txt")
1169 d.addCallback(lambda res:
1170 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
1171 self.NEWFILE_CONTENTS))
1174 def test_PUT_NEWFILEURL_unlinked_mdmf(self):
1175 # this should get us a few segments of an MDMF mutable file,
1176 # which we can then test for.
1177 contents = self.NEWFILE_CONTENTS * 300000
1178 d = self.PUT("/uri?format=mdmf",
1180 def _got_filecap(filecap):
1181 self.failUnless(filecap.startswith("URI:MDMF"))
1183 d.addCallback(_got_filecap)
1184 d.addCallback(lambda filecap: self.GET("/uri/%s?t=json" % filecap))
1185 d.addCallback(lambda json: self.failUnlessIn("MDMF", json))
1188 def test_PUT_NEWFILEURL_unlinked_sdmf(self):
1189 contents = self.NEWFILE_CONTENTS * 300000
1190 d = self.PUT("/uri?format=sdmf",
1192 d.addCallback(lambda filecap: self.GET("/uri/%s?t=json" % filecap))
1193 d.addCallback(lambda json: self.failUnlessIn("SDMF", json))
1196 def test_PUT_NEWFILEURL_unlinked_bad_format(self):
1197 contents = self.NEWFILE_CONTENTS * 300000
1198 return self.shouldHTTPError("PUT_NEWFILEURL_unlinked_bad_format",
1199 400, "Bad Request", "Unknown format: foo",
1200 self.PUT, "/uri?format=foo",
1203 def test_PUT_NEWFILEURL_range_bad(self):
1204 headers = {"content-range": "bytes 1-10/%d" % len(self.NEWFILE_CONTENTS)}
1205 target = self.public_url + "/foo/new.txt"
1206 d = self.shouldFail2(error.Error, "test_PUT_NEWFILEURL_range_bad",
1207 "501 Not Implemented",
1208 "Content-Range in PUT not yet supported",
1209 # (and certainly not for immutable files)
1210 self.PUT, target, self.NEWFILE_CONTENTS[1:11],
1212 d.addCallback(lambda res:
1213 self.failIfNodeHasChild(self._foo_node, u"new.txt"))
1216 def test_PUT_NEWFILEURL_mutable(self):
1217 d = self.PUT(self.public_url + "/foo/new.txt?mutable=true",
1218 self.NEWFILE_CONTENTS)
1219 # TODO: we lose the response code, so we can't check this
1220 #self.failUnlessReallyEqual(responsecode, 201)
1221 def _check_uri(res):
1222 u = uri.from_string_mutable_filenode(res)
1223 self.failUnless(u.is_mutable())
1224 self.failIf(u.is_readonly())
1226 d.addCallback(_check_uri)
1227 d.addCallback(self.failUnlessURIMatchesRWChild, self._foo_node, u"new.txt")
1228 d.addCallback(lambda res:
1229 self.failUnlessMutableChildContentsAre(self._foo_node,
1231 self.NEWFILE_CONTENTS))
1234 def test_PUT_NEWFILEURL_mutable_toobig(self):
1235 # It is okay to upload large mutable files, so we should be able
1237 d = self.PUT(self.public_url + "/foo/new.txt?mutable=true",
1238 "b" * (self.s.MUTABLE_SIZELIMIT + 1))
1241 def test_PUT_NEWFILEURL_replace(self):
1242 d = self.PUT(self.public_url + "/foo/bar.txt", self.NEWFILE_CONTENTS)
1243 # TODO: we lose the response code, so we can't check this
1244 #self.failUnlessReallyEqual(responsecode, 200)
1245 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"bar.txt")
1246 d.addCallback(lambda res:
1247 self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
1248 self.NEWFILE_CONTENTS))
1251 def test_PUT_NEWFILEURL_bad_t(self):
1252 d = self.shouldFail2(error.Error, "PUT_bad_t", "400 Bad Request",
1253 "PUT to a file: bad t=bogus",
1254 self.PUT, self.public_url + "/foo/bar.txt?t=bogus",
1258 def test_PUT_NEWFILEURL_no_replace(self):
1259 d = self.PUT(self.public_url + "/foo/bar.txt?replace=false",
1260 self.NEWFILE_CONTENTS)
1261 d.addBoth(self.shouldFail, error.Error, "PUT_NEWFILEURL_no_replace",
1263 "There was already a child by that name, and you asked me "
1264 "to not replace it")
1267 def test_PUT_NEWFILEURL_mkdirs(self):
1268 d = self.PUT(self.public_url + "/foo/newdir/new.txt", self.NEWFILE_CONTENTS)
1270 d.addCallback(self.failUnlessURIMatchesROChild, fn, u"newdir/new.txt")
1271 d.addCallback(lambda res: self.failIfNodeHasChild(fn, u"new.txt"))
1272 d.addCallback(lambda res: self.failUnlessNodeHasChild(fn, u"newdir"))
1273 d.addCallback(lambda res:
1274 self.failUnlessChildContentsAre(fn, u"newdir/new.txt",
1275 self.NEWFILE_CONTENTS))
1278 def test_PUT_NEWFILEURL_blocked(self):
1279 d = self.PUT(self.public_url + "/foo/blockingfile/new.txt",
1280 self.NEWFILE_CONTENTS)
1281 d.addBoth(self.shouldFail, error.Error, "PUT_NEWFILEURL_blocked",
1283 "Unable to create directory 'blockingfile': a file was in the way")
1286 def test_PUT_NEWFILEURL_emptyname(self):
1287 # an empty pathname component (i.e. a double-slash) is disallowed
1288 d = self.shouldFail2(error.Error, "test_PUT_NEWFILEURL_emptyname",
1290 "The webapi does not allow empty pathname components",
1291 self.PUT, self.public_url + "/foo//new.txt", "")
1294 def test_DELETE_FILEURL(self):
1295 d = self.DELETE(self.public_url + "/foo/bar.txt")
1296 d.addCallback(lambda res:
1297 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
1300 def test_DELETE_FILEURL_missing(self):
1301 d = self.DELETE(self.public_url + "/foo/missing")
1302 d.addBoth(self.should404, "test_DELETE_FILEURL_missing")
1305 def test_DELETE_FILEURL_missing2(self):
1306 d = self.DELETE(self.public_url + "/missing/missing")
1307 d.addBoth(self.should404, "test_DELETE_FILEURL_missing2")
1310 def failUnlessHasBarDotTxtMetadata(self, res):
1311 data = simplejson.loads(res)
1312 self.failUnless(isinstance(data, list))
1313 self.failUnlessIn("metadata", data[1])
1314 self.failUnlessIn("tahoe", data[1]["metadata"])
1315 self.failUnlessIn("linkcrtime", data[1]["metadata"]["tahoe"])
1316 self.failUnlessIn("linkmotime", data[1]["metadata"]["tahoe"])
1317 self.failUnlessReallyEqual(data[1]["metadata"]["tahoe"]["linkcrtime"],
1318 self._bar_txt_metadata["tahoe"]["linkcrtime"])
1320 def test_GET_FILEURL_json(self):
1321 # twisted.web.http.parse_qs ignores any query args without an '=', so
1322 # I can't do "GET /path?json", I have to do "GET /path/t=json"
1323 # instead. This may make it tricky to emulate the S3 interface
1325 d = self.GET(self.public_url + "/foo/bar.txt?t=json")
1327 self.failUnlessIsBarJSON(data)
1328 self.failUnlessHasBarDotTxtMetadata(data)
1330 d.addCallback(_check1)
1333 def test_GET_FILEURL_json_mutable_type(self):
1334 # The JSON should include format, which says whether the
1335 # file is SDMF or MDMF
1336 d = self.PUT("/uri?format=mdmf",
1337 self.NEWFILE_CONTENTS * 300000)
1338 d.addCallback(lambda filecap: self.GET("/uri/%s?t=json" % filecap))
1339 def _got_json(json, version):
1340 data = simplejson.loads(json)
1341 assert "filenode" == data[0]
1343 assert isinstance(data, dict)
1345 self.failUnlessIn("format", data)
1346 self.failUnlessEqual(data["format"], version)
1348 d.addCallback(_got_json, "MDMF")
1349 # Now make an SDMF file and check that it is reported correctly.
1350 d.addCallback(lambda ignored:
1351 self.PUT("/uri?format=sdmf",
1352 self.NEWFILE_CONTENTS * 300000))
1353 d.addCallback(lambda filecap: self.GET("/uri/%s?t=json" % filecap))
1354 d.addCallback(_got_json, "SDMF")
1357 def test_GET_FILEURL_json_mdmf(self):
1358 d = self.GET("/uri/%s?t=json" % urllib.quote(self._quux_txt_uri))
1359 d.addCallback(self.failUnlessIsQuuxJSON)
1362 def test_GET_FILEURL_json_missing(self):
1363 d = self.GET(self.public_url + "/foo/missing?json")
1364 d.addBoth(self.should404, "test_GET_FILEURL_json_missing")
1367 def test_GET_FILEURL_uri(self):
1368 d = self.GET(self.public_url + "/foo/bar.txt?t=uri")
1370 self.failUnlessReallyEqual(res, self._bar_txt_uri)
1371 d.addCallback(_check)
1372 d.addCallback(lambda res:
1373 self.GET(self.public_url + "/foo/bar.txt?t=readonly-uri"))
1375 # for now, for files, uris and readonly-uris are the same
1376 self.failUnlessReallyEqual(res, self._bar_txt_uri)
1377 d.addCallback(_check2)
1380 def test_GET_FILEURL_badtype(self):
1381 d = self.shouldHTTPError("GET t=bogus", 400, "Bad Request",
1384 self.public_url + "/foo/bar.txt?t=bogus")
1387 def test_CSS_FILE(self):
1388 d = self.GET("/tahoe.css", followRedirect=True)
1390 CSS_STYLE=re.compile('toolbar\s{.+text-align:\scenter.+toolbar-item.+display:\sinline',re.DOTALL)
1391 self.failUnless(CSS_STYLE.search(res), res)
1392 d.addCallback(_check)
1395 def test_GET_FILEURL_uri_missing(self):
1396 d = self.GET(self.public_url + "/foo/missing?t=uri")
1397 d.addBoth(self.should404, "test_GET_FILEURL_uri_missing")
1400 def _check_upload_and_mkdir_forms(self, html):
1401 # We should have a form to create a file, with radio buttons that allow
1402 # the user to toggle whether it is a CHK/LIT (default), SDMF, or MDMF file.
1403 self.failUnlessIn('name="t" value="upload"', html)
1404 self.failUnlessIn('input checked="checked" type="radio" id="upload-chk" value="chk" name="format"', html)
1405 self.failUnlessIn('input type="radio" id="upload-sdmf" value="sdmf" name="format"', html)
1406 self.failUnlessIn('input type="radio" id="upload-mdmf" value="mdmf" name="format"', html)
1408 # We should also have the ability to create a mutable directory, with
1409 # radio buttons that allow the user to toggle whether it is an SDMF (default)
1410 # or MDMF directory.
1411 self.failUnlessIn('name="t" value="mkdir"', html)
1412 self.failUnlessIn('input checked="checked" type="radio" id="mkdir-sdmf" value="sdmf" name="format"', html)
1413 self.failUnlessIn('input type="radio" id="mkdir-mdmf" value="mdmf" name="format"', html)
1415 self.failUnlessIn(FAVICON_MARKUP, html)
1417 def test_GET_DIRECTORY_html(self):
1418 d = self.GET(self.public_url + "/foo", followRedirect=True)
1420 self.failUnlessIn('<div class="toolbar-item"><a href="../../..">Return to Welcome page</a></div>', html)
1421 self._check_upload_and_mkdir_forms(html)
1422 self.failUnlessIn("quux", html)
1423 d.addCallback(_check)
1426 def test_GET_DIRECTORY_html_filenode_encoding(self):
1427 d = self.GET(self.public_url + "/foo", followRedirect=True)
1429 # Check if encoded entries are there
1430 self.failUnlessIn('@@named=/' + self._htmlname_urlencoded + '">'
1431 + self._htmlname_escaped + '</a>', html)
1432 self.failUnlessIn('value="' + self._htmlname_escaped_attr + '"', html)
1433 self.failIfIn(self._htmlname_escaped_double, html)
1434 # Make sure that Nevow escaping actually works by checking for unsafe characters
1435 # and that '&' is escaped.
1437 self.failUnlessIn(entity, self._htmlname_raw)
1438 self.failIfIn(entity, self._htmlname_escaped)
1439 self.failUnlessIn('&', re.sub(r'&(amp|lt|gt|quot|apos);', '', self._htmlname_raw))
1440 self.failIfIn('&', re.sub(r'&(amp|lt|gt|quot|apos);', '', self._htmlname_escaped))
1441 d.addCallback(_check)
1444 def test_GET_root_html(self):
1446 d.addCallback(self._check_upload_and_mkdir_forms)
1449 def test_GET_DIRURL(self):
1450 # the addSlash means we get a redirect here
1451 # from /uri/$URI/foo/ , we need ../../../ to get back to the root
1453 d = self.GET(self.public_url + "/foo", followRedirect=True)
1455 self.failUnlessIn('<a href="%s">Return to Welcome page' % ROOT, res)
1457 # the FILE reference points to a URI, but it should end in bar.txt
1458 bar_url = ("%s/file/%s/@@named=/bar.txt" %
1459 (ROOT, urllib.quote(self._bar_txt_uri)))
1460 get_bar = "".join([r'<td>FILE</td>',
1462 r'<a href="%s">bar.txt</a>' % bar_url,
1464 r'\s+<td align="right">%d</td>' % len(self.BAR_CONTENTS),
1466 self.failUnless(re.search(get_bar, res), res)
1467 for label in ['unlink', 'rename/move']:
1468 for line in res.split("\n"):
1469 # find the line that contains the relevant button for bar.txt
1470 if ("form action" in line and
1471 ('value="%s"' % (label,)) in line and
1472 'value="bar.txt"' in line):
1473 # the form target should use a relative URL
1474 foo_url = urllib.quote("%s/uri/%s/" % (ROOT, self._foo_uri))
1475 self.failUnlessIn('action="%s"' % foo_url, line)
1476 # and the when_done= should too
1477 #done_url = urllib.quote(???)
1478 #self.failUnlessIn('name="when_done" value="%s"' % done_url, line)
1480 # 'unlink' needs to use POST because it directly has a side effect
1481 if label == 'unlink':
1482 self.failUnlessIn('method="post"', line)
1485 self.fail("unable to find '%s bar.txt' line" % (label,))
1487 # the DIR reference just points to a URI
1488 sub_url = ("%s/uri/%s/" % (ROOT, urllib.quote(self._sub_uri)))
1489 get_sub = ((r'<td>DIR</td>')
1490 +r'\s+<td><a href="%s">sub</a></td>' % sub_url)
1491 self.failUnless(re.search(get_sub, res), res)
1492 d.addCallback(_check)
1494 # look at a readonly directory
1495 d.addCallback(lambda res:
1496 self.GET(self.public_url + "/reedownlee", followRedirect=True))
1498 self.failUnlessIn("(read-only)", res)
1499 self.failIfIn("Upload a file", res)
1500 d.addCallback(_check2)
1502 # and at a directory that contains a readonly directory
1503 d.addCallback(lambda res:
1504 self.GET(self.public_url, followRedirect=True))
1506 self.failUnless(re.search('<td>DIR-RO</td>'
1507 r'\s+<td><a href="[\.\/]+/uri/URI%3ADIR2-RO%3A[^"]+">reedownlee</a></td>', res), res)
1508 d.addCallback(_check3)
1510 # and an empty directory
1511 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty/"))
1513 self.failUnlessIn("directory is empty", res)
1514 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)
1515 self.failUnless(MKDIR_BUTTON_RE.search(res), res)
1516 d.addCallback(_check4)
1518 # and at a literal directory
1519 tiny_litdir_uri = "URI:DIR2-LIT:gqytunj2onug64tufqzdcosvkjetutcjkq5gw4tvm5vwszdgnz5hgyzufqydulbshj5x2lbm" # contains one child which is itself also LIT
1520 d.addCallback(lambda res:
1521 self.GET("/uri/" + tiny_litdir_uri + "/", followRedirect=True))
1523 self.failUnlessIn('(immutable)', res)
1524 self.failUnless(re.search('<td>FILE</td>'
1525 r'\s+<td><a href="[\.\/]+/file/URI%3ALIT%3Akrugkidfnzsc4/@@named=/short">short</a></td>', res), res)
1526 d.addCallback(_check5)
1529 def test_GET_DIRURL_badtype(self):
1530 d = self.shouldHTTPError("test_GET_DIRURL_badtype",
1534 self.public_url + "/foo?t=bogus")
1537 def test_GET_DIRURL_json(self):
1538 d = self.GET(self.public_url + "/foo?t=json")
1539 d.addCallback(self.failUnlessIsFooJSON)
1542 def test_GET_DIRURL_json_format(self):
1543 d = self.PUT(self.public_url + \
1544 "/foo/sdmf.txt?format=sdmf",
1545 self.NEWFILE_CONTENTS * 300000)
1546 d.addCallback(lambda ignored:
1547 self.PUT(self.public_url + \
1548 "/foo/mdmf.txt?format=mdmf",
1549 self.NEWFILE_CONTENTS * 300000))
1550 # Now we have an MDMF and SDMF file in the directory. If we GET
1551 # its JSON, we should see their encodings.
1552 d.addCallback(lambda ignored:
1553 self.GET(self.public_url + "/foo?t=json"))
1554 def _got_json(json):
1555 data = simplejson.loads(json)
1556 assert data[0] == "dirnode"
1559 kids = data['children']
1561 mdmf_data = kids['mdmf.txt'][1]
1562 self.failUnlessIn("format", mdmf_data)
1563 self.failUnlessEqual(mdmf_data["format"], "MDMF")
1565 sdmf_data = kids['sdmf.txt'][1]
1566 self.failUnlessIn("format", sdmf_data)
1567 self.failUnlessEqual(sdmf_data["format"], "SDMF")
1568 d.addCallback(_got_json)
1572 def test_POST_DIRURL_manifest_no_ophandle(self):
1573 d = self.shouldFail2(error.Error,
1574 "test_POST_DIRURL_manifest_no_ophandle",
1576 "slow operation requires ophandle=",
1577 self.POST, self.public_url, t="start-manifest")
1580 def test_POST_DIRURL_manifest(self):
1581 d = defer.succeed(None)
1582 def getman(ignored, output):
1583 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=125",
1584 followRedirect=True)
1585 d.addCallback(self.wait_for_operation, "125")
1586 d.addCallback(self.get_operation_results, "125", output)
1588 d.addCallback(getman, None)
1589 def _got_html(manifest):
1590 self.failUnlessIn("Manifest of SI=", manifest)
1591 self.failUnlessIn("<td>sub</td>", manifest)
1592 self.failUnlessIn(self._sub_uri, manifest)
1593 self.failUnlessIn("<td>sub/baz.txt</td>", manifest)
1594 self.failUnlessIn(FAVICON_MARKUP, manifest)
1595 d.addCallback(_got_html)
1597 # both t=status and unadorned GET should be identical
1598 d.addCallback(lambda res: self.GET("/operations/125"))
1599 d.addCallback(_got_html)
1601 d.addCallback(getman, "html")
1602 d.addCallback(_got_html)
1603 d.addCallback(getman, "text")
1604 def _got_text(manifest):
1605 self.failUnlessIn("\nsub " + self._sub_uri + "\n", manifest)
1606 self.failUnlessIn("\nsub/baz.txt URI:CHK:", manifest)
1607 d.addCallback(_got_text)
1608 d.addCallback(getman, "JSON")
1610 data = res["manifest"]
1612 for (path_list, cap) in data:
1613 got[tuple(path_list)] = cap
1614 self.failUnlessReallyEqual(to_str(got[(u"sub",)]), self._sub_uri)
1615 self.failUnlessIn((u"sub", u"baz.txt"), got)
1616 self.failUnlessIn("finished", res)
1617 self.failUnlessIn("origin", res)
1618 self.failUnlessIn("storage-index", res)
1619 self.failUnlessIn("verifycaps", res)
1620 self.failUnlessIn("stats", res)
1621 d.addCallback(_got_json)
1624 def test_POST_DIRURL_deepsize_no_ophandle(self):
1625 d = self.shouldFail2(error.Error,
1626 "test_POST_DIRURL_deepsize_no_ophandle",
1628 "slow operation requires ophandle=",
1629 self.POST, self.public_url, t="start-deep-size")
1632 def test_POST_DIRURL_deepsize(self):
1633 d = self.POST(self.public_url + "/foo/?t=start-deep-size&ophandle=126",
1634 followRedirect=True)
1635 d.addCallback(self.wait_for_operation, "126")
1636 d.addCallback(self.get_operation_results, "126", "json")
1637 def _got_json(data):
1638 self.failUnlessReallyEqual(data["finished"], True)
1640 self.failUnless(size > 1000)
1641 d.addCallback(_got_json)
1642 d.addCallback(self.get_operation_results, "126", "text")
1644 mo = re.search(r'^size: (\d+)$', res, re.M)
1645 self.failUnless(mo, res)
1646 size = int(mo.group(1))
1647 # with directories, the size varies.
1648 self.failUnless(size > 1000)
1649 d.addCallback(_got_text)
1652 def test_POST_DIRURL_deepstats_no_ophandle(self):
1653 d = self.shouldFail2(error.Error,
1654 "test_POST_DIRURL_deepstats_no_ophandle",
1656 "slow operation requires ophandle=",
1657 self.POST, self.public_url, t="start-deep-stats")
1660 def test_POST_DIRURL_deepstats(self):
1661 d = self.POST(self.public_url + "/foo/?t=start-deep-stats&ophandle=127",
1662 followRedirect=True)
1663 d.addCallback(self.wait_for_operation, "127")
1664 d.addCallback(self.get_operation_results, "127", "json")
1665 def _got_json(stats):
1666 expected = {"count-immutable-files": 4,
1667 "count-mutable-files": 2,
1668 "count-literal-files": 0,
1670 "count-directories": 3,
1671 "size-immutable-files": 76,
1672 "size-literal-files": 0,
1673 #"size-directories": 1912, # varies
1674 #"largest-directory": 1590,
1675 "largest-directory-children": 8,
1676 "largest-immutable-file": 19,
1678 for k,v in expected.iteritems():
1679 self.failUnlessReallyEqual(stats[k], v,
1680 "stats[%s] was %s, not %s" %
1682 self.failUnlessReallyEqual(stats["size-files-histogram"],
1684 d.addCallback(_got_json)
1687 def test_POST_DIRURL_stream_manifest(self):
1688 d = self.POST(self.public_url + "/foo/?t=stream-manifest")
1690 self.failUnless(res.endswith("\n"))
1691 units = [simplejson.loads(t) for t in res[:-1].split("\n")]
1692 self.failUnlessReallyEqual(len(units), 10)
1693 self.failUnlessEqual(units[-1]["type"], "stats")
1695 self.failUnlessEqual(first["path"], [])
1696 self.failUnlessReallyEqual(to_str(first["cap"]), self._foo_uri)
1697 self.failUnlessEqual(first["type"], "directory")
1698 baz = [u for u in units[:-1] if to_str(u["cap"]) == self._baz_file_uri][0]
1699 self.failUnlessEqual(baz["path"], ["sub", "baz.txt"])
1700 self.failIfEqual(baz["storage-index"], None)
1701 self.failIfEqual(baz["verifycap"], None)
1702 self.failIfEqual(baz["repaircap"], None)
1703 # XXX: Add quux and baz to this test.
1705 d.addCallback(_check)
1708 def test_GET_DIRURL_uri(self):
1709 d = self.GET(self.public_url + "/foo?t=uri")
1711 self.failUnlessReallyEqual(to_str(res), self._foo_uri)
1712 d.addCallback(_check)
1715 def test_GET_DIRURL_readonly_uri(self):
1716 d = self.GET(self.public_url + "/foo?t=readonly-uri")
1718 self.failUnlessReallyEqual(to_str(res), self._foo_readonly_uri)
1719 d.addCallback(_check)
1722 def test_PUT_NEWDIRURL(self):
1723 d = self.PUT(self.public_url + "/foo/newdir?t=mkdir", "")
1724 d.addCallback(lambda res:
1725 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1726 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1727 d.addCallback(self.failUnlessNodeKeysAre, [])
1730 def test_PUT_NEWDIRURL_mdmf(self):
1731 d = self.PUT(self.public_url + "/foo/newdir?t=mkdir&format=mdmf", "")
1732 d.addCallback(lambda res:
1733 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1734 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1735 d.addCallback(lambda node:
1736 self.failUnlessEqual(node._node.get_version(), MDMF_VERSION))
1739 def test_PUT_NEWDIRURL_sdmf(self):
1740 d = self.PUT(self.public_url + "/foo/newdir?t=mkdir&format=sdmf",
1742 d.addCallback(lambda res:
1743 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1744 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1745 d.addCallback(lambda node:
1746 self.failUnlessEqual(node._node.get_version(), SDMF_VERSION))
1749 def test_PUT_NEWDIRURL_bad_format(self):
1750 return self.shouldHTTPError("PUT_NEWDIRURL_bad_format",
1751 400, "Bad Request", "Unknown format: foo",
1752 self.PUT, self.public_url +
1753 "/foo/newdir=?t=mkdir&format=foo", "")
1755 def test_POST_NEWDIRURL(self):
1756 d = self.POST2(self.public_url + "/foo/newdir?t=mkdir", "")
1757 d.addCallback(lambda res:
1758 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1759 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1760 d.addCallback(self.failUnlessNodeKeysAre, [])
1763 def test_POST_NEWDIRURL_mdmf(self):
1764 d = self.POST2(self.public_url + "/foo/newdir?t=mkdir&format=mdmf", "")
1765 d.addCallback(lambda res:
1766 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1767 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1768 d.addCallback(lambda node:
1769 self.failUnlessEqual(node._node.get_version(), MDMF_VERSION))
1772 def test_POST_NEWDIRURL_sdmf(self):
1773 d = self.POST2(self.public_url + "/foo/newdir?t=mkdir&format=sdmf", "")
1774 d.addCallback(lambda res:
1775 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1776 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1777 d.addCallback(lambda node:
1778 self.failUnlessEqual(node._node.get_version(), SDMF_VERSION))
1781 def test_POST_NEWDIRURL_bad_format(self):
1782 return self.shouldHTTPError("POST_NEWDIRURL_bad_format",
1783 400, "Bad Request", "Unknown format: foo",
1784 self.POST2, self.public_url + \
1785 "/foo/newdir?t=mkdir&format=foo", "")
1787 def test_POST_NEWDIRURL_emptyname(self):
1788 # an empty pathname component (i.e. a double-slash) is disallowed
1789 d = self.shouldFail2(error.Error, "POST_NEWDIRURL_emptyname",
1791 "The webapi does not allow empty pathname components, i.e. a double slash",
1792 self.POST, self.public_url + "//?t=mkdir")
1795 def _do_POST_NEWDIRURL_initial_children_test(self, version=None):
1796 (newkids, caps) = self._create_initial_children()
1797 query = "/foo/newdir?t=mkdir-with-children"
1798 if version == MDMF_VERSION:
1799 query += "&format=mdmf"
1800 elif version == SDMF_VERSION:
1801 query += "&format=sdmf"
1803 version = SDMF_VERSION # for later
1804 d = self.POST2(self.public_url + query,
1805 simplejson.dumps(newkids))
1807 n = self.s.create_node_from_uri(uri.strip())
1808 d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
1809 self.failUnlessEqual(n._node.get_version(), version)
1810 d2.addCallback(lambda ign:
1811 self.failUnlessROChildURIIs(n, u"child-imm",
1813 d2.addCallback(lambda ign:
1814 self.failUnlessRWChildURIIs(n, u"child-mutable",
1816 d2.addCallback(lambda ign:
1817 self.failUnlessROChildURIIs(n, u"child-mutable-ro",
1819 d2.addCallback(lambda ign:
1820 self.failUnlessROChildURIIs(n, u"unknownchild-ro",
1821 caps['unknown_rocap']))
1822 d2.addCallback(lambda ign:
1823 self.failUnlessRWChildURIIs(n, u"unknownchild-rw",
1824 caps['unknown_rwcap']))
1825 d2.addCallback(lambda ign:
1826 self.failUnlessROChildURIIs(n, u"unknownchild-imm",
1827 caps['unknown_immcap']))
1828 d2.addCallback(lambda ign:
1829 self.failUnlessRWChildURIIs(n, u"dirchild",
1831 d2.addCallback(lambda ign:
1832 self.failUnlessROChildURIIs(n, u"dirchild-lit",
1834 d2.addCallback(lambda ign:
1835 self.failUnlessROChildURIIs(n, u"dirchild-empty",
1836 caps['emptydircap']))
1838 d.addCallback(_check)
1839 d.addCallback(lambda res:
1840 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1841 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1842 d.addCallback(self.failUnlessNodeKeysAre, newkids.keys())
1843 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1844 d.addCallback(self.failUnlessROChildURIIs, u"child-imm", caps['filecap1'])
1847 def test_POST_NEWDIRURL_initial_children(self):
1848 return self._do_POST_NEWDIRURL_initial_children_test()
1850 def test_POST_NEWDIRURL_initial_children_mdmf(self):
1851 return self._do_POST_NEWDIRURL_initial_children_test(MDMF_VERSION)
1853 def test_POST_NEWDIRURL_initial_children_sdmf(self):
1854 return self._do_POST_NEWDIRURL_initial_children_test(SDMF_VERSION)
1856 def test_POST_NEWDIRURL_initial_children_bad_format(self):
1857 (newkids, caps) = self._create_initial_children()
1858 return self.shouldHTTPError("POST_NEWDIRURL_initial_children_bad_format",
1859 400, "Bad Request", "Unknown format: foo",
1860 self.POST2, self.public_url + \
1861 "/foo/newdir?t=mkdir-with-children&format=foo",
1862 simplejson.dumps(newkids))
1864 def test_POST_NEWDIRURL_immutable(self):
1865 (newkids, caps) = self._create_immutable_children()
1866 d = self.POST2(self.public_url + "/foo/newdir?t=mkdir-immutable",
1867 simplejson.dumps(newkids))
1869 n = self.s.create_node_from_uri(uri.strip())
1870 d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
1871 d2.addCallback(lambda ign:
1872 self.failUnlessROChildURIIs(n, u"child-imm",
1874 d2.addCallback(lambda ign:
1875 self.failUnlessROChildURIIs(n, u"unknownchild-imm",
1876 caps['unknown_immcap']))
1877 d2.addCallback(lambda ign:
1878 self.failUnlessROChildURIIs(n, u"dirchild-imm",
1880 d2.addCallback(lambda ign:
1881 self.failUnlessROChildURIIs(n, u"dirchild-lit",
1883 d2.addCallback(lambda ign:
1884 self.failUnlessROChildURIIs(n, u"dirchild-empty",
1885 caps['emptydircap']))
1887 d.addCallback(_check)
1888 d.addCallback(lambda res:
1889 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1890 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1891 d.addCallback(self.failUnlessNodeKeysAre, newkids.keys())
1892 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1893 d.addCallback(self.failUnlessROChildURIIs, u"child-imm", caps['filecap1'])
1894 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1895 d.addCallback(self.failUnlessROChildURIIs, u"unknownchild-imm", caps['unknown_immcap'])
1896 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1897 d.addCallback(self.failUnlessROChildURIIs, u"dirchild-imm", caps['immdircap'])
1898 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1899 d.addCallback(self.failUnlessROChildURIIs, u"dirchild-lit", caps['litdircap'])
1900 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1901 d.addCallback(self.failUnlessROChildURIIs, u"dirchild-empty", caps['emptydircap'])
1902 d.addErrback(self.explain_web_error)
1905 def test_POST_NEWDIRURL_immutable_bad(self):
1906 (newkids, caps) = self._create_initial_children()
1907 d = self.shouldFail2(error.Error, "test_POST_NEWDIRURL_immutable_bad",
1909 "needed to be immutable but was not",
1911 self.public_url + "/foo/newdir?t=mkdir-immutable",
1912 simplejson.dumps(newkids))
1915 def test_PUT_NEWDIRURL_exists(self):
1916 d = self.PUT(self.public_url + "/foo/sub?t=mkdir", "")
1917 d.addCallback(lambda res:
1918 self.failUnlessNodeHasChild(self._foo_node, u"sub"))
1919 d.addCallback(lambda res: self._foo_node.get(u"sub"))
1920 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
1923 def test_PUT_NEWDIRURL_blocked(self):
1924 d = self.shouldFail2(error.Error, "PUT_NEWDIRURL_blocked",
1925 "409 Conflict", "Unable to create directory 'bar.txt': a file was in the way",
1927 self.public_url + "/foo/bar.txt/sub?t=mkdir", "")
1928 d.addCallback(lambda res:
1929 self.failUnlessNodeHasChild(self._foo_node, u"sub"))
1930 d.addCallback(lambda res: self._foo_node.get(u"sub"))
1931 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
1934 def test_PUT_NEWDIRURL_mkdirs(self):
1935 d = self.PUT(self.public_url + "/foo/subdir/newdir?t=mkdir", "")
1936 d.addCallback(lambda res:
1937 self.failIfNodeHasChild(self._foo_node, u"newdir"))
1938 d.addCallback(lambda res:
1939 self.failUnlessNodeHasChild(self._foo_node, u"subdir"))
1940 d.addCallback(lambda res:
1941 self._foo_node.get_child_at_path(u"subdir/newdir"))
1942 d.addCallback(self.failUnlessNodeKeysAre, [])
1945 def test_PUT_NEWDIRURL_mkdirs_mdmf(self):
1946 d = self.PUT(self.public_url + "/foo/subdir/newdir?t=mkdir&format=mdmf", "")
1947 d.addCallback(lambda ignored:
1948 self.failUnlessNodeHasChild(self._foo_node, u"subdir"))
1949 d.addCallback(lambda ignored:
1950 self.failIfNodeHasChild(self._foo_node, u"newdir"))
1951 d.addCallback(lambda ignored:
1952 self._foo_node.get_child_at_path(u"subdir"))
1953 def _got_subdir(subdir):
1954 # XXX: What we want?
1955 #self.failUnlessEqual(subdir._node.get_version(), MDMF_VERSION)
1956 self.failUnlessNodeHasChild(subdir, u"newdir")
1957 return subdir.get_child_at_path(u"newdir")
1958 d.addCallback(_got_subdir)
1959 d.addCallback(lambda newdir:
1960 self.failUnlessEqual(newdir._node.get_version(), MDMF_VERSION))
1963 def test_PUT_NEWDIRURL_mkdirs_sdmf(self):
1964 d = self.PUT(self.public_url + "/foo/subdir/newdir?t=mkdir&format=sdmf", "")
1965 d.addCallback(lambda ignored:
1966 self.failUnlessNodeHasChild(self._foo_node, u"subdir"))
1967 d.addCallback(lambda ignored:
1968 self.failIfNodeHasChild(self._foo_node, u"newdir"))
1969 d.addCallback(lambda ignored:
1970 self._foo_node.get_child_at_path(u"subdir"))
1971 def _got_subdir(subdir):
1972 # XXX: What we want?
1973 #self.failUnlessEqual(subdir._node.get_version(), MDMF_VERSION)
1974 self.failUnlessNodeHasChild(subdir, u"newdir")
1975 return subdir.get_child_at_path(u"newdir")
1976 d.addCallback(_got_subdir)
1977 d.addCallback(lambda newdir:
1978 self.failUnlessEqual(newdir._node.get_version(), SDMF_VERSION))
1981 def test_PUT_NEWDIRURL_mkdirs_bad_format(self):
1982 return self.shouldHTTPError("PUT_NEWDIRURL_mkdirs_bad_format",
1983 400, "Bad Request", "Unknown format: foo",
1984 self.PUT, self.public_url + \
1985 "/foo/subdir/newdir?t=mkdir&format=foo",
1988 def test_DELETE_DIRURL(self):
1989 d = self.DELETE(self.public_url + "/foo")
1990 d.addCallback(lambda res:
1991 self.failIfNodeHasChild(self.public_root, u"foo"))
1994 def test_DELETE_DIRURL_missing(self):
1995 d = self.DELETE(self.public_url + "/foo/missing")
1996 d.addBoth(self.should404, "test_DELETE_DIRURL_missing")
1997 d.addCallback(lambda res:
1998 self.failUnlessNodeHasChild(self.public_root, u"foo"))
2001 def test_DELETE_DIRURL_missing2(self):
2002 d = self.DELETE(self.public_url + "/missing")
2003 d.addBoth(self.should404, "test_DELETE_DIRURL_missing2")
2006 def dump_root(self):
2008 w = webish.DirnodeWalkerMixin()
2009 def visitor(childpath, childnode, metadata):
2011 d = w.walk(self.public_root, visitor)
2014 def failUnlessNodeKeysAre(self, node, expected_keys):
2015 for k in expected_keys:
2016 assert isinstance(k, unicode)
2018 def _check(children):
2019 self.failUnlessReallyEqual(sorted(children.keys()), sorted(expected_keys))
2020 d.addCallback(_check)
2022 def failUnlessNodeHasChild(self, node, name):
2023 assert isinstance(name, unicode)
2025 def _check(children):
2026 self.failUnlessIn(name, children)
2027 d.addCallback(_check)
2029 def failIfNodeHasChild(self, node, name):
2030 assert isinstance(name, unicode)
2032 def _check(children):
2033 self.failIfIn(name, children)
2034 d.addCallback(_check)
2037 def failUnlessChildContentsAre(self, node, name, expected_contents):
2038 assert isinstance(name, unicode)
2039 d = node.get_child_at_path(name)
2040 d.addCallback(lambda node: download_to_data(node))
2041 def _check(contents):
2042 self.failUnlessReallyEqual(contents, expected_contents)
2043 d.addCallback(_check)
2046 def failUnlessMutableChildContentsAre(self, node, name, expected_contents):
2047 assert isinstance(name, unicode)
2048 d = node.get_child_at_path(name)
2049 d.addCallback(lambda node: node.download_best_version())
2050 def _check(contents):
2051 self.failUnlessReallyEqual(contents, expected_contents)
2052 d.addCallback(_check)
2055 def failUnlessRWChildURIIs(self, node, name, expected_uri):
2056 assert isinstance(name, unicode)
2057 d = node.get_child_at_path(name)
2059 self.failUnless(child.is_unknown() or not child.is_readonly())
2060 self.failUnlessReallyEqual(child.get_uri(), expected_uri.strip())
2061 self.failUnlessReallyEqual(child.get_write_uri(), expected_uri.strip())
2062 expected_ro_uri = self._make_readonly(expected_uri)
2064 self.failUnlessReallyEqual(child.get_readonly_uri(), expected_ro_uri.strip())
2065 d.addCallback(_check)
2068 def failUnlessROChildURIIs(self, node, name, expected_uri):
2069 assert isinstance(name, unicode)
2070 d = node.get_child_at_path(name)
2072 self.failUnless(child.is_unknown() or child.is_readonly())
2073 self.failUnlessReallyEqual(child.get_write_uri(), None)
2074 self.failUnlessReallyEqual(child.get_uri(), expected_uri.strip())
2075 self.failUnlessReallyEqual(child.get_readonly_uri(), expected_uri.strip())
2076 d.addCallback(_check)
2079 def failUnlessURIMatchesRWChild(self, got_uri, node, name):
2080 assert isinstance(name, unicode)
2081 d = node.get_child_at_path(name)
2083 self.failUnless(child.is_unknown() or not child.is_readonly())
2084 self.failUnlessReallyEqual(child.get_uri(), got_uri.strip())
2085 self.failUnlessReallyEqual(child.get_write_uri(), got_uri.strip())
2086 expected_ro_uri = self._make_readonly(got_uri)
2088 self.failUnlessReallyEqual(child.get_readonly_uri(), expected_ro_uri.strip())
2089 d.addCallback(_check)
2092 def failUnlessURIMatchesROChild(self, got_uri, node, name):
2093 assert isinstance(name, unicode)
2094 d = node.get_child_at_path(name)
2096 self.failUnless(child.is_unknown() or child.is_readonly())
2097 self.failUnlessReallyEqual(child.get_write_uri(), None)
2098 self.failUnlessReallyEqual(got_uri.strip(), child.get_uri())
2099 self.failUnlessReallyEqual(got_uri.strip(), child.get_readonly_uri())
2100 d.addCallback(_check)
2103 def failUnlessCHKURIHasContents(self, got_uri, contents):
2104 self.failUnless(self.get_all_contents()[got_uri] == contents)
2106 def test_POST_upload(self):
2107 d = self.POST(self.public_url + "/foo", t="upload",
2108 file=("new.txt", self.NEWFILE_CONTENTS))
2110 d.addCallback(self.failUnlessURIMatchesROChild, fn, u"new.txt")
2111 d.addCallback(lambda res:
2112 self.failUnlessChildContentsAre(fn, u"new.txt",
2113 self.NEWFILE_CONTENTS))
2116 def test_POST_upload_unicode(self):
2117 filename = u"n\u00e9wer.txt" # n e-acute w e r . t x t
2118 d = self.POST(self.public_url + "/foo", t="upload",
2119 file=(filename, self.NEWFILE_CONTENTS))
2121 d.addCallback(self.failUnlessURIMatchesROChild, fn, filename)
2122 d.addCallback(lambda res:
2123 self.failUnlessChildContentsAre(fn, filename,
2124 self.NEWFILE_CONTENTS))
2125 target_url = self.public_url + "/foo/" + filename.encode("utf-8")
2126 d.addCallback(lambda res: self.GET(target_url))
2127 d.addCallback(lambda contents: self.failUnlessReallyEqual(contents,
2128 self.NEWFILE_CONTENTS,
2132 def test_POST_upload_unicode_named(self):
2133 filename = u"n\u00e9wer.txt" # n e-acute w e r . t x t
2134 d = self.POST(self.public_url + "/foo", t="upload",
2136 file=("overridden", self.NEWFILE_CONTENTS))
2138 d.addCallback(self.failUnlessURIMatchesROChild, fn, filename)
2139 d.addCallback(lambda res:
2140 self.failUnlessChildContentsAre(fn, filename,
2141 self.NEWFILE_CONTENTS))
2142 target_url = self.public_url + "/foo/" + filename.encode("utf-8")
2143 d.addCallback(lambda res: self.GET(target_url))
2144 d.addCallback(lambda contents: self.failUnlessReallyEqual(contents,
2145 self.NEWFILE_CONTENTS,
2149 def test_POST_upload_no_link(self):
2150 d = self.POST("/uri", t="upload",
2151 file=("new.txt", self.NEWFILE_CONTENTS))
2152 def _check_upload_results(page):
2153 # this should be a page which describes the results of the upload
2154 # that just finished.
2155 self.failUnlessIn("Upload Results:", page)
2156 self.failUnlessIn("URI:", page)
2157 uri_re = re.compile("URI: <tt><span>(.*)</span>")
2158 mo = uri_re.search(page)
2159 self.failUnless(mo, page)
2160 new_uri = mo.group(1)
2162 d.addCallback(_check_upload_results)
2163 d.addCallback(self.failUnlessCHKURIHasContents, self.NEWFILE_CONTENTS)
2166 def test_POST_upload_no_link_whendone(self):
2167 d = self.POST("/uri", t="upload", when_done="/",
2168 file=("new.txt", self.NEWFILE_CONTENTS))
2169 d.addBoth(self.shouldRedirect, "/")
2172 def shouldRedirect2(self, which, checker, callable, *args, **kwargs):
2173 d = defer.maybeDeferred(callable, *args, **kwargs)
2175 if isinstance(res, failure.Failure):
2176 res.trap(error.PageRedirect)
2177 statuscode = res.value.status
2178 target = res.value.location
2179 return checker(statuscode, target)
2180 self.fail("%s: callable was supposed to redirect, not return '%s'"
2185 def test_POST_upload_no_link_whendone_results(self):
2186 def check(statuscode, target):
2187 self.failUnlessReallyEqual(statuscode, str(http.FOUND))
2188 self.failUnless(target.startswith(self.webish_url), target)
2189 return client.getPage(target, method="GET")
2190 # We encode "uri" as "%75ri" to exercise a case affected by ticket #1860.
2191 d = self.shouldRedirect2("test_POST_upload_no_link_whendone_results",
2193 self.POST, "/uri", t="upload",
2194 when_done="/%75ri/%(uri)s",
2195 file=("new.txt", self.NEWFILE_CONTENTS))
2196 d.addCallback(lambda res:
2197 self.failUnlessReallyEqual(res, self.NEWFILE_CONTENTS))
2200 def test_POST_upload_no_link_mutable(self):
2201 d = self.POST("/uri", t="upload", mutable="true",
2202 file=("new.txt", self.NEWFILE_CONTENTS))
2203 def _check(filecap):
2204 filecap = filecap.strip()
2205 self.failUnless(filecap.startswith("URI:SSK:"), filecap)
2206 self.filecap = filecap
2207 u = uri.WriteableSSKFileURI.init_from_string(filecap)
2208 self.failUnlessIn(u.get_storage_index(), self.get_all_contents())
2209 n = self.s.create_node_from_uri(filecap)
2210 return n.download_best_version()
2211 d.addCallback(_check)
2213 self.failUnlessReallyEqual(data, self.NEWFILE_CONTENTS)
2214 return self.GET("/uri/%s" % urllib.quote(self.filecap))
2215 d.addCallback(_check2)
2217 self.failUnlessReallyEqual(data, self.NEWFILE_CONTENTS)
2218 return self.GET("/file/%s" % urllib.quote(self.filecap))
2219 d.addCallback(_check3)
2221 self.failUnlessReallyEqual(data, self.NEWFILE_CONTENTS)
2222 d.addCallback(_check4)
2225 def test_POST_upload_no_link_mutable_toobig(self):
2226 # The SDMF size limit is no longer in place, so we should be
2227 # able to upload mutable files that are as large as we want them
2229 d = self.POST("/uri", t="upload", mutable="true",
2230 file=("new.txt", "b" * (self.s.MUTABLE_SIZELIMIT + 1)))
2234 def test_POST_upload_format_unlinked(self):
2235 def _check_upload_unlinked(ign, format, uri_prefix):
2236 filename = format + ".txt"
2237 d = self.POST("/uri?t=upload&format=" + format,
2238 file=(filename, self.NEWFILE_CONTENTS * 300000))
2239 def _got_results(results):
2240 if format.upper() in ("SDMF", "MDMF"):
2241 # webapi.rst says this returns a filecap
2244 # for immutable, it returns an "upload results page", and
2245 # the filecap is buried inside
2246 line = [l for l in results.split("\n") if "URI: " in l][0]
2247 mo = re.search(r'<span>([^<]+)</span>', line)
2248 filecap = mo.group(1)
2249 self.failUnless(filecap.startswith(uri_prefix),
2250 (uri_prefix, filecap))
2251 return self.GET("/uri/%s?t=json" % filecap)
2252 d.addCallback(_got_results)
2253 def _got_json(json):
2254 data = simplejson.loads(json)
2256 self.failUnlessIn("format", data)
2257 self.failUnlessEqual(data["format"], format.upper())
2258 d.addCallback(_got_json)
2260 d = defer.succeed(None)
2261 d.addCallback(_check_upload_unlinked, "chk", "URI:CHK")
2262 d.addCallback(_check_upload_unlinked, "CHK", "URI:CHK")
2263 d.addCallback(_check_upload_unlinked, "sdmf", "URI:SSK")
2264 d.addCallback(_check_upload_unlinked, "mdmf", "URI:MDMF")
2267 def test_POST_upload_bad_format_unlinked(self):
2268 return self.shouldHTTPError("POST_upload_bad_format_unlinked",
2269 400, "Bad Request", "Unknown format: foo",
2271 "/uri?t=upload&format=foo",
2272 file=("foo.txt", self.NEWFILE_CONTENTS * 300000))
2274 def test_POST_upload_format(self):
2275 def _check_upload(ign, format, uri_prefix, fn=None):
2276 filename = format + ".txt"
2277 d = self.POST(self.public_url +
2278 "/foo?t=upload&format=" + format,
2279 file=(filename, self.NEWFILE_CONTENTS * 300000))
2280 def _got_filecap(filecap):
2282 filenameu = unicode(filename)
2283 self.failUnlessURIMatchesRWChild(filecap, fn, filenameu)
2284 self.failUnless(filecap.startswith(uri_prefix))
2285 return self.GET(self.public_url + "/foo/%s?t=json" % filename)
2286 d.addCallback(_got_filecap)
2287 def _got_json(json):
2288 data = simplejson.loads(json)
2290 self.failUnlessIn("format", data)
2291 self.failUnlessEqual(data["format"], format.upper())
2292 d.addCallback(_got_json)
2295 d = defer.succeed(None)
2296 d.addCallback(_check_upload, "chk", "URI:CHK")
2297 d.addCallback(_check_upload, "sdmf", "URI:SSK", self._foo_node)
2298 d.addCallback(_check_upload, "mdmf", "URI:MDMF")
2299 d.addCallback(_check_upload, "MDMF", "URI:MDMF")
2302 def test_POST_upload_bad_format(self):
2303 return self.shouldHTTPError("POST_upload_bad_format",
2304 400, "Bad Request", "Unknown format: foo",
2305 self.POST, self.public_url + \
2306 "/foo?t=upload&format=foo",
2307 file=("foo.txt", self.NEWFILE_CONTENTS * 300000))
2309 def test_POST_upload_mutable(self):
2310 # this creates a mutable file
2311 d = self.POST(self.public_url + "/foo", t="upload", mutable="true",
2312 file=("new.txt", self.NEWFILE_CONTENTS))
2314 d.addCallback(self.failUnlessURIMatchesRWChild, fn, u"new.txt")
2315 d.addCallback(lambda res:
2316 self.failUnlessMutableChildContentsAre(fn, u"new.txt",
2317 self.NEWFILE_CONTENTS))
2318 d.addCallback(lambda res: self._foo_node.get(u"new.txt"))
2320 self.failUnless(IMutableFileNode.providedBy(newnode))
2321 self.failUnless(newnode.is_mutable())
2322 self.failIf(newnode.is_readonly())
2323 self._mutable_node = newnode
2324 self._mutable_uri = newnode.get_uri()
2327 # now upload it again and make sure that the URI doesn't change
2328 NEWER_CONTENTS = self.NEWFILE_CONTENTS + "newer\n"
2329 d.addCallback(lambda res:
2330 self.POST(self.public_url + "/foo", t="upload",
2332 file=("new.txt", NEWER_CONTENTS)))
2333 d.addCallback(self.failUnlessURIMatchesRWChild, fn, u"new.txt")
2334 d.addCallback(lambda res:
2335 self.failUnlessMutableChildContentsAre(fn, u"new.txt",
2337 d.addCallback(lambda res: self._foo_node.get(u"new.txt"))
2339 self.failUnless(IMutableFileNode.providedBy(newnode))
2340 self.failUnless(newnode.is_mutable())
2341 self.failIf(newnode.is_readonly())
2342 self.failUnlessReallyEqual(self._mutable_uri, newnode.get_uri())
2343 d.addCallback(_got2)
2345 # upload a second time, using PUT instead of POST
2346 NEW2_CONTENTS = NEWER_CONTENTS + "overwrite with PUT\n"
2347 d.addCallback(lambda res:
2348 self.PUT(self.public_url + "/foo/new.txt", NEW2_CONTENTS))
2349 d.addCallback(self.failUnlessURIMatchesRWChild, fn, u"new.txt")
2350 d.addCallback(lambda res:
2351 self.failUnlessMutableChildContentsAre(fn, u"new.txt",
2354 # finally list the directory, since mutable files are displayed
2355 # slightly differently
2357 d.addCallback(lambda res:
2358 self.GET(self.public_url + "/foo/",
2359 followRedirect=True))
2360 def _check_page(res):
2361 # TODO: assert more about the contents
2362 self.failUnlessIn("SSK", res)
2364 d.addCallback(_check_page)
2366 d.addCallback(lambda res: self._foo_node.get(u"new.txt"))
2368 self.failUnless(IMutableFileNode.providedBy(newnode))
2369 self.failUnless(newnode.is_mutable())
2370 self.failIf(newnode.is_readonly())
2371 self.failUnlessReallyEqual(self._mutable_uri, newnode.get_uri())
2372 d.addCallback(_got3)
2374 # look at the JSON form of the enclosing directory
2375 d.addCallback(lambda res:
2376 self.GET(self.public_url + "/foo/?t=json",
2377 followRedirect=True))
2378 def _check_page_json(res):
2379 parsed = simplejson.loads(res)
2380 self.failUnlessEqual(parsed[0], "dirnode")
2381 children = dict( [(unicode(name),value)
2383 in parsed[1]["children"].iteritems()] )
2384 self.failUnlessIn(u"new.txt", children)
2385 new_json = children[u"new.txt"]
2386 self.failUnlessEqual(new_json[0], "filenode")
2387 self.failUnless(new_json[1]["mutable"])
2388 self.failUnlessReallyEqual(to_str(new_json[1]["rw_uri"]), self._mutable_uri)
2389 ro_uri = self._mutable_node.get_readonly().to_string()
2390 self.failUnlessReallyEqual(to_str(new_json[1]["ro_uri"]), ro_uri)
2391 d.addCallback(_check_page_json)
2393 # and the JSON form of the file
2394 d.addCallback(lambda res:
2395 self.GET(self.public_url + "/foo/new.txt?t=json"))
2396 def _check_file_json(res):
2397 parsed = simplejson.loads(res)
2398 self.failUnlessEqual(parsed[0], "filenode")
2399 self.failUnless(parsed[1]["mutable"])
2400 self.failUnlessReallyEqual(to_str(parsed[1]["rw_uri"]), self._mutable_uri)
2401 ro_uri = self._mutable_node.get_readonly().to_string()
2402 self.failUnlessReallyEqual(to_str(parsed[1]["ro_uri"]), ro_uri)
2403 d.addCallback(_check_file_json)
2405 # and look at t=uri and t=readonly-uri
2406 d.addCallback(lambda res:
2407 self.GET(self.public_url + "/foo/new.txt?t=uri"))
2408 d.addCallback(lambda res: self.failUnlessReallyEqual(res, self._mutable_uri))
2409 d.addCallback(lambda res:
2410 self.GET(self.public_url + "/foo/new.txt?t=readonly-uri"))
2411 def _check_ro_uri(res):
2412 ro_uri = self._mutable_node.get_readonly().to_string()
2413 self.failUnlessReallyEqual(res, ro_uri)
2414 d.addCallback(_check_ro_uri)
2416 # make sure we can get to it from /uri/URI
2417 d.addCallback(lambda res:
2418 self.GET("/uri/%s" % urllib.quote(self._mutable_uri)))
2419 d.addCallback(lambda res:
2420 self.failUnlessReallyEqual(res, NEW2_CONTENTS))
2422 # and that HEAD computes the size correctly
2423 d.addCallback(lambda res:
2424 self.HEAD(self.public_url + "/foo/new.txt",
2425 return_response=True))
2426 def _got_headers((res, status, headers)):
2427 self.failUnlessReallyEqual(res, "")
2428 self.failUnlessReallyEqual(headers["content-length"][0],
2429 str(len(NEW2_CONTENTS)))
2430 self.failUnlessReallyEqual(headers["content-type"], ["text/plain"])
2431 d.addCallback(_got_headers)
2433 # make sure that outdated size limits aren't enforced anymore.
2434 d.addCallback(lambda ignored:
2435 self.POST(self.public_url + "/foo", t="upload",
2438 "b" * (self.s.MUTABLE_SIZELIMIT+1))))
2439 d.addErrback(self.dump_error)
2442 def test_POST_upload_mutable_toobig(self):
2443 # SDMF had a size limti that was removed a while ago. MDMF has
2444 # never had a size limit. Test to make sure that we do not
2445 # encounter errors when trying to upload large mutable files,
2446 # since there should be no coded prohibitions regarding large
2448 d = self.POST(self.public_url + "/foo",
2449 t="upload", mutable="true",
2450 file=("new.txt", "b" * (self.s.MUTABLE_SIZELIMIT + 1)))
2453 def dump_error(self, f):
2454 # if the web server returns an error code (like 400 Bad Request),
2455 # web.client.getPage puts the HTTP response body into the .response
2456 # attribute of the exception object that it gives back. It does not
2457 # appear in the Failure's repr(), so the ERROR that trial displays
2458 # will be rather terse and unhelpful. addErrback this method to the
2459 # end of your chain to get more information out of these errors.
2460 if f.check(error.Error):
2461 print "web.error.Error:"
2463 print f.value.response
2466 def test_POST_upload_replace(self):
2467 d = self.POST(self.public_url + "/foo", t="upload",
2468 file=("bar.txt", self.NEWFILE_CONTENTS))
2470 d.addCallback(self.failUnlessURIMatchesROChild, fn, u"bar.txt")
2471 d.addCallback(lambda res:
2472 self.failUnlessChildContentsAre(fn, u"bar.txt",
2473 self.NEWFILE_CONTENTS))
2476 def test_POST_upload_no_replace_ok(self):
2477 d = self.POST(self.public_url + "/foo?replace=false", t="upload",
2478 file=("new.txt", self.NEWFILE_CONTENTS))
2479 d.addCallback(lambda res: self.GET(self.public_url + "/foo/new.txt"))
2480 d.addCallback(lambda res: self.failUnlessReallyEqual(res,
2481 self.NEWFILE_CONTENTS))
2484 def test_POST_upload_no_replace_queryarg(self):
2485 d = self.POST(self.public_url + "/foo?replace=false", t="upload",
2486 file=("bar.txt", self.NEWFILE_CONTENTS))
2487 d.addBoth(self.shouldFail, error.Error,
2488 "POST_upload_no_replace_queryarg",
2490 "There was already a child by that name, and you asked me "
2491 "to not replace it")
2492 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
2493 d.addCallback(self.failUnlessIsBarDotTxt)
2496 def test_POST_upload_no_replace_field(self):
2497 d = self.POST(self.public_url + "/foo", t="upload", replace="false",
2498 file=("bar.txt", self.NEWFILE_CONTENTS))
2499 d.addBoth(self.shouldFail, error.Error, "POST_upload_no_replace_field",
2501 "There was already a child by that name, and you asked me "
2502 "to not replace it")
2503 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
2504 d.addCallback(self.failUnlessIsBarDotTxt)
2507 def test_POST_upload_whendone(self):
2508 d = self.POST(self.public_url + "/foo", t="upload", when_done="/THERE",
2509 file=("new.txt", self.NEWFILE_CONTENTS))
2510 d.addBoth(self.shouldRedirect, "/THERE")
2512 d.addCallback(lambda res:
2513 self.failUnlessChildContentsAre(fn, u"new.txt",
2514 self.NEWFILE_CONTENTS))
2517 def test_POST_upload_named(self):
2519 d = self.POST(self.public_url + "/foo", t="upload",
2520 name="new.txt", file=self.NEWFILE_CONTENTS)
2521 d.addCallback(self.failUnlessURIMatchesROChild, fn, u"new.txt")
2522 d.addCallback(lambda res:
2523 self.failUnlessChildContentsAre(fn, u"new.txt",
2524 self.NEWFILE_CONTENTS))
2527 def test_POST_upload_named_badfilename(self):
2528 d = self.POST(self.public_url + "/foo", t="upload",
2529 name="slashes/are/bad.txt", file=self.NEWFILE_CONTENTS)
2530 d.addBoth(self.shouldFail, error.Error,
2531 "test_POST_upload_named_badfilename",
2533 "name= may not contain a slash",
2535 # make sure that nothing was added
2536 d.addCallback(lambda res:
2537 self.failUnlessNodeKeysAre(self._foo_node,
2538 [self._htmlname_unicode,
2539 u"bar.txt", u"baz.txt", u"blockingfile",
2540 u"empty", u"n\u00fc.txt", u"quux.txt",
2544 def test_POST_FILEURL_check(self):
2545 bar_url = self.public_url + "/foo/bar.txt"
2546 d = self.POST(bar_url, t="check")
2548 self.failUnlessIn("Healthy :", res)
2549 d.addCallback(_check)
2550 redir_url = "http://allmydata.org/TARGET"
2551 def _check2(statuscode, target):
2552 self.failUnlessReallyEqual(statuscode, str(http.FOUND))
2553 self.failUnlessReallyEqual(target, redir_url)
2554 d.addCallback(lambda res:
2555 self.shouldRedirect2("test_POST_FILEURL_check",
2559 when_done=redir_url))
2560 d.addCallback(lambda res:
2561 self.POST(bar_url, t="check", return_to=redir_url))
2563 self.failUnlessIn("Healthy :", res)
2564 self.failUnlessIn("Return to file", res)
2565 self.failUnlessIn(redir_url, res)
2566 d.addCallback(_check3)
2568 d.addCallback(lambda res:
2569 self.POST(bar_url, t="check", output="JSON"))
2570 def _check_json(res):
2571 data = simplejson.loads(res)
2572 self.failUnlessIn("storage-index", data)
2573 self.failUnless(data["results"]["healthy"])
2574 d.addCallback(_check_json)
2578 def test_POST_FILEURL_check_and_repair(self):
2579 bar_url = self.public_url + "/foo/bar.txt"
2580 d = self.POST(bar_url, t="check", repair="true")
2582 self.failUnlessIn("Healthy :", res)
2583 d.addCallback(_check)
2584 redir_url = "http://allmydata.org/TARGET"
2585 def _check2(statuscode, target):
2586 self.failUnlessReallyEqual(statuscode, str(http.FOUND))
2587 self.failUnlessReallyEqual(target, redir_url)
2588 d.addCallback(lambda res:
2589 self.shouldRedirect2("test_POST_FILEURL_check_and_repair",
2592 t="check", repair="true",
2593 when_done=redir_url))
2594 d.addCallback(lambda res:
2595 self.POST(bar_url, t="check", return_to=redir_url))
2597 self.failUnlessIn("Healthy :", res)
2598 self.failUnlessIn("Return to file", res)
2599 self.failUnlessIn(redir_url, res)
2600 d.addCallback(_check3)
2603 def test_POST_DIRURL_check(self):
2604 foo_url = self.public_url + "/foo/"
2605 d = self.POST(foo_url, t="check")
2607 self.failUnlessIn("Healthy :", res)
2608 d.addCallback(_check)
2609 redir_url = "http://allmydata.org/TARGET"
2610 def _check2(statuscode, target):
2611 self.failUnlessReallyEqual(statuscode, str(http.FOUND))
2612 self.failUnlessReallyEqual(target, redir_url)
2613 d.addCallback(lambda res:
2614 self.shouldRedirect2("test_POST_DIRURL_check",
2618 when_done=redir_url))
2619 d.addCallback(lambda res:
2620 self.POST(foo_url, t="check", return_to=redir_url))
2622 self.failUnlessIn("Healthy :", res)
2623 self.failUnlessIn("Return to file/directory", res)
2624 self.failUnlessIn(redir_url, res)
2625 d.addCallback(_check3)
2627 d.addCallback(lambda res:
2628 self.POST(foo_url, t="check", output="JSON"))
2629 def _check_json(res):
2630 data = simplejson.loads(res)
2631 self.failUnlessIn("storage-index", data)
2632 self.failUnless(data["results"]["healthy"])
2633 d.addCallback(_check_json)
2637 def test_POST_DIRURL_check_and_repair(self):
2638 foo_url = self.public_url + "/foo/"
2639 d = self.POST(foo_url, t="check", repair="true")
2641 self.failUnlessIn("Healthy :", res)
2642 d.addCallback(_check)
2643 redir_url = "http://allmydata.org/TARGET"
2644 def _check2(statuscode, target):
2645 self.failUnlessReallyEqual(statuscode, str(http.FOUND))
2646 self.failUnlessReallyEqual(target, redir_url)
2647 d.addCallback(lambda res:
2648 self.shouldRedirect2("test_POST_DIRURL_check_and_repair",
2651 t="check", repair="true",
2652 when_done=redir_url))
2653 d.addCallback(lambda res:
2654 self.POST(foo_url, t="check", return_to=redir_url))
2656 self.failUnlessIn("Healthy :", res)
2657 self.failUnlessIn("Return to file/directory", res)
2658 self.failUnlessIn(redir_url, res)
2659 d.addCallback(_check3)
2662 def test_POST_FILEURL_mdmf_check(self):
2663 quux_url = "/uri/%s" % urllib.quote(self._quux_txt_uri)
2664 d = self.POST(quux_url, t="check")
2666 self.failUnlessIn("Healthy", res)
2667 d.addCallback(_check)
2668 quux_extension_url = "/uri/%s" % urllib.quote("%s:3:131073" % self._quux_txt_uri)
2669 d.addCallback(lambda ignored:
2670 self.POST(quux_extension_url, t="check"))
2671 d.addCallback(_check)
2674 def test_POST_FILEURL_mdmf_check_and_repair(self):
2675 quux_url = "/uri/%s" % urllib.quote(self._quux_txt_uri)
2676 d = self.POST(quux_url, t="check", repair="true")
2678 self.failUnlessIn("Healthy", res)
2679 d.addCallback(_check)
2680 quux_extension_url = "/uri/%s" % urllib.quote("%s:3:131073" % self._quux_txt_uri)
2681 d.addCallback(lambda ignored:
2682 self.POST(quux_extension_url, t="check", repair="true"))
2683 d.addCallback(_check)
2686 def wait_for_operation(self, ignored, ophandle):
2687 url = "/operations/" + ophandle
2688 url += "?t=status&output=JSON"
2691 data = simplejson.loads(res)
2692 if not data["finished"]:
2693 d = self.stall(delay=1.0)
2694 d.addCallback(self.wait_for_operation, ophandle)
2700 def get_operation_results(self, ignored, ophandle, output=None):
2701 url = "/operations/" + ophandle
2704 url += "&output=" + output
2707 if output and output.lower() == "json":
2708 return simplejson.loads(res)
2713 def test_POST_DIRURL_deepcheck_no_ophandle(self):
2714 d = self.shouldFail2(error.Error,
2715 "test_POST_DIRURL_deepcheck_no_ophandle",
2717 "slow operation requires ophandle=",
2718 self.POST, self.public_url, t="start-deep-check")
2721 def test_POST_DIRURL_deepcheck(self):
2722 def _check_redirect(statuscode, target):
2723 self.failUnlessReallyEqual(statuscode, str(http.FOUND))
2724 self.failUnless(target.endswith("/operations/123"))
2725 d = self.shouldRedirect2("test_POST_DIRURL_deepcheck", _check_redirect,
2726 self.POST, self.public_url,
2727 t="start-deep-check", ophandle="123")
2728 d.addCallback(self.wait_for_operation, "123")
2729 def _check_json(data):
2730 self.failUnlessReallyEqual(data["finished"], True)
2731 self.failUnlessReallyEqual(data["count-objects-checked"], 11)
2732 self.failUnlessReallyEqual(data["count-objects-healthy"], 11)
2733 d.addCallback(_check_json)
2734 d.addCallback(self.get_operation_results, "123", "html")
2735 def _check_html(res):
2736 self.failUnlessIn("Objects Checked: <span>11</span>", res)
2737 self.failUnlessIn("Objects Healthy: <span>11</span>", res)
2738 self.failUnlessIn(FAVICON_MARKUP, res)
2739 d.addCallback(_check_html)
2741 d.addCallback(lambda res:
2742 self.GET("/operations/123/"))
2743 d.addCallback(_check_html) # should be the same as without the slash
2745 d.addCallback(lambda res:
2746 self.shouldFail2(error.Error, "one", "404 Not Found",
2747 "No detailed results for SI bogus",
2748 self.GET, "/operations/123/bogus"))
2750 foo_si = self._foo_node.get_storage_index()
2751 foo_si_s = base32.b2a(foo_si)
2752 d.addCallback(lambda res:
2753 self.GET("/operations/123/%s?output=JSON" % foo_si_s))
2754 def _check_foo_json(res):
2755 data = simplejson.loads(res)
2756 self.failUnlessEqual(data["storage-index"], foo_si_s)
2757 self.failUnless(data["results"]["healthy"])
2758 d.addCallback(_check_foo_json)
2761 def test_POST_DIRURL_deepcheck_and_repair(self):
2762 d = self.POST(self.public_url, t="start-deep-check", repair="true",
2763 ophandle="124", output="json", followRedirect=True)
2764 d.addCallback(self.wait_for_operation, "124")
2765 def _check_json(data):
2766 self.failUnlessReallyEqual(data["finished"], True)
2767 self.failUnlessReallyEqual(data["count-objects-checked"], 11)
2768 self.failUnlessReallyEqual(data["count-objects-healthy-pre-repair"], 11)
2769 self.failUnlessReallyEqual(data["count-objects-unhealthy-pre-repair"], 0)
2770 self.failUnlessReallyEqual(data["count-corrupt-shares-pre-repair"], 0)
2771 self.failUnlessReallyEqual(data["count-repairs-attempted"], 0)
2772 self.failUnlessReallyEqual(data["count-repairs-successful"], 0)
2773 self.failUnlessReallyEqual(data["count-repairs-unsuccessful"], 0)
2774 self.failUnlessReallyEqual(data["count-objects-healthy-post-repair"], 11)
2775 self.failUnlessReallyEqual(data["count-objects-unhealthy-post-repair"], 0)
2776 self.failUnlessReallyEqual(data["count-corrupt-shares-post-repair"], 0)
2777 d.addCallback(_check_json)
2778 d.addCallback(self.get_operation_results, "124", "html")
2779 def _check_html(res):
2780 self.failUnlessIn("Objects Checked: <span>11</span>", res)
2782 self.failUnlessIn("Objects Healthy (before repair): <span>11</span>", res)
2783 self.failUnlessIn("Objects Unhealthy (before repair): <span>0</span>", res)
2784 self.failUnlessIn("Corrupt Shares (before repair): <span>0</span>", res)
2786 self.failUnlessIn("Repairs Attempted: <span>0</span>", res)
2787 self.failUnlessIn("Repairs Successful: <span>0</span>", res)
2788 self.failUnlessIn("Repairs Unsuccessful: <span>0</span>", res)
2790 self.failUnlessIn("Objects Healthy (after repair): <span>11</span>", res)
2791 self.failUnlessIn("Objects Unhealthy (after repair): <span>0</span>", res)
2792 self.failUnlessIn("Corrupt Shares (after repair): <span>0</span>", res)
2794 self.failUnlessIn(FAVICON_MARKUP, res)
2795 d.addCallback(_check_html)
2798 def test_POST_FILEURL_bad_t(self):
2799 d = self.shouldFail2(error.Error, "POST_bad_t", "400 Bad Request",
2800 "POST to file: bad t=bogus",
2801 self.POST, self.public_url + "/foo/bar.txt",
2805 def test_POST_mkdir(self): # return value?
2806 d = self.POST(self.public_url + "/foo", t="mkdir", name="newdir")
2807 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2808 d.addCallback(self.failUnlessNodeKeysAre, [])
2811 def test_POST_mkdir_mdmf(self):
2812 d = self.POST(self.public_url + "/foo?t=mkdir&name=newdir&format=mdmf")
2813 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2814 d.addCallback(lambda node:
2815 self.failUnlessEqual(node._node.get_version(), MDMF_VERSION))
2818 def test_POST_mkdir_sdmf(self):
2819 d = self.POST(self.public_url + "/foo?t=mkdir&name=newdir&format=sdmf")
2820 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2821 d.addCallback(lambda node:
2822 self.failUnlessEqual(node._node.get_version(), SDMF_VERSION))
2825 def test_POST_mkdir_bad_format(self):
2826 return self.shouldHTTPError("POST_mkdir_bad_format",
2827 400, "Bad Request", "Unknown format: foo",
2828 self.POST, self.public_url +
2829 "/foo?t=mkdir&name=newdir&format=foo")
2831 def test_POST_mkdir_initial_children(self):
2832 (newkids, caps) = self._create_initial_children()
2833 d = self.POST2(self.public_url +
2834 "/foo?t=mkdir-with-children&name=newdir",
2835 simplejson.dumps(newkids))
2836 d.addCallback(lambda res:
2837 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
2838 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2839 d.addCallback(self.failUnlessNodeKeysAre, newkids.keys())
2840 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2841 d.addCallback(self.failUnlessROChildURIIs, u"child-imm", caps['filecap1'])
2844 def test_POST_mkdir_initial_children_mdmf(self):
2845 (newkids, caps) = self._create_initial_children()
2846 d = self.POST2(self.public_url +
2847 "/foo?t=mkdir-with-children&name=newdir&format=mdmf",
2848 simplejson.dumps(newkids))
2849 d.addCallback(lambda res:
2850 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
2851 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2852 d.addCallback(lambda node:
2853 self.failUnlessEqual(node._node.get_version(), MDMF_VERSION))
2854 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2855 d.addCallback(self.failUnlessROChildURIIs, u"child-imm",
2860 def test_POST_mkdir_initial_children_sdmf(self):
2861 (newkids, caps) = self._create_initial_children()
2862 d = self.POST2(self.public_url +
2863 "/foo?t=mkdir-with-children&name=newdir&format=sdmf",
2864 simplejson.dumps(newkids))
2865 d.addCallback(lambda res:
2866 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
2867 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2868 d.addCallback(lambda node:
2869 self.failUnlessEqual(node._node.get_version(), SDMF_VERSION))
2870 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2871 d.addCallback(self.failUnlessROChildURIIs, u"child-imm",
2875 def test_POST_mkdir_initial_children_bad_format(self):
2876 (newkids, caps) = self._create_initial_children()
2877 return self.shouldHTTPError("POST_mkdir_initial_children_bad_format",
2878 400, "Bad Request", "Unknown format: foo",
2879 self.POST, self.public_url + \
2880 "/foo?t=mkdir-with-children&name=newdir&format=foo",
2881 simplejson.dumps(newkids))
2883 def test_POST_mkdir_immutable(self):
2884 (newkids, caps) = self._create_immutable_children()
2885 d = self.POST2(self.public_url +
2886 "/foo?t=mkdir-immutable&name=newdir",
2887 simplejson.dumps(newkids))
2888 d.addCallback(lambda res:
2889 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
2890 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2891 d.addCallback(self.failUnlessNodeKeysAre, newkids.keys())
2892 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2893 d.addCallback(self.failUnlessROChildURIIs, u"child-imm", caps['filecap1'])
2894 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2895 d.addCallback(self.failUnlessROChildURIIs, u"unknownchild-imm", caps['unknown_immcap'])
2896 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2897 d.addCallback(self.failUnlessROChildURIIs, u"dirchild-imm", caps['immdircap'])
2898 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2899 d.addCallback(self.failUnlessROChildURIIs, u"dirchild-lit", caps['litdircap'])
2900 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2901 d.addCallback(self.failUnlessROChildURIIs, u"dirchild-empty", caps['emptydircap'])
2904 def test_POST_mkdir_immutable_bad(self):
2905 (newkids, caps) = self._create_initial_children()
2906 d = self.shouldFail2(error.Error, "POST_mkdir_immutable_bad",
2908 "needed to be immutable but was not",
2911 "/foo?t=mkdir-immutable&name=newdir",
2912 simplejson.dumps(newkids))
2915 def test_POST_mkdir_2(self):
2916 d = self.POST(self.public_url + "/foo/newdir?t=mkdir", "")
2917 d.addCallback(lambda res:
2918 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
2919 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2920 d.addCallback(self.failUnlessNodeKeysAre, [])
2923 def test_POST_mkdirs_2(self):
2924 d = self.POST(self.public_url + "/foo/bardir/newdir?t=mkdir", "")
2925 d.addCallback(lambda res:
2926 self.failUnlessNodeHasChild(self._foo_node, u"bardir"))
2927 d.addCallback(lambda res: self._foo_node.get(u"bardir"))
2928 d.addCallback(lambda bardirnode: bardirnode.get(u"newdir"))
2929 d.addCallback(self.failUnlessNodeKeysAre, [])
2932 def test_POST_mkdir_no_parentdir_noredirect(self):
2933 d = self.POST("/uri?t=mkdir")
2934 def _after_mkdir(res):
2935 uri.DirectoryURI.init_from_string(res)
2936 d.addCallback(_after_mkdir)
2939 def test_POST_mkdir_no_parentdir_noredirect_mdmf(self):
2940 d = self.POST("/uri?t=mkdir&format=mdmf")
2941 def _after_mkdir(res):
2942 u = uri.from_string(res)
2943 # Check that this is an MDMF writecap
2944 self.failUnlessIsInstance(u, uri.MDMFDirectoryURI)
2945 d.addCallback(_after_mkdir)
2948 def test_POST_mkdir_no_parentdir_noredirect_sdmf(self):
2949 d = self.POST("/uri?t=mkdir&format=sdmf")
2950 def _after_mkdir(res):
2951 u = uri.from_string(res)
2952 self.failUnlessIsInstance(u, uri.DirectoryURI)
2953 d.addCallback(_after_mkdir)
2956 def test_POST_mkdir_no_parentdir_noredirect_bad_format(self):
2957 return self.shouldHTTPError("POST_mkdir_no_parentdir_noredirect_bad_format",
2958 400, "Bad Request", "Unknown format: foo",
2959 self.POST, self.public_url +
2960 "/uri?t=mkdir&format=foo")
2962 def test_POST_mkdir_no_parentdir_noredirect2(self):
2963 # make sure form-based arguments (as on the welcome page) still work
2964 d = self.POST("/uri", t="mkdir")
2965 def _after_mkdir(res):
2966 uri.DirectoryURI.init_from_string(res)
2967 d.addCallback(_after_mkdir)
2968 d.addErrback(self.explain_web_error)
2971 def test_POST_mkdir_no_parentdir_redirect(self):
2972 d = self.POST("/uri?t=mkdir&redirect_to_result=true")
2973 d.addBoth(self.shouldRedirect, None, statuscode='303')
2974 def _check_target(target):
2975 target = urllib.unquote(target)
2976 self.failUnless(target.startswith("uri/URI:DIR2:"), target)
2977 d.addCallback(_check_target)
2980 def test_POST_mkdir_no_parentdir_redirect2(self):
2981 d = self.POST("/uri", t="mkdir", redirect_to_result="true")
2982 d.addBoth(self.shouldRedirect, None, statuscode='303')
2983 def _check_target(target):
2984 target = urllib.unquote(target)
2985 self.failUnless(target.startswith("uri/URI:DIR2:"), target)
2986 d.addCallback(_check_target)
2987 d.addErrback(self.explain_web_error)
2990 def _make_readonly(self, u):
2991 ro_uri = uri.from_string(u).get_readonly()
2994 return ro_uri.to_string()
2996 def _create_initial_children(self):
2997 contents, n, filecap1 = self.makefile(12)
2998 md1 = {"metakey1": "metavalue1"}
2999 filecap2 = make_mutable_file_uri()
3000 node3 = self.s.create_node_from_uri(make_mutable_file_uri())
3001 filecap3 = node3.get_readonly_uri()
3002 node4 = self.s.create_node_from_uri(make_mutable_file_uri())
3003 dircap = DirectoryNode(node4, None, None).get_uri()
3004 mdmfcap = make_mutable_file_uri(mdmf=True)
3005 litdircap = "URI:DIR2-LIT:ge3dumj2mewdcotyfqydulbshj5x2lbm"
3006 emptydircap = "URI:DIR2-LIT:"
3007 newkids = {u"child-imm": ["filenode", {"rw_uri": filecap1,
3008 "ro_uri": self._make_readonly(filecap1),
3009 "metadata": md1, }],
3010 u"child-mutable": ["filenode", {"rw_uri": filecap2,
3011 "ro_uri": self._make_readonly(filecap2)}],
3012 u"child-mutable-ro": ["filenode", {"ro_uri": filecap3}],
3013 u"unknownchild-rw": ["unknown", {"rw_uri": unknown_rwcap,
3014 "ro_uri": unknown_rocap}],
3015 u"unknownchild-ro": ["unknown", {"ro_uri": unknown_rocap}],
3016 u"unknownchild-imm": ["unknown", {"ro_uri": unknown_immcap}],
3017 u"dirchild": ["dirnode", {"rw_uri": dircap,
3018 "ro_uri": self._make_readonly(dircap)}],
3019 u"dirchild-lit": ["dirnode", {"ro_uri": litdircap}],
3020 u"dirchild-empty": ["dirnode", {"ro_uri": emptydircap}],
3021 u"child-mutable-mdmf": ["filenode", {"rw_uri": mdmfcap,
3022 "ro_uri": self._make_readonly(mdmfcap)}],
3024 return newkids, {'filecap1': filecap1,
3025 'filecap2': filecap2,
3026 'filecap3': filecap3,
3027 'unknown_rwcap': unknown_rwcap,
3028 'unknown_rocap': unknown_rocap,
3029 'unknown_immcap': unknown_immcap,
3031 'litdircap': litdircap,
3032 'emptydircap': emptydircap,
3035 def _create_immutable_children(self):
3036 contents, n, filecap1 = self.makefile(12)
3037 md1 = {"metakey1": "metavalue1"}
3038 tnode = create_chk_filenode("immutable directory contents\n"*10,
3039 self.get_all_contents())
3040 dnode = DirectoryNode(tnode, None, None)
3041 assert not dnode.is_mutable()
3042 immdircap = dnode.get_uri()
3043 litdircap = "URI:DIR2-LIT:ge3dumj2mewdcotyfqydulbshj5x2lbm"
3044 emptydircap = "URI:DIR2-LIT:"
3045 newkids = {u"child-imm": ["filenode", {"ro_uri": filecap1,
3046 "metadata": md1, }],
3047 u"unknownchild-imm": ["unknown", {"ro_uri": unknown_immcap}],
3048 u"dirchild-imm": ["dirnode", {"ro_uri": immdircap}],
3049 u"dirchild-lit": ["dirnode", {"ro_uri": litdircap}],
3050 u"dirchild-empty": ["dirnode", {"ro_uri": emptydircap}],
3052 return newkids, {'filecap1': filecap1,
3053 'unknown_immcap': unknown_immcap,
3054 'immdircap': immdircap,
3055 'litdircap': litdircap,
3056 'emptydircap': emptydircap}
3058 def test_POST_mkdir_no_parentdir_initial_children(self):
3059 (newkids, caps) = self._create_initial_children()
3060 d = self.POST2("/uri?t=mkdir-with-children", simplejson.dumps(newkids))
3061 def _after_mkdir(res):
3062 self.failUnless(res.startswith("URI:DIR"), res)
3063 n = self.s.create_node_from_uri(res)
3064 d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
3065 d2.addCallback(lambda ign:
3066 self.failUnlessROChildURIIs(n, u"child-imm",
3068 d2.addCallback(lambda ign:
3069 self.failUnlessRWChildURIIs(n, u"child-mutable",
3071 d2.addCallback(lambda ign:
3072 self.failUnlessROChildURIIs(n, u"child-mutable-ro",
3074 d2.addCallback(lambda ign:
3075 self.failUnlessRWChildURIIs(n, u"unknownchild-rw",
3076 caps['unknown_rwcap']))
3077 d2.addCallback(lambda ign:
3078 self.failUnlessROChildURIIs(n, u"unknownchild-ro",
3079 caps['unknown_rocap']))
3080 d2.addCallback(lambda ign:
3081 self.failUnlessROChildURIIs(n, u"unknownchild-imm",
3082 caps['unknown_immcap']))
3083 d2.addCallback(lambda ign:
3084 self.failUnlessRWChildURIIs(n, u"dirchild",
3087 d.addCallback(_after_mkdir)
3090 def test_POST_mkdir_no_parentdir_unexpected_children(self):
3091 # the regular /uri?t=mkdir operation is specified to ignore its body.
3092 # Only t=mkdir-with-children pays attention to it.
3093 (newkids, caps) = self._create_initial_children()
3094 d = self.shouldHTTPError("POST_mkdir_no_parentdir_unexpected_children",
3096 "t=mkdir does not accept children=, "
3097 "try t=mkdir-with-children instead",
3098 self.POST2, "/uri?t=mkdir", # without children
3099 simplejson.dumps(newkids))
3102 def test_POST_noparent_bad(self):
3103 d = self.shouldHTTPError("POST_noparent_bad",
3105 "/uri accepts only PUT, PUT?t=mkdir, "
3106 "POST?t=upload, and POST?t=mkdir",
3107 self.POST, "/uri?t=bogus")
3110 def test_POST_mkdir_no_parentdir_immutable(self):
3111 (newkids, caps) = self._create_immutable_children()
3112 d = self.POST2("/uri?t=mkdir-immutable", simplejson.dumps(newkids))
3113 def _after_mkdir(res):
3114 self.failUnless(res.startswith("URI:DIR"), res)
3115 n = self.s.create_node_from_uri(res)
3116 d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
3117 d2.addCallback(lambda ign:
3118 self.failUnlessROChildURIIs(n, u"child-imm",
3120 d2.addCallback(lambda ign:
3121 self.failUnlessROChildURIIs(n, u"unknownchild-imm",
3122 caps['unknown_immcap']))
3123 d2.addCallback(lambda ign:
3124 self.failUnlessROChildURIIs(n, u"dirchild-imm",
3126 d2.addCallback(lambda ign:
3127 self.failUnlessROChildURIIs(n, u"dirchild-lit",
3129 d2.addCallback(lambda ign:
3130 self.failUnlessROChildURIIs(n, u"dirchild-empty",
3131 caps['emptydircap']))
3133 d.addCallback(_after_mkdir)
3136 def test_POST_mkdir_no_parentdir_immutable_bad(self):
3137 (newkids, caps) = self._create_initial_children()
3138 d = self.shouldFail2(error.Error,
3139 "test_POST_mkdir_no_parentdir_immutable_bad",
3141 "needed to be immutable but was not",
3143 "/uri?t=mkdir-immutable",
3144 simplejson.dumps(newkids))
3147 def test_welcome_page_mkdir_button(self):
3148 # Fetch the welcome page.
3150 def _after_get_welcome_page(res):
3151 MKDIR_BUTTON_RE = re.compile(
3152 '<form action="([^"]*)" method="post".*?'
3153 '<input type="hidden" name="t" value="([^"]*)" />'
3154 '<input type="hidden" name="([^"]*)" value="([^"]*)" />'
3155 '<input type="submit" value="Create a directory" />',
3157 mo = MKDIR_BUTTON_RE.search(res)
3158 formaction = mo.group(1)
3160 formaname = mo.group(3)
3161 formavalue = mo.group(4)
3162 return (formaction, formt, formaname, formavalue)
3163 d.addCallback(_after_get_welcome_page)
3164 def _after_parse_form(res):
3165 (formaction, formt, formaname, formavalue) = res
3166 return self.POST("/%s?t=%s&%s=%s" % (formaction, formt, formaname, formavalue))
3167 d.addCallback(_after_parse_form)
3168 d.addBoth(self.shouldRedirect, None, statuscode='303')
3171 def test_POST_mkdir_replace(self): # return value?
3172 d = self.POST(self.public_url + "/foo", t="mkdir", name="sub")
3173 d.addCallback(lambda res: self._foo_node.get(u"sub"))
3174 d.addCallback(self.failUnlessNodeKeysAre, [])
3177 def test_POST_mkdir_no_replace_queryarg(self): # return value?
3178 d = self.POST(self.public_url + "/foo?replace=false", t="mkdir", name="sub")
3179 d.addBoth(self.shouldFail, error.Error,
3180 "POST_mkdir_no_replace_queryarg",
3182 "There was already a child by that name, and you asked me "
3183 "to not replace it")
3184 d.addCallback(lambda res: self._foo_node.get(u"sub"))
3185 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
3188 def test_POST_mkdir_no_replace_field(self): # return value?
3189 d = self.POST(self.public_url + "/foo", t="mkdir", name="sub",
3191 d.addBoth(self.shouldFail, error.Error, "POST_mkdir_no_replace_field",
3193 "There was already a child by that name, and you asked me "
3194 "to not replace it")
3195 d.addCallback(lambda res: self._foo_node.get(u"sub"))
3196 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
3199 def test_POST_mkdir_whendone_field(self):
3200 d = self.POST(self.public_url + "/foo",
3201 t="mkdir", name="newdir", when_done="/THERE")
3202 d.addBoth(self.shouldRedirect, "/THERE")
3203 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
3204 d.addCallback(self.failUnlessNodeKeysAre, [])
3207 def test_POST_mkdir_whendone_queryarg(self):
3208 d = self.POST(self.public_url + "/foo?when_done=/THERE",
3209 t="mkdir", name="newdir")
3210 d.addBoth(self.shouldRedirect, "/THERE")
3211 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
3212 d.addCallback(self.failUnlessNodeKeysAre, [])
3215 def test_POST_bad_t(self):
3216 d = self.shouldFail2(error.Error, "POST_bad_t",
3218 "POST to a directory with bad t=BOGUS",
3219 self.POST, self.public_url + "/foo", t="BOGUS")
3222 def test_POST_set_children(self, command_name="set_children"):
3223 contents9, n9, newuri9 = self.makefile(9)
3224 contents10, n10, newuri10 = self.makefile(10)
3225 contents11, n11, newuri11 = self.makefile(11)
3228 "atomic_added_1": [ "filenode", { "rw_uri": "%s",
3231 "ctime": 1002777696.7564139,
3232 "mtime": 1002777696.7564139
3235 "atomic_added_2": [ "filenode", { "rw_uri": "%s",
3238 "ctime": 1002777696.7564139,
3239 "mtime": 1002777696.7564139
3242 "atomic_added_3": [ "filenode", { "rw_uri": "%s",
3245 "ctime": 1002777696.7564139,
3246 "mtime": 1002777696.7564139
3249 }""" % (newuri9, newuri10, newuri11)
3251 url = self.webish_url + self.public_url + "/foo" + "?t=" + command_name
3253 d = client.getPage(url, method="POST", postdata=reqbody)
3255 self.failUnlessURIMatchesROChild(newuri9, self._foo_node, u"atomic_added_1")
3256 self.failUnlessURIMatchesROChild(newuri10, self._foo_node, u"atomic_added_2")
3257 self.failUnlessURIMatchesROChild(newuri11, self._foo_node, u"atomic_added_3")
3259 d.addCallback(_then)
3260 d.addErrback(self.dump_error)
3263 def test_POST_set_children_with_hyphen(self):
3264 return self.test_POST_set_children(command_name="set-children")
3266 def test_POST_link_uri(self):
3267 contents, n, newuri = self.makefile(8)
3268 d = self.POST(self.public_url + "/foo", t="uri", name="new.txt", uri=newuri)
3269 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"new.txt")
3270 d.addCallback(lambda res:
3271 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
3275 def test_POST_link_uri_replace(self):
3276 contents, n, newuri = self.makefile(8)
3277 d = self.POST(self.public_url + "/foo", t="uri", name="bar.txt", uri=newuri)
3278 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"bar.txt")
3279 d.addCallback(lambda res:
3280 self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
3284 def test_POST_link_uri_unknown_bad(self):
3285 d = self.POST(self.public_url + "/foo", t="uri", name="future.txt", uri=unknown_rwcap)
3286 d.addBoth(self.shouldFail, error.Error,
3287 "POST_link_uri_unknown_bad",
3289 "unknown cap in a write slot")
3292 def test_POST_link_uri_unknown_ro_good(self):
3293 d = self.POST(self.public_url + "/foo", t="uri", name="future-ro.txt", uri=unknown_rocap)
3294 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"future-ro.txt")
3297 def test_POST_link_uri_unknown_imm_good(self):
3298 d = self.POST(self.public_url + "/foo", t="uri", name="future-imm.txt", uri=unknown_immcap)
3299 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"future-imm.txt")
3302 def test_POST_link_uri_no_replace_queryarg(self):
3303 contents, n, newuri = self.makefile(8)
3304 d = self.POST(self.public_url + "/foo?replace=false", t="uri",
3305 name="bar.txt", uri=newuri)
3306 d.addBoth(self.shouldFail, error.Error,
3307 "POST_link_uri_no_replace_queryarg",
3309 "There was already a child by that name, and you asked me "
3310 "to not replace it")
3311 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
3312 d.addCallback(self.failUnlessIsBarDotTxt)
3315 def test_POST_link_uri_no_replace_field(self):
3316 contents, n, newuri = self.makefile(8)
3317 d = self.POST(self.public_url + "/foo", t="uri", replace="false",
3318 name="bar.txt", uri=newuri)
3319 d.addBoth(self.shouldFail, error.Error,
3320 "POST_link_uri_no_replace_field",
3322 "There was already a child by that name, and you asked me "
3323 "to not replace it")
3324 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
3325 d.addCallback(self.failUnlessIsBarDotTxt)
3328 def test_POST_delete(self, command_name='delete'):
3329 d = self._foo_node.list()
3330 def _check_before(children):
3331 self.failUnlessIn(u"bar.txt", children)
3332 d.addCallback(_check_before)
3333 d.addCallback(lambda res: self.POST(self.public_url + "/foo", t=command_name, name="bar.txt"))
3334 d.addCallback(lambda res: self._foo_node.list())
3335 def _check_after(children):
3336 self.failIfIn(u"bar.txt", children)
3337 d.addCallback(_check_after)
3340 def test_POST_unlink(self):
3341 return self.test_POST_delete(command_name='unlink')
3343 def test_POST_rename_file(self):
3344 d = self.POST(self.public_url + "/foo", t="rename",
3345 from_name="bar.txt", to_name='wibble.txt')
3346 d.addCallback(lambda res:
3347 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
3348 d.addCallback(lambda res:
3349 self.failUnlessNodeHasChild(self._foo_node, u"wibble.txt"))
3350 d.addCallback(lambda res: self.GET(self.public_url + "/foo/wibble.txt"))
3351 d.addCallback(self.failUnlessIsBarDotTxt)
3352 d.addCallback(lambda res: self.GET(self.public_url + "/foo/wibble.txt?t=json"))
3353 d.addCallback(self.failUnlessIsBarJSON)
3356 def test_POST_rename_file_redundant(self):
3357 d = self.POST(self.public_url + "/foo", t="rename",
3358 from_name="bar.txt", to_name='bar.txt')
3359 d.addCallback(lambda res:
3360 self.failUnlessNodeHasChild(self._foo_node, u"bar.txt"))
3361 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
3362 d.addCallback(self.failUnlessIsBarDotTxt)
3363 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt?t=json"))
3364 d.addCallback(self.failUnlessIsBarJSON)
3367 def test_POST_rename_file_replace(self):
3368 # rename a file and replace a directory with it
3369 d = self.POST(self.public_url + "/foo", t="rename",
3370 from_name="bar.txt", to_name='empty')
3371 d.addCallback(lambda res:
3372 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
3373 d.addCallback(lambda res:
3374 self.failUnlessNodeHasChild(self._foo_node, u"empty"))
3375 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty"))
3376 d.addCallback(self.failUnlessIsBarDotTxt)
3377 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
3378 d.addCallback(self.failUnlessIsBarJSON)
3381 def test_POST_rename_file_no_replace_queryarg(self):
3382 # rename a file and replace a directory with it
3383 d = self.POST(self.public_url + "/foo?replace=false", t="rename",
3384 from_name="bar.txt", to_name='empty')
3385 d.addBoth(self.shouldFail, error.Error,
3386 "POST_rename_file_no_replace_queryarg",
3388 "There was already a child by that name, and you asked me "
3389 "to not replace it")
3390 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
3391 d.addCallback(self.failUnlessIsEmptyJSON)
3394 def test_POST_rename_file_no_replace_field(self):
3395 # rename a file and replace a directory with it
3396 d = self.POST(self.public_url + "/foo", t="rename", replace="false",
3397 from_name="bar.txt", to_name='empty')
3398 d.addBoth(self.shouldFail, error.Error,
3399 "POST_rename_file_no_replace_field",
3401 "There was already a child by that name, and you asked me "
3402 "to not replace it")
3403 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
3404 d.addCallback(self.failUnlessIsEmptyJSON)
3407 def failUnlessIsEmptyJSON(self, res):
3408 data = simplejson.loads(res)
3409 self.failUnlessEqual(data[0], "dirnode", data)
3410 self.failUnlessReallyEqual(len(data[1]["children"]), 0)
3412 def test_POST_rename_file_slash_fail(self):
3413 d = self.POST(self.public_url + "/foo", t="rename",
3414 from_name="bar.txt", to_name='kirk/spock.txt')
3415 d.addBoth(self.shouldFail, error.Error,
3416 "test_POST_rename_file_slash_fail",
3418 "to_name= may not contain a slash",
3420 d.addCallback(lambda res:
3421 self.failUnlessNodeHasChild(self._foo_node, u"bar.txt"))
3424 def test_POST_rename_dir(self):
3425 d = self.POST(self.public_url, t="rename",
3426 from_name="foo", to_name='plunk')
3427 d.addCallback(lambda res:
3428 self.failIfNodeHasChild(self.public_root, u"foo"))
3429 d.addCallback(lambda res:
3430 self.failUnlessNodeHasChild(self.public_root, u"plunk"))
3431 d.addCallback(lambda res: self.GET(self.public_url + "/plunk?t=json"))
3432 d.addCallback(self.failUnlessIsFooJSON)
3435 def test_POST_move_file(self):
3436 d = self.POST(self.public_url + "/foo", t="move",
3437 from_name="bar.txt", to_dir="sub")
3438 d.addCallback(lambda res:
3439 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
3440 d.addCallback(lambda res:
3441 self.failUnlessNodeHasChild(self._sub_node, u"bar.txt"))
3442 d.addCallback(lambda res: self.GET(self.public_url + "/foo/sub/bar.txt"))
3443 d.addCallback(self.failUnlessIsBarDotTxt)
3444 d.addCallback(lambda res: self.GET(self.public_url + "/foo/sub/bar.txt?t=json"))
3445 d.addCallback(self.failUnlessIsBarJSON)
3448 def test_POST_move_file_new_name(self):
3449 d = self.POST(self.public_url + "/foo", t="move",
3450 from_name="bar.txt", to_name="wibble.txt", to_dir="sub")
3451 d.addCallback(lambda res:
3452 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
3453 d.addCallback(lambda res:
3454 self.failIfNodeHasChild(self._sub_node, u"bar.txt"))
3455 d.addCallback(lambda res:
3456 self.failUnlessNodeHasChild(self._sub_node, u"wibble.txt"))
3457 d.addCallback(lambda res: self.GET(self.public_url + "/foo/sub/wibble.txt"))
3458 d.addCallback(self.failUnlessIsBarDotTxt)
3459 d.addCallback(lambda res: self.GET(self.public_url + "/foo/sub/wibble.txt?t=json"))
3460 d.addCallback(self.failUnlessIsBarJSON)
3463 def test_POST_move_file_replace(self):
3464 d = self.POST(self.public_url + "/foo", t="move",
3465 from_name="bar.txt", to_name="baz.txt", to_dir="sub")
3466 d.addCallback(lambda res:
3467 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
3468 d.addCallback(lambda res: self.GET(self.public_url + "/foo/sub/baz.txt"))
3469 d.addCallback(self.failUnlessIsBarDotTxt)
3470 d.addCallback(lambda res: self.GET(self.public_url + "/foo/sub/baz.txt?t=json"))
3471 d.addCallback(self.failUnlessIsBarJSON)
3474 def test_POST_move_file_no_replace(self):
3475 d = self.shouldFail2(error.Error, "POST_move_file_no_replace",
3477 "There was already a child by that name, and you asked me to not replace it",
3478 self.POST, self.public_url + "/foo", t="move",
3479 replace="false", from_name="bar.txt",
3480 to_name="baz.txt", to_dir="sub")
3481 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
3482 d.addCallback(self.failUnlessIsBarDotTxt)
3483 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt?t=json"))
3484 d.addCallback(self.failUnlessIsBarJSON)
3485 d.addCallback(lambda res: self.GET(self.public_url + "/foo/sub/baz.txt"))
3486 d.addCallback(self.failUnlessIsSubBazDotTxt)
3489 def test_POST_move_file_slash_fail(self):
3490 d = self.shouldFail2(error.Error, "test_POST_rename_file_slash_fail",
3492 "to_name= may not contain a slash",
3493 self.POST, self.public_url + "/foo", t="move",
3494 from_name="bar.txt",
3495 to_name="slash/fail.txt", to_dir="sub")
3496 d.addCallback(lambda res:
3497 self.failUnlessNodeHasChild(self._foo_node, u"bar.txt"))
3498 d.addCallback(lambda res:
3499 self.failIfNodeHasChild(self._sub_node, u"slash/fail.txt"))
3500 d.addCallback(lambda ign:
3501 self.shouldFail2(error.Error,
3502 "test_POST_rename_file_slash_fail2",
3504 "from_name= may not contain a slash",
3505 self.POST, self.public_url + "/foo",
3507 from_name="nope/bar.txt",
3508 to_name="fail.txt", to_dir="sub"))
3511 def test_POST_move_file_no_target(self):
3512 d = self.shouldFail2(error.Error, "POST_move_file_no_target",
3514 "move requires from_name and to_dir",
3515 self.POST, self.public_url + "/foo", t="move",
3516 from_name="bar.txt", to_name="baz.txt")
3517 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
3518 d.addCallback(self.failUnlessIsBarDotTxt)
3519 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt?t=json"))
3520 d.addCallback(self.failUnlessIsBarJSON)
3521 d.addCallback(lambda res: self.GET(self.public_url + "/foo/baz.txt"))
3522 d.addCallback(self.failUnlessIsBazDotTxt)
3525 def test_POST_move_file_bad_target_type(self):
3526 d = self.shouldFail2(error.Error, "test_POST_move_file_bad_target_type",
3527 "400 Bad Request", "invalid target_type parameter",
3529 self.public_url + "/foo", t="move",
3530 target_type="*D", from_name="bar.txt",
3534 def test_POST_move_file_multi_level(self):
3535 d = self.POST(self.public_url + "/foo/sub/level2?t=mkdir", "")
3536 d.addCallback(lambda res: self.POST(self.public_url + "/foo", t="move",
3537 from_name="bar.txt", to_dir="sub/level2"))
3538 d.addCallback(lambda res: self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
3539 d.addCallback(lambda res: self.failIfNodeHasChild(self._sub_node, u"bar.txt"))
3540 d.addCallback(lambda res: self.GET(self.public_url + "/foo/sub/level2/bar.txt"))
3541 d.addCallback(self.failUnlessIsBarDotTxt)
3542 d.addCallback(lambda res: self.GET(self.public_url + "/foo/sub/level2/bar.txt?t=json"))
3543 d.addCallback(self.failUnlessIsBarJSON)
3546 def test_POST_move_file_to_uri(self):
3547 d = self.POST(self.public_url + "/foo", t="move", target_type="uri",
3548 from_name="bar.txt", to_dir=self._sub_uri)
3549 d.addCallback(lambda res:
3550 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
3551 d.addCallback(lambda res: self.GET(self.public_url + "/foo/sub/bar.txt"))
3552 d.addCallback(self.failUnlessIsBarDotTxt)
3553 d.addCallback(lambda res: self.GET(self.public_url + "/foo/sub/bar.txt?t=json"))
3554 d.addCallback(self.failUnlessIsBarJSON)
3557 def test_POST_move_file_to_nonexist_dir(self):
3558 d = self.shouldFail2(error.Error, "POST_move_file_to_nonexist_dir",
3559 "404 Not Found", "No such child: nopechucktesta",
3560 self.POST, self.public_url + "/foo", t="move",
3561 from_name="bar.txt", to_dir="nopechucktesta")
3564 def test_POST_move_file_into_file(self):
3565 d = self.shouldFail2(error.Error, "POST_move_file_into_file",
3566 "400 Bad Request", "to_dir is not a directory",
3567 self.POST, self.public_url + "/foo", t="move",
3568 from_name="bar.txt", to_dir="baz.txt")
3569 d.addCallback(lambda res: self.GET(self.public_url + "/foo/baz.txt"))
3570 d.addCallback(self.failUnlessIsBazDotTxt)
3571 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
3572 d.addCallback(self.failUnlessIsBarDotTxt)
3573 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt?t=json"))
3574 d.addCallback(self.failUnlessIsBarJSON)
3577 def test_POST_move_file_to_bad_uri(self):
3578 d = self.shouldFail2(error.Error, "POST_move_file_to_bad_uri",
3579 "400 Bad Request", "to_dir is not a directory",
3580 self.POST, self.public_url + "/foo", t="move",
3581 from_name="bar.txt", target_type="uri",
3582 to_dir="URI:DIR2:mn5jlyjnrjeuydyswlzyui72i:rmneifcj6k6sycjljjhj3f6majsq2zqffydnnul5hfa4j577arma")
3583 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
3584 d.addCallback(self.failUnlessIsBarDotTxt)
3585 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt?t=json"))
3586 d.addCallback(self.failUnlessIsBarJSON)
3589 def test_POST_move_dir(self):
3590 d = self.POST(self.public_url + "/foo", t="move",
3591 from_name="bar.txt", to_dir="empty")
3592 d.addCallback(lambda res: self.POST(self.public_url + "/foo",
3593 t="move", from_name="empty", to_dir="sub"))
3594 d.addCallback(lambda res:
3595 self.failIfNodeHasChild(self._foo_node, u"empty"))
3596 d.addCallback(lambda res:
3597 self.failUnlessNodeHasChild(self._sub_node, u"empty"))
3598 d.addCallback(lambda res:
3599 self._sub_node.get_child_at_path(u"empty"))
3600 d.addCallback(lambda node:
3601 self.failUnlessNodeHasChild(node, u"bar.txt"))
3602 d.addCallback(lambda res:
3603 self.GET(self.public_url + "/foo/sub/empty/bar.txt"))
3604 d.addCallback(self.failUnlessIsBarDotTxt)
3607 def shouldRedirect(self, res, target=None, statuscode=None, which=""):
3608 """ If target is not None then the redirection has to go to target. If
3609 statuscode is not None then the redirection has to be accomplished with
3610 that HTTP status code."""
3611 if not isinstance(res, failure.Failure):
3612 to_where = (target is None) and "somewhere" or ("to " + target)
3613 self.fail("%s: we were expecting to get redirected %s, not get an"
3614 " actual page: %s" % (which, to_where, res))
3615 res.trap(error.PageRedirect)
3616 if statuscode is not None:
3617 self.failUnlessReallyEqual(res.value.status, statuscode,
3618 "%s: not a redirect" % which)
3619 if target is not None:
3620 # the PageRedirect does not seem to capture the uri= query arg
3621 # properly, so we can't check for it.
3622 realtarget = self.webish_url + target
3623 self.failUnlessReallyEqual(res.value.location, realtarget,
3624 "%s: wrong target" % which)
3625 return res.value.location
3627 def test_GET_URI_form(self):
3628 base = "/uri?uri=%s" % self._bar_txt_uri
3629 # this is supposed to give us a redirect to /uri/$URI, plus arguments
3630 targetbase = "/uri/%s" % urllib.quote(self._bar_txt_uri)
3632 d.addBoth(self.shouldRedirect, targetbase)
3633 d.addCallback(lambda res: self.GET(base+"&filename=bar.txt"))
3634 d.addBoth(self.shouldRedirect, targetbase+"?filename=bar.txt")
3635 d.addCallback(lambda res: self.GET(base+"&t=json"))
3636 d.addBoth(self.shouldRedirect, targetbase+"?t=json")
3637 d.addCallback(self.log, "about to get file by uri")
3638 d.addCallback(lambda res: self.GET(base, followRedirect=True))
3639 d.addCallback(self.failUnlessIsBarDotTxt)
3640 d.addCallback(self.log, "got file by uri, about to get dir by uri")
3641 d.addCallback(lambda res: self.GET("/uri?uri=%s&t=json" % self._foo_uri,
3642 followRedirect=True))
3643 d.addCallback(self.failUnlessIsFooJSON)
3644 d.addCallback(self.log, "got dir by uri")
3648 def test_GET_URI_form_bad(self):
3649 d = self.shouldFail2(error.Error, "test_GET_URI_form_bad",
3650 "400 Bad Request", "GET /uri requires uri=",
3654 def test_GET_rename_form(self):
3655 d = self.GET(self.public_url + "/foo?t=rename-form&name=bar.txt",
3656 followRedirect=True)
3658 self.failUnlessIn('name="when_done" value="."', res)
3659 self.failUnless(re.search(r'name="from_name" value="bar\.txt"', res))
3660 self.failUnlessIn(FAVICON_MARKUP, res)
3661 d.addCallback(_check)
3664 def log(self, res, msg):
3665 #print "MSG: %s RES: %s" % (msg, res)
3669 def test_GET_URI_URL(self):
3670 base = "/uri/%s" % self._bar_txt_uri
3672 d.addCallback(self.failUnlessIsBarDotTxt)
3673 d.addCallback(lambda res: self.GET(base+"?filename=bar.txt"))
3674 d.addCallback(self.failUnlessIsBarDotTxt)
3675 d.addCallback(lambda res: self.GET(base+"?filename=bar.txt&save=true"))
3676 d.addCallback(self.failUnlessIsBarDotTxt)
3679 def test_GET_URI_URL_dir(self):
3680 base = "/uri/%s?t=json" % self._foo_uri
3682 d.addCallback(self.failUnlessIsFooJSON)
3685 def test_GET_URI_URL_missing(self):
3686 base = "/uri/%s" % self._bad_file_uri
3687 d = self.shouldHTTPError("test_GET_URI_URL_missing",
3688 http.GONE, None, "NotEnoughSharesError",
3690 # TODO: how can we exercise both sides of WebDownloadTarget.fail
3691 # here? we must arrange for a download to fail after target.open()
3692 # has been called, and then inspect the response to see that it is
3693 # shorter than we expected.
3696 def test_PUT_DIRURL_uri(self):
3697 d = self.s.create_dirnode()
3699 new_uri = dn.get_uri()
3700 # replace /foo with a new (empty) directory
3701 d = self.PUT(self.public_url + "/foo?t=uri", new_uri)
3702 d.addCallback(lambda res:
3703 self.failUnlessReallyEqual(res.strip(), new_uri))
3704 d.addCallback(lambda res:
3705 self.failUnlessRWChildURIIs(self.public_root,
3709 d.addCallback(_made_dir)
3712 def test_PUT_DIRURL_uri_noreplace(self):
3713 d = self.s.create_dirnode()
3715 new_uri = dn.get_uri()
3716 # replace /foo with a new (empty) directory, but ask that
3717 # replace=false, so it should fail
3718 d = self.shouldFail2(error.Error, "test_PUT_DIRURL_uri_noreplace",
3719 "409 Conflict", "There was already a child by that name, and you asked me to not replace it",
3721 self.public_url + "/foo?t=uri&replace=false",
3723 d.addCallback(lambda res:
3724 self.failUnlessRWChildURIIs(self.public_root,
3728 d.addCallback(_made_dir)
3731 def test_PUT_DIRURL_bad_t(self):
3732 d = self.shouldFail2(error.Error, "test_PUT_DIRURL_bad_t",
3733 "400 Bad Request", "PUT to a directory",
3734 self.PUT, self.public_url + "/foo?t=BOGUS", "")
3735 d.addCallback(lambda res:
3736 self.failUnlessRWChildURIIs(self.public_root,
3741 def test_PUT_NEWFILEURL_uri(self):
3742 contents, n, new_uri = self.makefile(8)
3743 d = self.PUT(self.public_url + "/foo/new.txt?t=uri", new_uri)
3744 d.addCallback(lambda res: self.failUnlessReallyEqual(res.strip(), new_uri))
3745 d.addCallback(lambda res:
3746 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
3750 def test_PUT_NEWFILEURL_mdmf(self):
3751 new_contents = self.NEWFILE_CONTENTS * 300000
3752 d = self.PUT(self.public_url + \
3753 "/foo/mdmf.txt?format=mdmf",
3755 d.addCallback(lambda ignored:
3756 self.GET(self.public_url + "/foo/mdmf.txt?t=json"))
3757 def _got_json(json):
3758 data = simplejson.loads(json)
3760 self.failUnlessIn("format", data)
3761 self.failUnlessEqual(data["format"], "MDMF")
3762 self.failUnless(data['rw_uri'].startswith("URI:MDMF"))
3763 self.failUnless(data['ro_uri'].startswith("URI:MDMF"))
3764 d.addCallback(_got_json)
3767 def test_PUT_NEWFILEURL_sdmf(self):
3768 new_contents = self.NEWFILE_CONTENTS * 300000
3769 d = self.PUT(self.public_url + \
3770 "/foo/sdmf.txt?format=sdmf",
3772 d.addCallback(lambda ignored:
3773 self.GET(self.public_url + "/foo/sdmf.txt?t=json"))
3774 def _got_json(json):
3775 data = simplejson.loads(json)
3777 self.failUnlessIn("format", data)
3778 self.failUnlessEqual(data["format"], "SDMF")
3779 d.addCallback(_got_json)
3782 def test_PUT_NEWFILEURL_bad_format(self):
3783 new_contents = self.NEWFILE_CONTENTS * 300000
3784 return self.shouldHTTPError("PUT_NEWFILEURL_bad_format",
3785 400, "Bad Request", "Unknown format: foo",
3786 self.PUT, self.public_url + \
3787 "/foo/foo.txt?format=foo",
3790 def test_PUT_NEWFILEURL_uri_replace(self):
3791 contents, n, new_uri = self.makefile(8)
3792 d = self.PUT(self.public_url + "/foo/bar.txt?t=uri", new_uri)
3793 d.addCallback(lambda res: self.failUnlessReallyEqual(res.strip(), new_uri))
3794 d.addCallback(lambda res:
3795 self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
3799 def test_PUT_NEWFILEURL_uri_no_replace(self):
3800 contents, n, new_uri = self.makefile(8)
3801 d = self.PUT(self.public_url + "/foo/bar.txt?t=uri&replace=false", new_uri)
3802 d.addBoth(self.shouldFail, error.Error,
3803 "PUT_NEWFILEURL_uri_no_replace",
3805 "There was already a child by that name, and you asked me "
3806 "to not replace it")
3809 def test_PUT_NEWFILEURL_uri_unknown_bad(self):
3810 d = self.PUT(self.public_url + "/foo/put-future.txt?t=uri", unknown_rwcap)
3811 d.addBoth(self.shouldFail, error.Error,
3812 "POST_put_uri_unknown_bad",
3814 "unknown cap in a write slot")
3817 def test_PUT_NEWFILEURL_uri_unknown_ro_good(self):
3818 d = self.PUT(self.public_url + "/foo/put-future-ro.txt?t=uri", unknown_rocap)
3819 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node,
3820 u"put-future-ro.txt")
3823 def test_PUT_NEWFILEURL_uri_unknown_imm_good(self):
3824 d = self.PUT(self.public_url + "/foo/put-future-imm.txt?t=uri", unknown_immcap)
3825 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node,
3826 u"put-future-imm.txt")
3829 def test_PUT_NEWFILE_URI(self):
3830 file_contents = "New file contents here\n"
3831 d = self.PUT("/uri", file_contents)
3833 assert isinstance(uri, str), uri
3834 self.failUnlessIn(uri, self.get_all_contents())
3835 self.failUnlessReallyEqual(self.get_all_contents()[uri],
3837 return self.GET("/uri/%s" % uri)
3838 d.addCallback(_check)
3840 self.failUnlessReallyEqual(res, file_contents)
3841 d.addCallback(_check2)
3844 def test_PUT_NEWFILE_URI_not_mutable(self):
3845 file_contents = "New file contents here\n"
3846 d = self.PUT("/uri?mutable=false", file_contents)
3848 assert isinstance(uri, str), uri
3849 self.failUnlessIn(uri, self.get_all_contents())
3850 self.failUnlessReallyEqual(self.get_all_contents()[uri],
3852 return self.GET("/uri/%s" % uri)
3853 d.addCallback(_check)
3855 self.failUnlessReallyEqual(res, file_contents)
3856 d.addCallback(_check2)
3859 def test_PUT_NEWFILE_URI_only_PUT(self):
3860 d = self.PUT("/uri?t=bogus", "")
3861 d.addBoth(self.shouldFail, error.Error,
3862 "PUT_NEWFILE_URI_only_PUT",
3864 "/uri accepts only PUT, PUT?t=mkdir, POST?t=upload, and POST?t=mkdir")
3867 def test_PUT_NEWFILE_URI_mutable(self):
3868 file_contents = "New file contents here\n"
3869 d = self.PUT("/uri?mutable=true", file_contents)
3870 def _check1(filecap):
3871 filecap = filecap.strip()
3872 self.failUnless(filecap.startswith("URI:SSK:"), filecap)
3873 self.filecap = filecap
3874 u = uri.WriteableSSKFileURI.init_from_string(filecap)
3875 self.failUnlessIn(u.get_storage_index(), self.get_all_contents())
3876 n = self.s.create_node_from_uri(filecap)
3877 return n.download_best_version()
3878 d.addCallback(_check1)
3880 self.failUnlessReallyEqual(data, file_contents)
3881 return self.GET("/uri/%s" % urllib.quote(self.filecap))
3882 d.addCallback(_check2)
3884 self.failUnlessReallyEqual(res, file_contents)
3885 d.addCallback(_check3)
3888 def test_PUT_mkdir(self):
3889 d = self.PUT("/uri?t=mkdir", "")
3891 n = self.s.create_node_from_uri(uri.strip())
3892 d2 = self.failUnlessNodeKeysAre(n, [])
3893 d2.addCallback(lambda res:
3894 self.GET("/uri/%s?t=json" % uri))
3896 d.addCallback(_check)
3897 d.addCallback(self.failUnlessIsEmptyJSON)
3900 def test_PUT_mkdir_mdmf(self):
3901 d = self.PUT("/uri?t=mkdir&format=mdmf", "")
3903 u = uri.from_string(res)
3904 # Check that this is an MDMF writecap
3905 self.failUnlessIsInstance(u, uri.MDMFDirectoryURI)
3909 def test_PUT_mkdir_sdmf(self):
3910 d = self.PUT("/uri?t=mkdir&format=sdmf", "")
3912 u = uri.from_string(res)
3913 self.failUnlessIsInstance(u, uri.DirectoryURI)
3917 def test_PUT_mkdir_bad_format(self):
3918 return self.shouldHTTPError("PUT_mkdir_bad_format",
3919 400, "Bad Request", "Unknown format: foo",
3920 self.PUT, "/uri?t=mkdir&format=foo",
3923 def test_POST_check(self):
3924 d = self.POST(self.public_url + "/foo", t="check", name="bar.txt")
3926 # this returns a string form of the results, which are probably
3927 # None since we're using fake filenodes.
3928 # TODO: verify that the check actually happened, by changing
3929 # FakeCHKFileNode to count how many times .check() has been
3932 d.addCallback(_done)
3936 def test_PUT_update_at_offset(self):
3937 file_contents = "test file" * 100000 # about 900 KiB
3938 d = self.PUT("/uri?mutable=true", file_contents)
3940 self.filecap = filecap
3941 new_data = file_contents[:100]
3942 new = "replaced and so on"
3944 new_data += file_contents[len(new_data):]
3945 assert len(new_data) == len(file_contents)
3946 self.new_data = new_data
3947 d.addCallback(_then)
3948 d.addCallback(lambda ignored:
3949 self.PUT("/uri/%s?replace=True&offset=100" % self.filecap,
3950 "replaced and so on"))
3951 def _get_data(filecap):
3952 n = self.s.create_node_from_uri(filecap)
3953 return n.download_best_version()
3954 d.addCallback(_get_data)
3955 d.addCallback(lambda results:
3956 self.failUnlessEqual(results, self.new_data))
3957 # Now try appending things to the file
3958 d.addCallback(lambda ignored:
3959 self.PUT("/uri/%s?offset=%d" % (self.filecap, len(self.new_data)),
3961 d.addCallback(_get_data)
3962 d.addCallback(lambda results:
3963 self.failUnlessEqual(results, self.new_data + ("puppies" * 100)))
3964 # and try replacing the beginning of the file
3965 d.addCallback(lambda ignored:
3966 self.PUT("/uri/%s?offset=0" % self.filecap, "begin"))
3967 d.addCallback(_get_data)
3968 d.addCallback(lambda results:
3969 self.failUnlessEqual(results, "begin"+self.new_data[len("begin"):]+("puppies"*100)))
3972 def test_PUT_update_at_invalid_offset(self):
3973 file_contents = "test file" * 100000 # about 900 KiB
3974 d = self.PUT("/uri?mutable=true", file_contents)
3976 self.filecap = filecap
3977 d.addCallback(_then)
3978 # Negative offsets should cause an error.
3979 d.addCallback(lambda ignored:
3980 self.shouldHTTPError("PUT_update_at_invalid_offset",
3984 "/uri/%s?offset=-1" % self.filecap,
3988 def test_PUT_update_at_offset_immutable(self):
3989 file_contents = "Test file" * 100000
3990 d = self.PUT("/uri", file_contents)
3992 self.filecap = filecap
3993 d.addCallback(_then)
3994 d.addCallback(lambda ignored:
3995 self.shouldHTTPError("PUT_update_at_offset_immutable",
3999 "/uri/%s?offset=50" % self.filecap,
4004 def test_bad_method(self):
4005 url = self.webish_url + self.public_url + "/foo/bar.txt"
4006 d = self.shouldHTTPError("bad_method",
4007 501, "Not Implemented",
4008 "I don't know how to treat a BOGUS request.",
4009 client.getPage, url, method="BOGUS")
4012 def test_short_url(self):
4013 url = self.webish_url + "/uri"
4014 d = self.shouldHTTPError("short_url", 501, "Not Implemented",
4015 "I don't know how to treat a DELETE request.",
4016 client.getPage, url, method="DELETE")
4019 def test_ophandle_bad(self):
4020 url = self.webish_url + "/operations/bogus?t=status"
4021 d = self.shouldHTTPError("ophandle_bad", 404, "404 Not Found",
4022 "unknown/expired handle 'bogus'",
4023 client.getPage, url)
4026 def test_ophandle_cancel(self):
4027 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=128",
4028 followRedirect=True)
4029 d.addCallback(lambda ignored:
4030 self.GET("/operations/128?t=status&output=JSON"))
4032 data = simplejson.loads(res)
4033 self.failUnless("finished" in data, res)
4034 monitor = self.ws.root.child_operations.handles["128"][0]
4035 d = self.POST("/operations/128?t=cancel&output=JSON")
4037 data = simplejson.loads(res)
4038 self.failUnless("finished" in data, res)
4039 # t=cancel causes the handle to be forgotten
4040 self.failUnless(monitor.is_cancelled())
4041 d.addCallback(_check2)
4043 d.addCallback(_check1)
4044 d.addCallback(lambda ignored:
4045 self.shouldHTTPError("ophandle_cancel",
4046 404, "404 Not Found",
4047 "unknown/expired handle '128'",
4049 "/operations/128?t=status&output=JSON"))
4052 def test_ophandle_retainfor(self):
4053 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=129&retain-for=60",
4054 followRedirect=True)
4055 d.addCallback(lambda ignored:
4056 self.GET("/operations/129?t=status&output=JSON&retain-for=0"))
4058 data = simplejson.loads(res)
4059 self.failUnless("finished" in data, res)
4060 d.addCallback(_check1)
4061 # the retain-for=0 will cause the handle to be expired very soon
4062 d.addCallback(lambda ign:
4063 self.clock.advance(2.0))
4064 d.addCallback(lambda ignored:
4065 self.shouldHTTPError("ophandle_retainfor",
4066 404, "404 Not Found",
4067 "unknown/expired handle '129'",
4069 "/operations/129?t=status&output=JSON"))
4072 def test_ophandle_release_after_complete(self):
4073 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=130",
4074 followRedirect=True)
4075 d.addCallback(self.wait_for_operation, "130")
4076 d.addCallback(lambda ignored:
4077 self.GET("/operations/130?t=status&output=JSON&release-after-complete=true"))
4078 # the release-after-complete=true will cause the handle to be expired
4079 d.addCallback(lambda ignored:
4080 self.shouldHTTPError("ophandle_release_after_complete",
4081 404, "404 Not Found",
4082 "unknown/expired handle '130'",
4084 "/operations/130?t=status&output=JSON"))
4087 def test_uncollected_ophandle_expiration(self):
4088 # uncollected ophandles should expire after 4 days
4089 def _make_uncollected_ophandle(ophandle):
4090 d = self.POST(self.public_url +
4091 "/foo/?t=start-manifest&ophandle=%d" % ophandle,
4092 followRedirect=False)
4093 # When we start the operation, the webapi server will want
4094 # to redirect us to the page for the ophandle, so we get
4095 # confirmation that the operation has started. If the
4096 # manifest operation has finished by the time we get there,
4097 # following that redirect (by setting followRedirect=True
4098 # above) has the side effect of collecting the ophandle that
4099 # we've just created, which means that we can't use the
4100 # ophandle to test the uncollected timeout anymore. So,
4101 # instead, catch the 302 here and don't follow it.
4102 d.addBoth(self.should302, "uncollected_ophandle_creation")
4104 # Create an ophandle, don't collect it, then advance the clock by
4105 # 4 days - 1 second and make sure that the ophandle is still there.
4106 d = _make_uncollected_ophandle(131)
4107 d.addCallback(lambda ign:
4108 self.clock.advance((96*60*60) - 1)) # 96 hours = 4 days
4109 d.addCallback(lambda ign:
4110 self.GET("/operations/131?t=status&output=JSON"))
4112 data = simplejson.loads(res)
4113 self.failUnless("finished" in data, res)
4114 d.addCallback(_check1)
4115 # Create an ophandle, don't collect it, then try to collect it
4116 # after 4 days. It should be gone.
4117 d.addCallback(lambda ign:
4118 _make_uncollected_ophandle(132))
4119 d.addCallback(lambda ign:
4120 self.clock.advance(96*60*60))
4121 d.addCallback(lambda ign:
4122 self.shouldHTTPError("uncollected_ophandle_expired_after_100_hours",
4123 404, "404 Not Found",
4124 "unknown/expired handle '132'",
4126 "/operations/132?t=status&output=JSON"))
4129 def test_collected_ophandle_expiration(self):
4130 # collected ophandles should expire after 1 day
4131 def _make_collected_ophandle(ophandle):
4132 d = self.POST(self.public_url +
4133 "/foo/?t=start-manifest&ophandle=%d" % ophandle,
4134 followRedirect=True)
4135 # By following the initial redirect, we collect the ophandle
4136 # we've just created.
4138 # Create a collected ophandle, then collect it after 23 hours
4139 # and 59 seconds to make sure that it is still there.
4140 d = _make_collected_ophandle(133)
4141 d.addCallback(lambda ign:
4142 self.clock.advance((24*60*60) - 1))
4143 d.addCallback(lambda ign:
4144 self.GET("/operations/133?t=status&output=JSON"))
4146 data = simplejson.loads(res)
4147 self.failUnless("finished" in data, res)
4148 d.addCallback(_check1)
4149 # Create another uncollected ophandle, then try to collect it
4150 # after 24 hours to make sure that it is gone.
4151 d.addCallback(lambda ign:
4152 _make_collected_ophandle(134))
4153 d.addCallback(lambda ign:
4154 self.clock.advance(24*60*60))
4155 d.addCallback(lambda ign:
4156 self.shouldHTTPError("collected_ophandle_expired_after_1_day",
4157 404, "404 Not Found",
4158 "unknown/expired handle '134'",
4160 "/operations/134?t=status&output=JSON"))
4163 def test_incident(self):
4164 d = self.POST("/report_incident", details="eek")
4166 self.failIfIn("<html>", res)
4167 self.failUnlessIn("Thank you for your report!", res)
4168 d.addCallback(_done)
4171 def test_static(self):
4172 webdir = os.path.join(self.staticdir, "subdir")
4173 fileutil.make_dirs(webdir)
4174 f = open(os.path.join(webdir, "hello.txt"), "wb")
4178 d = self.GET("/static/subdir/hello.txt")
4180 self.failUnlessReallyEqual(res, "hello")
4181 d.addCallback(_check)
4185 class IntroducerWeb(unittest.TestCase):
4190 d = defer.succeed(None)
4192 d.addCallback(lambda ign: self.node.stopService())
4193 d.addCallback(flushEventualQueue)
4196 def test_welcome(self):
4197 basedir = "web.IntroducerWeb.test_welcome"
4199 fileutil.write(os.path.join(basedir, "tahoe.cfg"), "[node]\nweb.port = tcp:0\n")
4200 self.node = IntroducerNode(basedir)
4201 self.ws = self.node.getServiceNamed("webish")
4203 d = fireEventually(None)
4204 d.addCallback(lambda ign: self.node.startService())
4205 d.addCallback(lambda ign: self.node.when_tub_ready())
4207 d.addCallback(lambda ign: self.GET("/"))
4209 self.failUnlessIn('Welcome to the Tahoe-LAFS Introducer', res)
4210 self.failUnlessIn(FAVICON_MARKUP, res)
4211 d.addCallback(_check)
4214 def GET(self, urlpath, followRedirect=False, return_response=False,
4216 # if return_response=True, this fires with (data, statuscode,
4217 # respheaders) instead of just data.
4218 assert not isinstance(urlpath, unicode)
4219 url = self.ws.getURL().rstrip('/') + urlpath
4220 factory = HTTPClientGETFactory(url, method="GET",
4221 followRedirect=followRedirect, **kwargs)
4222 reactor.connectTCP("localhost", self.ws.getPortnum(), factory)
4223 d = factory.deferred
4224 def _got_data(data):
4225 return (data, factory.status, factory.response_headers)
4227 d.addCallback(_got_data)
4228 return factory.deferred
4231 class Util(ShouldFailMixin, testutil.ReallyEqualMixin, unittest.TestCase):
4232 def test_load_file(self):
4233 # This will raise an exception unless a well-formed XML file is found under that name.
4234 common.getxmlfile('directory.xhtml').load()
4236 def test_parse_replace_arg(self):
4237 self.failUnlessReallyEqual(common.parse_replace_arg("true"), True)
4238 self.failUnlessReallyEqual(common.parse_replace_arg("false"), False)
4239 self.failUnlessReallyEqual(common.parse_replace_arg("only-files"),
4241 self.shouldFail(AssertionError, "test_parse_replace_arg", "",
4242 common.parse_replace_arg, "only_fles")
4244 def test_abbreviate_time(self):
4245 self.failUnlessReallyEqual(common.abbreviate_time(None), "")
4246 self.failUnlessReallyEqual(common.abbreviate_time(1.234), "1.23s")
4247 self.failUnlessReallyEqual(common.abbreviate_time(0.123), "123ms")
4248 self.failUnlessReallyEqual(common.abbreviate_time(0.00123), "1.2ms")
4249 self.failUnlessReallyEqual(common.abbreviate_time(0.000123), "123us")
4250 self.failUnlessReallyEqual(common.abbreviate_time(-123000), "-123000000000us")
4252 def test_compute_rate(self):
4253 self.failUnlessReallyEqual(common.compute_rate(None, None), None)
4254 self.failUnlessReallyEqual(common.compute_rate(None, 1), None)
4255 self.failUnlessReallyEqual(common.compute_rate(250000, None), None)
4256 self.failUnlessReallyEqual(common.compute_rate(250000, 0), None)
4257 self.failUnlessReallyEqual(common.compute_rate(250000, 10), 25000.0)
4258 self.failUnlessReallyEqual(common.compute_rate(0, 10), 0.0)
4259 self.shouldFail(AssertionError, "test_compute_rate", "",
4260 common.compute_rate, -100, 10)
4261 self.shouldFail(AssertionError, "test_compute_rate", "",
4262 common.compute_rate, 100, -10)
4265 rate = common.compute_rate(10*1000*1000, 1)
4266 self.failUnlessReallyEqual(common.abbreviate_rate(rate), "10.00MBps")
4268 def test_abbreviate_rate(self):
4269 self.failUnlessReallyEqual(common.abbreviate_rate(None), "")
4270 self.failUnlessReallyEqual(common.abbreviate_rate(1234000), "1.23MBps")
4271 self.failUnlessReallyEqual(common.abbreviate_rate(12340), "12.3kBps")
4272 self.failUnlessReallyEqual(common.abbreviate_rate(123), "123Bps")
4274 def test_abbreviate_size(self):
4275 self.failUnlessReallyEqual(common.abbreviate_size(None), "")
4276 self.failUnlessReallyEqual(common.abbreviate_size(1.23*1000*1000*1000), "1.23GB")
4277 self.failUnlessReallyEqual(common.abbreviate_size(1.23*1000*1000), "1.23MB")
4278 self.failUnlessReallyEqual(common.abbreviate_size(1230), "1.2kB")
4279 self.failUnlessReallyEqual(common.abbreviate_size(123), "123B")
4281 def test_plural(self):
4283 return "%d second%s" % (s, status.plural(s))
4284 self.failUnlessReallyEqual(convert(0), "0 seconds")
4285 self.failUnlessReallyEqual(convert(1), "1 second")
4286 self.failUnlessReallyEqual(convert(2), "2 seconds")
4288 return "has share%s: %s" % (status.plural(s), ",".join(s))
4289 self.failUnlessReallyEqual(convert2([]), "has shares: ")
4290 self.failUnlessReallyEqual(convert2(["1"]), "has share: 1")
4291 self.failUnlessReallyEqual(convert2(["1","2"]), "has shares: 1,2")
4294 class Grid(GridTestMixin, WebErrorMixin, ShouldFailMixin, testutil.ReallyEqualMixin, unittest.TestCase):
4296 def CHECK(self, ign, which, args, clientnum=0):
4297 fileurl = self.fileurls[which]
4298 url = fileurl + "?" + args
4299 return self.GET(url, method="POST", clientnum=clientnum)
4301 def test_filecheck(self):
4302 self.basedir = "web/Grid/filecheck"
4304 c0 = self.g.clients[0]
4307 d = c0.upload(upload.Data(DATA, convergence=""))
4308 def _stash_uri(ur, which):
4309 self.uris[which] = ur.get_uri()
4310 d.addCallback(_stash_uri, "good")
4311 d.addCallback(lambda ign:
4312 c0.upload(upload.Data(DATA+"1", convergence="")))
4313 d.addCallback(_stash_uri, "sick")
4314 d.addCallback(lambda ign:
4315 c0.upload(upload.Data(DATA+"2", convergence="")))
4316 d.addCallback(_stash_uri, "dead")
4317 def _stash_mutable_uri(n, which):
4318 self.uris[which] = n.get_uri()
4319 assert isinstance(self.uris[which], str)
4320 d.addCallback(lambda ign:
4321 c0.create_mutable_file(publish.MutableData(DATA+"3")))
4322 d.addCallback(_stash_mutable_uri, "corrupt")
4323 d.addCallback(lambda ign:
4324 c0.upload(upload.Data("literal", convergence="")))
4325 d.addCallback(_stash_uri, "small")
4326 d.addCallback(lambda ign: c0.create_immutable_dirnode({}))
4327 d.addCallback(_stash_mutable_uri, "smalldir")
4329 def _compute_fileurls(ignored):
4331 for which in self.uris:
4332 self.fileurls[which] = "uri/" + urllib.quote(self.uris[which])
4333 d.addCallback(_compute_fileurls)
4335 def _clobber_shares(ignored):
4336 good_shares = self.find_uri_shares(self.uris["good"])
4337 self.failUnlessReallyEqual(len(good_shares), 10)
4338 sick_shares = self.find_uri_shares(self.uris["sick"])
4339 os.unlink(sick_shares[0][2])
4340 dead_shares = self.find_uri_shares(self.uris["dead"])
4341 for i in range(1, 10):
4342 os.unlink(dead_shares[i][2])
4343 c_shares = self.find_uri_shares(self.uris["corrupt"])
4344 cso = CorruptShareOptions()
4345 cso.stdout = StringIO()
4346 cso.parseOptions([c_shares[0][2]])
4348 d.addCallback(_clobber_shares)
4350 d.addCallback(self.CHECK, "good", "t=check")
4351 def _got_html_good(res):
4352 self.failUnlessIn("Healthy", res)
4353 self.failIfIn("Not Healthy", res)
4354 self.failUnlessIn(FAVICON_MARKUP, res)
4355 d.addCallback(_got_html_good)
4356 d.addCallback(self.CHECK, "good", "t=check&return_to=somewhere")
4357 def _got_html_good_return_to(res):
4358 self.failUnlessIn("Healthy", res)
4359 self.failIfIn("Not Healthy", res)
4360 self.failUnlessIn('<a href="somewhere">Return to file', res)
4361 d.addCallback(_got_html_good_return_to)
4362 d.addCallback(self.CHECK, "good", "t=check&output=json")
4363 def _got_json_good(res):
4364 r = simplejson.loads(res)
4365 self.failUnlessEqual(r["summary"], "Healthy")
4366 self.failUnless(r["results"]["healthy"])
4367 self.failIf(r["results"]["needs-rebalancing"])
4368 self.failUnless(r["results"]["recoverable"])
4369 d.addCallback(_got_json_good)
4371 d.addCallback(self.CHECK, "small", "t=check")
4372 def _got_html_small(res):
4373 self.failUnlessIn("Literal files are always healthy", res)
4374 self.failIfIn("Not Healthy", res)
4375 d.addCallback(_got_html_small)
4376 d.addCallback(self.CHECK, "small", "t=check&return_to=somewhere")
4377 def _got_html_small_return_to(res):
4378 self.failUnlessIn("Literal files are always healthy", res)
4379 self.failIfIn("Not Healthy", res)
4380 self.failUnlessIn('<a href="somewhere">Return to file', res)
4381 d.addCallback(_got_html_small_return_to)
4382 d.addCallback(self.CHECK, "small", "t=check&output=json")
4383 def _got_json_small(res):
4384 r = simplejson.loads(res)
4385 self.failUnlessEqual(r["storage-index"], "")
4386 self.failUnless(r["results"]["healthy"])
4387 d.addCallback(_got_json_small)
4389 d.addCallback(self.CHECK, "smalldir", "t=check")
4390 def _got_html_smalldir(res):
4391 self.failUnlessIn("Literal files are always healthy", res)
4392 self.failIfIn("Not Healthy", res)
4393 d.addCallback(_got_html_smalldir)
4394 d.addCallback(self.CHECK, "smalldir", "t=check&output=json")
4395 def _got_json_smalldir(res):
4396 r = simplejson.loads(res)
4397 self.failUnlessEqual(r["storage-index"], "")
4398 self.failUnless(r["results"]["healthy"])
4399 d.addCallback(_got_json_smalldir)
4401 d.addCallback(self.CHECK, "sick", "t=check")
4402 def _got_html_sick(res):
4403 self.failUnlessIn("Not Healthy", res)
4404 d.addCallback(_got_html_sick)
4405 d.addCallback(self.CHECK, "sick", "t=check&output=json")
4406 def _got_json_sick(res):
4407 r = simplejson.loads(res)
4408 self.failUnlessEqual(r["summary"],
4409 "Not Healthy: 9 shares (enc 3-of-10)")
4410 self.failIf(r["results"]["healthy"])
4411 self.failIf(r["results"]["needs-rebalancing"])
4412 self.failUnless(r["results"]["recoverable"])
4413 d.addCallback(_got_json_sick)
4415 d.addCallback(self.CHECK, "dead", "t=check")
4416 def _got_html_dead(res):
4417 self.failUnlessIn("Not Healthy", res)
4418 d.addCallback(_got_html_dead)
4419 d.addCallback(self.CHECK, "dead", "t=check&output=json")
4420 def _got_json_dead(res):
4421 r = simplejson.loads(res)
4422 self.failUnlessEqual(r["summary"],
4423 "Not Healthy: 1 shares (enc 3-of-10)")
4424 self.failIf(r["results"]["healthy"])
4425 self.failIf(r["results"]["needs-rebalancing"])
4426 self.failIf(r["results"]["recoverable"])
4427 d.addCallback(_got_json_dead)
4429 d.addCallback(self.CHECK, "corrupt", "t=check&verify=true")
4430 def _got_html_corrupt(res):
4431 self.failUnlessIn("Not Healthy! : Unhealthy", res)
4432 d.addCallback(_got_html_corrupt)
4433 d.addCallback(self.CHECK, "corrupt", "t=check&verify=true&output=json")
4434 def _got_json_corrupt(res):
4435 r = simplejson.loads(res)
4436 self.failUnlessIn("Unhealthy: 9 shares (enc 3-of-10)", r["summary"])
4437 self.failIf(r["results"]["healthy"])
4438 self.failUnless(r["results"]["recoverable"])
4439 self.failUnlessReallyEqual(r["results"]["count-shares-good"], 9)
4440 self.failUnlessReallyEqual(r["results"]["count-corrupt-shares"], 1)
4441 d.addCallback(_got_json_corrupt)
4443 d.addErrback(self.explain_web_error)
4446 def test_repair_html(self):
4447 self.basedir = "web/Grid/repair_html"
4449 c0 = self.g.clients[0]
4452 d = c0.upload(upload.Data(DATA, convergence=""))
4453 def _stash_uri(ur, which):
4454 self.uris[which] = ur.get_uri()
4455 d.addCallback(_stash_uri, "good")
4456 d.addCallback(lambda ign:
4457 c0.upload(upload.Data(DATA+"1", convergence="")))
4458 d.addCallback(_stash_uri, "sick")
4459 d.addCallback(lambda ign:
4460 c0.upload(upload.Data(DATA+"2", convergence="")))
4461 d.addCallback(_stash_uri, "dead")
4462 def _stash_mutable_uri(n, which):
4463 self.uris[which] = n.get_uri()
4464 assert isinstance(self.uris[which], str)
4465 d.addCallback(lambda ign:
4466 c0.create_mutable_file(publish.MutableData(DATA+"3")))
4467 d.addCallback(_stash_mutable_uri, "corrupt")
4469 def _compute_fileurls(ignored):
4471 for which in self.uris:
4472 self.fileurls[which] = "uri/" + urllib.quote(self.uris[which])
4473 d.addCallback(_compute_fileurls)
4475 def _clobber_shares(ignored):
4476 good_shares = self.find_uri_shares(self.uris["good"])
4477 self.failUnlessReallyEqual(len(good_shares), 10)
4478 sick_shares = self.find_uri_shares(self.uris["sick"])
4479 os.unlink(sick_shares[0][2])
4480 dead_shares = self.find_uri_shares(self.uris["dead"])
4481 for i in range(1, 10):
4482 os.unlink(dead_shares[i][2])
4483 c_shares = self.find_uri_shares(self.uris["corrupt"])
4484 cso = CorruptShareOptions()
4485 cso.stdout = StringIO()
4486 cso.parseOptions([c_shares[0][2]])
4488 d.addCallback(_clobber_shares)
4490 d.addCallback(self.CHECK, "good", "t=check&repair=true")
4491 def _got_html_good(res):
4492 self.failUnlessIn("Healthy", res)
4493 self.failIfIn("Not Healthy", res)
4494 self.failUnlessIn("No repair necessary", res)
4495 self.failUnlessIn(FAVICON_MARKUP, res)
4496 d.addCallback(_got_html_good)
4498 d.addCallback(self.CHECK, "sick", "t=check&repair=true")
4499 def _got_html_sick(res):
4500 self.failUnlessIn("Healthy : healthy", res)
4501 self.failIfIn("Not Healthy", res)
4502 self.failUnlessIn("Repair successful", res)
4503 d.addCallback(_got_html_sick)
4505 # repair of a dead file will fail, of course, but it isn't yet
4506 # clear how this should be reported. Right now it shows up as
4509 #d.addCallback(self.CHECK, "dead", "t=check&repair=true")
4510 #def _got_html_dead(res):
4512 # self.failUnlessIn("Healthy : healthy", res)
4513 # self.failIfIn("Not Healthy", res)
4514 # self.failUnlessIn("No repair necessary", res)
4515 #d.addCallback(_got_html_dead)
4517 d.addCallback(self.CHECK, "corrupt", "t=check&verify=true&repair=true")
4518 def _got_html_corrupt(res):
4519 self.failUnlessIn("Healthy : Healthy", res)
4520 self.failIfIn("Not Healthy", res)
4521 self.failUnlessIn("Repair successful", res)
4522 d.addCallback(_got_html_corrupt)
4524 d.addErrback(self.explain_web_error)
4527 def test_repair_json(self):
4528 self.basedir = "web/Grid/repair_json"
4530 c0 = self.g.clients[0]
4533 d = c0.upload(upload.Data(DATA+"1", convergence=""))
4534 def _stash_uri(ur, which):
4535 self.uris[which] = ur.get_uri()
4536 d.addCallback(_stash_uri, "sick")
4538 def _compute_fileurls(ignored):
4540 for which in self.uris:
4541 self.fileurls[which] = "uri/" + urllib.quote(self.uris[which])
4542 d.addCallback(_compute_fileurls)
4544 def _clobber_shares(ignored):
4545 sick_shares = self.find_uri_shares(self.uris["sick"])
4546 os.unlink(sick_shares[0][2])
4547 d.addCallback(_clobber_shares)
4549 d.addCallback(self.CHECK, "sick", "t=check&repair=true&output=json")
4550 def _got_json_sick(res):
4551 r = simplejson.loads(res)
4552 self.failUnlessReallyEqual(r["repair-attempted"], True)
4553 self.failUnlessReallyEqual(r["repair-successful"], True)
4554 self.failUnlessEqual(r["pre-repair-results"]["summary"],
4555 "Not Healthy: 9 shares (enc 3-of-10)")
4556 self.failIf(r["pre-repair-results"]["results"]["healthy"])
4557 self.failUnlessEqual(r["post-repair-results"]["summary"], "healthy")
4558 self.failUnless(r["post-repair-results"]["results"]["healthy"])
4559 d.addCallback(_got_json_sick)
4561 d.addErrback(self.explain_web_error)
4564 def test_unknown(self, immutable=False):
4565 self.basedir = "web/Grid/unknown"
4567 self.basedir = "web/Grid/unknown-immutable"
4570 c0 = self.g.clients[0]
4574 # the future cap format may contain slashes, which must be tolerated
4575 expected_info_url = "uri/%s?t=info" % urllib.quote(unknown_rwcap,
4579 name = u"future-imm"
4580 future_node = UnknownNode(None, unknown_immcap, deep_immutable=True)
4581 d = c0.create_immutable_dirnode({name: (future_node, {})})
4584 future_node = UnknownNode(unknown_rwcap, unknown_rocap)
4585 d = c0.create_dirnode()
4587 def _stash_root_and_create_file(n):
4589 self.rooturl = "uri/" + urllib.quote(n.get_uri()) + "/"
4590 self.rourl = "uri/" + urllib.quote(n.get_readonly_uri()) + "/"
4592 return self.rootnode.set_node(name, future_node)
4593 d.addCallback(_stash_root_and_create_file)
4595 # make sure directory listing tolerates unknown nodes
4596 d.addCallback(lambda ign: self.GET(self.rooturl))
4597 def _check_directory_html(res, expected_type_suffix):
4598 pattern = re.compile(r'<td>\?%s</td>[ \t\n\r]*'
4599 '<td>%s</td>' % (expected_type_suffix, str(name)),
4601 self.failUnless(re.search(pattern, res), res)
4602 # find the More Info link for name, should be relative
4603 mo = re.search(r'<a href="([^"]+)">More Info</a>', res)
4604 info_url = mo.group(1)
4605 self.failUnlessReallyEqual(info_url, "%s?t=info" % (str(name),))
4607 d.addCallback(_check_directory_html, "-IMM")
4609 d.addCallback(_check_directory_html, "")
4611 d.addCallback(lambda ign: self.GET(self.rooturl+"?t=json"))
4612 def _check_directory_json(res, expect_rw_uri):
4613 data = simplejson.loads(res)
4614 self.failUnlessEqual(data[0], "dirnode")
4615 f = data[1]["children"][name]
4616 self.failUnlessEqual(f[0], "unknown")
4618 self.failUnlessReallyEqual(to_str(f[1]["rw_uri"]), unknown_rwcap, data)
4620 self.failIfIn("rw_uri", f[1])
4622 self.failUnlessReallyEqual(to_str(f[1]["ro_uri"]), unknown_immcap, data)
4624 self.failUnlessReallyEqual(to_str(f[1]["ro_uri"]), unknown_rocap, data)
4625 self.failUnlessIn("metadata", f[1])
4626 d.addCallback(_check_directory_json, expect_rw_uri=not immutable)
4628 def _check_info(res, expect_rw_uri, expect_ro_uri):
4629 self.failUnlessIn("Object Type: <span>unknown</span>", res)
4631 self.failUnlessIn(unknown_rwcap, res)
4634 self.failUnlessIn(unknown_immcap, res)
4636 self.failUnlessIn(unknown_rocap, res)
4638 self.failIfIn(unknown_rocap, res)
4639 self.failIfIn("Raw data as", res)
4640 self.failIfIn("Directory writecap", res)
4641 self.failIfIn("Checker Operations", res)
4642 self.failIfIn("Mutable File Operations", res)
4643 self.failIfIn("Directory Operations", res)
4645 # FIXME: these should have expect_rw_uri=not immutable; I don't know
4646 # why they fail. Possibly related to ticket #922.
4648 d.addCallback(lambda ign: self.GET(expected_info_url))
4649 d.addCallback(_check_info, expect_rw_uri=False, expect_ro_uri=False)
4650 d.addCallback(lambda ign: self.GET("%s%s?t=info" % (self.rooturl, str(name))))
4651 d.addCallback(_check_info, expect_rw_uri=False, expect_ro_uri=True)
4653 def _check_json(res, expect_rw_uri):
4654 data = simplejson.loads(res)
4655 self.failUnlessEqual(data[0], "unknown")
4657 self.failUnlessReallyEqual(to_str(data[1]["rw_uri"]), unknown_rwcap, data)
4659 self.failIfIn("rw_uri", data[1])
4662 self.failUnlessReallyEqual(to_str(data[1]["ro_uri"]), unknown_immcap, data)
4663 self.failUnlessReallyEqual(data[1]["mutable"], False)
4665 self.failUnlessReallyEqual(to_str(data[1]["ro_uri"]), unknown_rocap, data)
4666 self.failUnlessReallyEqual(data[1]["mutable"], True)
4668 self.failUnlessReallyEqual(to_str(data[1]["ro_uri"]), unknown_rocap, data)
4669 self.failIfIn("mutable", data[1])
4671 # TODO: check metadata contents
4672 self.failUnlessIn("metadata", data[1])
4674 d.addCallback(lambda ign: self.GET("%s%s?t=json" % (self.rooturl, str(name))))
4675 d.addCallback(_check_json, expect_rw_uri=not immutable)
4677 # and make sure that a read-only version of the directory can be
4678 # rendered too. This version will not have unknown_rwcap, whether
4679 # or not future_node was immutable.
4680 d.addCallback(lambda ign: self.GET(self.rourl))
4682 d.addCallback(_check_directory_html, "-IMM")
4684 d.addCallback(_check_directory_html, "-RO")
4686 d.addCallback(lambda ign: self.GET(self.rourl+"?t=json"))
4687 d.addCallback(_check_directory_json, expect_rw_uri=False)
4689 d.addCallback(lambda ign: self.GET("%s%s?t=json" % (self.rourl, str(name))))
4690 d.addCallback(_check_json, expect_rw_uri=False)
4692 # TODO: check that getting t=info from the Info link in the ro directory
4693 # works, and does not include the writecap URI.
4696 def test_immutable_unknown(self):
4697 return self.test_unknown(immutable=True)
4699 def test_mutant_dirnodes_are_omitted(self):
4700 self.basedir = "web/Grid/mutant_dirnodes_are_omitted"
4703 c = self.g.clients[0]
4708 lonely_uri = "URI:LIT:n5xgk" # LIT for "one"
4709 mut_write_uri = "URI:SSK:vfvcbdfbszyrsaxchgevhmmlii:euw4iw7bbnkrrwpzuburbhppuxhc3gwxv26f6imekhz7zyw2ojnq"
4710 mut_read_uri = "URI:SSK-RO:e3mdrzfwhoq42hy5ubcz6rp3o4:ybyibhnp3vvwuq2vaw2ckjmesgkklfs6ghxleztqidihjyofgw7q"
4712 # This method tests mainly dirnode, but we'd have to duplicate code in order to
4713 # test the dirnode and web layers separately.
4715 # 'lonely' is a valid LIT child, 'ro' is a mutant child with an SSK-RO readcap,
4716 # and 'write-in-ro' is a mutant child with an SSK writecap in the ro_uri field.
4717 # When the directory is read, the mutants should be silently disposed of, leaving
4718 # their lonely sibling.
4719 # We don't test the case of a retrieving a cap from the encrypted rw_uri field,
4720 # because immutable directories don't have a writecap and therefore that field
4721 # isn't (and can't be) decrypted.
4722 # TODO: The field still exists in the netstring. Technically we should check what
4723 # happens if something is put there (_unpack_contents should raise ValueError),
4724 # but that can wait.
4726 lonely_child = nm.create_from_cap(lonely_uri)
4727 mutant_ro_child = nm.create_from_cap(mut_read_uri)
4728 mutant_write_in_ro_child = nm.create_from_cap(mut_write_uri)
4730 def _by_hook_or_by_crook():
4732 for n in [mutant_ro_child, mutant_write_in_ro_child]:
4733 n.is_allowed_in_immutable_directory = _by_hook_or_by_crook
4735 mutant_write_in_ro_child.get_write_uri = lambda: None
4736 mutant_write_in_ro_child.get_readonly_uri = lambda: mut_write_uri
4738 kids = {u"lonely": (lonely_child, {}),
4739 u"ro": (mutant_ro_child, {}),
4740 u"write-in-ro": (mutant_write_in_ro_child, {}),
4742 d = c.create_immutable_dirnode(kids)
4745 self.failUnless(isinstance(dn, dirnode.DirectoryNode))
4746 self.failIf(dn.is_mutable())
4747 self.failUnless(dn.is_readonly())
4748 # This checks that if we somehow ended up calling dn._decrypt_rwcapdata, it would fail.
4749 self.failIf(hasattr(dn._node, 'get_writekey'))
4751 self.failUnlessIn("RO-IMM", rep)
4753 self.failUnlessIn("CHK", cap.to_string())
4756 self.rooturl = "uri/" + urllib.quote(dn.get_uri()) + "/"
4757 return download_to_data(dn._node)
4758 d.addCallback(_created)
4760 def _check_data(data):
4761 # Decode the netstring representation of the directory to check that all children
4762 # are present. This is a bit of an abstraction violation, but there's not really
4763 # any other way to do it given that the real DirectoryNode._unpack_contents would
4764 # strip the mutant children out (which is what we're trying to test, later).
4767 while position < len(data):
4768 entries, position = split_netstring(data, 1, position)
4770 (name_utf8, ro_uri, rwcapdata, metadata_s), subpos = split_netstring(entry, 4)
4771 name = name_utf8.decode("utf-8")
4772 self.failUnlessEqual(rwcapdata, "")
4773 self.failUnlessIn(name, kids)
4774 (expected_child, ign) = kids[name]
4775 self.failUnlessReallyEqual(ro_uri, expected_child.get_readonly_uri())
4778 self.failUnlessReallyEqual(numkids, 3)
4779 return self.rootnode.list()
4780 d.addCallback(_check_data)
4782 # Now when we use the real directory listing code, the mutants should be absent.
4783 def _check_kids(children):
4784 self.failUnlessReallyEqual(sorted(children.keys()), [u"lonely"])
4785 lonely_node, lonely_metadata = children[u"lonely"]
4787 self.failUnlessReallyEqual(lonely_node.get_write_uri(), None)
4788 self.failUnlessReallyEqual(lonely_node.get_readonly_uri(), lonely_uri)
4789 d.addCallback(_check_kids)
4791 d.addCallback(lambda ign: nm.create_from_cap(self.cap.to_string()))
4792 d.addCallback(lambda n: n.list())
4793 d.addCallback(_check_kids) # again with dirnode recreated from cap
4795 # Make sure the lonely child can be listed in HTML...
4796 d.addCallback(lambda ign: self.GET(self.rooturl))
4797 def _check_html(res):
4798 self.failIfIn("URI:SSK", res)
4799 get_lonely = "".join([r'<td>FILE</td>',
4801 r'<a href="[^"]+%s[^"]+">lonely</a>' % (urllib.quote(lonely_uri),),
4803 r'\s+<td align="right">%d</td>' % len("one"),
4805 self.failUnless(re.search(get_lonely, res), res)
4807 # find the More Info link for name, should be relative
4808 mo = re.search(r'<a href="([^"]+)">More Info</a>', res)
4809 info_url = mo.group(1)
4810 self.failUnless(info_url.endswith(urllib.quote(lonely_uri) + "?t=info"), info_url)
4811 d.addCallback(_check_html)
4814 d.addCallback(lambda ign: self.GET(self.rooturl+"?t=json"))
4815 def _check_json(res):
4816 data = simplejson.loads(res)
4817 self.failUnlessEqual(data[0], "dirnode")
4818 listed_children = data[1]["children"]
4819 self.failUnlessReallyEqual(sorted(listed_children.keys()), [u"lonely"])
4820 ll_type, ll_data = listed_children[u"lonely"]
4821 self.failUnlessEqual(ll_type, "filenode")
4822 self.failIfIn("rw_uri", ll_data)
4823 self.failUnlessReallyEqual(to_str(ll_data["ro_uri"]), lonely_uri)
4824 d.addCallback(_check_json)
4827 def test_deep_check(self):
4828 self.basedir = "web/Grid/deep_check"
4830 c0 = self.g.clients[0]
4834 d = c0.create_dirnode()
4835 def _stash_root_and_create_file(n):
4837 self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri()) + "/"
4838 return n.add_file(u"good", upload.Data(DATA, convergence=""))
4839 d.addCallback(_stash_root_and_create_file)
4840 def _stash_uri(fn, which):
4841 self.uris[which] = fn.get_uri()
4843 d.addCallback(_stash_uri, "good")
4844 d.addCallback(lambda ign:
4845 self.rootnode.add_file(u"small",
4846 upload.Data("literal",
4848 d.addCallback(_stash_uri, "small")
4849 d.addCallback(lambda ign:
4850 self.rootnode.add_file(u"sick",
4851 upload.Data(DATA+"1",
4853 d.addCallback(_stash_uri, "sick")
4855 # this tests that deep-check and stream-manifest will ignore
4856 # UnknownNode instances. Hopefully this will also cover deep-stats.
4857 future_node = UnknownNode(unknown_rwcap, unknown_rocap)
4858 d.addCallback(lambda ign: self.rootnode.set_node(u"future", future_node))
4860 def _clobber_shares(ignored):
4861 self.delete_shares_numbered(self.uris["sick"], [0,1])
4862 d.addCallback(_clobber_shares)
4870 d.addCallback(self.CHECK, "root", "t=stream-deep-check")
4873 units = [simplejson.loads(line)
4874 for line in res.splitlines()
4877 print "response is:", res
4878 print "undecodeable line was '%s'" % line
4880 self.failUnlessReallyEqual(len(units), 5+1)
4881 # should be parent-first
4883 self.failUnlessEqual(u0["path"], [])
4884 self.failUnlessEqual(u0["type"], "directory")
4885 self.failUnlessReallyEqual(to_str(u0["cap"]), self.rootnode.get_uri())
4886 u0cr = u0["check-results"]
4887 self.failUnlessReallyEqual(u0cr["results"]["count-shares-good"], 10)
4889 ugood = [u for u in units
4890 if u["type"] == "file" and u["path"] == [u"good"]][0]
4891 self.failUnlessReallyEqual(to_str(ugood["cap"]), self.uris["good"])
4892 ugoodcr = ugood["check-results"]
4893 self.failUnlessReallyEqual(ugoodcr["results"]["count-shares-good"], 10)
4896 self.failUnlessEqual(stats["type"], "stats")
4898 self.failUnlessReallyEqual(s["count-immutable-files"], 2)
4899 self.failUnlessReallyEqual(s["count-literal-files"], 1)
4900 self.failUnlessReallyEqual(s["count-directories"], 1)
4901 self.failUnlessReallyEqual(s["count-unknown"], 1)
4902 d.addCallback(_done)
4904 d.addCallback(self.CHECK, "root", "t=stream-manifest")
4905 def _check_manifest(res):
4906 self.failUnless(res.endswith("\n"))
4907 units = [simplejson.loads(t) for t in res[:-1].split("\n")]
4908 self.failUnlessReallyEqual(len(units), 5+1)
4909 self.failUnlessEqual(units[-1]["type"], "stats")
4911 self.failUnlessEqual(first["path"], [])
4912 self.failUnlessEqual(to_str(first["cap"]), self.rootnode.get_uri())
4913 self.failUnlessEqual(first["type"], "directory")
4914 stats = units[-1]["stats"]
4915 self.failUnlessReallyEqual(stats["count-immutable-files"], 2)
4916 self.failUnlessReallyEqual(stats["count-literal-files"], 1)
4917 self.failUnlessReallyEqual(stats["count-mutable-files"], 0)
4918 self.failUnlessReallyEqual(stats["count-immutable-files"], 2)
4919 self.failUnlessReallyEqual(stats["count-unknown"], 1)
4920 d.addCallback(_check_manifest)
4922 # now add root/subdir and root/subdir/grandchild, then make subdir
4923 # unrecoverable, then see what happens
4925 d.addCallback(lambda ign:
4926 self.rootnode.create_subdirectory(u"subdir"))
4927 d.addCallback(_stash_uri, "subdir")
4928 d.addCallback(lambda subdir_node:
4929 subdir_node.add_file(u"grandchild",
4930 upload.Data(DATA+"2",
4932 d.addCallback(_stash_uri, "grandchild")
4934 d.addCallback(lambda ign:
4935 self.delete_shares_numbered(self.uris["subdir"],
4943 # root/subdir [unrecoverable]
4944 # root/subdir/grandchild
4946 # how should a streaming-JSON API indicate fatal error?
4947 # answer: emit ERROR: instead of a JSON string
4949 d.addCallback(self.CHECK, "root", "t=stream-manifest")
4950 def _check_broken_manifest(res):
4951 lines = res.splitlines()
4953 for (i,line) in enumerate(lines)
4954 if line.startswith("ERROR:")]
4956 self.fail("no ERROR: in output: %s" % (res,))
4957 first_error = error_lines[0]
4958 error_line = lines[first_error]
4959 error_msg = lines[first_error+1:]
4960 error_msg_s = "\n".join(error_msg) + "\n"
4961 self.failUnlessIn("ERROR: UnrecoverableFileError(no recoverable versions)",
4963 self.failUnless(len(error_msg) > 2, error_msg_s) # some traceback
4964 units = [simplejson.loads(line) for line in lines[:first_error]]
4965 self.failUnlessReallyEqual(len(units), 6) # includes subdir
4966 last_unit = units[-1]
4967 self.failUnlessEqual(last_unit["path"], ["subdir"])
4968 d.addCallback(_check_broken_manifest)
4970 d.addCallback(self.CHECK, "root", "t=stream-deep-check")
4971 def _check_broken_deepcheck(res):
4972 lines = res.splitlines()
4974 for (i,line) in enumerate(lines)
4975 if line.startswith("ERROR:")]
4977 self.fail("no ERROR: in output: %s" % (res,))
4978 first_error = error_lines[0]
4979 error_line = lines[first_error]
4980 error_msg = lines[first_error+1:]
4981 error_msg_s = "\n".join(error_msg) + "\n"
4982 self.failUnlessIn("ERROR: UnrecoverableFileError(no recoverable versions)",
4984 self.failUnless(len(error_msg) > 2, error_msg_s) # some traceback
4985 units = [simplejson.loads(line) for line in lines[:first_error]]
4986 self.failUnlessReallyEqual(len(units), 6) # includes subdir
4987 last_unit = units[-1]
4988 self.failUnlessEqual(last_unit["path"], ["subdir"])
4989 r = last_unit["check-results"]["results"]
4990 self.failUnlessReallyEqual(r["count-recoverable-versions"], 0)
4991 self.failUnlessReallyEqual(r["count-shares-good"], 1)
4992 self.failUnlessReallyEqual(r["recoverable"], False)
4993 d.addCallback(_check_broken_deepcheck)
4995 d.addErrback(self.explain_web_error)
4998 def test_deep_check_and_repair(self):
4999 self.basedir = "web/Grid/deep_check_and_repair"
5001 c0 = self.g.clients[0]
5005 d = c0.create_dirnode()
5006 def _stash_root_and_create_file(n):
5008 self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri()) + "/"
5009 return n.add_file(u"good", upload.Data(DATA, convergence=""))
5010 d.addCallback(_stash_root_and_create_file)
5011 def _stash_uri(fn, which):
5012 self.uris[which] = fn.get_uri()
5013 d.addCallback(_stash_uri, "good")
5014 d.addCallback(lambda ign:
5015 self.rootnode.add_file(u"small",
5016 upload.Data("literal",
5018 d.addCallback(_stash_uri, "small")
5019 d.addCallback(lambda ign:
5020 self.rootnode.add_file(u"sick",
5021 upload.Data(DATA+"1",
5023 d.addCallback(_stash_uri, "sick")
5024 #d.addCallback(lambda ign:
5025 # self.rootnode.add_file(u"dead",
5026 # upload.Data(DATA+"2",
5028 #d.addCallback(_stash_uri, "dead")
5030 #d.addCallback(lambda ign: c0.create_mutable_file("mutable"))
5031 #d.addCallback(lambda fn: self.rootnode.set_node(u"corrupt", fn))
5032 #d.addCallback(_stash_uri, "corrupt")
5034 def _clobber_shares(ignored):
5035 good_shares = self.find_uri_shares(self.uris["good"])
5036 self.failUnlessReallyEqual(len(good_shares), 10)
5037 sick_shares = self.find_uri_shares(self.uris["sick"])
5038 os.unlink(sick_shares[0][2])
5039 #dead_shares = self.find_uri_shares(self.uris["dead"])
5040 #for i in range(1, 10):
5041 # os.unlink(dead_shares[i][2])
5043 #c_shares = self.find_uri_shares(self.uris["corrupt"])
5044 #cso = CorruptShareOptions()
5045 #cso.stdout = StringIO()
5046 #cso.parseOptions([c_shares[0][2]])
5048 d.addCallback(_clobber_shares)
5051 # root/good CHK, 10 shares
5053 # root/sick CHK, 9 shares
5055 d.addCallback(self.CHECK, "root", "t=stream-deep-check&repair=true")
5057 units = [simplejson.loads(line)
5058 for line in res.splitlines()
5060 self.failUnlessReallyEqual(len(units), 4+1)
5061 # should be parent-first
5063 self.failUnlessEqual(u0["path"], [])
5064 self.failUnlessEqual(u0["type"], "directory")
5065 self.failUnlessReallyEqual(to_str(u0["cap"]), self.rootnode.get_uri())
5066 u0crr = u0["check-and-repair-results"]
5067 self.failUnlessReallyEqual(u0crr["repair-attempted"], False)
5068 self.failUnlessReallyEqual(u0crr["pre-repair-results"]["results"]["count-shares-good"], 10)
5070 ugood = [u for u in units
5071 if u["type"] == "file" and u["path"] == [u"good"]][0]
5072 self.failUnlessEqual(to_str(ugood["cap"]), self.uris["good"])
5073 ugoodcrr = ugood["check-and-repair-results"]
5074 self.failUnlessReallyEqual(ugoodcrr["repair-attempted"], False)
5075 self.failUnlessReallyEqual(ugoodcrr["pre-repair-results"]["results"]["count-shares-good"], 10)
5077 usick = [u for u in units
5078 if u["type"] == "file" and u["path"] == [u"sick"]][0]
5079 self.failUnlessReallyEqual(to_str(usick["cap"]), self.uris["sick"])
5080 usickcrr = usick["check-and-repair-results"]
5081 self.failUnlessReallyEqual(usickcrr["repair-attempted"], True)
5082 self.failUnlessReallyEqual(usickcrr["repair-successful"], True)
5083 self.failUnlessReallyEqual(usickcrr["pre-repair-results"]["results"]["count-shares-good"], 9)
5084 self.failUnlessReallyEqual(usickcrr["post-repair-results"]["results"]["count-shares-good"], 10)
5087 self.failUnlessEqual(stats["type"], "stats")
5089 self.failUnlessReallyEqual(s["count-immutable-files"], 2)
5090 self.failUnlessReallyEqual(s["count-literal-files"], 1)
5091 self.failUnlessReallyEqual(s["count-directories"], 1)
5092 d.addCallback(_done)
5094 d.addErrback(self.explain_web_error)
5097 def _count_leases(self, ignored, which):
5098 u = self.uris[which]
5099 shares = self.find_uri_shares(u)
5101 for shnum, serverid, fn in shares:
5102 sf = get_share_file(fn)
5103 num_leases = len(list(sf.get_leases()))
5104 lease_counts.append( (fn, num_leases) )
5107 def _assert_leasecount(self, lease_counts, expected):
5108 for (fn, num_leases) in lease_counts:
5109 if num_leases != expected:
5110 self.fail("expected %d leases, have %d, on %s" %
5111 (expected, num_leases, fn))
5113 def test_add_lease(self):
5114 self.basedir = "web/Grid/add_lease"
5115 self.set_up_grid(num_clients=2)
5116 c0 = self.g.clients[0]
5119 d = c0.upload(upload.Data(DATA, convergence=""))
5120 def _stash_uri(ur, which):
5121 self.uris[which] = ur.get_uri()
5122 d.addCallback(_stash_uri, "one")
5123 d.addCallback(lambda ign:
5124 c0.upload(upload.Data(DATA+"1", convergence="")))
5125 d.addCallback(_stash_uri, "two")
5126 def _stash_mutable_uri(n, which):
5127 self.uris[which] = n.get_uri()
5128 assert isinstance(self.uris[which], str)
5129 d.addCallback(lambda ign:
5130 c0.create_mutable_file(publish.MutableData(DATA+"2")))
5131 d.addCallback(_stash_mutable_uri, "mutable")
5133 def _compute_fileurls(ignored):
5135 for which in self.uris:
5136 self.fileurls[which] = "uri/" + urllib.quote(self.uris[which])
5137 d.addCallback(_compute_fileurls)
5139 d.addCallback(self._count_leases, "one")
5140 d.addCallback(self._assert_leasecount, 1)
5141 d.addCallback(self._count_leases, "two")
5142 d.addCallback(self._assert_leasecount, 1)
5143 d.addCallback(self._count_leases, "mutable")
5144 d.addCallback(self._assert_leasecount, 1)
5146 d.addCallback(self.CHECK, "one", "t=check") # no add-lease
5147 def _got_html_good(res):
5148 self.failUnlessIn("Healthy", res)
5149 self.failIfIn("Not Healthy", res)
5150 d.addCallback(_got_html_good)
5152 d.addCallback(self._count_leases, "one")
5153 d.addCallback(self._assert_leasecount, 1)
5154 d.addCallback(self._count_leases, "two")
5155 d.addCallback(self._assert_leasecount, 1)
5156 d.addCallback(self._count_leases, "mutable")
5157 d.addCallback(self._assert_leasecount, 1)
5159 # this CHECK uses the original client, which uses the same
5160 # lease-secrets, so it will just renew the original lease
5161 d.addCallback(self.CHECK, "one", "t=check&add-lease=true")
5162 d.addCallback(_got_html_good)
5164 d.addCallback(self._count_leases, "one")
5165 d.addCallback(self._assert_leasecount, 1)
5166 d.addCallback(self._count_leases, "two")
5167 d.addCallback(self._assert_leasecount, 1)
5168 d.addCallback(self._count_leases, "mutable")
5169 d.addCallback(self._assert_leasecount, 1)
5171 # this CHECK uses an alternate client, which adds a second lease
5172 d.addCallback(self.CHECK, "one", "t=check&add-lease=true", clientnum=1)
5173 d.addCallback(_got_html_good)
5175 d.addCallback(self._count_leases, "one")
5176 d.addCallback(self._assert_leasecount, 2)
5177 d.addCallback(self._count_leases, "two")
5178 d.addCallback(self._assert_leasecount, 1)
5179 d.addCallback(self._count_leases, "mutable")
5180 d.addCallback(self._assert_leasecount, 1)
5182 d.addCallback(self.CHECK, "mutable", "t=check&add-lease=true")
5183 d.addCallback(_got_html_good)
5185 d.addCallback(self._count_leases, "one")
5186 d.addCallback(self._assert_leasecount, 2)
5187 d.addCallback(self._count_leases, "two")
5188 d.addCallback(self._assert_leasecount, 1)
5189 d.addCallback(self._count_leases, "mutable")
5190 d.addCallback(self._assert_leasecount, 1)
5192 d.addCallback(self.CHECK, "mutable", "t=check&add-lease=true",
5194 d.addCallback(_got_html_good)
5196 d.addCallback(self._count_leases, "one")
5197 d.addCallback(self._assert_leasecount, 2)
5198 d.addCallback(self._count_leases, "two")
5199 d.addCallback(self._assert_leasecount, 1)
5200 d.addCallback(self._count_leases, "mutable")
5201 d.addCallback(self._assert_leasecount, 2)
5203 d.addErrback(self.explain_web_error)
5206 def test_deep_add_lease(self):
5207 self.basedir = "web/Grid/deep_add_lease"
5208 self.set_up_grid(num_clients=2)
5209 c0 = self.g.clients[0]
5213 d = c0.create_dirnode()
5214 def _stash_root_and_create_file(n):
5216 self.uris["root"] = n.get_uri()
5217 self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri()) + "/"
5218 return n.add_file(u"one", upload.Data(DATA, convergence=""))
5219 d.addCallback(_stash_root_and_create_file)
5220 def _stash_uri(fn, which):
5221 self.uris[which] = fn.get_uri()
5222 d.addCallback(_stash_uri, "one")
5223 d.addCallback(lambda ign:
5224 self.rootnode.add_file(u"small",
5225 upload.Data("literal",
5227 d.addCallback(_stash_uri, "small")
5229 d.addCallback(lambda ign:
5230 c0.create_mutable_file(publish.MutableData("mutable")))
5231 d.addCallback(lambda fn: self.rootnode.set_node(u"mutable", fn))
5232 d.addCallback(_stash_uri, "mutable")
5234 d.addCallback(self.CHECK, "root", "t=stream-deep-check") # no add-lease
5236 units = [simplejson.loads(line)
5237 for line in res.splitlines()
5239 # root, one, small, mutable, stats
5240 self.failUnlessReallyEqual(len(units), 4+1)
5241 d.addCallback(_done)
5243 d.addCallback(self._count_leases, "root")
5244 d.addCallback(self._assert_leasecount, 1)
5245 d.addCallback(self._count_leases, "one")
5246 d.addCallback(self._assert_leasecount, 1)
5247 d.addCallback(self._count_leases, "mutable")
5248 d.addCallback(self._assert_leasecount, 1)
5250 d.addCallback(self.CHECK, "root", "t=stream-deep-check&add-lease=true")
5251 d.addCallback(_done)
5253 d.addCallback(self._count_leases, "root")
5254 d.addCallback(self._assert_leasecount, 1)
5255 d.addCallback(self._count_leases, "one")
5256 d.addCallback(self._assert_leasecount, 1)
5257 d.addCallback(self._count_leases, "mutable")
5258 d.addCallback(self._assert_leasecount, 1)
5260 d.addCallback(self.CHECK, "root", "t=stream-deep-check&add-lease=true",
5262 d.addCallback(_done)
5264 d.addCallback(self._count_leases, "root")
5265 d.addCallback(self._assert_leasecount, 2)
5266 d.addCallback(self._count_leases, "one")
5267 d.addCallback(self._assert_leasecount, 2)
5268 d.addCallback(self._count_leases, "mutable")
5269 d.addCallback(self._assert_leasecount, 2)
5271 d.addErrback(self.explain_web_error)
5275 def test_exceptions(self):
5276 self.basedir = "web/Grid/exceptions"
5277 self.set_up_grid(num_clients=1, num_servers=2)
5278 c0 = self.g.clients[0]
5279 c0.DEFAULT_ENCODING_PARAMETERS['happy'] = 2
5282 d = c0.create_dirnode()
5284 self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri()) + "/"
5285 self.fileurls["imaginary"] = self.fileurls["root"] + "imaginary"
5287 d.addCallback(_stash_root)
5288 d.addCallback(lambda ign: c0.upload(upload.Data(DATA, convergence="")))
5290 self.fileurls["1share"] = "uri/" + urllib.quote(ur.get_uri())
5291 self.delete_shares_numbered(ur.get_uri(), range(1,10))
5293 u = uri.from_string(ur.get_uri())
5294 u.key = testutil.flip_bit(u.key, 0)
5295 baduri = u.to_string()
5296 self.fileurls["0shares"] = "uri/" + urllib.quote(baduri)
5297 d.addCallback(_stash_bad)
5298 d.addCallback(lambda ign: c0.create_dirnode())
5299 def _mangle_dirnode_1share(n):
5301 url = self.fileurls["dir-1share"] = "uri/" + urllib.quote(u) + "/"
5302 self.fileurls["dir-1share-json"] = url + "?t=json"
5303 self.delete_shares_numbered(u, range(1,10))
5304 d.addCallback(_mangle_dirnode_1share)
5305 d.addCallback(lambda ign: c0.create_dirnode())
5306 def _mangle_dirnode_0share(n):
5308 url = self.fileurls["dir-0share"] = "uri/" + urllib.quote(u) + "/"
5309 self.fileurls["dir-0share-json"] = url + "?t=json"
5310 self.delete_shares_numbered(u, range(0,10))
5311 d.addCallback(_mangle_dirnode_0share)
5313 # NotEnoughSharesError should be reported sensibly, with a
5314 # text/plain explanation of the problem, and perhaps some
5315 # information on which shares *could* be found.
5317 d.addCallback(lambda ignored:
5318 self.shouldHTTPError("GET unrecoverable",
5319 410, "Gone", "NoSharesError",
5320 self.GET, self.fileurls["0shares"]))
5321 def _check_zero_shares(body):
5322 self.failIfIn("<html>", body)
5323 body = " ".join(body.strip().split())
5324 exp = ("NoSharesError: no shares could be found. "
5325 "Zero shares usually indicates a corrupt URI, or that "
5326 "no servers were connected, but it might also indicate "
5327 "severe corruption. You should perform a filecheck on "
5328 "this object to learn more. The full error message is: "
5329 "no shares (need 3). Last failure: None")
5330 self.failUnlessReallyEqual(exp, body)
5331 d.addCallback(_check_zero_shares)
5334 d.addCallback(lambda ignored:
5335 self.shouldHTTPError("GET 1share",
5336 410, "Gone", "NotEnoughSharesError",
5337 self.GET, self.fileurls["1share"]))
5338 def _check_one_share(body):
5339 self.failIfIn("<html>", body)
5340 body = " ".join(body.strip().split())
5341 msgbase = ("NotEnoughSharesError: This indicates that some "
5342 "servers were unavailable, or that shares have been "
5343 "lost to server departure, hard drive failure, or disk "
5344 "corruption. You should perform a filecheck on "
5345 "this object to learn more. The full error message is:"
5347 msg1 = msgbase + (" ran out of shares:"
5350 " overdue= unused= need 3. Last failure: None")
5351 msg2 = msgbase + (" ran out of shares:"
5353 " pending=Share(sh0-on-xgru5)"
5354 " overdue= unused= need 3. Last failure: None")
5355 self.failUnless(body == msg1 or body == msg2, body)
5356 d.addCallback(_check_one_share)
5358 d.addCallback(lambda ignored:
5359 self.shouldHTTPError("GET imaginary",
5360 404, "Not Found", None,
5361 self.GET, self.fileurls["imaginary"]))
5362 def _missing_child(body):
5363 self.failUnlessIn("No such child: imaginary", body)
5364 d.addCallback(_missing_child)
5366 d.addCallback(lambda ignored: self.GET(self.fileurls["dir-0share"]))
5367 def _check_0shares_dir_html(body):
5368 self.failUnlessIn("<html>", body)
5369 # we should see the regular page, but without the child table or
5371 body = " ".join(body.strip().split())
5372 self.failUnlessIn('href="?t=info">More info on this directory',
5374 exp = ("UnrecoverableFileError: the directory (or mutable file) "
5375 "could not be retrieved, because there were insufficient "
5376 "good shares. This might indicate that no servers were "
5377 "connected, insufficient servers were connected, the URI "
5378 "was corrupt, or that shares have been lost due to server "
5379 "departure, hard drive failure, or disk corruption. You "
5380 "should perform a filecheck on this object to learn more.")
5381 self.failUnlessIn(exp, body)
5382 self.failUnlessIn("No upload forms: directory is unreadable", body)
5383 d.addCallback(_check_0shares_dir_html)
5385 d.addCallback(lambda ignored: self.GET(self.fileurls["dir-1share"]))
5386 def _check_1shares_dir_html(body):
5387 # at some point, we'll split UnrecoverableFileError into 0-shares
5388 # and some-shares like we did for immutable files (since there
5389 # are different sorts of advice to offer in each case). For now,
5390 # they present the same way.
5391 self.failUnlessIn("<html>", body)
5392 body = " ".join(body.strip().split())
5393 self.failUnlessIn('href="?t=info">More info on this directory',
5395 exp = ("UnrecoverableFileError: the directory (or mutable file) "
5396 "could not be retrieved, because there were insufficient "
5397 "good shares. This might indicate that no servers were "
5398 "connected, insufficient servers were connected, the URI "
5399 "was corrupt, or that shares have been lost due to server "
5400 "departure, hard drive failure, or disk corruption. You "
5401 "should perform a filecheck on this object to learn more.")
5402 self.failUnlessIn(exp, body)
5403 self.failUnlessIn("No upload forms: directory is unreadable", body)
5404 d.addCallback(_check_1shares_dir_html)
5406 d.addCallback(lambda ignored:
5407 self.shouldHTTPError("GET dir-0share-json",
5408 410, "Gone", "UnrecoverableFileError",
5410 self.fileurls["dir-0share-json"]))
5411 def _check_unrecoverable_file(body):
5412 self.failIfIn("<html>", body)
5413 body = " ".join(body.strip().split())
5414 exp = ("UnrecoverableFileError: the directory (or mutable file) "
5415 "could not be retrieved, because there were insufficient "
5416 "good shares. This might indicate that no servers were "
5417 "connected, insufficient servers were connected, the URI "
5418 "was corrupt, or that shares have been lost due to server "
5419 "departure, hard drive failure, or disk corruption. You "
5420 "should perform a filecheck on this object to learn more.")
5421 self.failUnlessReallyEqual(exp, body)
5422 d.addCallback(_check_unrecoverable_file)
5424 d.addCallback(lambda ignored:
5425 self.shouldHTTPError("GET dir-1share-json",
5426 410, "Gone", "UnrecoverableFileError",
5428 self.fileurls["dir-1share-json"]))
5429 d.addCallback(_check_unrecoverable_file)
5431 d.addCallback(lambda ignored:
5432 self.shouldHTTPError("GET imaginary",
5433 404, "Not Found", None,
5434 self.GET, self.fileurls["imaginary"]))
5436 # attach a webapi child that throws a random error, to test how it
5438 w = c0.getServiceNamed("webish")
5439 w.root.putChild("ERRORBOOM", ErrorBoom())
5441 # "Accept: */*" : should get a text/html stack trace
5442 # "Accept: text/plain" : should get a text/plain stack trace
5443 # "Accept: text/plain, application/octet-stream" : text/plain (CLI)
5444 # no Accept header: should get a text/html stack trace
5446 d.addCallback(lambda ignored:
5447 self.shouldHTTPError("GET errorboom_html",
5448 500, "Internal Server Error", None,
5449 self.GET, "ERRORBOOM",
5450 headers={"accept": "*/*"}))
5451 def _internal_error_html1(body):
5452 self.failUnlessIn("<html>", "expected HTML, not '%s'" % body)
5453 d.addCallback(_internal_error_html1)
5455 d.addCallback(lambda ignored:
5456 self.shouldHTTPError("GET errorboom_text",
5457 500, "Internal Server Error", None,
5458 self.GET, "ERRORBOOM",
5459 headers={"accept": "text/plain"}))
5460 def _internal_error_text2(body):
5461 self.failIfIn("<html>", body)
5462 self.failUnless(body.startswith("Traceback "), body)
5463 d.addCallback(_internal_error_text2)
5465 CLI_accepts = "text/plain, application/octet-stream"
5466 d.addCallback(lambda ignored:
5467 self.shouldHTTPError("GET errorboom_text",
5468 500, "Internal Server Error", None,
5469 self.GET, "ERRORBOOM",
5470 headers={"accept": CLI_accepts}))
5471 def _internal_error_text3(body):
5472 self.failIfIn("<html>", body)
5473 self.failUnless(body.startswith("Traceback "), body)
5474 d.addCallback(_internal_error_text3)
5476 d.addCallback(lambda ignored:
5477 self.shouldHTTPError("GET errorboom_text",
5478 500, "Internal Server Error", None,
5479 self.GET, "ERRORBOOM"))
5480 def _internal_error_html4(body):
5481 self.failUnlessIn("<html>", body)
5482 d.addCallback(_internal_error_html4)
5484 def _flush_errors(res):
5485 # Trial: please ignore the CompletelyUnhandledError in the logs
5486 self.flushLoggedErrors(CompletelyUnhandledError)
5488 d.addBoth(_flush_errors)
5492 def test_blacklist(self):
5493 # download from a blacklisted URI, get an error
5494 self.basedir = "web/Grid/blacklist"
5496 c0 = self.g.clients[0]
5497 c0_basedir = c0.basedir
5498 fn = os.path.join(c0_basedir, "access.blacklist")
5500 DATA = "off-limits " * 50
5502 d = c0.upload(upload.Data(DATA, convergence=""))
5503 def _stash_uri_and_create_dir(ur):
5504 self.uri = ur.get_uri()
5505 self.url = "uri/"+self.uri
5506 u = uri.from_string_filenode(self.uri)
5507 self.si = u.get_storage_index()
5508 childnode = c0.create_node_from_uri(self.uri, None)
5509 return c0.create_dirnode({u"blacklisted.txt": (childnode,{}) })
5510 d.addCallback(_stash_uri_and_create_dir)
5511 def _stash_dir(node):
5512 self.dir_node = node
5513 self.dir_uri = node.get_uri()
5514 self.dir_url = "uri/"+self.dir_uri
5515 d.addCallback(_stash_dir)
5516 d.addCallback(lambda ign: self.GET(self.dir_url, followRedirect=True))
5517 def _check_dir_html(body):
5518 self.failUnlessIn("<html>", body)
5519 self.failUnlessIn("blacklisted.txt</a>", body)
5520 d.addCallback(_check_dir_html)
5521 d.addCallback(lambda ign: self.GET(self.url))
5522 d.addCallback(lambda body: self.failUnlessEqual(DATA, body))
5524 def _blacklist(ign):
5526 f.write(" # this is a comment\n")
5528 f.write("\n") # also exercise blank lines
5529 f.write("%s %s\n" % (base32.b2a(self.si), "off-limits to you"))
5531 # clients should be checking the blacklist each time, so we don't
5532 # need to restart the client
5533 d.addCallback(_blacklist)
5534 d.addCallback(lambda ign: self.shouldHTTPError("get_from_blacklisted_uri",
5536 "Access Prohibited: off-limits",
5537 self.GET, self.url))
5539 # We should still be able to list the parent directory, in HTML...
5540 d.addCallback(lambda ign: self.GET(self.dir_url, followRedirect=True))
5541 def _check_dir_html2(body):
5542 self.failUnlessIn("<html>", body)
5543 self.failUnlessIn("blacklisted.txt</strike>", body)
5544 d.addCallback(_check_dir_html2)
5546 # ... and in JSON (used by CLI).
5547 d.addCallback(lambda ign: self.GET(self.dir_url+"?t=json", followRedirect=True))
5548 def _check_dir_json(res):
5549 data = simplejson.loads(res)
5550 self.failUnless(isinstance(data, list), data)
5551 self.failUnlessEqual(data[0], "dirnode")
5552 self.failUnless(isinstance(data[1], dict), data)
5553 self.failUnlessIn("children", data[1])
5554 self.failUnlessIn("blacklisted.txt", data[1]["children"])
5555 childdata = data[1]["children"]["blacklisted.txt"]
5556 self.failUnless(isinstance(childdata, list), data)
5557 self.failUnlessEqual(childdata[0], "filenode")
5558 self.failUnless(isinstance(childdata[1], dict), data)
5559 d.addCallback(_check_dir_json)
5561 def _unblacklist(ign):
5562 open(fn, "w").close()
5563 # the Blacklist object watches mtime to tell when the file has
5564 # changed, but on windows this test will run faster than the
5565 # filesystem's mtime resolution. So we edit Blacklist.last_mtime
5566 # to force a reload.
5567 self.g.clients[0].blacklist.last_mtime -= 2.0
5568 d.addCallback(_unblacklist)
5570 # now a read should work
5571 d.addCallback(lambda ign: self.GET(self.url))
5572 d.addCallback(lambda body: self.failUnlessEqual(DATA, body))
5574 # read again to exercise the blacklist-is-unchanged logic
5575 d.addCallback(lambda ign: self.GET(self.url))
5576 d.addCallback(lambda body: self.failUnlessEqual(DATA, body))
5578 # now add a blacklisted directory, and make sure files under it are
5581 childnode = c0.create_node_from_uri(self.uri, None)
5582 return c0.create_dirnode({u"child": (childnode,{}) })
5583 d.addCallback(_add_dir)
5584 def _get_dircap(dn):
5585 self.dir_si_b32 = base32.b2a(dn.get_storage_index())
5586 self.dir_url_base = "uri/"+dn.get_write_uri()
5587 self.dir_url_json1 = "uri/"+dn.get_write_uri()+"?t=json"
5588 self.dir_url_json2 = "uri/"+dn.get_write_uri()+"/?t=json"
5589 self.dir_url_json_ro = "uri/"+dn.get_readonly_uri()+"/?t=json"
5590 self.child_url = "uri/"+dn.get_readonly_uri()+"/child"
5591 d.addCallback(_get_dircap)
5592 d.addCallback(lambda ign: self.GET(self.dir_url_base, followRedirect=True))
5593 d.addCallback(lambda body: self.failUnlessIn("<html>", body))
5594 d.addCallback(lambda ign: self.GET(self.dir_url_json1))
5595 d.addCallback(lambda res: simplejson.loads(res)) # just check it decodes
5596 d.addCallback(lambda ign: self.GET(self.dir_url_json2))
5597 d.addCallback(lambda res: simplejson.loads(res)) # just check it decodes
5598 d.addCallback(lambda ign: self.GET(self.dir_url_json_ro))
5599 d.addCallback(lambda res: simplejson.loads(res)) # just check it decodes
5600 d.addCallback(lambda ign: self.GET(self.child_url))
5601 d.addCallback(lambda body: self.failUnlessEqual(DATA, body))
5603 def _block_dir(ign):
5605 f.write("%s %s\n" % (self.dir_si_b32, "dir-off-limits to you"))
5607 self.g.clients[0].blacklist.last_mtime -= 2.0
5608 d.addCallback(_block_dir)
5609 d.addCallback(lambda ign: self.shouldHTTPError("get_from_blacklisted_dir base",
5611 "Access Prohibited: dir-off-limits",
5612 self.GET, self.dir_url_base))
5613 d.addCallback(lambda ign: self.shouldHTTPError("get_from_blacklisted_dir json1",
5615 "Access Prohibited: dir-off-limits",
5616 self.GET, self.dir_url_json1))
5617 d.addCallback(lambda ign: self.shouldHTTPError("get_from_blacklisted_dir json2",
5619 "Access Prohibited: dir-off-limits",
5620 self.GET, self.dir_url_json2))
5621 d.addCallback(lambda ign: self.shouldHTTPError("get_from_blacklisted_dir json_ro",
5623 "Access Prohibited: dir-off-limits",
5624 self.GET, self.dir_url_json_ro))
5625 d.addCallback(lambda ign: self.shouldHTTPError("get_from_blacklisted_dir child",
5627 "Access Prohibited: dir-off-limits",
5628 self.GET, self.child_url))
5632 class CompletelyUnhandledError(Exception):
5634 class ErrorBoom(rend.Page):
5635 def beforeRender(self, ctx):
5636 raise CompletelyUnhandledError("whoops")