1 import os.path, re, urllib, time
3 from StringIO import StringIO
4 from twisted.application import service
5 from twisted.trial import unittest
6 from twisted.internet import defer, reactor
7 from twisted.internet.task import Clock
8 from twisted.web import client, error, http
9 from twisted.python import failure, log
10 from nevow import rend
11 from allmydata import interfaces, uri, webish, dirnode
12 from allmydata.storage.shares import get_share_file
13 from allmydata.storage_client import StorageFarmBroker
14 from allmydata.immutable import upload
15 from allmydata.immutable.downloader.status import DownloadStatus
16 from allmydata.dirnode import DirectoryNode
17 from allmydata.nodemaker import NodeMaker
18 from allmydata.unknown import UnknownNode
19 from allmydata.web import status, common
20 from allmydata.scripts.debug import CorruptShareOptions, corrupt_share
21 from allmydata.util import fileutil, base32, hashutil
22 from allmydata.util.consumer import download_to_data
23 from allmydata.util.netstring import split_netstring
24 from allmydata.util.encodingutil import to_str
25 from allmydata.test.common import FakeCHKFileNode, FakeMutableFileNode, \
26 create_chk_filenode, WebErrorMixin, ShouldFailMixin, \
27 make_mutable_file_uri, create_mutable_filenode
28 from allmydata.interfaces import IMutableFileNode, SDMF_VERSION, MDMF_VERSION
29 from allmydata.mutable import servermap, publish, retrieve
30 import allmydata.test.common_util as testutil
31 from allmydata.test.no_network import GridTestMixin
32 from allmydata.test.common_web import HTTPClientGETFactory, \
34 from allmydata.client import Client, SecretHolder
36 # create a fake uploader/downloader, and a couple of fake dirnodes, then
37 # create a webserver that works against them
39 timeout = 480 # Most of these take longer than 240 seconds on Francois's arm box.
41 unknown_rwcap = u"lafs://from_the_future_rw_\u263A".encode('utf-8')
42 unknown_rocap = u"ro.lafs://readonly_from_the_future_ro_\u263A".encode('utf-8')
43 unknown_immcap = u"imm.lafs://immutable_from_the_future_imm_\u263A".encode('utf-8')
45 FAVICON_MARKUP = '<link href="/icon.png" rel="shortcut icon" />'
48 class FakeStatsProvider:
50 stats = {'stats': {}, 'counters': {}}
53 class FakeNodeMaker(NodeMaker):
58 'max_segment_size':128*1024 # 1024=KiB
60 def _create_lit(self, cap):
61 return FakeCHKFileNode(cap)
62 def _create_immutable(self, cap):
63 return FakeCHKFileNode(cap)
64 def _create_mutable(self, cap):
65 return FakeMutableFileNode(None,
67 self.encoding_params, None).init_from_cap(cap)
68 def create_mutable_file(self, contents="", keysize=None,
69 version=SDMF_VERSION):
70 n = FakeMutableFileNode(None, None, self.encoding_params, None)
71 return n.create(contents, version=version)
73 class FakeUploader(service.Service):
75 def upload(self, uploadable):
76 d = uploadable.get_size()
77 d.addCallback(lambda size: uploadable.read(size))
80 n = create_chk_filenode(data)
81 results = upload.UploadResults()
82 results.uri = n.get_uri()
84 d.addCallback(_got_data)
86 def get_helper_info(self):
90 def __init__(self, binaryserverid):
91 self.binaryserverid = binaryserverid
92 def get_name(self): return "short"
93 def get_longname(self): return "long"
94 def get_serverid(self): return self.binaryserverid
97 ds = DownloadStatus("storage_index", 1234)
100 serverA = FakeIServer(hashutil.tagged_hash("foo", "serverid_a")[:20])
101 serverB = FakeIServer(hashutil.tagged_hash("foo", "serverid_b")[:20])
102 storage_index = hashutil.storage_index_hash("SI")
103 e0 = ds.add_segment_request(0, now)
105 e0.deliver(now+1, 0, 100, 0.5) # when, start,len, decodetime
106 e1 = ds.add_segment_request(1, now+2)
108 # two outstanding requests
109 e2 = ds.add_segment_request(2, now+4)
110 e3 = ds.add_segment_request(3, now+5)
111 del e2,e3 # hush pyflakes
113 # simulate a segment which gets delivered faster than a system clock tick (ticket #1166)
114 e = ds.add_segment_request(4, now)
116 e.deliver(now, 0, 140, 0.5)
118 e = ds.add_dyhb_request(serverA, now)
119 e.finished([1,2], now+1)
120 e = ds.add_dyhb_request(serverB, now+2) # left unfinished
122 e = ds.add_read_event(0, 120, now)
123 e.update(60, 0.5, 0.1) # bytes, decrypttime, pausetime
125 e = ds.add_read_event(120, 30, now+2) # left unfinished
127 e = ds.add_block_request(serverA, 1, 100, 20, now)
128 e.finished(20, now+1)
129 e = ds.add_block_request(serverB, 1, 120, 30, now+1) # left unfinished
131 # make sure that add_read_event() can come first too
132 ds1 = DownloadStatus(storage_index, 1234)
133 e = ds1.add_read_event(0, 120, now)
134 e.update(60, 0.5, 0.1) # bytes, decrypttime, pausetime
140 _all_upload_status = [upload.UploadStatus()]
141 _all_download_status = [build_one_ds()]
142 _all_mapupdate_statuses = [servermap.UpdateStatus()]
143 _all_publish_statuses = [publish.PublishStatus()]
144 _all_retrieve_statuses = [retrieve.RetrieveStatus()]
146 def list_all_upload_statuses(self):
147 return self._all_upload_status
148 def list_all_download_statuses(self):
149 return self._all_download_status
150 def list_all_mapupdate_statuses(self):
151 return self._all_mapupdate_statuses
152 def list_all_publish_statuses(self):
153 return self._all_publish_statuses
154 def list_all_retrieve_statuses(self):
155 return self._all_retrieve_statuses
156 def list_all_helper_statuses(self):
159 class FakeClient(Client):
161 # don't upcall to Client.__init__, since we only want to initialize a
163 service.MultiService.__init__(self)
164 self.nodeid = "fake_nodeid"
165 self.nickname = "fake_nickname"
166 self.introducer_furl = "None"
167 self.stats_provider = FakeStatsProvider()
168 self._secret_holder = SecretHolder("lease secret", "convergence secret")
170 self.convergence = "some random string"
171 self.storage_broker = StorageFarmBroker(None, permute_peers=True)
172 self.introducer_client = None
173 self.history = FakeHistory()
174 self.uploader = FakeUploader()
175 self.uploader.setServiceParent(self)
176 self.blacklist = None
177 self.nodemaker = FakeNodeMaker(None, self._secret_holder, None,
180 self.mutable_file_default = SDMF_VERSION
182 def startService(self):
183 return service.MultiService.startService(self)
184 def stopService(self):
185 return service.MultiService.stopService(self)
187 MUTABLE_SIZELIMIT = FakeMutableFileNode.MUTABLE_SIZELIMIT
189 class WebMixin(object):
191 self.s = FakeClient()
192 self.s.startService()
193 self.staticdir = self.mktemp()
195 self.ws = webish.WebishServer(self.s, "0", staticdir=self.staticdir,
197 self.ws.setServiceParent(self.s)
198 self.webish_port = self.ws.getPortnum()
199 self.webish_url = self.ws.getURL()
200 assert self.webish_url.endswith("/")
201 self.webish_url = self.webish_url[:-1] # these tests add their own /
203 l = [ self.s.create_dirnode() for x in range(6) ]
204 d = defer.DeferredList(l)
206 self.public_root = res[0][1]
207 assert interfaces.IDirectoryNode.providedBy(self.public_root), res
208 self.public_url = "/uri/" + self.public_root.get_uri()
209 self.private_root = res[1][1]
213 self._foo_uri = foo.get_uri()
214 self._foo_readonly_uri = foo.get_readonly_uri()
215 self._foo_verifycap = foo.get_verify_cap().to_string()
216 # NOTE: we ignore the deferred on all set_uri() calls, because we
217 # know the fake nodes do these synchronously
218 self.public_root.set_uri(u"foo", foo.get_uri(),
219 foo.get_readonly_uri())
221 self.BAR_CONTENTS, n, self._bar_txt_uri = self.makefile(0)
222 foo.set_uri(u"bar.txt", self._bar_txt_uri, self._bar_txt_uri)
223 self._bar_txt_verifycap = n.get_verify_cap().to_string()
226 # XXX: Do we ever use this?
227 self.BAZ_CONTENTS, n, self._baz_txt_uri, self._baz_txt_readonly_uri = self.makefile_mutable(0)
229 foo.set_uri(u"baz.txt", self._baz_txt_uri, self._baz_txt_readonly_uri)
232 self.QUUX_CONTENTS, n, self._quux_txt_uri, self._quux_txt_readonly_uri = self.makefile_mutable(0, mdmf=True)
233 assert self._quux_txt_uri.startswith("URI:MDMF")
234 foo.set_uri(u"quux.txt", self._quux_txt_uri, self._quux_txt_readonly_uri)
236 foo.set_uri(u"empty", res[3][1].get_uri(),
237 res[3][1].get_readonly_uri())
238 sub_uri = res[4][1].get_uri()
239 self._sub_uri = sub_uri
240 foo.set_uri(u"sub", sub_uri, sub_uri)
241 sub = self.s.create_node_from_uri(sub_uri)
243 _ign, n, blocking_uri = self.makefile(1)
244 foo.set_uri(u"blockingfile", blocking_uri, blocking_uri)
246 unicode_filename = u"n\u00fc.txt" # n u-umlaut . t x t
247 # ok, unicode calls it LATIN SMALL LETTER U WITH DIAERESIS but I
248 # still think of it as an umlaut
249 foo.set_uri(unicode_filename, self._bar_txt_uri, self._bar_txt_uri)
251 _ign, n, baz_file = self.makefile(2)
252 self._baz_file_uri = baz_file
253 sub.set_uri(u"baz.txt", baz_file, baz_file)
255 _ign, n, self._bad_file_uri = self.makefile(3)
256 # this uri should not be downloadable
257 del FakeCHKFileNode.all_contents[self._bad_file_uri]
260 self.public_root.set_uri(u"reedownlee", rodir.get_readonly_uri(),
261 rodir.get_readonly_uri())
262 rodir.set_uri(u"nor", baz_file, baz_file)
268 # public/foo/quux.txt
269 # public/foo/blockingfile
272 # public/foo/sub/baz.txt
274 # public/reedownlee/nor
275 self.NEWFILE_CONTENTS = "newfile contents\n"
277 return foo.get_metadata_for(u"bar.txt")
279 def _got_metadata(metadata):
280 self._bar_txt_metadata = metadata
281 d.addCallback(_got_metadata)
284 def makefile(self, number):
285 contents = "contents of file %s\n" % number
286 n = create_chk_filenode(contents)
287 return contents, n, n.get_uri()
289 def makefile_mutable(self, number, mdmf=False):
290 contents = "contents of mutable file %s\n" % number
291 n = create_mutable_filenode(contents, mdmf)
292 return contents, n, n.get_uri(), n.get_readonly_uri()
295 return self.s.stopService()
297 def failUnlessIsBarDotTxt(self, res):
298 self.failUnlessReallyEqual(res, self.BAR_CONTENTS, res)
300 def failUnlessIsQuuxDotTxt(self, res):
301 self.failUnlessReallyEqual(res, self.QUUX_CONTENTS, res)
303 def failUnlessIsBazDotTxt(self, res):
304 self.failUnlessReallyEqual(res, self.BAZ_CONTENTS, res)
306 def failUnlessIsBarJSON(self, res):
307 data = simplejson.loads(res)
308 self.failUnless(isinstance(data, list))
309 self.failUnlessEqual(data[0], "filenode")
310 self.failUnless(isinstance(data[1], dict))
311 self.failIf(data[1]["mutable"])
312 self.failIfIn("rw_uri", data[1]) # immutable
313 self.failUnlessReallyEqual(to_str(data[1]["ro_uri"]), self._bar_txt_uri)
314 self.failUnlessReallyEqual(to_str(data[1]["verify_uri"]), self._bar_txt_verifycap)
315 self.failUnlessReallyEqual(data[1]["size"], len(self.BAR_CONTENTS))
317 def failUnlessIsQuuxJSON(self, res, readonly=False):
318 data = simplejson.loads(res)
319 self.failUnless(isinstance(data, list))
320 self.failUnlessEqual(data[0], "filenode")
321 self.failUnless(isinstance(data[1], dict))
323 return self.failUnlessIsQuuxDotTxtMetadata(metadata, readonly)
325 def failUnlessIsQuuxDotTxtMetadata(self, metadata, readonly):
326 self.failUnless(metadata['mutable'])
328 self.failIfIn("rw_uri", metadata)
330 self.failUnlessIn("rw_uri", metadata)
331 self.failUnlessEqual(metadata['rw_uri'], self._quux_txt_uri)
332 self.failUnlessIn("ro_uri", metadata)
333 self.failUnlessEqual(metadata['ro_uri'], self._quux_txt_readonly_uri)
334 self.failUnlessReallyEqual(metadata['size'], len(self.QUUX_CONTENTS))
336 def failUnlessIsFooJSON(self, res):
337 data = simplejson.loads(res)
338 self.failUnless(isinstance(data, list))
339 self.failUnlessEqual(data[0], "dirnode", res)
340 self.failUnless(isinstance(data[1], dict))
341 self.failUnless(data[1]["mutable"])
342 self.failUnlessIn("rw_uri", data[1]) # mutable
343 self.failUnlessReallyEqual(to_str(data[1]["rw_uri"]), self._foo_uri)
344 self.failUnlessReallyEqual(to_str(data[1]["ro_uri"]), self._foo_readonly_uri)
345 self.failUnlessReallyEqual(to_str(data[1]["verify_uri"]), self._foo_verifycap)
347 kidnames = sorted([unicode(n) for n in data[1]["children"]])
348 self.failUnlessEqual(kidnames,
349 [u"bar.txt", u"baz.txt", u"blockingfile",
350 u"empty", u"n\u00fc.txt", u"quux.txt", u"sub"])
351 kids = dict( [(unicode(name),value)
353 in data[1]["children"].iteritems()] )
354 self.failUnlessEqual(kids[u"sub"][0], "dirnode")
355 self.failUnlessIn("metadata", kids[u"sub"][1])
356 self.failUnlessIn("tahoe", kids[u"sub"][1]["metadata"])
357 tahoe_md = kids[u"sub"][1]["metadata"]["tahoe"]
358 self.failUnlessIn("linkcrtime", tahoe_md)
359 self.failUnlessIn("linkmotime", tahoe_md)
360 self.failUnlessEqual(kids[u"bar.txt"][0], "filenode")
361 self.failUnlessReallyEqual(kids[u"bar.txt"][1]["size"], len(self.BAR_CONTENTS))
362 self.failUnlessReallyEqual(to_str(kids[u"bar.txt"][1]["ro_uri"]), self._bar_txt_uri)
363 self.failUnlessReallyEqual(to_str(kids[u"bar.txt"][1]["verify_uri"]),
364 self._bar_txt_verifycap)
365 self.failUnlessIn("metadata", kids[u"bar.txt"][1])
366 self.failUnlessIn("tahoe", kids[u"bar.txt"][1]["metadata"])
367 self.failUnlessReallyEqual(kids[u"bar.txt"][1]["metadata"]["tahoe"]["linkcrtime"],
368 self._bar_txt_metadata["tahoe"]["linkcrtime"])
369 self.failUnlessReallyEqual(to_str(kids[u"n\u00fc.txt"][1]["ro_uri"]),
371 self.failUnlessIn("quux.txt", kids)
372 self.failUnlessReallyEqual(to_str(kids[u"quux.txt"][1]["rw_uri"]),
374 self.failUnlessReallyEqual(to_str(kids[u"quux.txt"][1]["ro_uri"]),
375 self._quux_txt_readonly_uri)
377 def GET(self, urlpath, followRedirect=False, return_response=False,
379 # if return_response=True, this fires with (data, statuscode,
380 # respheaders) instead of just data.
381 assert not isinstance(urlpath, unicode)
382 url = self.webish_url + urlpath
383 factory = HTTPClientGETFactory(url, method="GET",
384 followRedirect=followRedirect, **kwargs)
385 reactor.connectTCP("localhost", self.webish_port, factory)
388 return (data, factory.status, factory.response_headers)
390 d.addCallback(_got_data)
391 return factory.deferred
393 def HEAD(self, urlpath, return_response=False, **kwargs):
394 # this requires some surgery, because twisted.web.client doesn't want
395 # to give us back the response headers.
396 factory = HTTPClientHEADFactory(urlpath, method="HEAD", **kwargs)
397 reactor.connectTCP("localhost", self.webish_port, factory)
400 return (data, factory.status, factory.response_headers)
402 d.addCallback(_got_data)
403 return factory.deferred
405 def PUT(self, urlpath, data, **kwargs):
406 url = self.webish_url + urlpath
407 return client.getPage(url, method="PUT", postdata=data, **kwargs)
409 def DELETE(self, urlpath):
410 url = self.webish_url + urlpath
411 return client.getPage(url, method="DELETE")
413 def POST(self, urlpath, followRedirect=False, **fields):
414 sepbase = "boogabooga"
418 form.append('Content-Disposition: form-data; name="_charset"')
422 for name, value in fields.iteritems():
423 if isinstance(value, tuple):
424 filename, value = value
425 form.append('Content-Disposition: form-data; name="%s"; '
426 'filename="%s"' % (name, filename.encode("utf-8")))
428 form.append('Content-Disposition: form-data; name="%s"' % name)
430 if isinstance(value, unicode):
431 value = value.encode("utf-8")
434 assert isinstance(value, str)
441 body = "\r\n".join(form) + "\r\n"
442 headers["content-type"] = "multipart/form-data; boundary=%s" % sepbase
443 return self.POST2(urlpath, body, headers, followRedirect)
445 def POST2(self, urlpath, body="", headers={}, followRedirect=False):
446 url = self.webish_url + urlpath
447 return client.getPage(url, method="POST", postdata=body,
448 headers=headers, followRedirect=followRedirect)
450 def shouldFail(self, res, expected_failure, which,
451 substring=None, response_substring=None):
452 if isinstance(res, failure.Failure):
453 res.trap(expected_failure)
455 self.failUnlessIn(substring, str(res), which)
456 if response_substring:
457 self.failUnlessIn(response_substring, res.value.response, which)
459 self.fail("%s was supposed to raise %s, not get '%s'" %
460 (which, expected_failure, res))
462 def shouldFail2(self, expected_failure, which, substring,
464 callable, *args, **kwargs):
465 assert substring is None or isinstance(substring, str)
466 assert response_substring is None or isinstance(response_substring, str)
467 d = defer.maybeDeferred(callable, *args, **kwargs)
469 if isinstance(res, failure.Failure):
470 res.trap(expected_failure)
472 self.failUnlessIn(substring, str(res), which)
473 if response_substring:
474 self.failUnlessIn(response_substring, res.value.response, which)
476 self.fail("%s was supposed to raise %s, not get '%s'" %
477 (which, expected_failure, res))
481 def should404(self, res, which):
482 if isinstance(res, failure.Failure):
483 res.trap(error.Error)
484 self.failUnlessReallyEqual(res.value.status, "404")
486 self.fail("%s was supposed to Error(404), not get '%s'" %
489 def should302(self, res, which):
490 if isinstance(res, failure.Failure):
491 res.trap(error.Error)
492 self.failUnlessReallyEqual(res.value.status, "302")
494 self.fail("%s was supposed to Error(302), not get '%s'" %
498 class Web(WebMixin, WebErrorMixin, testutil.StallMixin, testutil.ReallyEqualMixin, unittest.TestCase):
499 def test_create(self):
502 def test_welcome(self):
505 self.failUnlessIn('Welcome To Tahoe-LAFS', res)
506 self.failUnlessIn(FAVICON_MARKUP, res)
508 self.s.basedir = 'web/test_welcome'
509 fileutil.make_dirs("web/test_welcome")
510 fileutil.make_dirs("web/test_welcome/private")
512 d.addCallback(_check)
515 def test_status(self):
516 h = self.s.get_history()
517 dl_num = h.list_all_download_statuses()[0].get_counter()
518 ul_num = h.list_all_upload_statuses()[0].get_counter()
519 mu_num = h.list_all_mapupdate_statuses()[0].get_counter()
520 pub_num = h.list_all_publish_statuses()[0].get_counter()
521 ret_num = h.list_all_retrieve_statuses()[0].get_counter()
522 d = self.GET("/status", followRedirect=True)
524 self.failUnlessIn('Upload and Download Status', res)
525 self.failUnlessIn('"down-%d"' % dl_num, res)
526 self.failUnlessIn('"up-%d"' % ul_num, res)
527 self.failUnlessIn('"mapupdate-%d"' % mu_num, res)
528 self.failUnlessIn('"publish-%d"' % pub_num, res)
529 self.failUnlessIn('"retrieve-%d"' % ret_num, res)
530 d.addCallback(_check)
531 d.addCallback(lambda res: self.GET("/status/?t=json"))
532 def _check_json(res):
533 data = simplejson.loads(res)
534 self.failUnless(isinstance(data, dict))
535 #active = data["active"]
536 # TODO: test more. We need a way to fake an active operation
538 d.addCallback(_check_json)
540 d.addCallback(lambda res: self.GET("/status/down-%d" % dl_num))
542 self.failUnlessIn("File Download Status", res)
543 d.addCallback(_check_dl)
544 d.addCallback(lambda res: self.GET("/status/down-%d/event_json" % dl_num))
545 def _check_dl_json(res):
546 data = simplejson.loads(res)
547 self.failUnless(isinstance(data, dict))
548 self.failUnlessIn("read", data)
549 self.failUnlessEqual(data["read"][0]["length"], 120)
550 self.failUnlessEqual(data["segment"][0]["segment_length"], 100)
551 self.failUnlessEqual(data["segment"][2]["segment_number"], 2)
552 self.failUnlessEqual(data["segment"][2]["finish_time"], None)
553 phwr_id = base32.b2a(hashutil.tagged_hash("foo", "serverid_a")[:20])
554 cmpu_id = base32.b2a(hashutil.tagged_hash("foo", "serverid_b")[:20])
555 # serverids[] keys are strings, since that's what JSON does, but
556 # we'd really like them to be ints
557 self.failUnlessEqual(data["serverids"]["0"], "phwr")
558 self.failUnless(data["serverids"].has_key("1"), data["serverids"])
559 self.failUnlessEqual(data["serverids"]["1"], "cmpu", data["serverids"])
560 self.failUnlessEqual(data["server_info"][phwr_id]["short"], "phwr")
561 self.failUnlessEqual(data["server_info"][cmpu_id]["short"], "cmpu")
562 self.failUnlessIn("dyhb", data)
563 self.failUnlessIn("misc", data)
564 d.addCallback(_check_dl_json)
565 d.addCallback(lambda res: self.GET("/status/up-%d" % ul_num))
567 self.failUnlessIn("File Upload Status", res)
568 d.addCallback(_check_ul)
569 d.addCallback(lambda res: self.GET("/status/mapupdate-%d" % mu_num))
570 def _check_mapupdate(res):
571 self.failUnlessIn("Mutable File Servermap Update Status", res)
572 d.addCallback(_check_mapupdate)
573 d.addCallback(lambda res: self.GET("/status/publish-%d" % pub_num))
574 def _check_publish(res):
575 self.failUnlessIn("Mutable File Publish Status", res)
576 d.addCallback(_check_publish)
577 d.addCallback(lambda res: self.GET("/status/retrieve-%d" % ret_num))
578 def _check_retrieve(res):
579 self.failUnlessIn("Mutable File Retrieve Status", res)
580 d.addCallback(_check_retrieve)
584 def test_status_numbers(self):
585 drrm = status.DownloadResultsRendererMixin()
586 self.failUnlessReallyEqual(drrm.render_time(None, None), "")
587 self.failUnlessReallyEqual(drrm.render_time(None, 2.5), "2.50s")
588 self.failUnlessReallyEqual(drrm.render_time(None, 0.25), "250ms")
589 self.failUnlessReallyEqual(drrm.render_time(None, 0.0021), "2.1ms")
590 self.failUnlessReallyEqual(drrm.render_time(None, 0.000123), "123us")
591 self.failUnlessReallyEqual(drrm.render_rate(None, None), "")
592 self.failUnlessReallyEqual(drrm.render_rate(None, 2500000), "2.50MBps")
593 self.failUnlessReallyEqual(drrm.render_rate(None, 30100), "30.1kBps")
594 self.failUnlessReallyEqual(drrm.render_rate(None, 123), "123Bps")
596 urrm = status.UploadResultsRendererMixin()
597 self.failUnlessReallyEqual(urrm.render_time(None, None), "")
598 self.failUnlessReallyEqual(urrm.render_time(None, 2.5), "2.50s")
599 self.failUnlessReallyEqual(urrm.render_time(None, 0.25), "250ms")
600 self.failUnlessReallyEqual(urrm.render_time(None, 0.0021), "2.1ms")
601 self.failUnlessReallyEqual(urrm.render_time(None, 0.000123), "123us")
602 self.failUnlessReallyEqual(urrm.render_rate(None, None), "")
603 self.failUnlessReallyEqual(urrm.render_rate(None, 2500000), "2.50MBps")
604 self.failUnlessReallyEqual(urrm.render_rate(None, 30100), "30.1kBps")
605 self.failUnlessReallyEqual(urrm.render_rate(None, 123), "123Bps")
607 def test_GET_FILEURL(self):
608 d = self.GET(self.public_url + "/foo/bar.txt")
609 d.addCallback(self.failUnlessIsBarDotTxt)
612 def test_GET_FILEURL_range(self):
613 headers = {"range": "bytes=1-10"}
614 d = self.GET(self.public_url + "/foo/bar.txt", headers=headers,
615 return_response=True)
616 def _got((res, status, headers)):
617 self.failUnlessReallyEqual(int(status), 206)
618 self.failUnless(headers.has_key("content-range"))
619 self.failUnlessReallyEqual(headers["content-range"][0],
620 "bytes 1-10/%d" % len(self.BAR_CONTENTS))
621 self.failUnlessReallyEqual(res, self.BAR_CONTENTS[1:11])
625 def test_GET_FILEURL_partial_range(self):
626 headers = {"range": "bytes=5-"}
627 length = len(self.BAR_CONTENTS)
628 d = self.GET(self.public_url + "/foo/bar.txt", headers=headers,
629 return_response=True)
630 def _got((res, status, headers)):
631 self.failUnlessReallyEqual(int(status), 206)
632 self.failUnless(headers.has_key("content-range"))
633 self.failUnlessReallyEqual(headers["content-range"][0],
634 "bytes 5-%d/%d" % (length-1, length))
635 self.failUnlessReallyEqual(res, self.BAR_CONTENTS[5:])
639 def test_GET_FILEURL_partial_end_range(self):
640 headers = {"range": "bytes=-5"}
641 length = len(self.BAR_CONTENTS)
642 d = self.GET(self.public_url + "/foo/bar.txt", headers=headers,
643 return_response=True)
644 def _got((res, status, headers)):
645 self.failUnlessReallyEqual(int(status), 206)
646 self.failUnless(headers.has_key("content-range"))
647 self.failUnlessReallyEqual(headers["content-range"][0],
648 "bytes %d-%d/%d" % (length-5, length-1, length))
649 self.failUnlessReallyEqual(res, self.BAR_CONTENTS[-5:])
653 def test_GET_FILEURL_partial_range_overrun(self):
654 headers = {"range": "bytes=100-200"}
655 d = self.shouldFail2(error.Error, "test_GET_FILEURL_range_overrun",
656 "416 Requested Range not satisfiable",
657 "First beyond end of file",
658 self.GET, self.public_url + "/foo/bar.txt",
662 def test_HEAD_FILEURL_range(self):
663 headers = {"range": "bytes=1-10"}
664 d = self.HEAD(self.public_url + "/foo/bar.txt", headers=headers,
665 return_response=True)
666 def _got((res, status, headers)):
667 self.failUnlessReallyEqual(res, "")
668 self.failUnlessReallyEqual(int(status), 206)
669 self.failUnless(headers.has_key("content-range"))
670 self.failUnlessReallyEqual(headers["content-range"][0],
671 "bytes 1-10/%d" % len(self.BAR_CONTENTS))
675 def test_HEAD_FILEURL_partial_range(self):
676 headers = {"range": "bytes=5-"}
677 length = len(self.BAR_CONTENTS)
678 d = self.HEAD(self.public_url + "/foo/bar.txt", headers=headers,
679 return_response=True)
680 def _got((res, status, headers)):
681 self.failUnlessReallyEqual(int(status), 206)
682 self.failUnless(headers.has_key("content-range"))
683 self.failUnlessReallyEqual(headers["content-range"][0],
684 "bytes 5-%d/%d" % (length-1, length))
688 def test_HEAD_FILEURL_partial_end_range(self):
689 headers = {"range": "bytes=-5"}
690 length = len(self.BAR_CONTENTS)
691 d = self.HEAD(self.public_url + "/foo/bar.txt", headers=headers,
692 return_response=True)
693 def _got((res, status, headers)):
694 self.failUnlessReallyEqual(int(status), 206)
695 self.failUnless(headers.has_key("content-range"))
696 self.failUnlessReallyEqual(headers["content-range"][0],
697 "bytes %d-%d/%d" % (length-5, length-1, length))
701 def test_HEAD_FILEURL_partial_range_overrun(self):
702 headers = {"range": "bytes=100-200"}
703 d = self.shouldFail2(error.Error, "test_HEAD_FILEURL_range_overrun",
704 "416 Requested Range not satisfiable",
706 self.HEAD, self.public_url + "/foo/bar.txt",
710 def test_GET_FILEURL_range_bad(self):
711 headers = {"range": "BOGUS=fizbop-quarnak"}
712 d = self.GET(self.public_url + "/foo/bar.txt", headers=headers,
713 return_response=True)
714 def _got((res, status, headers)):
715 self.failUnlessReallyEqual(int(status), 200)
716 self.failUnless(not headers.has_key("content-range"))
717 self.failUnlessReallyEqual(res, self.BAR_CONTENTS)
721 def test_HEAD_FILEURL(self):
722 d = self.HEAD(self.public_url + "/foo/bar.txt", return_response=True)
723 def _got((res, status, headers)):
724 self.failUnlessReallyEqual(res, "")
725 self.failUnlessReallyEqual(headers["content-length"][0],
726 str(len(self.BAR_CONTENTS)))
727 self.failUnlessReallyEqual(headers["content-type"], ["text/plain"])
731 def test_GET_FILEURL_named(self):
732 base = "/file/%s" % urllib.quote(self._bar_txt_uri)
733 base2 = "/named/%s" % urllib.quote(self._bar_txt_uri)
734 d = self.GET(base + "/@@name=/blah.txt")
735 d.addCallback(self.failUnlessIsBarDotTxt)
736 d.addCallback(lambda res: self.GET(base + "/blah.txt"))
737 d.addCallback(self.failUnlessIsBarDotTxt)
738 d.addCallback(lambda res: self.GET(base + "/ignore/lots/blah.txt"))
739 d.addCallback(self.failUnlessIsBarDotTxt)
740 d.addCallback(lambda res: self.GET(base2 + "/@@name=/blah.txt"))
741 d.addCallback(self.failUnlessIsBarDotTxt)
742 save_url = base + "?save=true&filename=blah.txt"
743 d.addCallback(lambda res: self.GET(save_url))
744 d.addCallback(self.failUnlessIsBarDotTxt) # TODO: check headers
745 u_filename = u"n\u00e9wer.txt" # n e-acute w e r . t x t
746 u_fn_e = urllib.quote(u_filename.encode("utf-8"))
747 u_url = base + "?save=true&filename=" + u_fn_e
748 d.addCallback(lambda res: self.GET(u_url))
749 d.addCallback(self.failUnlessIsBarDotTxt) # TODO: check headers
752 def test_PUT_FILEURL_named_bad(self):
753 base = "/file/%s" % urllib.quote(self._bar_txt_uri)
754 d = self.shouldFail2(error.Error, "test_PUT_FILEURL_named_bad",
756 "/file can only be used with GET or HEAD",
757 self.PUT, base + "/@@name=/blah.txt", "")
761 def test_GET_DIRURL_named_bad(self):
762 base = "/file/%s" % urllib.quote(self._foo_uri)
763 d = self.shouldFail2(error.Error, "test_PUT_DIRURL_named_bad",
766 self.GET, base + "/@@name=/blah.txt")
769 def test_GET_slash_file_bad(self):
770 d = self.shouldFail2(error.Error, "test_GET_slash_file_bad",
772 "/file must be followed by a file-cap and a name",
776 def test_GET_unhandled_URI_named(self):
777 contents, n, newuri = self.makefile(12)
778 verifier_cap = n.get_verify_cap().to_string()
779 base = "/file/%s" % urllib.quote(verifier_cap)
780 # client.create_node_from_uri() can't handle verify-caps
781 d = self.shouldFail2(error.Error, "GET_unhandled_URI_named",
782 "400 Bad Request", "is not a file-cap",
786 def test_GET_unhandled_URI(self):
787 contents, n, newuri = self.makefile(12)
788 verifier_cap = n.get_verify_cap().to_string()
789 base = "/uri/%s" % urllib.quote(verifier_cap)
790 # client.create_node_from_uri() can't handle verify-caps
791 d = self.shouldFail2(error.Error, "test_GET_unhandled_URI",
793 "GET unknown URI type: can only do t=info",
797 def test_GET_FILE_URI(self):
798 base = "/uri/%s" % urllib.quote(self._bar_txt_uri)
800 d.addCallback(self.failUnlessIsBarDotTxt)
803 def test_GET_FILE_URI_mdmf(self):
804 base = "/uri/%s" % urllib.quote(self._quux_txt_uri)
806 d.addCallback(self.failUnlessIsQuuxDotTxt)
809 def test_GET_FILE_URI_mdmf_extensions(self):
810 base = "/uri/%s" % urllib.quote("%s:RANDOMSTUFF" % self._quux_txt_uri)
812 d.addCallback(self.failUnlessIsQuuxDotTxt)
815 def test_GET_FILE_URI_mdmf_readonly(self):
816 base = "/uri/%s" % urllib.quote(self._quux_txt_readonly_uri)
818 d.addCallback(self.failUnlessIsQuuxDotTxt)
821 def test_GET_FILE_URI_badchild(self):
822 base = "/uri/%s/boguschild" % urllib.quote(self._bar_txt_uri)
823 errmsg = "Files have no children, certainly not named 'boguschild'"
824 d = self.shouldFail2(error.Error, "test_GET_FILE_URI_badchild",
825 "400 Bad Request", errmsg,
829 def test_PUT_FILE_URI_badchild(self):
830 base = "/uri/%s/boguschild" % urllib.quote(self._bar_txt_uri)
831 errmsg = "Cannot create directory 'boguschild', because its parent is a file, not a directory"
832 d = self.shouldFail2(error.Error, "test_GET_FILE_URI_badchild",
833 "400 Bad Request", errmsg,
837 def test_PUT_FILE_URI_mdmf(self):
838 base = "/uri/%s" % urllib.quote(self._quux_txt_uri)
839 self._quux_new_contents = "new_contents"
841 d.addCallback(lambda res:
842 self.failUnlessIsQuuxDotTxt(res))
843 d.addCallback(lambda ignored:
844 self.PUT(base, self._quux_new_contents))
845 d.addCallback(lambda ignored:
847 d.addCallback(lambda res:
848 self.failUnlessReallyEqual(res, self._quux_new_contents))
851 def test_PUT_FILE_URI_mdmf_extensions(self):
852 base = "/uri/%s" % urllib.quote("%s:EXTENSIONSTUFF" % self._quux_txt_uri)
853 self._quux_new_contents = "new_contents"
855 d.addCallback(lambda res: self.failUnlessIsQuuxDotTxt(res))
856 d.addCallback(lambda ignored: self.PUT(base, self._quux_new_contents))
857 d.addCallback(lambda ignored: self.GET(base))
858 d.addCallback(lambda res: self.failUnlessEqual(self._quux_new_contents,
862 def test_PUT_FILE_URI_mdmf_readonly(self):
863 # We're not allowed to PUT things to a readonly cap.
864 base = "/uri/%s" % self._quux_txt_readonly_uri
866 d.addCallback(lambda res:
867 self.failUnlessIsQuuxDotTxt(res))
868 # What should we get here? We get a 500 error now; that's not right.
869 d.addCallback(lambda ignored:
870 self.shouldFail2(error.Error, "test_PUT_FILE_URI_mdmf_readonly",
871 "400 Bad Request", "read-only cap",
872 self.PUT, base, "new data"))
875 def test_PUT_FILE_URI_sdmf_readonly(self):
876 # We're not allowed to put things to a readonly cap.
877 base = "/uri/%s" % self._baz_txt_readonly_uri
879 d.addCallback(lambda res:
880 self.failUnlessIsBazDotTxt(res))
881 d.addCallback(lambda ignored:
882 self.shouldFail2(error.Error, "test_PUT_FILE_URI_sdmf_readonly",
883 "400 Bad Request", "read-only cap",
884 self.PUT, base, "new_data"))
887 # TODO: version of this with a Unicode filename
888 def test_GET_FILEURL_save(self):
889 d = self.GET(self.public_url + "/foo/bar.txt?filename=bar.txt&save=true",
890 return_response=True)
891 def _got((res, statuscode, headers)):
892 content_disposition = headers["content-disposition"][0]
893 self.failUnless(content_disposition == 'attachment; filename="bar.txt"', content_disposition)
894 self.failUnlessIsBarDotTxt(res)
898 def test_GET_FILEURL_missing(self):
899 d = self.GET(self.public_url + "/foo/missing")
900 d.addBoth(self.should404, "test_GET_FILEURL_missing")
903 def test_GET_FILEURL_info_mdmf(self):
904 d = self.GET("/uri/%s?t=info" % self._quux_txt_uri)
906 self.failUnlessIn("mutable file (mdmf)", res)
907 self.failUnlessIn(self._quux_txt_uri, res)
908 self.failUnlessIn(self._quux_txt_readonly_uri, res)
912 def test_GET_FILEURL_info_mdmf_readonly(self):
913 d = self.GET("/uri/%s?t=info" % self._quux_txt_readonly_uri)
915 self.failUnlessIn("mutable file (mdmf)", res)
916 self.failIfIn(self._quux_txt_uri, res)
917 self.failUnlessIn(self._quux_txt_readonly_uri, res)
921 def test_GET_FILEURL_info_sdmf(self):
922 d = self.GET("/uri/%s?t=info" % self._baz_txt_uri)
924 self.failUnlessIn("mutable file (sdmf)", res)
925 self.failUnlessIn(self._baz_txt_uri, res)
929 def test_GET_FILEURL_info_mdmf_extensions(self):
930 d = self.GET("/uri/%s:STUFF?t=info" % self._quux_txt_uri)
932 self.failUnlessIn("mutable file (mdmf)", res)
933 self.failUnlessIn(self._quux_txt_uri, res)
934 self.failUnlessIn(self._quux_txt_readonly_uri, res)
938 def test_PUT_overwrite_only_files(self):
939 # create a directory, put a file in that directory.
940 contents, n, filecap = self.makefile(8)
941 d = self.PUT(self.public_url + "/foo/dir?t=mkdir", "")
942 d.addCallback(lambda res:
943 self.PUT(self.public_url + "/foo/dir/file1.txt",
944 self.NEWFILE_CONTENTS))
945 # try to overwrite the file with replace=only-files
947 d.addCallback(lambda res:
948 self.PUT(self.public_url + "/foo/dir/file1.txt?t=uri&replace=only-files",
950 d.addCallback(lambda res:
951 self.shouldFail2(error.Error, "PUT_bad_t", "409 Conflict",
952 "There was already a child by that name, and you asked me "
954 self.PUT, self.public_url + "/foo/dir?t=uri&replace=only-files",
958 def test_PUT_NEWFILEURL(self):
959 d = self.PUT(self.public_url + "/foo/new.txt", self.NEWFILE_CONTENTS)
960 # TODO: we lose the response code, so we can't check this
961 #self.failUnlessReallyEqual(responsecode, 201)
962 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"new.txt")
963 d.addCallback(lambda res:
964 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
965 self.NEWFILE_CONTENTS))
968 def test_PUT_NEWFILEURL_not_mutable(self):
969 d = self.PUT(self.public_url + "/foo/new.txt?mutable=false",
970 self.NEWFILE_CONTENTS)
971 # TODO: we lose the response code, so we can't check this
972 #self.failUnlessReallyEqual(responsecode, 201)
973 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"new.txt")
974 d.addCallback(lambda res:
975 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
976 self.NEWFILE_CONTENTS))
979 def test_PUT_NEWFILEURL_unlinked_mdmf(self):
980 # this should get us a few segments of an MDMF mutable file,
981 # which we can then test for.
982 contents = self.NEWFILE_CONTENTS * 300000
983 d = self.PUT("/uri?format=mdmf",
985 def _got_filecap(filecap):
986 self.failUnless(filecap.startswith("URI:MDMF"))
988 d.addCallback(_got_filecap)
989 d.addCallback(lambda filecap: self.GET("/uri/%s?t=json" % filecap))
990 d.addCallback(lambda json: self.failUnlessIn("MDMF", json))
993 def test_PUT_NEWFILEURL_unlinked_sdmf(self):
994 contents = self.NEWFILE_CONTENTS * 300000
995 d = self.PUT("/uri?format=sdmf",
997 d.addCallback(lambda filecap: self.GET("/uri/%s?t=json" % filecap))
998 d.addCallback(lambda json: self.failUnlessIn("SDMF", json))
1001 def test_PUT_NEWFILEURL_unlinked_bad_format(self):
1002 contents = self.NEWFILE_CONTENTS * 300000
1003 return self.shouldHTTPError("PUT_NEWFILEURL_unlinked_bad_format",
1004 400, "Bad Request", "Unknown format: foo",
1005 self.PUT, "/uri?format=foo",
1008 def test_PUT_NEWFILEURL_range_bad(self):
1009 headers = {"content-range": "bytes 1-10/%d" % len(self.NEWFILE_CONTENTS)}
1010 target = self.public_url + "/foo/new.txt"
1011 d = self.shouldFail2(error.Error, "test_PUT_NEWFILEURL_range_bad",
1012 "501 Not Implemented",
1013 "Content-Range in PUT not yet supported",
1014 # (and certainly not for immutable files)
1015 self.PUT, target, self.NEWFILE_CONTENTS[1:11],
1017 d.addCallback(lambda res:
1018 self.failIfNodeHasChild(self._foo_node, u"new.txt"))
1021 def test_PUT_NEWFILEURL_mutable(self):
1022 d = self.PUT(self.public_url + "/foo/new.txt?mutable=true",
1023 self.NEWFILE_CONTENTS)
1024 # TODO: we lose the response code, so we can't check this
1025 #self.failUnlessReallyEqual(responsecode, 201)
1026 def _check_uri(res):
1027 u = uri.from_string_mutable_filenode(res)
1028 self.failUnless(u.is_mutable())
1029 self.failIf(u.is_readonly())
1031 d.addCallback(_check_uri)
1032 d.addCallback(self.failUnlessURIMatchesRWChild, self._foo_node, u"new.txt")
1033 d.addCallback(lambda res:
1034 self.failUnlessMutableChildContentsAre(self._foo_node,
1036 self.NEWFILE_CONTENTS))
1039 def test_PUT_NEWFILEURL_mutable_toobig(self):
1040 # It is okay to upload large mutable files, so we should be able
1042 d = self.PUT(self.public_url + "/foo/new.txt?mutable=true",
1043 "b" * (self.s.MUTABLE_SIZELIMIT + 1))
1046 def test_PUT_NEWFILEURL_replace(self):
1047 d = self.PUT(self.public_url + "/foo/bar.txt", self.NEWFILE_CONTENTS)
1048 # TODO: we lose the response code, so we can't check this
1049 #self.failUnlessReallyEqual(responsecode, 200)
1050 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"bar.txt")
1051 d.addCallback(lambda res:
1052 self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
1053 self.NEWFILE_CONTENTS))
1056 def test_PUT_NEWFILEURL_bad_t(self):
1057 d = self.shouldFail2(error.Error, "PUT_bad_t", "400 Bad Request",
1058 "PUT to a file: bad t=bogus",
1059 self.PUT, self.public_url + "/foo/bar.txt?t=bogus",
1063 def test_PUT_NEWFILEURL_no_replace(self):
1064 d = self.PUT(self.public_url + "/foo/bar.txt?replace=false",
1065 self.NEWFILE_CONTENTS)
1066 d.addBoth(self.shouldFail, error.Error, "PUT_NEWFILEURL_no_replace",
1068 "There was already a child by that name, and you asked me "
1069 "to not replace it")
1072 def test_PUT_NEWFILEURL_mkdirs(self):
1073 d = self.PUT(self.public_url + "/foo/newdir/new.txt", self.NEWFILE_CONTENTS)
1075 d.addCallback(self.failUnlessURIMatchesROChild, fn, u"newdir/new.txt")
1076 d.addCallback(lambda res: self.failIfNodeHasChild(fn, u"new.txt"))
1077 d.addCallback(lambda res: self.failUnlessNodeHasChild(fn, u"newdir"))
1078 d.addCallback(lambda res:
1079 self.failUnlessChildContentsAre(fn, u"newdir/new.txt",
1080 self.NEWFILE_CONTENTS))
1083 def test_PUT_NEWFILEURL_blocked(self):
1084 d = self.PUT(self.public_url + "/foo/blockingfile/new.txt",
1085 self.NEWFILE_CONTENTS)
1086 d.addBoth(self.shouldFail, error.Error, "PUT_NEWFILEURL_blocked",
1088 "Unable to create directory 'blockingfile': a file was in the way")
1091 def test_PUT_NEWFILEURL_emptyname(self):
1092 # an empty pathname component (i.e. a double-slash) is disallowed
1093 d = self.shouldFail2(error.Error, "test_PUT_NEWFILEURL_emptyname",
1095 "The webapi does not allow empty pathname components",
1096 self.PUT, self.public_url + "/foo//new.txt", "")
1099 def test_DELETE_FILEURL(self):
1100 d = self.DELETE(self.public_url + "/foo/bar.txt")
1101 d.addCallback(lambda res:
1102 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
1105 def test_DELETE_FILEURL_missing(self):
1106 d = self.DELETE(self.public_url + "/foo/missing")
1107 d.addBoth(self.should404, "test_DELETE_FILEURL_missing")
1110 def test_DELETE_FILEURL_missing2(self):
1111 d = self.DELETE(self.public_url + "/missing/missing")
1112 d.addBoth(self.should404, "test_DELETE_FILEURL_missing2")
1115 def failUnlessHasBarDotTxtMetadata(self, res):
1116 data = simplejson.loads(res)
1117 self.failUnless(isinstance(data, list))
1118 self.failUnlessIn("metadata", data[1])
1119 self.failUnlessIn("tahoe", data[1]["metadata"])
1120 self.failUnlessIn("linkcrtime", data[1]["metadata"]["tahoe"])
1121 self.failUnlessIn("linkmotime", data[1]["metadata"]["tahoe"])
1122 self.failUnlessReallyEqual(data[1]["metadata"]["tahoe"]["linkcrtime"],
1123 self._bar_txt_metadata["tahoe"]["linkcrtime"])
1125 def test_GET_FILEURL_json(self):
1126 # twisted.web.http.parse_qs ignores any query args without an '=', so
1127 # I can't do "GET /path?json", I have to do "GET /path/t=json"
1128 # instead. This may make it tricky to emulate the S3 interface
1130 d = self.GET(self.public_url + "/foo/bar.txt?t=json")
1132 self.failUnlessIsBarJSON(data)
1133 self.failUnlessHasBarDotTxtMetadata(data)
1135 d.addCallback(_check1)
1138 def test_GET_FILEURL_json_mutable_type(self):
1139 # The JSON should include format, which says whether the
1140 # file is SDMF or MDMF
1141 d = self.PUT("/uri?format=mdmf",
1142 self.NEWFILE_CONTENTS * 300000)
1143 d.addCallback(lambda filecap: self.GET("/uri/%s?t=json" % filecap))
1144 def _got_json(json, version):
1145 data = simplejson.loads(json)
1146 assert "filenode" == data[0]
1148 assert isinstance(data, dict)
1150 self.failUnlessIn("format", data)
1151 self.failUnlessEqual(data["format"], version)
1153 d.addCallback(_got_json, "MDMF")
1154 # Now make an SDMF file and check that it is reported correctly.
1155 d.addCallback(lambda ignored:
1156 self.PUT("/uri?format=sdmf",
1157 self.NEWFILE_CONTENTS * 300000))
1158 d.addCallback(lambda filecap: self.GET("/uri/%s?t=json" % filecap))
1159 d.addCallback(_got_json, "SDMF")
1162 def test_GET_FILEURL_json_mdmf(self):
1163 d = self.GET("/uri/%s?t=json" % urllib.quote(self._quux_txt_uri))
1164 d.addCallback(self.failUnlessIsQuuxJSON)
1167 def test_GET_FILEURL_json_missing(self):
1168 d = self.GET(self.public_url + "/foo/missing?json")
1169 d.addBoth(self.should404, "test_GET_FILEURL_json_missing")
1172 def test_GET_FILEURL_uri(self):
1173 d = self.GET(self.public_url + "/foo/bar.txt?t=uri")
1175 self.failUnlessReallyEqual(res, self._bar_txt_uri)
1176 d.addCallback(_check)
1177 d.addCallback(lambda res:
1178 self.GET(self.public_url + "/foo/bar.txt?t=readonly-uri"))
1180 # for now, for files, uris and readonly-uris are the same
1181 self.failUnlessReallyEqual(res, self._bar_txt_uri)
1182 d.addCallback(_check2)
1185 def test_GET_FILEURL_badtype(self):
1186 d = self.shouldHTTPError("GET t=bogus", 400, "Bad Request",
1189 self.public_url + "/foo/bar.txt?t=bogus")
1192 def test_CSS_FILE(self):
1193 d = self.GET("/tahoe.css", followRedirect=True)
1195 CSS_STYLE=re.compile('toolbar\s{.+text-align:\scenter.+toolbar-item.+display:\sinline',re.DOTALL)
1196 self.failUnless(CSS_STYLE.search(res), res)
1197 d.addCallback(_check)
1200 def test_GET_FILEURL_uri_missing(self):
1201 d = self.GET(self.public_url + "/foo/missing?t=uri")
1202 d.addBoth(self.should404, "test_GET_FILEURL_uri_missing")
1205 def _check_upload_and_mkdir_forms(self, html):
1206 # We should have a form to create a file, with radio buttons that allow
1207 # the user to toggle whether it is a CHK/LIT (default), SDMF, or MDMF file.
1208 self.failUnlessIn('name="t" value="upload"', html)
1209 self.failUnlessIn('input checked="checked" type="radio" id="upload-chk" value="chk" name="format"', html)
1210 self.failUnlessIn('input type="radio" id="upload-sdmf" value="sdmf" name="format"', html)
1211 self.failUnlessIn('input type="radio" id="upload-mdmf" value="mdmf" name="format"', html)
1213 # We should also have the ability to create a mutable directory, with
1214 # radio buttons that allow the user to toggle whether it is an SDMF (default)
1215 # or MDMF directory.
1216 self.failUnlessIn('name="t" value="mkdir"', html)
1217 self.failUnlessIn('input checked="checked" type="radio" id="mkdir-sdmf" value="sdmf" name="format"', html)
1218 self.failUnlessIn('input type="radio" id="mkdir-mdmf" value="mdmf" name="format"', html)
1220 self.failUnlessIn(FAVICON_MARKUP, html)
1222 def test_GET_DIRECTORY_html(self):
1223 d = self.GET(self.public_url + "/foo", followRedirect=True)
1225 self.failUnlessIn('<div class="toolbar-item"><a href="../../..">Return to Welcome page</a></div>', html)
1226 self._check_upload_and_mkdir_forms(html)
1227 self.failUnlessIn("quux", html)
1228 d.addCallback(_check)
1231 def test_GET_root_html(self):
1233 d.addCallback(self._check_upload_and_mkdir_forms)
1236 def test_GET_DIRURL(self):
1237 # the addSlash means we get a redirect here
1238 # from /uri/$URI/foo/ , we need ../../../ to get back to the root
1240 d = self.GET(self.public_url + "/foo", followRedirect=True)
1242 self.failUnlessIn('<a href="%s">Return to Welcome page' % ROOT, res)
1244 # the FILE reference points to a URI, but it should end in bar.txt
1245 bar_url = ("%s/file/%s/@@named=/bar.txt" %
1246 (ROOT, urllib.quote(self._bar_txt_uri)))
1247 get_bar = "".join([r'<td>FILE</td>',
1249 r'<a href="%s">bar.txt</a>' % bar_url,
1251 r'\s+<td align="right">%d</td>' % len(self.BAR_CONTENTS),
1253 self.failUnless(re.search(get_bar, res), res)
1254 for label in ['unlink', 'rename']:
1255 for line in res.split("\n"):
1256 # find the line that contains the relevant button for bar.txt
1257 if ("form action" in line and
1258 ('value="%s"' % (label,)) in line and
1259 'value="bar.txt"' in line):
1260 # the form target should use a relative URL
1261 foo_url = urllib.quote("%s/uri/%s/" % (ROOT, self._foo_uri))
1262 self.failUnlessIn('action="%s"' % foo_url, line)
1263 # and the when_done= should too
1264 #done_url = urllib.quote(???)
1265 #self.failUnlessIn('name="when_done" value="%s"' % done_url, line)
1267 # 'unlink' needs to use POST because it directly has a side effect
1268 if label == 'unlink':
1269 self.failUnlessIn('method="post"', line)
1272 self.fail("unable to find '%s bar.txt' line" % (label,), res)
1274 # the DIR reference just points to a URI
1275 sub_url = ("%s/uri/%s/" % (ROOT, urllib.quote(self._sub_uri)))
1276 get_sub = ((r'<td>DIR</td>')
1277 +r'\s+<td><a href="%s">sub</a></td>' % sub_url)
1278 self.failUnless(re.search(get_sub, res), res)
1279 d.addCallback(_check)
1281 # look at a readonly directory
1282 d.addCallback(lambda res:
1283 self.GET(self.public_url + "/reedownlee", followRedirect=True))
1285 self.failUnlessIn("(read-only)", res)
1286 self.failIfIn("Upload a file", res)
1287 d.addCallback(_check2)
1289 # and at a directory that contains a readonly directory
1290 d.addCallback(lambda res:
1291 self.GET(self.public_url, followRedirect=True))
1293 self.failUnless(re.search('<td>DIR-RO</td>'
1294 r'\s+<td><a href="[\.\/]+/uri/URI%3ADIR2-RO%3A[^"]+">reedownlee</a></td>', res), res)
1295 d.addCallback(_check3)
1297 # and an empty directory
1298 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty/"))
1300 self.failUnlessIn("directory is empty", res)
1301 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)
1302 self.failUnless(MKDIR_BUTTON_RE.search(res), res)
1303 d.addCallback(_check4)
1305 # and at a literal directory
1306 tiny_litdir_uri = "URI:DIR2-LIT:gqytunj2onug64tufqzdcosvkjetutcjkq5gw4tvm5vwszdgnz5hgyzufqydulbshj5x2lbm" # contains one child which is itself also LIT
1307 d.addCallback(lambda res:
1308 self.GET("/uri/" + tiny_litdir_uri + "/", followRedirect=True))
1310 self.failUnlessIn('(immutable)', res)
1311 self.failUnless(re.search('<td>FILE</td>'
1312 r'\s+<td><a href="[\.\/]+/file/URI%3ALIT%3Akrugkidfnzsc4/@@named=/short">short</a></td>', res), res)
1313 d.addCallback(_check5)
1316 def test_GET_DIRURL_badtype(self):
1317 d = self.shouldHTTPError("test_GET_DIRURL_badtype",
1321 self.public_url + "/foo?t=bogus")
1324 def test_GET_DIRURL_json(self):
1325 d = self.GET(self.public_url + "/foo?t=json")
1326 d.addCallback(self.failUnlessIsFooJSON)
1329 def test_GET_DIRURL_json_format(self):
1330 d = self.PUT(self.public_url + \
1331 "/foo/sdmf.txt?format=sdmf",
1332 self.NEWFILE_CONTENTS * 300000)
1333 d.addCallback(lambda ignored:
1334 self.PUT(self.public_url + \
1335 "/foo/mdmf.txt?format=mdmf",
1336 self.NEWFILE_CONTENTS * 300000))
1337 # Now we have an MDMF and SDMF file in the directory. If we GET
1338 # its JSON, we should see their encodings.
1339 d.addCallback(lambda ignored:
1340 self.GET(self.public_url + "/foo?t=json"))
1341 def _got_json(json):
1342 data = simplejson.loads(json)
1343 assert data[0] == "dirnode"
1346 kids = data['children']
1348 mdmf_data = kids['mdmf.txt'][1]
1349 self.failUnlessIn("format", mdmf_data)
1350 self.failUnlessEqual(mdmf_data["format"], "MDMF")
1352 sdmf_data = kids['sdmf.txt'][1]
1353 self.failUnlessIn("format", sdmf_data)
1354 self.failUnlessEqual(sdmf_data["format"], "SDMF")
1355 d.addCallback(_got_json)
1359 def test_POST_DIRURL_manifest_no_ophandle(self):
1360 d = self.shouldFail2(error.Error,
1361 "test_POST_DIRURL_manifest_no_ophandle",
1363 "slow operation requires ophandle=",
1364 self.POST, self.public_url, t="start-manifest")
1367 def test_POST_DIRURL_manifest(self):
1368 d = defer.succeed(None)
1369 def getman(ignored, output):
1370 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=125",
1371 followRedirect=True)
1372 d.addCallback(self.wait_for_operation, "125")
1373 d.addCallback(self.get_operation_results, "125", output)
1375 d.addCallback(getman, None)
1376 def _got_html(manifest):
1377 self.failUnlessIn("Manifest of SI=", manifest)
1378 self.failUnlessIn("<td>sub</td>", manifest)
1379 self.failUnlessIn(self._sub_uri, manifest)
1380 self.failUnlessIn("<td>sub/baz.txt</td>", manifest)
1381 self.failUnlessIn(FAVICON_MARKUP, manifest)
1382 d.addCallback(_got_html)
1384 # both t=status and unadorned GET should be identical
1385 d.addCallback(lambda res: self.GET("/operations/125"))
1386 d.addCallback(_got_html)
1388 d.addCallback(getman, "html")
1389 d.addCallback(_got_html)
1390 d.addCallback(getman, "text")
1391 def _got_text(manifest):
1392 self.failUnlessIn("\nsub " + self._sub_uri + "\n", manifest)
1393 self.failUnlessIn("\nsub/baz.txt URI:CHK:", manifest)
1394 d.addCallback(_got_text)
1395 d.addCallback(getman, "JSON")
1397 data = res["manifest"]
1399 for (path_list, cap) in data:
1400 got[tuple(path_list)] = cap
1401 self.failUnlessReallyEqual(to_str(got[(u"sub",)]), self._sub_uri)
1402 self.failUnlessIn((u"sub", u"baz.txt"), got)
1403 self.failUnlessIn("finished", res)
1404 self.failUnlessIn("origin", res)
1405 self.failUnlessIn("storage-index", res)
1406 self.failUnlessIn("verifycaps", res)
1407 self.failUnlessIn("stats", res)
1408 d.addCallback(_got_json)
1411 def test_POST_DIRURL_deepsize_no_ophandle(self):
1412 d = self.shouldFail2(error.Error,
1413 "test_POST_DIRURL_deepsize_no_ophandle",
1415 "slow operation requires ophandle=",
1416 self.POST, self.public_url, t="start-deep-size")
1419 def test_POST_DIRURL_deepsize(self):
1420 d = self.POST(self.public_url + "/foo/?t=start-deep-size&ophandle=126",
1421 followRedirect=True)
1422 d.addCallback(self.wait_for_operation, "126")
1423 d.addCallback(self.get_operation_results, "126", "json")
1424 def _got_json(data):
1425 self.failUnlessReallyEqual(data["finished"], True)
1427 self.failUnless(size > 1000)
1428 d.addCallback(_got_json)
1429 d.addCallback(self.get_operation_results, "126", "text")
1431 mo = re.search(r'^size: (\d+)$', res, re.M)
1432 self.failUnless(mo, res)
1433 size = int(mo.group(1))
1434 # with directories, the size varies.
1435 self.failUnless(size > 1000)
1436 d.addCallback(_got_text)
1439 def test_POST_DIRURL_deepstats_no_ophandle(self):
1440 d = self.shouldFail2(error.Error,
1441 "test_POST_DIRURL_deepstats_no_ophandle",
1443 "slow operation requires ophandle=",
1444 self.POST, self.public_url, t="start-deep-stats")
1447 def test_POST_DIRURL_deepstats(self):
1448 d = self.POST(self.public_url + "/foo/?t=start-deep-stats&ophandle=127",
1449 followRedirect=True)
1450 d.addCallback(self.wait_for_operation, "127")
1451 d.addCallback(self.get_operation_results, "127", "json")
1452 def _got_json(stats):
1453 expected = {"count-immutable-files": 3,
1454 "count-mutable-files": 2,
1455 "count-literal-files": 0,
1457 "count-directories": 3,
1458 "size-immutable-files": 57,
1459 "size-literal-files": 0,
1460 #"size-directories": 1912, # varies
1461 #"largest-directory": 1590,
1462 "largest-directory-children": 7,
1463 "largest-immutable-file": 19,
1465 for k,v in expected.iteritems():
1466 self.failUnlessReallyEqual(stats[k], v,
1467 "stats[%s] was %s, not %s" %
1469 self.failUnlessReallyEqual(stats["size-files-histogram"],
1471 d.addCallback(_got_json)
1474 def test_POST_DIRURL_stream_manifest(self):
1475 d = self.POST(self.public_url + "/foo/?t=stream-manifest")
1477 self.failUnless(res.endswith("\n"))
1478 units = [simplejson.loads(t) for t in res[:-1].split("\n")]
1479 self.failUnlessReallyEqual(len(units), 9)
1480 self.failUnlessEqual(units[-1]["type"], "stats")
1482 self.failUnlessEqual(first["path"], [])
1483 self.failUnlessReallyEqual(to_str(first["cap"]), self._foo_uri)
1484 self.failUnlessEqual(first["type"], "directory")
1485 baz = [u for u in units[:-1] if to_str(u["cap"]) == self._baz_file_uri][0]
1486 self.failUnlessEqual(baz["path"], ["sub", "baz.txt"])
1487 self.failIfEqual(baz["storage-index"], None)
1488 self.failIfEqual(baz["verifycap"], None)
1489 self.failIfEqual(baz["repaircap"], None)
1490 # XXX: Add quux and baz to this test.
1492 d.addCallback(_check)
1495 def test_GET_DIRURL_uri(self):
1496 d = self.GET(self.public_url + "/foo?t=uri")
1498 self.failUnlessReallyEqual(to_str(res), self._foo_uri)
1499 d.addCallback(_check)
1502 def test_GET_DIRURL_readonly_uri(self):
1503 d = self.GET(self.public_url + "/foo?t=readonly-uri")
1505 self.failUnlessReallyEqual(to_str(res), self._foo_readonly_uri)
1506 d.addCallback(_check)
1509 def test_PUT_NEWDIRURL(self):
1510 d = self.PUT(self.public_url + "/foo/newdir?t=mkdir", "")
1511 d.addCallback(lambda res:
1512 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1513 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1514 d.addCallback(self.failUnlessNodeKeysAre, [])
1517 def test_PUT_NEWDIRURL_mdmf(self):
1518 d = self.PUT(self.public_url + "/foo/newdir?t=mkdir&format=mdmf", "")
1519 d.addCallback(lambda res:
1520 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1521 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1522 d.addCallback(lambda node:
1523 self.failUnlessEqual(node._node.get_version(), MDMF_VERSION))
1526 def test_PUT_NEWDIRURL_sdmf(self):
1527 d = self.PUT(self.public_url + "/foo/newdir?t=mkdir&format=sdmf",
1529 d.addCallback(lambda res:
1530 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1531 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1532 d.addCallback(lambda node:
1533 self.failUnlessEqual(node._node.get_version(), SDMF_VERSION))
1536 def test_PUT_NEWDIRURL_bad_format(self):
1537 return self.shouldHTTPError("PUT_NEWDIRURL_bad_format",
1538 400, "Bad Request", "Unknown format: foo",
1539 self.PUT, self.public_url +
1540 "/foo/newdir=?t=mkdir&format=foo", "")
1542 def test_POST_NEWDIRURL(self):
1543 d = self.POST2(self.public_url + "/foo/newdir?t=mkdir", "")
1544 d.addCallback(lambda res:
1545 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1546 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1547 d.addCallback(self.failUnlessNodeKeysAre, [])
1550 def test_POST_NEWDIRURL_mdmf(self):
1551 d = self.POST2(self.public_url + "/foo/newdir?t=mkdir&format=mdmf", "")
1552 d.addCallback(lambda res:
1553 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1554 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1555 d.addCallback(lambda node:
1556 self.failUnlessEqual(node._node.get_version(), MDMF_VERSION))
1559 def test_POST_NEWDIRURL_sdmf(self):
1560 d = self.POST2(self.public_url + "/foo/newdir?t=mkdir&format=sdmf", "")
1561 d.addCallback(lambda res:
1562 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1563 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1564 d.addCallback(lambda node:
1565 self.failUnlessEqual(node._node.get_version(), SDMF_VERSION))
1568 def test_POST_NEWDIRURL_bad_format(self):
1569 return self.shouldHTTPError("POST_NEWDIRURL_bad_format",
1570 400, "Bad Request", "Unknown format: foo",
1571 self.POST2, self.public_url + \
1572 "/foo/newdir?t=mkdir&format=foo", "")
1574 def test_POST_NEWDIRURL_emptyname(self):
1575 # an empty pathname component (i.e. a double-slash) is disallowed
1576 d = self.shouldFail2(error.Error, "POST_NEWDIRURL_emptyname",
1578 "The webapi does not allow empty pathname components, i.e. a double slash",
1579 self.POST, self.public_url + "//?t=mkdir")
1582 def _do_POST_NEWDIRURL_initial_children_test(self, version=None):
1583 (newkids, caps) = self._create_initial_children()
1584 query = "/foo/newdir?t=mkdir-with-children"
1585 if version == MDMF_VERSION:
1586 query += "&format=mdmf"
1587 elif version == SDMF_VERSION:
1588 query += "&format=sdmf"
1590 version = SDMF_VERSION # for later
1591 d = self.POST2(self.public_url + query,
1592 simplejson.dumps(newkids))
1594 n = self.s.create_node_from_uri(uri.strip())
1595 d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
1596 self.failUnlessEqual(n._node.get_version(), version)
1597 d2.addCallback(lambda ign:
1598 self.failUnlessROChildURIIs(n, u"child-imm",
1600 d2.addCallback(lambda ign:
1601 self.failUnlessRWChildURIIs(n, u"child-mutable",
1603 d2.addCallback(lambda ign:
1604 self.failUnlessROChildURIIs(n, u"child-mutable-ro",
1606 d2.addCallback(lambda ign:
1607 self.failUnlessROChildURIIs(n, u"unknownchild-ro",
1608 caps['unknown_rocap']))
1609 d2.addCallback(lambda ign:
1610 self.failUnlessRWChildURIIs(n, u"unknownchild-rw",
1611 caps['unknown_rwcap']))
1612 d2.addCallback(lambda ign:
1613 self.failUnlessROChildURIIs(n, u"unknownchild-imm",
1614 caps['unknown_immcap']))
1615 d2.addCallback(lambda ign:
1616 self.failUnlessRWChildURIIs(n, u"dirchild",
1618 d2.addCallback(lambda ign:
1619 self.failUnlessROChildURIIs(n, u"dirchild-lit",
1621 d2.addCallback(lambda ign:
1622 self.failUnlessROChildURIIs(n, u"dirchild-empty",
1623 caps['emptydircap']))
1625 d.addCallback(_check)
1626 d.addCallback(lambda res:
1627 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1628 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1629 d.addCallback(self.failUnlessNodeKeysAre, newkids.keys())
1630 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1631 d.addCallback(self.failUnlessROChildURIIs, u"child-imm", caps['filecap1'])
1634 def test_POST_NEWDIRURL_initial_children(self):
1635 return self._do_POST_NEWDIRURL_initial_children_test()
1637 def test_POST_NEWDIRURL_initial_children_mdmf(self):
1638 return self._do_POST_NEWDIRURL_initial_children_test(MDMF_VERSION)
1640 def test_POST_NEWDIRURL_initial_children_sdmf(self):
1641 return self._do_POST_NEWDIRURL_initial_children_test(SDMF_VERSION)
1643 def test_POST_NEWDIRURL_initial_children_bad_format(self):
1644 (newkids, caps) = self._create_initial_children()
1645 return self.shouldHTTPError("POST_NEWDIRURL_initial_children_bad_format",
1646 400, "Bad Request", "Unknown format: foo",
1647 self.POST2, self.public_url + \
1648 "/foo/newdir?t=mkdir-with-children&format=foo",
1649 simplejson.dumps(newkids))
1651 def test_POST_NEWDIRURL_immutable(self):
1652 (newkids, caps) = self._create_immutable_children()
1653 d = self.POST2(self.public_url + "/foo/newdir?t=mkdir-immutable",
1654 simplejson.dumps(newkids))
1656 n = self.s.create_node_from_uri(uri.strip())
1657 d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
1658 d2.addCallback(lambda ign:
1659 self.failUnlessROChildURIIs(n, u"child-imm",
1661 d2.addCallback(lambda ign:
1662 self.failUnlessROChildURIIs(n, u"unknownchild-imm",
1663 caps['unknown_immcap']))
1664 d2.addCallback(lambda ign:
1665 self.failUnlessROChildURIIs(n, u"dirchild-imm",
1667 d2.addCallback(lambda ign:
1668 self.failUnlessROChildURIIs(n, u"dirchild-lit",
1670 d2.addCallback(lambda ign:
1671 self.failUnlessROChildURIIs(n, u"dirchild-empty",
1672 caps['emptydircap']))
1674 d.addCallback(_check)
1675 d.addCallback(lambda res:
1676 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1677 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1678 d.addCallback(self.failUnlessNodeKeysAre, newkids.keys())
1679 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1680 d.addCallback(self.failUnlessROChildURIIs, u"child-imm", caps['filecap1'])
1681 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1682 d.addCallback(self.failUnlessROChildURIIs, u"unknownchild-imm", caps['unknown_immcap'])
1683 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1684 d.addCallback(self.failUnlessROChildURIIs, u"dirchild-imm", caps['immdircap'])
1685 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1686 d.addCallback(self.failUnlessROChildURIIs, u"dirchild-lit", caps['litdircap'])
1687 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1688 d.addCallback(self.failUnlessROChildURIIs, u"dirchild-empty", caps['emptydircap'])
1689 d.addErrback(self.explain_web_error)
1692 def test_POST_NEWDIRURL_immutable_bad(self):
1693 (newkids, caps) = self._create_initial_children()
1694 d = self.shouldFail2(error.Error, "test_POST_NEWDIRURL_immutable_bad",
1696 "needed to be immutable but was not",
1698 self.public_url + "/foo/newdir?t=mkdir-immutable",
1699 simplejson.dumps(newkids))
1702 def test_PUT_NEWDIRURL_exists(self):
1703 d = self.PUT(self.public_url + "/foo/sub?t=mkdir", "")
1704 d.addCallback(lambda res:
1705 self.failUnlessNodeHasChild(self._foo_node, u"sub"))
1706 d.addCallback(lambda res: self._foo_node.get(u"sub"))
1707 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
1710 def test_PUT_NEWDIRURL_blocked(self):
1711 d = self.shouldFail2(error.Error, "PUT_NEWDIRURL_blocked",
1712 "409 Conflict", "Unable to create directory 'bar.txt': a file was in the way",
1714 self.public_url + "/foo/bar.txt/sub?t=mkdir", "")
1715 d.addCallback(lambda res:
1716 self.failUnlessNodeHasChild(self._foo_node, u"sub"))
1717 d.addCallback(lambda res: self._foo_node.get(u"sub"))
1718 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
1721 def test_PUT_NEWDIRURL_mkdir_p(self):
1722 d = defer.succeed(None)
1723 d.addCallback(lambda res: self.POST(self.public_url + "/foo", t='mkdir', name='mkp'))
1724 d.addCallback(lambda res: self.failUnlessNodeHasChild(self._foo_node, u"mkp"))
1725 d.addCallback(lambda res: self._foo_node.get(u"mkp"))
1726 def mkdir_p(mkpnode):
1727 url = '/uri/%s?t=mkdir-p&path=/sub1/sub2' % urllib.quote(mkpnode.get_uri())
1729 def made_subsub(ssuri):
1730 d = self._foo_node.get_child_at_path(u"mkp/sub1/sub2")
1731 d.addCallback(lambda ssnode: self.failUnlessReallyEqual(ssnode.get_uri(), ssuri))
1733 d.addCallback(lambda uri2: self.failUnlessReallyEqual(uri2, ssuri))
1735 d.addCallback(made_subsub)
1737 d.addCallback(mkdir_p)
1740 def test_PUT_NEWDIRURL_mkdirs(self):
1741 d = self.PUT(self.public_url + "/foo/subdir/newdir?t=mkdir", "")
1742 d.addCallback(lambda res:
1743 self.failIfNodeHasChild(self._foo_node, u"newdir"))
1744 d.addCallback(lambda res:
1745 self.failUnlessNodeHasChild(self._foo_node, u"subdir"))
1746 d.addCallback(lambda res:
1747 self._foo_node.get_child_at_path(u"subdir/newdir"))
1748 d.addCallback(self.failUnlessNodeKeysAre, [])
1751 def test_PUT_NEWDIRURL_mkdirs_mdmf(self):
1752 d = self.PUT(self.public_url + "/foo/subdir/newdir?t=mkdir&format=mdmf", "")
1753 d.addCallback(lambda ignored:
1754 self.failUnlessNodeHasChild(self._foo_node, u"subdir"))
1755 d.addCallback(lambda ignored:
1756 self.failIfNodeHasChild(self._foo_node, u"newdir"))
1757 d.addCallback(lambda ignored:
1758 self._foo_node.get_child_at_path(u"subdir"))
1759 def _got_subdir(subdir):
1760 # XXX: What we want?
1761 #self.failUnlessEqual(subdir._node.get_version(), MDMF_VERSION)
1762 self.failUnlessNodeHasChild(subdir, u"newdir")
1763 return subdir.get_child_at_path(u"newdir")
1764 d.addCallback(_got_subdir)
1765 d.addCallback(lambda newdir:
1766 self.failUnlessEqual(newdir._node.get_version(), MDMF_VERSION))
1769 def test_PUT_NEWDIRURL_mkdirs_sdmf(self):
1770 d = self.PUT(self.public_url + "/foo/subdir/newdir?t=mkdir&format=sdmf", "")
1771 d.addCallback(lambda ignored:
1772 self.failUnlessNodeHasChild(self._foo_node, u"subdir"))
1773 d.addCallback(lambda ignored:
1774 self.failIfNodeHasChild(self._foo_node, u"newdir"))
1775 d.addCallback(lambda ignored:
1776 self._foo_node.get_child_at_path(u"subdir"))
1777 def _got_subdir(subdir):
1778 # XXX: What we want?
1779 #self.failUnlessEqual(subdir._node.get_version(), MDMF_VERSION)
1780 self.failUnlessNodeHasChild(subdir, u"newdir")
1781 return subdir.get_child_at_path(u"newdir")
1782 d.addCallback(_got_subdir)
1783 d.addCallback(lambda newdir:
1784 self.failUnlessEqual(newdir._node.get_version(), SDMF_VERSION))
1787 def test_PUT_NEWDIRURL_mkdirs_bad_format(self):
1788 return self.shouldHTTPError("PUT_NEWDIRURL_mkdirs_bad_format",
1789 400, "Bad Request", "Unknown format: foo",
1790 self.PUT, self.public_url + \
1791 "/foo/subdir/newdir?t=mkdir&format=foo",
1794 def test_DELETE_DIRURL(self):
1795 d = self.DELETE(self.public_url + "/foo")
1796 d.addCallback(lambda res:
1797 self.failIfNodeHasChild(self.public_root, u"foo"))
1800 def test_DELETE_DIRURL_missing(self):
1801 d = self.DELETE(self.public_url + "/foo/missing")
1802 d.addBoth(self.should404, "test_DELETE_DIRURL_missing")
1803 d.addCallback(lambda res:
1804 self.failUnlessNodeHasChild(self.public_root, u"foo"))
1807 def test_DELETE_DIRURL_missing2(self):
1808 d = self.DELETE(self.public_url + "/missing")
1809 d.addBoth(self.should404, "test_DELETE_DIRURL_missing2")
1812 def dump_root(self):
1814 w = webish.DirnodeWalkerMixin()
1815 def visitor(childpath, childnode, metadata):
1817 d = w.walk(self.public_root, visitor)
1820 def failUnlessNodeKeysAre(self, node, expected_keys):
1821 for k in expected_keys:
1822 assert isinstance(k, unicode)
1824 def _check(children):
1825 self.failUnlessReallyEqual(sorted(children.keys()), sorted(expected_keys))
1826 d.addCallback(_check)
1828 def failUnlessNodeHasChild(self, node, name):
1829 assert isinstance(name, unicode)
1831 def _check(children):
1832 self.failUnlessIn(name, children)
1833 d.addCallback(_check)
1835 def failIfNodeHasChild(self, node, name):
1836 assert isinstance(name, unicode)
1838 def _check(children):
1839 self.failIfIn(name, children)
1840 d.addCallback(_check)
1843 def failUnlessChildContentsAre(self, node, name, expected_contents):
1844 assert isinstance(name, unicode)
1845 d = node.get_child_at_path(name)
1846 d.addCallback(lambda node: download_to_data(node))
1847 def _check(contents):
1848 self.failUnlessReallyEqual(contents, expected_contents)
1849 d.addCallback(_check)
1852 def failUnlessMutableChildContentsAre(self, node, name, expected_contents):
1853 assert isinstance(name, unicode)
1854 d = node.get_child_at_path(name)
1855 d.addCallback(lambda node: node.download_best_version())
1856 def _check(contents):
1857 self.failUnlessReallyEqual(contents, expected_contents)
1858 d.addCallback(_check)
1861 def failUnlessRWChildURIIs(self, node, name, expected_uri):
1862 assert isinstance(name, unicode)
1863 d = node.get_child_at_path(name)
1865 self.failUnless(child.is_unknown() or not child.is_readonly())
1866 self.failUnlessReallyEqual(child.get_uri(), expected_uri.strip())
1867 self.failUnlessReallyEqual(child.get_write_uri(), expected_uri.strip())
1868 expected_ro_uri = self._make_readonly(expected_uri)
1870 self.failUnlessReallyEqual(child.get_readonly_uri(), expected_ro_uri.strip())
1871 d.addCallback(_check)
1874 def failUnlessROChildURIIs(self, node, name, expected_uri):
1875 assert isinstance(name, unicode)
1876 d = node.get_child_at_path(name)
1878 self.failUnless(child.is_unknown() or child.is_readonly())
1879 self.failUnlessReallyEqual(child.get_write_uri(), None)
1880 self.failUnlessReallyEqual(child.get_uri(), expected_uri.strip())
1881 self.failUnlessReallyEqual(child.get_readonly_uri(), expected_uri.strip())
1882 d.addCallback(_check)
1885 def failUnlessURIMatchesRWChild(self, got_uri, node, name):
1886 assert isinstance(name, unicode)
1887 d = node.get_child_at_path(name)
1889 self.failUnless(child.is_unknown() or not child.is_readonly())
1890 self.failUnlessReallyEqual(child.get_uri(), got_uri.strip())
1891 self.failUnlessReallyEqual(child.get_write_uri(), got_uri.strip())
1892 expected_ro_uri = self._make_readonly(got_uri)
1894 self.failUnlessReallyEqual(child.get_readonly_uri(), expected_ro_uri.strip())
1895 d.addCallback(_check)
1898 def failUnlessURIMatchesROChild(self, got_uri, node, name):
1899 assert isinstance(name, unicode)
1900 d = node.get_child_at_path(name)
1902 self.failUnless(child.is_unknown() or child.is_readonly())
1903 self.failUnlessReallyEqual(child.get_write_uri(), None)
1904 self.failUnlessReallyEqual(got_uri.strip(), child.get_uri())
1905 self.failUnlessReallyEqual(got_uri.strip(), child.get_readonly_uri())
1906 d.addCallback(_check)
1909 def failUnlessCHKURIHasContents(self, got_uri, contents):
1910 self.failUnless(FakeCHKFileNode.all_contents[got_uri] == contents)
1912 def test_POST_upload(self):
1913 d = self.POST(self.public_url + "/foo", t="upload",
1914 file=("new.txt", self.NEWFILE_CONTENTS))
1916 d.addCallback(self.failUnlessURIMatchesROChild, fn, u"new.txt")
1917 d.addCallback(lambda res:
1918 self.failUnlessChildContentsAre(fn, u"new.txt",
1919 self.NEWFILE_CONTENTS))
1922 def test_POST_upload_unicode(self):
1923 filename = u"n\u00e9wer.txt" # n e-acute w e r . t x t
1924 d = self.POST(self.public_url + "/foo", t="upload",
1925 file=(filename, self.NEWFILE_CONTENTS))
1927 d.addCallback(self.failUnlessURIMatchesROChild, fn, filename)
1928 d.addCallback(lambda res:
1929 self.failUnlessChildContentsAre(fn, filename,
1930 self.NEWFILE_CONTENTS))
1931 target_url = self.public_url + "/foo/" + filename.encode("utf-8")
1932 d.addCallback(lambda res: self.GET(target_url))
1933 d.addCallback(lambda contents: self.failUnlessReallyEqual(contents,
1934 self.NEWFILE_CONTENTS,
1938 def test_POST_upload_unicode_named(self):
1939 filename = u"n\u00e9wer.txt" # n e-acute w e r . t x t
1940 d = self.POST(self.public_url + "/foo", t="upload",
1942 file=("overridden", self.NEWFILE_CONTENTS))
1944 d.addCallback(self.failUnlessURIMatchesROChild, fn, filename)
1945 d.addCallback(lambda res:
1946 self.failUnlessChildContentsAre(fn, filename,
1947 self.NEWFILE_CONTENTS))
1948 target_url = self.public_url + "/foo/" + filename.encode("utf-8")
1949 d.addCallback(lambda res: self.GET(target_url))
1950 d.addCallback(lambda contents: self.failUnlessReallyEqual(contents,
1951 self.NEWFILE_CONTENTS,
1955 def test_POST_upload_no_link(self):
1956 d = self.POST("/uri", t="upload",
1957 file=("new.txt", self.NEWFILE_CONTENTS))
1958 def _check_upload_results(page):
1959 # this should be a page which describes the results of the upload
1960 # that just finished.
1961 self.failUnlessIn("Upload Results:", page)
1962 self.failUnlessIn("URI:", page)
1963 uri_re = re.compile("URI: <tt><span>(.*)</span>")
1964 mo = uri_re.search(page)
1965 self.failUnless(mo, page)
1966 new_uri = mo.group(1)
1968 d.addCallback(_check_upload_results)
1969 d.addCallback(self.failUnlessCHKURIHasContents, self.NEWFILE_CONTENTS)
1972 def test_POST_upload_no_link_whendone(self):
1973 d = self.POST("/uri", t="upload", when_done="/",
1974 file=("new.txt", self.NEWFILE_CONTENTS))
1975 d.addBoth(self.shouldRedirect, "/")
1978 def shouldRedirect2(self, which, checker, callable, *args, **kwargs):
1979 d = defer.maybeDeferred(callable, *args, **kwargs)
1981 if isinstance(res, failure.Failure):
1982 res.trap(error.PageRedirect)
1983 statuscode = res.value.status
1984 target = res.value.location
1985 return checker(statuscode, target)
1986 self.fail("%s: callable was supposed to redirect, not return '%s'"
1991 def test_POST_upload_no_link_whendone_results(self):
1992 def check(statuscode, target):
1993 self.failUnlessReallyEqual(statuscode, str(http.FOUND))
1994 self.failUnless(target.startswith(self.webish_url), target)
1995 return client.getPage(target, method="GET")
1996 d = self.shouldRedirect2("test_POST_upload_no_link_whendone_results",
1998 self.POST, "/uri", t="upload",
1999 when_done="/uri/%(uri)s",
2000 file=("new.txt", self.NEWFILE_CONTENTS))
2001 d.addCallback(lambda res:
2002 self.failUnlessReallyEqual(res, self.NEWFILE_CONTENTS))
2005 def test_POST_upload_no_link_mutable(self):
2006 d = self.POST("/uri", t="upload", mutable="true",
2007 file=("new.txt", self.NEWFILE_CONTENTS))
2008 def _check(filecap):
2009 filecap = filecap.strip()
2010 self.failUnless(filecap.startswith("URI:SSK:"), filecap)
2011 self.filecap = filecap
2012 u = uri.WriteableSSKFileURI.init_from_string(filecap)
2013 self.failUnlessIn(u.get_storage_index(), FakeMutableFileNode.all_contents)
2014 n = self.s.create_node_from_uri(filecap)
2015 return n.download_best_version()
2016 d.addCallback(_check)
2018 self.failUnlessReallyEqual(data, self.NEWFILE_CONTENTS)
2019 return self.GET("/uri/%s" % urllib.quote(self.filecap))
2020 d.addCallback(_check2)
2022 self.failUnlessReallyEqual(data, self.NEWFILE_CONTENTS)
2023 return self.GET("/file/%s" % urllib.quote(self.filecap))
2024 d.addCallback(_check3)
2026 self.failUnlessReallyEqual(data, self.NEWFILE_CONTENTS)
2027 d.addCallback(_check4)
2030 def test_POST_upload_no_link_mutable_toobig(self):
2031 # The SDMF size limit is no longer in place, so we should be
2032 # able to upload mutable files that are as large as we want them
2034 d = self.POST("/uri", t="upload", mutable="true",
2035 file=("new.txt", "b" * (self.s.MUTABLE_SIZELIMIT + 1)))
2039 def test_POST_upload_format_unlinked(self):
2040 def _check_upload_unlinked(ign, format, uri_prefix):
2041 filename = format + ".txt"
2042 d = self.POST("/uri?t=upload&format=" + format,
2043 file=(filename, self.NEWFILE_CONTENTS * 300000))
2044 def _got_results(results):
2045 if format.upper() in ("SDMF", "MDMF"):
2046 # webapi.rst says this returns a filecap
2049 # for immutable, it returns an "upload results page", and
2050 # the filecap is buried inside
2051 line = [l for l in results.split("\n") if "URI: " in l][0]
2052 mo = re.search(r'<span>([^<]+)</span>', line)
2053 filecap = mo.group(1)
2054 self.failUnless(filecap.startswith(uri_prefix),
2055 (uri_prefix, filecap))
2056 return self.GET("/uri/%s?t=json" % filecap)
2057 d.addCallback(_got_results)
2058 def _got_json(json):
2059 data = simplejson.loads(json)
2061 self.failUnlessIn("format", data)
2062 self.failUnlessEqual(data["format"], format.upper())
2063 d.addCallback(_got_json)
2065 d = defer.succeed(None)
2066 d.addCallback(_check_upload_unlinked, "chk", "URI:CHK")
2067 d.addCallback(_check_upload_unlinked, "CHK", "URI:CHK")
2068 d.addCallback(_check_upload_unlinked, "sdmf", "URI:SSK")
2069 d.addCallback(_check_upload_unlinked, "mdmf", "URI:MDMF")
2072 def test_POST_upload_bad_format_unlinked(self):
2073 return self.shouldHTTPError("POST_upload_bad_format_unlinked",
2074 400, "Bad Request", "Unknown format: foo",
2076 "/uri?t=upload&format=foo",
2077 file=("foo.txt", self.NEWFILE_CONTENTS * 300000))
2079 def test_POST_upload_format(self):
2080 def _check_upload(ign, format, uri_prefix, fn=None):
2081 filename = format + ".txt"
2082 d = self.POST(self.public_url +
2083 "/foo?t=upload&format=" + format,
2084 file=(filename, self.NEWFILE_CONTENTS * 300000))
2085 def _got_filecap(filecap):
2087 filenameu = unicode(filename)
2088 self.failUnlessURIMatchesRWChild(filecap, fn, filenameu)
2089 self.failUnless(filecap.startswith(uri_prefix))
2090 return self.GET(self.public_url + "/foo/%s?t=json" % filename)
2091 d.addCallback(_got_filecap)
2092 def _got_json(json):
2093 data = simplejson.loads(json)
2095 self.failUnlessIn("format", data)
2096 self.failUnlessEqual(data["format"], format.upper())
2097 d.addCallback(_got_json)
2100 d = defer.succeed(None)
2101 d.addCallback(_check_upload, "chk", "URI:CHK")
2102 d.addCallback(_check_upload, "sdmf", "URI:SSK", self._foo_node)
2103 d.addCallback(_check_upload, "mdmf", "URI:MDMF")
2104 d.addCallback(_check_upload, "MDMF", "URI:MDMF")
2107 def test_POST_upload_bad_format(self):
2108 return self.shouldHTTPError("POST_upload_bad_format",
2109 400, "Bad Request", "Unknown format: foo",
2110 self.POST, self.public_url + \
2111 "/foo?t=upload&format=foo",
2112 file=("foo.txt", self.NEWFILE_CONTENTS * 300000))
2114 def test_POST_upload_mutable(self):
2115 # this creates a mutable file
2116 d = self.POST(self.public_url + "/foo", t="upload", mutable="true",
2117 file=("new.txt", self.NEWFILE_CONTENTS))
2119 d.addCallback(self.failUnlessURIMatchesRWChild, fn, u"new.txt")
2120 d.addCallback(lambda res:
2121 self.failUnlessMutableChildContentsAre(fn, u"new.txt",
2122 self.NEWFILE_CONTENTS))
2123 d.addCallback(lambda res: self._foo_node.get(u"new.txt"))
2125 self.failUnless(IMutableFileNode.providedBy(newnode))
2126 self.failUnless(newnode.is_mutable())
2127 self.failIf(newnode.is_readonly())
2128 self._mutable_node = newnode
2129 self._mutable_uri = newnode.get_uri()
2132 # now upload it again and make sure that the URI doesn't change
2133 NEWER_CONTENTS = self.NEWFILE_CONTENTS + "newer\n"
2134 d.addCallback(lambda res:
2135 self.POST(self.public_url + "/foo", t="upload",
2137 file=("new.txt", NEWER_CONTENTS)))
2138 d.addCallback(self.failUnlessURIMatchesRWChild, fn, u"new.txt")
2139 d.addCallback(lambda res:
2140 self.failUnlessMutableChildContentsAre(fn, u"new.txt",
2142 d.addCallback(lambda res: self._foo_node.get(u"new.txt"))
2144 self.failUnless(IMutableFileNode.providedBy(newnode))
2145 self.failUnless(newnode.is_mutable())
2146 self.failIf(newnode.is_readonly())
2147 self.failUnlessReallyEqual(self._mutable_uri, newnode.get_uri())
2148 d.addCallback(_got2)
2150 # upload a second time, using PUT instead of POST
2151 NEW2_CONTENTS = NEWER_CONTENTS + "overwrite with PUT\n"
2152 d.addCallback(lambda res:
2153 self.PUT(self.public_url + "/foo/new.txt", NEW2_CONTENTS))
2154 d.addCallback(self.failUnlessURIMatchesRWChild, fn, u"new.txt")
2155 d.addCallback(lambda res:
2156 self.failUnlessMutableChildContentsAre(fn, u"new.txt",
2159 # finally list the directory, since mutable files are displayed
2160 # slightly differently
2162 d.addCallback(lambda res:
2163 self.GET(self.public_url + "/foo/",
2164 followRedirect=True))
2165 def _check_page(res):
2166 # TODO: assert more about the contents
2167 self.failUnlessIn("SSK", res)
2169 d.addCallback(_check_page)
2171 d.addCallback(lambda res: self._foo_node.get(u"new.txt"))
2173 self.failUnless(IMutableFileNode.providedBy(newnode))
2174 self.failUnless(newnode.is_mutable())
2175 self.failIf(newnode.is_readonly())
2176 self.failUnlessReallyEqual(self._mutable_uri, newnode.get_uri())
2177 d.addCallback(_got3)
2179 # look at the JSON form of the enclosing directory
2180 d.addCallback(lambda res:
2181 self.GET(self.public_url + "/foo/?t=json",
2182 followRedirect=True))
2183 def _check_page_json(res):
2184 parsed = simplejson.loads(res)
2185 self.failUnlessEqual(parsed[0], "dirnode")
2186 children = dict( [(unicode(name),value)
2188 in parsed[1]["children"].iteritems()] )
2189 self.failUnlessIn(u"new.txt", children)
2190 new_json = children[u"new.txt"]
2191 self.failUnlessEqual(new_json[0], "filenode")
2192 self.failUnless(new_json[1]["mutable"])
2193 self.failUnlessReallyEqual(to_str(new_json[1]["rw_uri"]), self._mutable_uri)
2194 ro_uri = self._mutable_node.get_readonly().to_string()
2195 self.failUnlessReallyEqual(to_str(new_json[1]["ro_uri"]), ro_uri)
2196 d.addCallback(_check_page_json)
2198 # and the JSON form of the file
2199 d.addCallback(lambda res:
2200 self.GET(self.public_url + "/foo/new.txt?t=json"))
2201 def _check_file_json(res):
2202 parsed = simplejson.loads(res)
2203 self.failUnlessEqual(parsed[0], "filenode")
2204 self.failUnless(parsed[1]["mutable"])
2205 self.failUnlessReallyEqual(to_str(parsed[1]["rw_uri"]), self._mutable_uri)
2206 ro_uri = self._mutable_node.get_readonly().to_string()
2207 self.failUnlessReallyEqual(to_str(parsed[1]["ro_uri"]), ro_uri)
2208 d.addCallback(_check_file_json)
2210 # and look at t=uri and t=readonly-uri
2211 d.addCallback(lambda res:
2212 self.GET(self.public_url + "/foo/new.txt?t=uri"))
2213 d.addCallback(lambda res: self.failUnlessReallyEqual(res, self._mutable_uri))
2214 d.addCallback(lambda res:
2215 self.GET(self.public_url + "/foo/new.txt?t=readonly-uri"))
2216 def _check_ro_uri(res):
2217 ro_uri = self._mutable_node.get_readonly().to_string()
2218 self.failUnlessReallyEqual(res, ro_uri)
2219 d.addCallback(_check_ro_uri)
2221 # make sure we can get to it from /uri/URI
2222 d.addCallback(lambda res:
2223 self.GET("/uri/%s" % urllib.quote(self._mutable_uri)))
2224 d.addCallback(lambda res:
2225 self.failUnlessReallyEqual(res, NEW2_CONTENTS))
2227 # and that HEAD computes the size correctly
2228 d.addCallback(lambda res:
2229 self.HEAD(self.public_url + "/foo/new.txt",
2230 return_response=True))
2231 def _got_headers((res, status, headers)):
2232 self.failUnlessReallyEqual(res, "")
2233 self.failUnlessReallyEqual(headers["content-length"][0],
2234 str(len(NEW2_CONTENTS)))
2235 self.failUnlessReallyEqual(headers["content-type"], ["text/plain"])
2236 d.addCallback(_got_headers)
2238 # make sure that outdated size limits aren't enforced anymore.
2239 d.addCallback(lambda ignored:
2240 self.POST(self.public_url + "/foo", t="upload",
2243 "b" * (self.s.MUTABLE_SIZELIMIT+1))))
2244 d.addErrback(self.dump_error)
2247 def test_POST_upload_mutable_toobig(self):
2248 # SDMF had a size limti that was removed a while ago. MDMF has
2249 # never had a size limit. Test to make sure that we do not
2250 # encounter errors when trying to upload large mutable files,
2251 # since there should be no coded prohibitions regarding large
2253 d = self.POST(self.public_url + "/foo",
2254 t="upload", mutable="true",
2255 file=("new.txt", "b" * (self.s.MUTABLE_SIZELIMIT + 1)))
2258 def dump_error(self, f):
2259 # if the web server returns an error code (like 400 Bad Request),
2260 # web.client.getPage puts the HTTP response body into the .response
2261 # attribute of the exception object that it gives back. It does not
2262 # appear in the Failure's repr(), so the ERROR that trial displays
2263 # will be rather terse and unhelpful. addErrback this method to the
2264 # end of your chain to get more information out of these errors.
2265 if f.check(error.Error):
2266 print "web.error.Error:"
2268 print f.value.response
2271 def test_POST_upload_replace(self):
2272 d = self.POST(self.public_url + "/foo", t="upload",
2273 file=("bar.txt", self.NEWFILE_CONTENTS))
2275 d.addCallback(self.failUnlessURIMatchesROChild, fn, u"bar.txt")
2276 d.addCallback(lambda res:
2277 self.failUnlessChildContentsAre(fn, u"bar.txt",
2278 self.NEWFILE_CONTENTS))
2281 def test_POST_upload_no_replace_ok(self):
2282 d = self.POST(self.public_url + "/foo?replace=false", t="upload",
2283 file=("new.txt", self.NEWFILE_CONTENTS))
2284 d.addCallback(lambda res: self.GET(self.public_url + "/foo/new.txt"))
2285 d.addCallback(lambda res: self.failUnlessReallyEqual(res,
2286 self.NEWFILE_CONTENTS))
2289 def test_POST_upload_no_replace_queryarg(self):
2290 d = self.POST(self.public_url + "/foo?replace=false", t="upload",
2291 file=("bar.txt", self.NEWFILE_CONTENTS))
2292 d.addBoth(self.shouldFail, error.Error,
2293 "POST_upload_no_replace_queryarg",
2295 "There was already a child by that name, and you asked me "
2296 "to not replace it")
2297 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
2298 d.addCallback(self.failUnlessIsBarDotTxt)
2301 def test_POST_upload_no_replace_field(self):
2302 d = self.POST(self.public_url + "/foo", t="upload", replace="false",
2303 file=("bar.txt", self.NEWFILE_CONTENTS))
2304 d.addBoth(self.shouldFail, error.Error, "POST_upload_no_replace_field",
2306 "There was already a child by that name, and you asked me "
2307 "to not replace it")
2308 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
2309 d.addCallback(self.failUnlessIsBarDotTxt)
2312 def test_POST_upload_whendone(self):
2313 d = self.POST(self.public_url + "/foo", t="upload", when_done="/THERE",
2314 file=("new.txt", self.NEWFILE_CONTENTS))
2315 d.addBoth(self.shouldRedirect, "/THERE")
2317 d.addCallback(lambda res:
2318 self.failUnlessChildContentsAre(fn, u"new.txt",
2319 self.NEWFILE_CONTENTS))
2322 def test_POST_upload_named(self):
2324 d = self.POST(self.public_url + "/foo", t="upload",
2325 name="new.txt", file=self.NEWFILE_CONTENTS)
2326 d.addCallback(self.failUnlessURIMatchesROChild, fn, u"new.txt")
2327 d.addCallback(lambda res:
2328 self.failUnlessChildContentsAre(fn, u"new.txt",
2329 self.NEWFILE_CONTENTS))
2332 def test_POST_upload_named_badfilename(self):
2333 d = self.POST(self.public_url + "/foo", t="upload",
2334 name="slashes/are/bad.txt", file=self.NEWFILE_CONTENTS)
2335 d.addBoth(self.shouldFail, error.Error,
2336 "test_POST_upload_named_badfilename",
2338 "name= may not contain a slash",
2340 # make sure that nothing was added
2341 d.addCallback(lambda res:
2342 self.failUnlessNodeKeysAre(self._foo_node,
2343 [u"bar.txt", u"baz.txt", u"blockingfile",
2344 u"empty", u"n\u00fc.txt", u"quux.txt",
2348 def test_POST_FILEURL_check(self):
2349 bar_url = self.public_url + "/foo/bar.txt"
2350 d = self.POST(bar_url, t="check")
2352 self.failUnlessIn("Healthy :", res)
2353 d.addCallback(_check)
2354 redir_url = "http://allmydata.org/TARGET"
2355 def _check2(statuscode, target):
2356 self.failUnlessReallyEqual(statuscode, str(http.FOUND))
2357 self.failUnlessReallyEqual(target, redir_url)
2358 d.addCallback(lambda res:
2359 self.shouldRedirect2("test_POST_FILEURL_check",
2363 when_done=redir_url))
2364 d.addCallback(lambda res:
2365 self.POST(bar_url, t="check", return_to=redir_url))
2367 self.failUnlessIn("Healthy :", res)
2368 self.failUnlessIn("Return to file", res)
2369 self.failUnlessIn(redir_url, res)
2370 d.addCallback(_check3)
2372 d.addCallback(lambda res:
2373 self.POST(bar_url, t="check", output="JSON"))
2374 def _check_json(res):
2375 data = simplejson.loads(res)
2376 self.failUnlessIn("storage-index", data)
2377 self.failUnless(data["results"]["healthy"])
2378 d.addCallback(_check_json)
2382 def test_POST_FILEURL_check_and_repair(self):
2383 bar_url = self.public_url + "/foo/bar.txt"
2384 d = self.POST(bar_url, t="check", repair="true")
2386 self.failUnlessIn("Healthy :", res)
2387 d.addCallback(_check)
2388 redir_url = "http://allmydata.org/TARGET"
2389 def _check2(statuscode, target):
2390 self.failUnlessReallyEqual(statuscode, str(http.FOUND))
2391 self.failUnlessReallyEqual(target, redir_url)
2392 d.addCallback(lambda res:
2393 self.shouldRedirect2("test_POST_FILEURL_check_and_repair",
2396 t="check", repair="true",
2397 when_done=redir_url))
2398 d.addCallback(lambda res:
2399 self.POST(bar_url, t="check", return_to=redir_url))
2401 self.failUnlessIn("Healthy :", res)
2402 self.failUnlessIn("Return to file", res)
2403 self.failUnlessIn(redir_url, res)
2404 d.addCallback(_check3)
2407 def test_POST_DIRURL_check(self):
2408 foo_url = self.public_url + "/foo/"
2409 d = self.POST(foo_url, t="check")
2411 self.failUnlessIn("Healthy :", res)
2412 d.addCallback(_check)
2413 redir_url = "http://allmydata.org/TARGET"
2414 def _check2(statuscode, target):
2415 self.failUnlessReallyEqual(statuscode, str(http.FOUND))
2416 self.failUnlessReallyEqual(target, redir_url)
2417 d.addCallback(lambda res:
2418 self.shouldRedirect2("test_POST_DIRURL_check",
2422 when_done=redir_url))
2423 d.addCallback(lambda res:
2424 self.POST(foo_url, t="check", return_to=redir_url))
2426 self.failUnlessIn("Healthy :", res)
2427 self.failUnlessIn("Return to file/directory", res)
2428 self.failUnlessIn(redir_url, res)
2429 d.addCallback(_check3)
2431 d.addCallback(lambda res:
2432 self.POST(foo_url, t="check", output="JSON"))
2433 def _check_json(res):
2434 data = simplejson.loads(res)
2435 self.failUnlessIn("storage-index", data)
2436 self.failUnless(data["results"]["healthy"])
2437 d.addCallback(_check_json)
2441 def test_POST_DIRURL_check_and_repair(self):
2442 foo_url = self.public_url + "/foo/"
2443 d = self.POST(foo_url, t="check", repair="true")
2445 self.failUnlessIn("Healthy :", res)
2446 d.addCallback(_check)
2447 redir_url = "http://allmydata.org/TARGET"
2448 def _check2(statuscode, target):
2449 self.failUnlessReallyEqual(statuscode, str(http.FOUND))
2450 self.failUnlessReallyEqual(target, redir_url)
2451 d.addCallback(lambda res:
2452 self.shouldRedirect2("test_POST_DIRURL_check_and_repair",
2455 t="check", repair="true",
2456 when_done=redir_url))
2457 d.addCallback(lambda res:
2458 self.POST(foo_url, t="check", return_to=redir_url))
2460 self.failUnlessIn("Healthy :", res)
2461 self.failUnlessIn("Return to file/directory", res)
2462 self.failUnlessIn(redir_url, res)
2463 d.addCallback(_check3)
2466 def test_POST_FILEURL_mdmf_check(self):
2467 quux_url = "/uri/%s" % urllib.quote(self._quux_txt_uri)
2468 d = self.POST(quux_url, t="check")
2470 self.failUnlessIn("Healthy", res)
2471 d.addCallback(_check)
2472 quux_extension_url = "/uri/%s" % urllib.quote("%s:3:131073" % self._quux_txt_uri)
2473 d.addCallback(lambda ignored:
2474 self.POST(quux_extension_url, t="check"))
2475 d.addCallback(_check)
2478 def test_POST_FILEURL_mdmf_check_and_repair(self):
2479 quux_url = "/uri/%s" % urllib.quote(self._quux_txt_uri)
2480 d = self.POST(quux_url, t="check", repair="true")
2482 self.failUnlessIn("Healthy", res)
2483 d.addCallback(_check)
2484 quux_extension_url = "/uri/%s" % urllib.quote("%s:3:131073" % self._quux_txt_uri)
2485 d.addCallback(lambda ignored:
2486 self.POST(quux_extension_url, t="check", repair="true"))
2487 d.addCallback(_check)
2490 def wait_for_operation(self, ignored, ophandle):
2491 url = "/operations/" + ophandle
2492 url += "?t=status&output=JSON"
2495 data = simplejson.loads(res)
2496 if not data["finished"]:
2497 d = self.stall(delay=1.0)
2498 d.addCallback(self.wait_for_operation, ophandle)
2504 def get_operation_results(self, ignored, ophandle, output=None):
2505 url = "/operations/" + ophandle
2508 url += "&output=" + output
2511 if output and output.lower() == "json":
2512 return simplejson.loads(res)
2517 def test_POST_DIRURL_deepcheck_no_ophandle(self):
2518 d = self.shouldFail2(error.Error,
2519 "test_POST_DIRURL_deepcheck_no_ophandle",
2521 "slow operation requires ophandle=",
2522 self.POST, self.public_url, t="start-deep-check")
2525 def test_POST_DIRURL_deepcheck(self):
2526 def _check_redirect(statuscode, target):
2527 self.failUnlessReallyEqual(statuscode, str(http.FOUND))
2528 self.failUnless(target.endswith("/operations/123"))
2529 d = self.shouldRedirect2("test_POST_DIRURL_deepcheck", _check_redirect,
2530 self.POST, self.public_url,
2531 t="start-deep-check", ophandle="123")
2532 d.addCallback(self.wait_for_operation, "123")
2533 def _check_json(data):
2534 self.failUnlessReallyEqual(data["finished"], True)
2535 self.failUnlessReallyEqual(data["count-objects-checked"], 10)
2536 self.failUnlessReallyEqual(data["count-objects-healthy"], 10)
2537 d.addCallback(_check_json)
2538 d.addCallback(self.get_operation_results, "123", "html")
2539 def _check_html(res):
2540 self.failUnlessIn("Objects Checked: <span>10</span>", res)
2541 self.failUnlessIn("Objects Healthy: <span>10</span>", res)
2542 self.failUnlessIn(FAVICON_MARKUP, res)
2543 d.addCallback(_check_html)
2545 d.addCallback(lambda res:
2546 self.GET("/operations/123/"))
2547 d.addCallback(_check_html) # should be the same as without the slash
2549 d.addCallback(lambda res:
2550 self.shouldFail2(error.Error, "one", "404 Not Found",
2551 "No detailed results for SI bogus",
2552 self.GET, "/operations/123/bogus"))
2554 foo_si = self._foo_node.get_storage_index()
2555 foo_si_s = base32.b2a(foo_si)
2556 d.addCallback(lambda res:
2557 self.GET("/operations/123/%s?output=JSON" % foo_si_s))
2558 def _check_foo_json(res):
2559 data = simplejson.loads(res)
2560 self.failUnlessEqual(data["storage-index"], foo_si_s)
2561 self.failUnless(data["results"]["healthy"])
2562 d.addCallback(_check_foo_json)
2565 def test_POST_DIRURL_deepcheck_and_repair(self):
2566 d = self.POST(self.public_url, t="start-deep-check", repair="true",
2567 ophandle="124", output="json", followRedirect=True)
2568 d.addCallback(self.wait_for_operation, "124")
2569 def _check_json(data):
2570 self.failUnlessReallyEqual(data["finished"], True)
2571 self.failUnlessReallyEqual(data["count-objects-checked"], 10)
2572 self.failUnlessReallyEqual(data["count-objects-healthy-pre-repair"], 10)
2573 self.failUnlessReallyEqual(data["count-objects-unhealthy-pre-repair"], 0)
2574 self.failUnlessReallyEqual(data["count-corrupt-shares-pre-repair"], 0)
2575 self.failUnlessReallyEqual(data["count-repairs-attempted"], 0)
2576 self.failUnlessReallyEqual(data["count-repairs-successful"], 0)
2577 self.failUnlessReallyEqual(data["count-repairs-unsuccessful"], 0)
2578 self.failUnlessReallyEqual(data["count-objects-healthy-post-repair"], 10)
2579 self.failUnlessReallyEqual(data["count-objects-unhealthy-post-repair"], 0)
2580 self.failUnlessReallyEqual(data["count-corrupt-shares-post-repair"], 0)
2581 d.addCallback(_check_json)
2582 d.addCallback(self.get_operation_results, "124", "html")
2583 def _check_html(res):
2584 self.failUnlessIn("Objects Checked: <span>10</span>", res)
2586 self.failUnlessIn("Objects Healthy (before repair): <span>10</span>", res)
2587 self.failUnlessIn("Objects Unhealthy (before repair): <span>0</span>", res)
2588 self.failUnlessIn("Corrupt Shares (before repair): <span>0</span>", res)
2590 self.failUnlessIn("Repairs Attempted: <span>0</span>", res)
2591 self.failUnlessIn("Repairs Successful: <span>0</span>", res)
2592 self.failUnlessIn("Repairs Unsuccessful: <span>0</span>", res)
2594 self.failUnlessIn("Objects Healthy (after repair): <span>10</span>", res)
2595 self.failUnlessIn("Objects Unhealthy (after repair): <span>0</span>", res)
2596 self.failUnlessIn("Corrupt Shares (after repair): <span>0</span>", res)
2598 self.failUnlessIn(FAVICON_MARKUP, res)
2599 d.addCallback(_check_html)
2602 def test_POST_FILEURL_bad_t(self):
2603 d = self.shouldFail2(error.Error, "POST_bad_t", "400 Bad Request",
2604 "POST to file: bad t=bogus",
2605 self.POST, self.public_url + "/foo/bar.txt",
2609 def test_POST_mkdir(self): # return value?
2610 d = self.POST(self.public_url + "/foo", t="mkdir", name="newdir")
2611 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2612 d.addCallback(self.failUnlessNodeKeysAre, [])
2615 def test_POST_mkdir_mdmf(self):
2616 d = self.POST(self.public_url + "/foo?t=mkdir&name=newdir&format=mdmf")
2617 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2618 d.addCallback(lambda node:
2619 self.failUnlessEqual(node._node.get_version(), MDMF_VERSION))
2622 def test_POST_mkdir_sdmf(self):
2623 d = self.POST(self.public_url + "/foo?t=mkdir&name=newdir&format=sdmf")
2624 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2625 d.addCallback(lambda node:
2626 self.failUnlessEqual(node._node.get_version(), SDMF_VERSION))
2629 def test_POST_mkdir_bad_format(self):
2630 return self.shouldHTTPError("POST_mkdir_bad_format",
2631 400, "Bad Request", "Unknown format: foo",
2632 self.POST, self.public_url +
2633 "/foo?t=mkdir&name=newdir&format=foo")
2635 def test_POST_mkdir_initial_children(self):
2636 (newkids, caps) = self._create_initial_children()
2637 d = self.POST2(self.public_url +
2638 "/foo?t=mkdir-with-children&name=newdir",
2639 simplejson.dumps(newkids))
2640 d.addCallback(lambda res:
2641 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
2642 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2643 d.addCallback(self.failUnlessNodeKeysAre, newkids.keys())
2644 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2645 d.addCallback(self.failUnlessROChildURIIs, u"child-imm", caps['filecap1'])
2648 def test_POST_mkdir_initial_children_mdmf(self):
2649 (newkids, caps) = self._create_initial_children()
2650 d = self.POST2(self.public_url +
2651 "/foo?t=mkdir-with-children&name=newdir&format=mdmf",
2652 simplejson.dumps(newkids))
2653 d.addCallback(lambda res:
2654 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
2655 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2656 d.addCallback(lambda node:
2657 self.failUnlessEqual(node._node.get_version(), MDMF_VERSION))
2658 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2659 d.addCallback(self.failUnlessROChildURIIs, u"child-imm",
2664 def test_POST_mkdir_initial_children_sdmf(self):
2665 (newkids, caps) = self._create_initial_children()
2666 d = self.POST2(self.public_url +
2667 "/foo?t=mkdir-with-children&name=newdir&format=sdmf",
2668 simplejson.dumps(newkids))
2669 d.addCallback(lambda res:
2670 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
2671 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2672 d.addCallback(lambda node:
2673 self.failUnlessEqual(node._node.get_version(), SDMF_VERSION))
2674 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2675 d.addCallback(self.failUnlessROChildURIIs, u"child-imm",
2679 def test_POST_mkdir_initial_children_bad_format(self):
2680 (newkids, caps) = self._create_initial_children()
2681 return self.shouldHTTPError("POST_mkdir_initial_children_bad_format",
2682 400, "Bad Request", "Unknown format: foo",
2683 self.POST, self.public_url + \
2684 "/foo?t=mkdir-with-children&name=newdir&format=foo",
2685 simplejson.dumps(newkids))
2687 def test_POST_mkdir_immutable(self):
2688 (newkids, caps) = self._create_immutable_children()
2689 d = self.POST2(self.public_url +
2690 "/foo?t=mkdir-immutable&name=newdir",
2691 simplejson.dumps(newkids))
2692 d.addCallback(lambda res:
2693 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
2694 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2695 d.addCallback(self.failUnlessNodeKeysAre, newkids.keys())
2696 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2697 d.addCallback(self.failUnlessROChildURIIs, u"child-imm", caps['filecap1'])
2698 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2699 d.addCallback(self.failUnlessROChildURIIs, u"unknownchild-imm", caps['unknown_immcap'])
2700 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2701 d.addCallback(self.failUnlessROChildURIIs, u"dirchild-imm", caps['immdircap'])
2702 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2703 d.addCallback(self.failUnlessROChildURIIs, u"dirchild-lit", caps['litdircap'])
2704 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2705 d.addCallback(self.failUnlessROChildURIIs, u"dirchild-empty", caps['emptydircap'])
2708 def test_POST_mkdir_immutable_bad(self):
2709 (newkids, caps) = self._create_initial_children()
2710 d = self.shouldFail2(error.Error, "POST_mkdir_immutable_bad",
2712 "needed to be immutable but was not",
2715 "/foo?t=mkdir-immutable&name=newdir",
2716 simplejson.dumps(newkids))
2719 def test_POST_mkdir_2(self):
2720 d = self.POST(self.public_url + "/foo/newdir?t=mkdir", "")
2721 d.addCallback(lambda res:
2722 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
2723 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2724 d.addCallback(self.failUnlessNodeKeysAre, [])
2727 def test_POST_mkdirs_2(self):
2728 d = self.POST(self.public_url + "/foo/bardir/newdir?t=mkdir", "")
2729 d.addCallback(lambda res:
2730 self.failUnlessNodeHasChild(self._foo_node, u"bardir"))
2731 d.addCallback(lambda res: self._foo_node.get(u"bardir"))
2732 d.addCallback(lambda bardirnode: bardirnode.get(u"newdir"))
2733 d.addCallback(self.failUnlessNodeKeysAre, [])
2736 def test_POST_mkdir_no_parentdir_noredirect(self):
2737 d = self.POST("/uri?t=mkdir")
2738 def _after_mkdir(res):
2739 uri.DirectoryURI.init_from_string(res)
2740 d.addCallback(_after_mkdir)
2743 def test_POST_mkdir_no_parentdir_noredirect_mdmf(self):
2744 d = self.POST("/uri?t=mkdir&format=mdmf")
2745 def _after_mkdir(res):
2746 u = uri.from_string(res)
2747 # Check that this is an MDMF writecap
2748 self.failUnlessIsInstance(u, uri.MDMFDirectoryURI)
2749 d.addCallback(_after_mkdir)
2752 def test_POST_mkdir_no_parentdir_noredirect_sdmf(self):
2753 d = self.POST("/uri?t=mkdir&format=sdmf")
2754 def _after_mkdir(res):
2755 u = uri.from_string(res)
2756 self.failUnlessIsInstance(u, uri.DirectoryURI)
2757 d.addCallback(_after_mkdir)
2760 def test_POST_mkdir_no_parentdir_noredirect_bad_format(self):
2761 return self.shouldHTTPError("POST_mkdir_no_parentdir_noredirect_bad_format",
2762 400, "Bad Request", "Unknown format: foo",
2763 self.POST, self.public_url +
2764 "/uri?t=mkdir&format=foo")
2766 def test_POST_mkdir_no_parentdir_noredirect2(self):
2767 # make sure form-based arguments (as on the welcome page) still work
2768 d = self.POST("/uri", t="mkdir")
2769 def _after_mkdir(res):
2770 uri.DirectoryURI.init_from_string(res)
2771 d.addCallback(_after_mkdir)
2772 d.addErrback(self.explain_web_error)
2775 def test_POST_mkdir_no_parentdir_redirect(self):
2776 d = self.POST("/uri?t=mkdir&redirect_to_result=true")
2777 d.addBoth(self.shouldRedirect, None, statuscode='303')
2778 def _check_target(target):
2779 target = urllib.unquote(target)
2780 self.failUnless(target.startswith("uri/URI:DIR2:"), target)
2781 d.addCallback(_check_target)
2784 def test_POST_mkdir_no_parentdir_redirect2(self):
2785 d = self.POST("/uri", t="mkdir", redirect_to_result="true")
2786 d.addBoth(self.shouldRedirect, None, statuscode='303')
2787 def _check_target(target):
2788 target = urllib.unquote(target)
2789 self.failUnless(target.startswith("uri/URI:DIR2:"), target)
2790 d.addCallback(_check_target)
2791 d.addErrback(self.explain_web_error)
2794 def _make_readonly(self, u):
2795 ro_uri = uri.from_string(u).get_readonly()
2798 return ro_uri.to_string()
2800 def _create_initial_children(self):
2801 contents, n, filecap1 = self.makefile(12)
2802 md1 = {"metakey1": "metavalue1"}
2803 filecap2 = make_mutable_file_uri()
2804 node3 = self.s.create_node_from_uri(make_mutable_file_uri())
2805 filecap3 = node3.get_readonly_uri()
2806 node4 = self.s.create_node_from_uri(make_mutable_file_uri())
2807 dircap = DirectoryNode(node4, None, None).get_uri()
2808 mdmfcap = make_mutable_file_uri(mdmf=True)
2809 litdircap = "URI:DIR2-LIT:ge3dumj2mewdcotyfqydulbshj5x2lbm"
2810 emptydircap = "URI:DIR2-LIT:"
2811 newkids = {u"child-imm": ["filenode", {"rw_uri": filecap1,
2812 "ro_uri": self._make_readonly(filecap1),
2813 "metadata": md1, }],
2814 u"child-mutable": ["filenode", {"rw_uri": filecap2,
2815 "ro_uri": self._make_readonly(filecap2)}],
2816 u"child-mutable-ro": ["filenode", {"ro_uri": filecap3}],
2817 u"unknownchild-rw": ["unknown", {"rw_uri": unknown_rwcap,
2818 "ro_uri": unknown_rocap}],
2819 u"unknownchild-ro": ["unknown", {"ro_uri": unknown_rocap}],
2820 u"unknownchild-imm": ["unknown", {"ro_uri": unknown_immcap}],
2821 u"dirchild": ["dirnode", {"rw_uri": dircap,
2822 "ro_uri": self._make_readonly(dircap)}],
2823 u"dirchild-lit": ["dirnode", {"ro_uri": litdircap}],
2824 u"dirchild-empty": ["dirnode", {"ro_uri": emptydircap}],
2825 u"child-mutable-mdmf": ["filenode", {"rw_uri": mdmfcap,
2826 "ro_uri": self._make_readonly(mdmfcap)}],
2828 return newkids, {'filecap1': filecap1,
2829 'filecap2': filecap2,
2830 'filecap3': filecap3,
2831 'unknown_rwcap': unknown_rwcap,
2832 'unknown_rocap': unknown_rocap,
2833 'unknown_immcap': unknown_immcap,
2835 'litdircap': litdircap,
2836 'emptydircap': emptydircap,
2839 def _create_immutable_children(self):
2840 contents, n, filecap1 = self.makefile(12)
2841 md1 = {"metakey1": "metavalue1"}
2842 tnode = create_chk_filenode("immutable directory contents\n"*10)
2843 dnode = DirectoryNode(tnode, None, None)
2844 assert not dnode.is_mutable()
2845 immdircap = dnode.get_uri()
2846 litdircap = "URI:DIR2-LIT:ge3dumj2mewdcotyfqydulbshj5x2lbm"
2847 emptydircap = "URI:DIR2-LIT:"
2848 newkids = {u"child-imm": ["filenode", {"ro_uri": filecap1,
2849 "metadata": md1, }],
2850 u"unknownchild-imm": ["unknown", {"ro_uri": unknown_immcap}],
2851 u"dirchild-imm": ["dirnode", {"ro_uri": immdircap}],
2852 u"dirchild-lit": ["dirnode", {"ro_uri": litdircap}],
2853 u"dirchild-empty": ["dirnode", {"ro_uri": emptydircap}],
2855 return newkids, {'filecap1': filecap1,
2856 'unknown_immcap': unknown_immcap,
2857 'immdircap': immdircap,
2858 'litdircap': litdircap,
2859 'emptydircap': emptydircap}
2861 def test_POST_mkdir_no_parentdir_initial_children(self):
2862 (newkids, caps) = self._create_initial_children()
2863 d = self.POST2("/uri?t=mkdir-with-children", simplejson.dumps(newkids))
2864 def _after_mkdir(res):
2865 self.failUnless(res.startswith("URI:DIR"), res)
2866 n = self.s.create_node_from_uri(res)
2867 d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
2868 d2.addCallback(lambda ign:
2869 self.failUnlessROChildURIIs(n, u"child-imm",
2871 d2.addCallback(lambda ign:
2872 self.failUnlessRWChildURIIs(n, u"child-mutable",
2874 d2.addCallback(lambda ign:
2875 self.failUnlessROChildURIIs(n, u"child-mutable-ro",
2877 d2.addCallback(lambda ign:
2878 self.failUnlessRWChildURIIs(n, u"unknownchild-rw",
2879 caps['unknown_rwcap']))
2880 d2.addCallback(lambda ign:
2881 self.failUnlessROChildURIIs(n, u"unknownchild-ro",
2882 caps['unknown_rocap']))
2883 d2.addCallback(lambda ign:
2884 self.failUnlessROChildURIIs(n, u"unknownchild-imm",
2885 caps['unknown_immcap']))
2886 d2.addCallback(lambda ign:
2887 self.failUnlessRWChildURIIs(n, u"dirchild",
2890 d.addCallback(_after_mkdir)
2893 def test_POST_mkdir_no_parentdir_unexpected_children(self):
2894 # the regular /uri?t=mkdir operation is specified to ignore its body.
2895 # Only t=mkdir-with-children pays attention to it.
2896 (newkids, caps) = self._create_initial_children()
2897 d = self.shouldHTTPError("POST_mkdir_no_parentdir_unexpected_children",
2899 "t=mkdir does not accept children=, "
2900 "try t=mkdir-with-children instead",
2901 self.POST2, "/uri?t=mkdir", # without children
2902 simplejson.dumps(newkids))
2905 def test_POST_noparent_bad(self):
2906 d = self.shouldHTTPError("POST_noparent_bad",
2908 "/uri accepts only PUT, PUT?t=mkdir, "
2909 "POST?t=upload, and POST?t=mkdir",
2910 self.POST, "/uri?t=bogus")
2913 def test_POST_mkdir_no_parentdir_immutable(self):
2914 (newkids, caps) = self._create_immutable_children()
2915 d = self.POST2("/uri?t=mkdir-immutable", simplejson.dumps(newkids))
2916 def _after_mkdir(res):
2917 self.failUnless(res.startswith("URI:DIR"), res)
2918 n = self.s.create_node_from_uri(res)
2919 d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
2920 d2.addCallback(lambda ign:
2921 self.failUnlessROChildURIIs(n, u"child-imm",
2923 d2.addCallback(lambda ign:
2924 self.failUnlessROChildURIIs(n, u"unknownchild-imm",
2925 caps['unknown_immcap']))
2926 d2.addCallback(lambda ign:
2927 self.failUnlessROChildURIIs(n, u"dirchild-imm",
2929 d2.addCallback(lambda ign:
2930 self.failUnlessROChildURIIs(n, u"dirchild-lit",
2932 d2.addCallback(lambda ign:
2933 self.failUnlessROChildURIIs(n, u"dirchild-empty",
2934 caps['emptydircap']))
2936 d.addCallback(_after_mkdir)
2939 def test_POST_mkdir_no_parentdir_immutable_bad(self):
2940 (newkids, caps) = self._create_initial_children()
2941 d = self.shouldFail2(error.Error,
2942 "test_POST_mkdir_no_parentdir_immutable_bad",
2944 "needed to be immutable but was not",
2946 "/uri?t=mkdir-immutable",
2947 simplejson.dumps(newkids))
2950 def test_welcome_page_mkdir_button(self):
2951 # Fetch the welcome page.
2953 def _after_get_welcome_page(res):
2954 MKDIR_BUTTON_RE = re.compile(
2955 '<form action="([^"]*)" method="post".*?'
2956 '<input type="hidden" name="t" value="([^"]*)" />'
2957 '<input type="hidden" name="([^"]*)" value="([^"]*)" />'
2958 '<input type="submit" value="Create a directory" />',
2960 mo = MKDIR_BUTTON_RE.search(res)
2961 formaction = mo.group(1)
2963 formaname = mo.group(3)
2964 formavalue = mo.group(4)
2965 return (formaction, formt, formaname, formavalue)
2966 d.addCallback(_after_get_welcome_page)
2967 def _after_parse_form(res):
2968 (formaction, formt, formaname, formavalue) = res
2969 return self.POST("/%s?t=%s&%s=%s" % (formaction, formt, formaname, formavalue))
2970 d.addCallback(_after_parse_form)
2971 d.addBoth(self.shouldRedirect, None, statuscode='303')
2974 def test_POST_mkdir_replace(self): # return value?
2975 d = self.POST(self.public_url + "/foo", t="mkdir", name="sub")
2976 d.addCallback(lambda res: self._foo_node.get(u"sub"))
2977 d.addCallback(self.failUnlessNodeKeysAre, [])
2980 def test_POST_mkdir_no_replace_queryarg(self): # return value?
2981 d = self.POST(self.public_url + "/foo?replace=false", t="mkdir", name="sub")
2982 d.addBoth(self.shouldFail, error.Error,
2983 "POST_mkdir_no_replace_queryarg",
2985 "There was already a child by that name, and you asked me "
2986 "to not replace it")
2987 d.addCallback(lambda res: self._foo_node.get(u"sub"))
2988 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
2991 def test_POST_mkdir_no_replace_field(self): # return value?
2992 d = self.POST(self.public_url + "/foo", t="mkdir", name="sub",
2994 d.addBoth(self.shouldFail, error.Error, "POST_mkdir_no_replace_field",
2996 "There was already a child by that name, and you asked me "
2997 "to not replace it")
2998 d.addCallback(lambda res: self._foo_node.get(u"sub"))
2999 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
3002 def test_POST_mkdir_whendone_field(self):
3003 d = self.POST(self.public_url + "/foo",
3004 t="mkdir", name="newdir", when_done="/THERE")
3005 d.addBoth(self.shouldRedirect, "/THERE")
3006 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
3007 d.addCallback(self.failUnlessNodeKeysAre, [])
3010 def test_POST_mkdir_whendone_queryarg(self):
3011 d = self.POST(self.public_url + "/foo?when_done=/THERE",
3012 t="mkdir", name="newdir")
3013 d.addBoth(self.shouldRedirect, "/THERE")
3014 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
3015 d.addCallback(self.failUnlessNodeKeysAre, [])
3018 def test_POST_bad_t(self):
3019 d = self.shouldFail2(error.Error, "POST_bad_t",
3021 "POST to a directory with bad t=BOGUS",
3022 self.POST, self.public_url + "/foo", t="BOGUS")
3025 def test_POST_set_children(self, command_name="set_children"):
3026 contents9, n9, newuri9 = self.makefile(9)
3027 contents10, n10, newuri10 = self.makefile(10)
3028 contents11, n11, newuri11 = self.makefile(11)
3031 "atomic_added_1": [ "filenode", { "rw_uri": "%s",
3034 "ctime": 1002777696.7564139,
3035 "mtime": 1002777696.7564139
3038 "atomic_added_2": [ "filenode", { "rw_uri": "%s",
3041 "ctime": 1002777696.7564139,
3042 "mtime": 1002777696.7564139
3045 "atomic_added_3": [ "filenode", { "rw_uri": "%s",
3048 "ctime": 1002777696.7564139,
3049 "mtime": 1002777696.7564139
3052 }""" % (newuri9, newuri10, newuri11)
3054 url = self.webish_url + self.public_url + "/foo" + "?t=" + command_name
3056 d = client.getPage(url, method="POST", postdata=reqbody)
3058 self.failUnlessURIMatchesROChild(newuri9, self._foo_node, u"atomic_added_1")
3059 self.failUnlessURIMatchesROChild(newuri10, self._foo_node, u"atomic_added_2")
3060 self.failUnlessURIMatchesROChild(newuri11, self._foo_node, u"atomic_added_3")
3062 d.addCallback(_then)
3063 d.addErrback(self.dump_error)
3066 def test_POST_set_children_with_hyphen(self):
3067 return self.test_POST_set_children(command_name="set-children")
3069 def test_POST_link_uri(self):
3070 contents, n, newuri = self.makefile(8)
3071 d = self.POST(self.public_url + "/foo", t="uri", name="new.txt", uri=newuri)
3072 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"new.txt")
3073 d.addCallback(lambda res:
3074 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
3078 def test_POST_link_uri_replace(self):
3079 contents, n, newuri = self.makefile(8)
3080 d = self.POST(self.public_url + "/foo", t="uri", name="bar.txt", uri=newuri)
3081 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"bar.txt")
3082 d.addCallback(lambda res:
3083 self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
3087 def test_POST_link_uri_unknown_bad(self):
3088 d = self.POST(self.public_url + "/foo", t="uri", name="future.txt", uri=unknown_rwcap)
3089 d.addBoth(self.shouldFail, error.Error,
3090 "POST_link_uri_unknown_bad",
3092 "unknown cap in a write slot")
3095 def test_POST_link_uri_unknown_ro_good(self):
3096 d = self.POST(self.public_url + "/foo", t="uri", name="future-ro.txt", uri=unknown_rocap)
3097 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"future-ro.txt")
3100 def test_POST_link_uri_unknown_imm_good(self):
3101 d = self.POST(self.public_url + "/foo", t="uri", name="future-imm.txt", uri=unknown_immcap)
3102 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"future-imm.txt")
3105 def test_POST_link_uri_no_replace_queryarg(self):
3106 contents, n, newuri = self.makefile(8)
3107 d = self.POST(self.public_url + "/foo?replace=false", t="uri",
3108 name="bar.txt", uri=newuri)
3109 d.addBoth(self.shouldFail, error.Error,
3110 "POST_link_uri_no_replace_queryarg",
3112 "There was already a child by that name, and you asked me "
3113 "to not replace it")
3114 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
3115 d.addCallback(self.failUnlessIsBarDotTxt)
3118 def test_POST_link_uri_no_replace_field(self):
3119 contents, n, newuri = self.makefile(8)
3120 d = self.POST(self.public_url + "/foo", t="uri", replace="false",
3121 name="bar.txt", uri=newuri)
3122 d.addBoth(self.shouldFail, error.Error,
3123 "POST_link_uri_no_replace_field",
3125 "There was already a child by that name, and you asked me "
3126 "to not replace it")
3127 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
3128 d.addCallback(self.failUnlessIsBarDotTxt)
3131 def test_POST_delete(self, command_name='delete'):
3132 d = self._foo_node.list()
3133 def _check_before(children):
3134 self.failUnlessIn(u"bar.txt", children)
3135 d.addCallback(_check_before)
3136 d.addCallback(lambda res: self.POST(self.public_url + "/foo", t=command_name, name="bar.txt"))
3137 d.addCallback(lambda res: self._foo_node.list())
3138 def _check_after(children):
3139 self.failIfIn(u"bar.txt", children)
3140 d.addCallback(_check_after)
3143 def test_POST_unlink(self):
3144 return self.test_POST_delete(command_name='unlink')
3146 def test_POST_rename_file(self):
3147 d = self.POST(self.public_url + "/foo", t="rename",
3148 from_name="bar.txt", to_name='wibble.txt')
3149 d.addCallback(lambda res:
3150 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
3151 d.addCallback(lambda res:
3152 self.failUnlessNodeHasChild(self._foo_node, u"wibble.txt"))
3153 d.addCallback(lambda res: self.GET(self.public_url + "/foo/wibble.txt"))
3154 d.addCallback(self.failUnlessIsBarDotTxt)
3155 d.addCallback(lambda res: self.GET(self.public_url + "/foo/wibble.txt?t=json"))
3156 d.addCallback(self.failUnlessIsBarJSON)
3159 def test_POST_rename_file_redundant(self):
3160 d = self.POST(self.public_url + "/foo", t="rename",
3161 from_name="bar.txt", to_name='bar.txt')
3162 d.addCallback(lambda res:
3163 self.failUnlessNodeHasChild(self._foo_node, u"bar.txt"))
3164 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
3165 d.addCallback(self.failUnlessIsBarDotTxt)
3166 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt?t=json"))
3167 d.addCallback(self.failUnlessIsBarJSON)
3170 def test_POST_rename_file_replace(self):
3171 # rename a file and replace a directory with it
3172 d = self.POST(self.public_url + "/foo", t="rename",
3173 from_name="bar.txt", to_name='empty')
3174 d.addCallback(lambda res:
3175 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
3176 d.addCallback(lambda res:
3177 self.failUnlessNodeHasChild(self._foo_node, u"empty"))
3178 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty"))
3179 d.addCallback(self.failUnlessIsBarDotTxt)
3180 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
3181 d.addCallback(self.failUnlessIsBarJSON)
3184 def test_POST_rename_file_no_replace_queryarg(self):
3185 # rename a file and replace a directory with it
3186 d = self.POST(self.public_url + "/foo?replace=false", t="rename",
3187 from_name="bar.txt", to_name='empty')
3188 d.addBoth(self.shouldFail, error.Error,
3189 "POST_rename_file_no_replace_queryarg",
3191 "There was already a child by that name, and you asked me "
3192 "to not replace it")
3193 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
3194 d.addCallback(self.failUnlessIsEmptyJSON)
3197 def test_POST_rename_file_no_replace_field(self):
3198 # rename a file and replace a directory with it
3199 d = self.POST(self.public_url + "/foo", t="rename", replace="false",
3200 from_name="bar.txt", to_name='empty')
3201 d.addBoth(self.shouldFail, error.Error,
3202 "POST_rename_file_no_replace_field",
3204 "There was already a child by that name, and you asked me "
3205 "to not replace it")
3206 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
3207 d.addCallback(self.failUnlessIsEmptyJSON)
3210 def failUnlessIsEmptyJSON(self, res):
3211 data = simplejson.loads(res)
3212 self.failUnlessEqual(data[0], "dirnode", data)
3213 self.failUnlessReallyEqual(len(data[1]["children"]), 0)
3215 def test_POST_rename_file_slash_fail(self):
3216 d = self.POST(self.public_url + "/foo", t="rename",
3217 from_name="bar.txt", to_name='kirk/spock.txt')
3218 d.addBoth(self.shouldFail, error.Error,
3219 "test_POST_rename_file_slash_fail",
3221 "to_name= may not contain a slash",
3223 d.addCallback(lambda res:
3224 self.failUnlessNodeHasChild(self._foo_node, u"bar.txt"))
3227 def test_POST_rename_dir(self):
3228 d = self.POST(self.public_url, t="rename",
3229 from_name="foo", to_name='plunk')
3230 d.addCallback(lambda res:
3231 self.failIfNodeHasChild(self.public_root, u"foo"))
3232 d.addCallback(lambda res:
3233 self.failUnlessNodeHasChild(self.public_root, u"plunk"))
3234 d.addCallback(lambda res: self.GET(self.public_url + "/plunk?t=json"))
3235 d.addCallback(self.failUnlessIsFooJSON)
3238 def shouldRedirect(self, res, target=None, statuscode=None, which=""):
3239 """ If target is not None then the redirection has to go to target. If
3240 statuscode is not None then the redirection has to be accomplished with
3241 that HTTP status code."""
3242 if not isinstance(res, failure.Failure):
3243 to_where = (target is None) and "somewhere" or ("to " + target)
3244 self.fail("%s: we were expecting to get redirected %s, not get an"
3245 " actual page: %s" % (which, to_where, res))
3246 res.trap(error.PageRedirect)
3247 if statuscode is not None:
3248 self.failUnlessReallyEqual(res.value.status, statuscode,
3249 "%s: not a redirect" % which)
3250 if target is not None:
3251 # the PageRedirect does not seem to capture the uri= query arg
3252 # properly, so we can't check for it.
3253 realtarget = self.webish_url + target
3254 self.failUnlessReallyEqual(res.value.location, realtarget,
3255 "%s: wrong target" % which)
3256 return res.value.location
3258 def test_GET_URI_form(self):
3259 base = "/uri?uri=%s" % self._bar_txt_uri
3260 # this is supposed to give us a redirect to /uri/$URI, plus arguments
3261 targetbase = "/uri/%s" % urllib.quote(self._bar_txt_uri)
3263 d.addBoth(self.shouldRedirect, targetbase)
3264 d.addCallback(lambda res: self.GET(base+"&filename=bar.txt"))
3265 d.addBoth(self.shouldRedirect, targetbase+"?filename=bar.txt")
3266 d.addCallback(lambda res: self.GET(base+"&t=json"))
3267 d.addBoth(self.shouldRedirect, targetbase+"?t=json")
3268 d.addCallback(self.log, "about to get file by uri")
3269 d.addCallback(lambda res: self.GET(base, followRedirect=True))
3270 d.addCallback(self.failUnlessIsBarDotTxt)
3271 d.addCallback(self.log, "got file by uri, about to get dir by uri")
3272 d.addCallback(lambda res: self.GET("/uri?uri=%s&t=json" % self._foo_uri,
3273 followRedirect=True))
3274 d.addCallback(self.failUnlessIsFooJSON)
3275 d.addCallback(self.log, "got dir by uri")
3279 def test_GET_URI_form_bad(self):
3280 d = self.shouldFail2(error.Error, "test_GET_URI_form_bad",
3281 "400 Bad Request", "GET /uri requires uri=",
3285 def test_GET_rename_form(self):
3286 d = self.GET(self.public_url + "/foo?t=rename-form&name=bar.txt",
3287 followRedirect=True)
3289 self.failUnlessIn('name="when_done" value="."', res)
3290 self.failUnless(re.search(r'name="from_name" value="bar\.txt"', res))
3291 self.failUnlessIn(FAVICON_MARKUP, res)
3292 d.addCallback(_check)
3295 def log(self, res, msg):
3296 #print "MSG: %s RES: %s" % (msg, res)
3300 def test_GET_URI_URL(self):
3301 base = "/uri/%s" % self._bar_txt_uri
3303 d.addCallback(self.failUnlessIsBarDotTxt)
3304 d.addCallback(lambda res: self.GET(base+"?filename=bar.txt"))
3305 d.addCallback(self.failUnlessIsBarDotTxt)
3306 d.addCallback(lambda res: self.GET(base+"?filename=bar.txt&save=true"))
3307 d.addCallback(self.failUnlessIsBarDotTxt)
3310 def test_GET_URI_URL_dir(self):
3311 base = "/uri/%s?t=json" % self._foo_uri
3313 d.addCallback(self.failUnlessIsFooJSON)
3316 def test_GET_URI_URL_missing(self):
3317 base = "/uri/%s" % self._bad_file_uri
3318 d = self.shouldHTTPError("test_GET_URI_URL_missing",
3319 http.GONE, None, "NotEnoughSharesError",
3321 # TODO: how can we exercise both sides of WebDownloadTarget.fail
3322 # here? we must arrange for a download to fail after target.open()
3323 # has been called, and then inspect the response to see that it is
3324 # shorter than we expected.
3327 def test_PUT_DIRURL_uri(self):
3328 d = self.s.create_dirnode()
3330 new_uri = dn.get_uri()
3331 # replace /foo with a new (empty) directory
3332 d = self.PUT(self.public_url + "/foo?t=uri", new_uri)
3333 d.addCallback(lambda res:
3334 self.failUnlessReallyEqual(res.strip(), new_uri))
3335 d.addCallback(lambda res:
3336 self.failUnlessRWChildURIIs(self.public_root,
3340 d.addCallback(_made_dir)
3343 def test_PUT_DIRURL_uri_noreplace(self):
3344 d = self.s.create_dirnode()
3346 new_uri = dn.get_uri()
3347 # replace /foo with a new (empty) directory, but ask that
3348 # replace=false, so it should fail
3349 d = self.shouldFail2(error.Error, "test_PUT_DIRURL_uri_noreplace",
3350 "409 Conflict", "There was already a child by that name, and you asked me to not replace it",
3352 self.public_url + "/foo?t=uri&replace=false",
3354 d.addCallback(lambda res:
3355 self.failUnlessRWChildURIIs(self.public_root,
3359 d.addCallback(_made_dir)
3362 def test_PUT_DIRURL_bad_t(self):
3363 d = self.shouldFail2(error.Error, "test_PUT_DIRURL_bad_t",
3364 "400 Bad Request", "PUT to a directory",
3365 self.PUT, self.public_url + "/foo?t=BOGUS", "")
3366 d.addCallback(lambda res:
3367 self.failUnlessRWChildURIIs(self.public_root,
3372 def test_PUT_NEWFILEURL_uri(self):
3373 contents, n, new_uri = self.makefile(8)
3374 d = self.PUT(self.public_url + "/foo/new.txt?t=uri", new_uri)
3375 d.addCallback(lambda res: self.failUnlessReallyEqual(res.strip(), new_uri))
3376 d.addCallback(lambda res:
3377 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
3381 def test_PUT_NEWFILEURL_mdmf(self):
3382 new_contents = self.NEWFILE_CONTENTS * 300000
3383 d = self.PUT(self.public_url + \
3384 "/foo/mdmf.txt?format=mdmf",
3386 d.addCallback(lambda ignored:
3387 self.GET(self.public_url + "/foo/mdmf.txt?t=json"))
3388 def _got_json(json):
3389 data = simplejson.loads(json)
3391 self.failUnlessIn("format", data)
3392 self.failUnlessEqual(data["format"], "MDMF")
3393 self.failUnless(data['rw_uri'].startswith("URI:MDMF"))
3394 self.failUnless(data['ro_uri'].startswith("URI:MDMF"))
3395 d.addCallback(_got_json)
3398 def test_PUT_NEWFILEURL_sdmf(self):
3399 new_contents = self.NEWFILE_CONTENTS * 300000
3400 d = self.PUT(self.public_url + \
3401 "/foo/sdmf.txt?format=sdmf",
3403 d.addCallback(lambda ignored:
3404 self.GET(self.public_url + "/foo/sdmf.txt?t=json"))
3405 def _got_json(json):
3406 data = simplejson.loads(json)
3408 self.failUnlessIn("format", data)
3409 self.failUnlessEqual(data["format"], "SDMF")
3410 d.addCallback(_got_json)
3413 def test_PUT_NEWFILEURL_bad_format(self):
3414 new_contents = self.NEWFILE_CONTENTS * 300000
3415 return self.shouldHTTPError("PUT_NEWFILEURL_bad_format",
3416 400, "Bad Request", "Unknown format: foo",
3417 self.PUT, self.public_url + \
3418 "/foo/foo.txt?format=foo",
3421 def test_PUT_NEWFILEURL_uri_replace(self):
3422 contents, n, new_uri = self.makefile(8)
3423 d = self.PUT(self.public_url + "/foo/bar.txt?t=uri", new_uri)
3424 d.addCallback(lambda res: self.failUnlessReallyEqual(res.strip(), new_uri))
3425 d.addCallback(lambda res:
3426 self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
3430 def test_PUT_NEWFILEURL_uri_no_replace(self):
3431 contents, n, new_uri = self.makefile(8)
3432 d = self.PUT(self.public_url + "/foo/bar.txt?t=uri&replace=false", new_uri)
3433 d.addBoth(self.shouldFail, error.Error,
3434 "PUT_NEWFILEURL_uri_no_replace",
3436 "There was already a child by that name, and you asked me "
3437 "to not replace it")
3440 def test_PUT_NEWFILEURL_uri_unknown_bad(self):
3441 d = self.PUT(self.public_url + "/foo/put-future.txt?t=uri", unknown_rwcap)
3442 d.addBoth(self.shouldFail, error.Error,
3443 "POST_put_uri_unknown_bad",
3445 "unknown cap in a write slot")
3448 def test_PUT_NEWFILEURL_uri_unknown_ro_good(self):
3449 d = self.PUT(self.public_url + "/foo/put-future-ro.txt?t=uri", unknown_rocap)
3450 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node,
3451 u"put-future-ro.txt")
3454 def test_PUT_NEWFILEURL_uri_unknown_imm_good(self):
3455 d = self.PUT(self.public_url + "/foo/put-future-imm.txt?t=uri", unknown_immcap)
3456 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node,
3457 u"put-future-imm.txt")
3460 def test_PUT_NEWFILE_URI(self):
3461 file_contents = "New file contents here\n"
3462 d = self.PUT("/uri", file_contents)
3464 assert isinstance(uri, str), uri
3465 self.failUnlessIn(uri, FakeCHKFileNode.all_contents)
3466 self.failUnlessReallyEqual(FakeCHKFileNode.all_contents[uri],
3468 return self.GET("/uri/%s" % uri)
3469 d.addCallback(_check)
3471 self.failUnlessReallyEqual(res, file_contents)
3472 d.addCallback(_check2)
3475 def test_PUT_NEWFILE_URI_not_mutable(self):
3476 file_contents = "New file contents here\n"
3477 d = self.PUT("/uri?mutable=false", file_contents)
3479 assert isinstance(uri, str), uri
3480 self.failUnlessIn(uri, FakeCHKFileNode.all_contents)
3481 self.failUnlessReallyEqual(FakeCHKFileNode.all_contents[uri],
3483 return self.GET("/uri/%s" % uri)
3484 d.addCallback(_check)
3486 self.failUnlessReallyEqual(res, file_contents)
3487 d.addCallback(_check2)
3490 def test_PUT_NEWFILE_URI_only_PUT(self):
3491 d = self.PUT("/uri?t=bogus", "")
3492 d.addBoth(self.shouldFail, error.Error,
3493 "PUT_NEWFILE_URI_only_PUT",
3495 "/uri accepts only PUT, PUT?t=mkdir, POST?t=upload, and POST?t=mkdir")
3498 def test_PUT_NEWFILE_URI_mutable(self):
3499 file_contents = "New file contents here\n"
3500 d = self.PUT("/uri?mutable=true", file_contents)
3501 def _check1(filecap):
3502 filecap = filecap.strip()
3503 self.failUnless(filecap.startswith("URI:SSK:"), filecap)
3504 self.filecap = filecap
3505 u = uri.WriteableSSKFileURI.init_from_string(filecap)
3506 self.failUnlessIn(u.get_storage_index(), FakeMutableFileNode.all_contents)
3507 n = self.s.create_node_from_uri(filecap)
3508 return n.download_best_version()
3509 d.addCallback(_check1)
3511 self.failUnlessReallyEqual(data, file_contents)
3512 return self.GET("/uri/%s" % urllib.quote(self.filecap))
3513 d.addCallback(_check2)
3515 self.failUnlessReallyEqual(res, file_contents)
3516 d.addCallback(_check3)
3519 def test_PUT_mkdir(self):
3520 d = self.PUT("/uri?t=mkdir", "")
3522 n = self.s.create_node_from_uri(uri.strip())
3523 d2 = self.failUnlessNodeKeysAre(n, [])
3524 d2.addCallback(lambda res:
3525 self.GET("/uri/%s?t=json" % uri))
3527 d.addCallback(_check)
3528 d.addCallback(self.failUnlessIsEmptyJSON)
3531 def test_PUT_mkdir_mdmf(self):
3532 d = self.PUT("/uri?t=mkdir&format=mdmf", "")
3534 u = uri.from_string(res)
3535 # Check that this is an MDMF writecap
3536 self.failUnlessIsInstance(u, uri.MDMFDirectoryURI)
3540 def test_PUT_mkdir_sdmf(self):
3541 d = self.PUT("/uri?t=mkdir&format=sdmf", "")
3543 u = uri.from_string(res)
3544 self.failUnlessIsInstance(u, uri.DirectoryURI)
3548 def test_PUT_mkdir_bad_format(self):
3549 return self.shouldHTTPError("PUT_mkdir_bad_format",
3550 400, "Bad Request", "Unknown format: foo",
3551 self.PUT, "/uri?t=mkdir&format=foo",
3554 def test_POST_check(self):
3555 d = self.POST(self.public_url + "/foo", t="check", name="bar.txt")
3557 # this returns a string form of the results, which are probably
3558 # None since we're using fake filenodes.
3559 # TODO: verify that the check actually happened, by changing
3560 # FakeCHKFileNode to count how many times .check() has been
3563 d.addCallback(_done)
3567 def test_PUT_update_at_offset(self):
3568 file_contents = "test file" * 100000 # about 900 KiB
3569 d = self.PUT("/uri?mutable=true", file_contents)
3571 self.filecap = filecap
3572 new_data = file_contents[:100]
3573 new = "replaced and so on"
3575 new_data += file_contents[len(new_data):]
3576 assert len(new_data) == len(file_contents)
3577 self.new_data = new_data
3578 d.addCallback(_then)
3579 d.addCallback(lambda ignored:
3580 self.PUT("/uri/%s?replace=True&offset=100" % self.filecap,
3581 "replaced and so on"))
3582 def _get_data(filecap):
3583 n = self.s.create_node_from_uri(filecap)
3584 return n.download_best_version()
3585 d.addCallback(_get_data)
3586 d.addCallback(lambda results:
3587 self.failUnlessEqual(results, self.new_data))
3588 # Now try appending things to the file
3589 d.addCallback(lambda ignored:
3590 self.PUT("/uri/%s?offset=%d" % (self.filecap, len(self.new_data)),
3592 d.addCallback(_get_data)
3593 d.addCallback(lambda results:
3594 self.failUnlessEqual(results, self.new_data + ("puppies" * 100)))
3595 # and try replacing the beginning of the file
3596 d.addCallback(lambda ignored:
3597 self.PUT("/uri/%s?offset=0" % self.filecap, "begin"))
3598 d.addCallback(_get_data)
3599 d.addCallback(lambda results:
3600 self.failUnlessEqual(results, "begin"+self.new_data[len("begin"):]+("puppies"*100)))
3603 def test_PUT_update_at_invalid_offset(self):
3604 file_contents = "test file" * 100000 # about 900 KiB
3605 d = self.PUT("/uri?mutable=true", file_contents)
3607 self.filecap = filecap
3608 d.addCallback(_then)
3609 # Negative offsets should cause an error.
3610 d.addCallback(lambda ignored:
3611 self.shouldHTTPError("PUT_update_at_invalid_offset",
3615 "/uri/%s?offset=-1" % self.filecap,
3619 def test_PUT_update_at_offset_immutable(self):
3620 file_contents = "Test file" * 100000
3621 d = self.PUT("/uri", file_contents)
3623 self.filecap = filecap
3624 d.addCallback(_then)
3625 d.addCallback(lambda ignored:
3626 self.shouldHTTPError("PUT_update_at_offset_immutable",
3630 "/uri/%s?offset=50" % self.filecap,
3635 def test_bad_method(self):
3636 url = self.webish_url + self.public_url + "/foo/bar.txt"
3637 d = self.shouldHTTPError("bad_method",
3638 501, "Not Implemented",
3639 "I don't know how to treat a BOGUS request.",
3640 client.getPage, url, method="BOGUS")
3643 def test_short_url(self):
3644 url = self.webish_url + "/uri"
3645 d = self.shouldHTTPError("short_url", 501, "Not Implemented",
3646 "I don't know how to treat a DELETE request.",
3647 client.getPage, url, method="DELETE")
3650 def test_ophandle_bad(self):
3651 url = self.webish_url + "/operations/bogus?t=status"
3652 d = self.shouldHTTPError("ophandle_bad", 404, "404 Not Found",
3653 "unknown/expired handle 'bogus'",
3654 client.getPage, url)
3657 def test_ophandle_cancel(self):
3658 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=128",
3659 followRedirect=True)
3660 d.addCallback(lambda ignored:
3661 self.GET("/operations/128?t=status&output=JSON"))
3663 data = simplejson.loads(res)
3664 self.failUnless("finished" in data, res)
3665 monitor = self.ws.root.child_operations.handles["128"][0]
3666 d = self.POST("/operations/128?t=cancel&output=JSON")
3668 data = simplejson.loads(res)
3669 self.failUnless("finished" in data, res)
3670 # t=cancel causes the handle to be forgotten
3671 self.failUnless(monitor.is_cancelled())
3672 d.addCallback(_check2)
3674 d.addCallback(_check1)
3675 d.addCallback(lambda ignored:
3676 self.shouldHTTPError("ophandle_cancel",
3677 404, "404 Not Found",
3678 "unknown/expired handle '128'",
3680 "/operations/128?t=status&output=JSON"))
3683 def test_ophandle_retainfor(self):
3684 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=129&retain-for=60",
3685 followRedirect=True)
3686 d.addCallback(lambda ignored:
3687 self.GET("/operations/129?t=status&output=JSON&retain-for=0"))
3689 data = simplejson.loads(res)
3690 self.failUnless("finished" in data, res)
3691 d.addCallback(_check1)
3692 # the retain-for=0 will cause the handle to be expired very soon
3693 d.addCallback(lambda ign:
3694 self.clock.advance(2.0))
3695 d.addCallback(lambda ignored:
3696 self.shouldHTTPError("ophandle_retainfor",
3697 404, "404 Not Found",
3698 "unknown/expired handle '129'",
3700 "/operations/129?t=status&output=JSON"))
3703 def test_ophandle_release_after_complete(self):
3704 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=130",
3705 followRedirect=True)
3706 d.addCallback(self.wait_for_operation, "130")
3707 d.addCallback(lambda ignored:
3708 self.GET("/operations/130?t=status&output=JSON&release-after-complete=true"))
3709 # the release-after-complete=true will cause the handle to be expired
3710 d.addCallback(lambda ignored:
3711 self.shouldHTTPError("ophandle_release_after_complete",
3712 404, "404 Not Found",
3713 "unknown/expired handle '130'",
3715 "/operations/130?t=status&output=JSON"))
3718 def test_uncollected_ophandle_expiration(self):
3719 # uncollected ophandles should expire after 4 days
3720 def _make_uncollected_ophandle(ophandle):
3721 d = self.POST(self.public_url +
3722 "/foo/?t=start-manifest&ophandle=%d" % ophandle,
3723 followRedirect=False)
3724 # When we start the operation, the webapi server will want
3725 # to redirect us to the page for the ophandle, so we get
3726 # confirmation that the operation has started. If the
3727 # manifest operation has finished by the time we get there,
3728 # following that redirect (by setting followRedirect=True
3729 # above) has the side effect of collecting the ophandle that
3730 # we've just created, which means that we can't use the
3731 # ophandle to test the uncollected timeout anymore. So,
3732 # instead, catch the 302 here and don't follow it.
3733 d.addBoth(self.should302, "uncollected_ophandle_creation")
3735 # Create an ophandle, don't collect it, then advance the clock by
3736 # 4 days - 1 second and make sure that the ophandle is still there.
3737 d = _make_uncollected_ophandle(131)
3738 d.addCallback(lambda ign:
3739 self.clock.advance((96*60*60) - 1)) # 96 hours = 4 days
3740 d.addCallback(lambda ign:
3741 self.GET("/operations/131?t=status&output=JSON"))
3743 data = simplejson.loads(res)
3744 self.failUnless("finished" in data, res)
3745 d.addCallback(_check1)
3746 # Create an ophandle, don't collect it, then try to collect it
3747 # after 4 days. It should be gone.
3748 d.addCallback(lambda ign:
3749 _make_uncollected_ophandle(132))
3750 d.addCallback(lambda ign:
3751 self.clock.advance(96*60*60))
3752 d.addCallback(lambda ign:
3753 self.shouldHTTPError("uncollected_ophandle_expired_after_100_hours",
3754 404, "404 Not Found",
3755 "unknown/expired handle '132'",
3757 "/operations/132?t=status&output=JSON"))
3760 def test_collected_ophandle_expiration(self):
3761 # collected ophandles should expire after 1 day
3762 def _make_collected_ophandle(ophandle):
3763 d = self.POST(self.public_url +
3764 "/foo/?t=start-manifest&ophandle=%d" % ophandle,
3765 followRedirect=True)
3766 # By following the initial redirect, we collect the ophandle
3767 # we've just created.
3769 # Create a collected ophandle, then collect it after 23 hours
3770 # and 59 seconds to make sure that it is still there.
3771 d = _make_collected_ophandle(133)
3772 d.addCallback(lambda ign:
3773 self.clock.advance((24*60*60) - 1))
3774 d.addCallback(lambda ign:
3775 self.GET("/operations/133?t=status&output=JSON"))
3777 data = simplejson.loads(res)
3778 self.failUnless("finished" in data, res)
3779 d.addCallback(_check1)
3780 # Create another uncollected ophandle, then try to collect it
3781 # after 24 hours to make sure that it is gone.
3782 d.addCallback(lambda ign:
3783 _make_collected_ophandle(134))
3784 d.addCallback(lambda ign:
3785 self.clock.advance(24*60*60))
3786 d.addCallback(lambda ign:
3787 self.shouldHTTPError("collected_ophandle_expired_after_1_day",
3788 404, "404 Not Found",
3789 "unknown/expired handle '134'",
3791 "/operations/134?t=status&output=JSON"))
3794 def test_incident(self):
3795 d = self.POST("/report_incident", details="eek")
3797 self.failIfIn("<html>", res)
3798 self.failUnlessIn("Thank you for your report!", res)
3799 d.addCallback(_done)
3802 def test_static(self):
3803 webdir = os.path.join(self.staticdir, "subdir")
3804 fileutil.make_dirs(webdir)
3805 f = open(os.path.join(webdir, "hello.txt"), "wb")
3809 d = self.GET("/static/subdir/hello.txt")
3811 self.failUnlessReallyEqual(res, "hello")
3812 d.addCallback(_check)
3816 class Util(ShouldFailMixin, testutil.ReallyEqualMixin, unittest.TestCase):
3817 def test_load_file(self):
3818 # This will raise an exception unless a well-formed XML file is found under that name.
3819 common.getxmlfile('directory.xhtml').load()
3821 def test_parse_replace_arg(self):
3822 self.failUnlessReallyEqual(common.parse_replace_arg("true"), True)
3823 self.failUnlessReallyEqual(common.parse_replace_arg("false"), False)
3824 self.failUnlessReallyEqual(common.parse_replace_arg("only-files"),
3826 self.shouldFail(AssertionError, "test_parse_replace_arg", "",
3827 common.parse_replace_arg, "only_fles")
3829 def test_abbreviate_time(self):
3830 self.failUnlessReallyEqual(common.abbreviate_time(None), "")
3831 self.failUnlessReallyEqual(common.abbreviate_time(1.234), "1.23s")
3832 self.failUnlessReallyEqual(common.abbreviate_time(0.123), "123ms")
3833 self.failUnlessReallyEqual(common.abbreviate_time(0.00123), "1.2ms")
3834 self.failUnlessReallyEqual(common.abbreviate_time(0.000123), "123us")
3835 self.failUnlessReallyEqual(common.abbreviate_time(-123000), "-123000000000us")
3837 def test_compute_rate(self):
3838 self.failUnlessReallyEqual(common.compute_rate(None, None), None)
3839 self.failUnlessReallyEqual(common.compute_rate(None, 1), None)
3840 self.failUnlessReallyEqual(common.compute_rate(250000, None), None)
3841 self.failUnlessReallyEqual(common.compute_rate(250000, 0), None)
3842 self.failUnlessReallyEqual(common.compute_rate(250000, 10), 25000.0)
3843 self.failUnlessReallyEqual(common.compute_rate(0, 10), 0.0)
3844 self.shouldFail(AssertionError, "test_compute_rate", "",
3845 common.compute_rate, -100, 10)
3846 self.shouldFail(AssertionError, "test_compute_rate", "",
3847 common.compute_rate, 100, -10)
3850 rate = common.compute_rate(10*1000*1000, 1)
3851 self.failUnlessReallyEqual(common.abbreviate_rate(rate), "10.00MBps")
3853 def test_abbreviate_rate(self):
3854 self.failUnlessReallyEqual(common.abbreviate_rate(None), "")
3855 self.failUnlessReallyEqual(common.abbreviate_rate(1234000), "1.23MBps")
3856 self.failUnlessReallyEqual(common.abbreviate_rate(12340), "12.3kBps")
3857 self.failUnlessReallyEqual(common.abbreviate_rate(123), "123Bps")
3859 def test_abbreviate_size(self):
3860 self.failUnlessReallyEqual(common.abbreviate_size(None), "")
3861 self.failUnlessReallyEqual(common.abbreviate_size(1.23*1000*1000*1000), "1.23GB")
3862 self.failUnlessReallyEqual(common.abbreviate_size(1.23*1000*1000), "1.23MB")
3863 self.failUnlessReallyEqual(common.abbreviate_size(1230), "1.2kB")
3864 self.failUnlessReallyEqual(common.abbreviate_size(123), "123B")
3866 def test_plural(self):
3868 return "%d second%s" % (s, status.plural(s))
3869 self.failUnlessReallyEqual(convert(0), "0 seconds")
3870 self.failUnlessReallyEqual(convert(1), "1 second")
3871 self.failUnlessReallyEqual(convert(2), "2 seconds")
3873 return "has share%s: %s" % (status.plural(s), ",".join(s))
3874 self.failUnlessReallyEqual(convert2([]), "has shares: ")
3875 self.failUnlessReallyEqual(convert2(["1"]), "has share: 1")
3876 self.failUnlessReallyEqual(convert2(["1","2"]), "has shares: 1,2")
3879 class Grid(GridTestMixin, WebErrorMixin, ShouldFailMixin, testutil.ReallyEqualMixin, unittest.TestCase):
3881 def CHECK(self, ign, which, args, clientnum=0):
3882 fileurl = self.fileurls[which]
3883 url = fileurl + "?" + args
3884 return self.GET(url, method="POST", clientnum=clientnum)
3886 def test_filecheck(self):
3887 self.basedir = "web/Grid/filecheck"
3889 c0 = self.g.clients[0]
3892 d = c0.upload(upload.Data(DATA, convergence=""))
3893 def _stash_uri(ur, which):
3894 self.uris[which] = ur.uri
3895 d.addCallback(_stash_uri, "good")
3896 d.addCallback(lambda ign:
3897 c0.upload(upload.Data(DATA+"1", convergence="")))
3898 d.addCallback(_stash_uri, "sick")
3899 d.addCallback(lambda ign:
3900 c0.upload(upload.Data(DATA+"2", convergence="")))
3901 d.addCallback(_stash_uri, "dead")
3902 def _stash_mutable_uri(n, which):
3903 self.uris[which] = n.get_uri()
3904 assert isinstance(self.uris[which], str)
3905 d.addCallback(lambda ign:
3906 c0.create_mutable_file(publish.MutableData(DATA+"3")))
3907 d.addCallback(_stash_mutable_uri, "corrupt")
3908 d.addCallback(lambda ign:
3909 c0.upload(upload.Data("literal", convergence="")))
3910 d.addCallback(_stash_uri, "small")
3911 d.addCallback(lambda ign: c0.create_immutable_dirnode({}))
3912 d.addCallback(_stash_mutable_uri, "smalldir")
3914 def _compute_fileurls(ignored):
3916 for which in self.uris:
3917 self.fileurls[which] = "uri/" + urllib.quote(self.uris[which])
3918 d.addCallback(_compute_fileurls)
3920 def _clobber_shares(ignored):
3921 good_shares = self.find_uri_shares(self.uris["good"])
3922 self.failUnlessReallyEqual(len(good_shares), 10)
3923 sick_shares = self.find_uri_shares(self.uris["sick"])
3924 os.unlink(sick_shares[0][2])
3925 dead_shares = self.find_uri_shares(self.uris["dead"])
3926 for i in range(1, 10):
3927 os.unlink(dead_shares[i][2])
3928 c_shares = self.find_uri_shares(self.uris["corrupt"])
3929 cso = CorruptShareOptions()
3930 cso.stdout = StringIO()
3931 cso.parseOptions([c_shares[0][2]])
3933 d.addCallback(_clobber_shares)
3935 d.addCallback(self.CHECK, "good", "t=check")
3936 def _got_html_good(res):
3937 self.failUnlessIn("Healthy", res)
3938 self.failIfIn("Not Healthy", res)
3939 self.failUnlessIn(FAVICON_MARKUP, res)
3940 d.addCallback(_got_html_good)
3941 d.addCallback(self.CHECK, "good", "t=check&return_to=somewhere")
3942 def _got_html_good_return_to(res):
3943 self.failUnlessIn("Healthy", res)
3944 self.failIfIn("Not Healthy", res)
3945 self.failUnlessIn('<a href="somewhere">Return to file', res)
3946 d.addCallback(_got_html_good_return_to)
3947 d.addCallback(self.CHECK, "good", "t=check&output=json")
3948 def _got_json_good(res):
3949 r = simplejson.loads(res)
3950 self.failUnlessEqual(r["summary"], "Healthy")
3951 self.failUnless(r["results"]["healthy"])
3952 self.failIf(r["results"]["needs-rebalancing"])
3953 self.failUnless(r["results"]["recoverable"])
3954 d.addCallback(_got_json_good)
3956 d.addCallback(self.CHECK, "small", "t=check")
3957 def _got_html_small(res):
3958 self.failUnlessIn("Literal files are always healthy", res)
3959 self.failIfIn("Not Healthy", res)
3960 d.addCallback(_got_html_small)
3961 d.addCallback(self.CHECK, "small", "t=check&return_to=somewhere")
3962 def _got_html_small_return_to(res):
3963 self.failUnlessIn("Literal files are always healthy", res)
3964 self.failIfIn("Not Healthy", res)
3965 self.failUnlessIn('<a href="somewhere">Return to file', res)
3966 d.addCallback(_got_html_small_return_to)
3967 d.addCallback(self.CHECK, "small", "t=check&output=json")
3968 def _got_json_small(res):
3969 r = simplejson.loads(res)
3970 self.failUnlessEqual(r["storage-index"], "")
3971 self.failUnless(r["results"]["healthy"])
3972 d.addCallback(_got_json_small)
3974 d.addCallback(self.CHECK, "smalldir", "t=check")
3975 def _got_html_smalldir(res):
3976 self.failUnlessIn("Literal files are always healthy", res)
3977 self.failIfIn("Not Healthy", res)
3978 d.addCallback(_got_html_smalldir)
3979 d.addCallback(self.CHECK, "smalldir", "t=check&output=json")
3980 def _got_json_smalldir(res):
3981 r = simplejson.loads(res)
3982 self.failUnlessEqual(r["storage-index"], "")
3983 self.failUnless(r["results"]["healthy"])
3984 d.addCallback(_got_json_smalldir)
3986 d.addCallback(self.CHECK, "sick", "t=check")
3987 def _got_html_sick(res):
3988 self.failUnlessIn("Not Healthy", res)
3989 d.addCallback(_got_html_sick)
3990 d.addCallback(self.CHECK, "sick", "t=check&output=json")
3991 def _got_json_sick(res):
3992 r = simplejson.loads(res)
3993 self.failUnlessEqual(r["summary"],
3994 "Not Healthy: 9 shares (enc 3-of-10)")
3995 self.failIf(r["results"]["healthy"])
3996 self.failIf(r["results"]["needs-rebalancing"])
3997 self.failUnless(r["results"]["recoverable"])
3998 d.addCallback(_got_json_sick)
4000 d.addCallback(self.CHECK, "dead", "t=check")
4001 def _got_html_dead(res):
4002 self.failUnlessIn("Not Healthy", res)
4003 d.addCallback(_got_html_dead)
4004 d.addCallback(self.CHECK, "dead", "t=check&output=json")
4005 def _got_json_dead(res):
4006 r = simplejson.loads(res)
4007 self.failUnlessEqual(r["summary"],
4008 "Not Healthy: 1 shares (enc 3-of-10)")
4009 self.failIf(r["results"]["healthy"])
4010 self.failIf(r["results"]["needs-rebalancing"])
4011 self.failIf(r["results"]["recoverable"])
4012 d.addCallback(_got_json_dead)
4014 d.addCallback(self.CHECK, "corrupt", "t=check&verify=true")
4015 def _got_html_corrupt(res):
4016 self.failUnlessIn("Not Healthy! : Unhealthy", res)
4017 d.addCallback(_got_html_corrupt)
4018 d.addCallback(self.CHECK, "corrupt", "t=check&verify=true&output=json")
4019 def _got_json_corrupt(res):
4020 r = simplejson.loads(res)
4021 self.failUnlessIn("Unhealthy: 9 shares (enc 3-of-10)", r["summary"])
4022 self.failIf(r["results"]["healthy"])
4023 self.failUnless(r["results"]["recoverable"])
4024 self.failUnlessReallyEqual(r["results"]["count-shares-good"], 9)
4025 self.failUnlessReallyEqual(r["results"]["count-corrupt-shares"], 1)
4026 d.addCallback(_got_json_corrupt)
4028 d.addErrback(self.explain_web_error)
4031 def test_repair_html(self):
4032 self.basedir = "web/Grid/repair_html"
4034 c0 = self.g.clients[0]
4037 d = c0.upload(upload.Data(DATA, convergence=""))
4038 def _stash_uri(ur, which):
4039 self.uris[which] = ur.uri
4040 d.addCallback(_stash_uri, "good")
4041 d.addCallback(lambda ign:
4042 c0.upload(upload.Data(DATA+"1", convergence="")))
4043 d.addCallback(_stash_uri, "sick")
4044 d.addCallback(lambda ign:
4045 c0.upload(upload.Data(DATA+"2", convergence="")))
4046 d.addCallback(_stash_uri, "dead")
4047 def _stash_mutable_uri(n, which):
4048 self.uris[which] = n.get_uri()
4049 assert isinstance(self.uris[which], str)
4050 d.addCallback(lambda ign:
4051 c0.create_mutable_file(publish.MutableData(DATA+"3")))
4052 d.addCallback(_stash_mutable_uri, "corrupt")
4054 def _compute_fileurls(ignored):
4056 for which in self.uris:
4057 self.fileurls[which] = "uri/" + urllib.quote(self.uris[which])
4058 d.addCallback(_compute_fileurls)
4060 def _clobber_shares(ignored):
4061 good_shares = self.find_uri_shares(self.uris["good"])
4062 self.failUnlessReallyEqual(len(good_shares), 10)
4063 sick_shares = self.find_uri_shares(self.uris["sick"])
4064 os.unlink(sick_shares[0][2])
4065 dead_shares = self.find_uri_shares(self.uris["dead"])
4066 for i in range(1, 10):
4067 os.unlink(dead_shares[i][2])
4068 c_shares = self.find_uri_shares(self.uris["corrupt"])
4069 cso = CorruptShareOptions()
4070 cso.stdout = StringIO()
4071 cso.parseOptions([c_shares[0][2]])
4073 d.addCallback(_clobber_shares)
4075 d.addCallback(self.CHECK, "good", "t=check&repair=true")
4076 def _got_html_good(res):
4077 self.failUnlessIn("Healthy", res)
4078 self.failIfIn("Not Healthy", res)
4079 self.failUnlessIn("No repair necessary", res)
4080 self.failUnlessIn(FAVICON_MARKUP, res)
4081 d.addCallback(_got_html_good)
4083 d.addCallback(self.CHECK, "sick", "t=check&repair=true")
4084 def _got_html_sick(res):
4085 self.failUnlessIn("Healthy : healthy", res)
4086 self.failIfIn("Not Healthy", res)
4087 self.failUnlessIn("Repair successful", res)
4088 d.addCallback(_got_html_sick)
4090 # repair of a dead file will fail, of course, but it isn't yet
4091 # clear how this should be reported. Right now it shows up as
4094 #d.addCallback(self.CHECK, "dead", "t=check&repair=true")
4095 #def _got_html_dead(res):
4097 # self.failUnlessIn("Healthy : healthy", res)
4098 # self.failIfIn("Not Healthy", res)
4099 # self.failUnlessIn("No repair necessary", res)
4100 #d.addCallback(_got_html_dead)
4102 d.addCallback(self.CHECK, "corrupt", "t=check&verify=true&repair=true")
4103 def _got_html_corrupt(res):
4104 self.failUnlessIn("Healthy : Healthy", res)
4105 self.failIfIn("Not Healthy", res)
4106 self.failUnlessIn("Repair successful", res)
4107 d.addCallback(_got_html_corrupt)
4109 d.addErrback(self.explain_web_error)
4112 def test_repair_json(self):
4113 self.basedir = "web/Grid/repair_json"
4115 c0 = self.g.clients[0]
4118 d = c0.upload(upload.Data(DATA+"1", convergence=""))
4119 def _stash_uri(ur, which):
4120 self.uris[which] = ur.uri
4121 d.addCallback(_stash_uri, "sick")
4123 def _compute_fileurls(ignored):
4125 for which in self.uris:
4126 self.fileurls[which] = "uri/" + urllib.quote(self.uris[which])
4127 d.addCallback(_compute_fileurls)
4129 def _clobber_shares(ignored):
4130 sick_shares = self.find_uri_shares(self.uris["sick"])
4131 os.unlink(sick_shares[0][2])
4132 d.addCallback(_clobber_shares)
4134 d.addCallback(self.CHECK, "sick", "t=check&repair=true&output=json")
4135 def _got_json_sick(res):
4136 r = simplejson.loads(res)
4137 self.failUnlessReallyEqual(r["repair-attempted"], True)
4138 self.failUnlessReallyEqual(r["repair-successful"], True)
4139 self.failUnlessEqual(r["pre-repair-results"]["summary"],
4140 "Not Healthy: 9 shares (enc 3-of-10)")
4141 self.failIf(r["pre-repair-results"]["results"]["healthy"])
4142 self.failUnlessEqual(r["post-repair-results"]["summary"], "healthy")
4143 self.failUnless(r["post-repair-results"]["results"]["healthy"])
4144 d.addCallback(_got_json_sick)
4146 d.addErrback(self.explain_web_error)
4149 def test_unknown(self, immutable=False):
4150 self.basedir = "web/Grid/unknown"
4152 self.basedir = "web/Grid/unknown-immutable"
4155 c0 = self.g.clients[0]
4159 # the future cap format may contain slashes, which must be tolerated
4160 expected_info_url = "uri/%s?t=info" % urllib.quote(unknown_rwcap,
4164 name = u"future-imm"
4165 future_node = UnknownNode(None, unknown_immcap, deep_immutable=True)
4166 d = c0.create_immutable_dirnode({name: (future_node, {})})
4169 future_node = UnknownNode(unknown_rwcap, unknown_rocap)
4170 d = c0.create_dirnode()
4172 def _stash_root_and_create_file(n):
4174 self.rooturl = "uri/" + urllib.quote(n.get_uri()) + "/"
4175 self.rourl = "uri/" + urllib.quote(n.get_readonly_uri()) + "/"
4177 return self.rootnode.set_node(name, future_node)
4178 d.addCallback(_stash_root_and_create_file)
4180 # make sure directory listing tolerates unknown nodes
4181 d.addCallback(lambda ign: self.GET(self.rooturl))
4182 def _check_directory_html(res, expected_type_suffix):
4183 pattern = re.compile(r'<td>\?%s</td>[ \t\n\r]*'
4184 '<td>%s</td>' % (expected_type_suffix, str(name)),
4186 self.failUnless(re.search(pattern, res), res)
4187 # find the More Info link for name, should be relative
4188 mo = re.search(r'<a href="([^"]+)">More Info</a>', res)
4189 info_url = mo.group(1)
4190 self.failUnlessReallyEqual(info_url, "%s?t=info" % (str(name),))
4192 d.addCallback(_check_directory_html, "-IMM")
4194 d.addCallback(_check_directory_html, "")
4196 d.addCallback(lambda ign: self.GET(self.rooturl+"?t=json"))
4197 def _check_directory_json(res, expect_rw_uri):
4198 data = simplejson.loads(res)
4199 self.failUnlessEqual(data[0], "dirnode")
4200 f = data[1]["children"][name]
4201 self.failUnlessEqual(f[0], "unknown")
4203 self.failUnlessReallyEqual(to_str(f[1]["rw_uri"]), unknown_rwcap, data)
4205 self.failIfIn("rw_uri", f[1])
4207 self.failUnlessReallyEqual(to_str(f[1]["ro_uri"]), unknown_immcap, data)
4209 self.failUnlessReallyEqual(to_str(f[1]["ro_uri"]), unknown_rocap, data)
4210 self.failUnlessIn("metadata", f[1])
4211 d.addCallback(_check_directory_json, expect_rw_uri=not immutable)
4213 def _check_info(res, expect_rw_uri, expect_ro_uri):
4214 self.failUnlessIn("Object Type: <span>unknown</span>", res)
4216 self.failUnlessIn(unknown_rwcap, res)
4219 self.failUnlessIn(unknown_immcap, res)
4221 self.failUnlessIn(unknown_rocap, res)
4223 self.failIfIn(unknown_rocap, res)
4224 self.failIfIn("Raw data as", res)
4225 self.failIfIn("Directory writecap", res)
4226 self.failIfIn("Checker Operations", res)
4227 self.failIfIn("Mutable File Operations", res)
4228 self.failIfIn("Directory Operations", res)
4230 # FIXME: these should have expect_rw_uri=not immutable; I don't know
4231 # why they fail. Possibly related to ticket #922.
4233 d.addCallback(lambda ign: self.GET(expected_info_url))
4234 d.addCallback(_check_info, expect_rw_uri=False, expect_ro_uri=False)
4235 d.addCallback(lambda ign: self.GET("%s%s?t=info" % (self.rooturl, str(name))))
4236 d.addCallback(_check_info, expect_rw_uri=False, expect_ro_uri=True)
4238 def _check_json(res, expect_rw_uri):
4239 data = simplejson.loads(res)
4240 self.failUnlessEqual(data[0], "unknown")
4242 self.failUnlessReallyEqual(to_str(data[1]["rw_uri"]), unknown_rwcap, data)
4244 self.failIfIn("rw_uri", data[1])
4247 self.failUnlessReallyEqual(to_str(data[1]["ro_uri"]), unknown_immcap, data)
4248 self.failUnlessReallyEqual(data[1]["mutable"], False)
4250 self.failUnlessReallyEqual(to_str(data[1]["ro_uri"]), unknown_rocap, data)
4251 self.failUnlessReallyEqual(data[1]["mutable"], True)
4253 self.failUnlessReallyEqual(to_str(data[1]["ro_uri"]), unknown_rocap, data)
4254 self.failIfIn("mutable", data[1])
4256 # TODO: check metadata contents
4257 self.failUnlessIn("metadata", data[1])
4259 d.addCallback(lambda ign: self.GET("%s%s?t=json" % (self.rooturl, str(name))))
4260 d.addCallback(_check_json, expect_rw_uri=not immutable)
4262 # and make sure that a read-only version of the directory can be
4263 # rendered too. This version will not have unknown_rwcap, whether
4264 # or not future_node was immutable.
4265 d.addCallback(lambda ign: self.GET(self.rourl))
4267 d.addCallback(_check_directory_html, "-IMM")
4269 d.addCallback(_check_directory_html, "-RO")
4271 d.addCallback(lambda ign: self.GET(self.rourl+"?t=json"))
4272 d.addCallback(_check_directory_json, expect_rw_uri=False)
4274 d.addCallback(lambda ign: self.GET("%s%s?t=json" % (self.rourl, str(name))))
4275 d.addCallback(_check_json, expect_rw_uri=False)
4277 # TODO: check that getting t=info from the Info link in the ro directory
4278 # works, and does not include the writecap URI.
4281 def test_immutable_unknown(self):
4282 return self.test_unknown(immutable=True)
4284 def test_mutant_dirnodes_are_omitted(self):
4285 self.basedir = "web/Grid/mutant_dirnodes_are_omitted"
4288 c = self.g.clients[0]
4293 lonely_uri = "URI:LIT:n5xgk" # LIT for "one"
4294 mut_write_uri = "URI:SSK:vfvcbdfbszyrsaxchgevhmmlii:euw4iw7bbnkrrwpzuburbhppuxhc3gwxv26f6imekhz7zyw2ojnq"
4295 mut_read_uri = "URI:SSK-RO:e3mdrzfwhoq42hy5ubcz6rp3o4:ybyibhnp3vvwuq2vaw2ckjmesgkklfs6ghxleztqidihjyofgw7q"
4297 # This method tests mainly dirnode, but we'd have to duplicate code in order to
4298 # test the dirnode and web layers separately.
4300 # 'lonely' is a valid LIT child, 'ro' is a mutant child with an SSK-RO readcap,
4301 # and 'write-in-ro' is a mutant child with an SSK writecap in the ro_uri field.
4302 # When the directory is read, the mutants should be silently disposed of, leaving
4303 # their lonely sibling.
4304 # We don't test the case of a retrieving a cap from the encrypted rw_uri field,
4305 # because immutable directories don't have a writecap and therefore that field
4306 # isn't (and can't be) decrypted.
4307 # TODO: The field still exists in the netstring. Technically we should check what
4308 # happens if something is put there (_unpack_contents should raise ValueError),
4309 # but that can wait.
4311 lonely_child = nm.create_from_cap(lonely_uri)
4312 mutant_ro_child = nm.create_from_cap(mut_read_uri)
4313 mutant_write_in_ro_child = nm.create_from_cap(mut_write_uri)
4315 def _by_hook_or_by_crook():
4317 for n in [mutant_ro_child, mutant_write_in_ro_child]:
4318 n.is_allowed_in_immutable_directory = _by_hook_or_by_crook
4320 mutant_write_in_ro_child.get_write_uri = lambda: None
4321 mutant_write_in_ro_child.get_readonly_uri = lambda: mut_write_uri
4323 kids = {u"lonely": (lonely_child, {}),
4324 u"ro": (mutant_ro_child, {}),
4325 u"write-in-ro": (mutant_write_in_ro_child, {}),
4327 d = c.create_immutable_dirnode(kids)
4330 self.failUnless(isinstance(dn, dirnode.DirectoryNode))
4331 self.failIf(dn.is_mutable())
4332 self.failUnless(dn.is_readonly())
4333 # This checks that if we somehow ended up calling dn._decrypt_rwcapdata, it would fail.
4334 self.failIf(hasattr(dn._node, 'get_writekey'))
4336 self.failUnlessIn("RO-IMM", rep)
4338 self.failUnlessIn("CHK", cap.to_string())
4341 self.rooturl = "uri/" + urllib.quote(dn.get_uri()) + "/"
4342 return download_to_data(dn._node)
4343 d.addCallback(_created)
4345 def _check_data(data):
4346 # Decode the netstring representation of the directory to check that all children
4347 # are present. This is a bit of an abstraction violation, but there's not really
4348 # any other way to do it given that the real DirectoryNode._unpack_contents would
4349 # strip the mutant children out (which is what we're trying to test, later).
4352 while position < len(data):
4353 entries, position = split_netstring(data, 1, position)
4355 (name_utf8, ro_uri, rwcapdata, metadata_s), subpos = split_netstring(entry, 4)
4356 name = name_utf8.decode("utf-8")
4357 self.failUnlessEqual(rwcapdata, "")
4358 self.failUnlessIn(name, kids)
4359 (expected_child, ign) = kids[name]
4360 self.failUnlessReallyEqual(ro_uri, expected_child.get_readonly_uri())
4363 self.failUnlessReallyEqual(numkids, 3)
4364 return self.rootnode.list()
4365 d.addCallback(_check_data)
4367 # Now when we use the real directory listing code, the mutants should be absent.
4368 def _check_kids(children):
4369 self.failUnlessReallyEqual(sorted(children.keys()), [u"lonely"])
4370 lonely_node, lonely_metadata = children[u"lonely"]
4372 self.failUnlessReallyEqual(lonely_node.get_write_uri(), None)
4373 self.failUnlessReallyEqual(lonely_node.get_readonly_uri(), lonely_uri)
4374 d.addCallback(_check_kids)
4376 d.addCallback(lambda ign: nm.create_from_cap(self.cap.to_string()))
4377 d.addCallback(lambda n: n.list())
4378 d.addCallback(_check_kids) # again with dirnode recreated from cap
4380 # Make sure the lonely child can be listed in HTML...
4381 d.addCallback(lambda ign: self.GET(self.rooturl))
4382 def _check_html(res):
4383 self.failIfIn("URI:SSK", res)
4384 get_lonely = "".join([r'<td>FILE</td>',
4386 r'<a href="[^"]+%s[^"]+">lonely</a>' % (urllib.quote(lonely_uri),),
4388 r'\s+<td align="right">%d</td>' % len("one"),
4390 self.failUnless(re.search(get_lonely, res), res)
4392 # find the More Info link for name, should be relative
4393 mo = re.search(r'<a href="([^"]+)">More Info</a>', res)
4394 info_url = mo.group(1)
4395 self.failUnless(info_url.endswith(urllib.quote(lonely_uri) + "?t=info"), info_url)
4396 d.addCallback(_check_html)
4399 d.addCallback(lambda ign: self.GET(self.rooturl+"?t=json"))
4400 def _check_json(res):
4401 data = simplejson.loads(res)
4402 self.failUnlessEqual(data[0], "dirnode")
4403 listed_children = data[1]["children"]
4404 self.failUnlessReallyEqual(sorted(listed_children.keys()), [u"lonely"])
4405 ll_type, ll_data = listed_children[u"lonely"]
4406 self.failUnlessEqual(ll_type, "filenode")
4407 self.failIfIn("rw_uri", ll_data)
4408 self.failUnlessReallyEqual(to_str(ll_data["ro_uri"]), lonely_uri)
4409 d.addCallback(_check_json)
4412 def test_deep_check(self):
4413 self.basedir = "web/Grid/deep_check"
4415 c0 = self.g.clients[0]
4419 d = c0.create_dirnode()
4420 def _stash_root_and_create_file(n):
4422 self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri()) + "/"
4423 return n.add_file(u"good", upload.Data(DATA, convergence=""))
4424 d.addCallback(_stash_root_and_create_file)
4425 def _stash_uri(fn, which):
4426 self.uris[which] = fn.get_uri()
4428 d.addCallback(_stash_uri, "good")
4429 d.addCallback(lambda ign:
4430 self.rootnode.add_file(u"small",
4431 upload.Data("literal",
4433 d.addCallback(_stash_uri, "small")
4434 d.addCallback(lambda ign:
4435 self.rootnode.add_file(u"sick",
4436 upload.Data(DATA+"1",
4438 d.addCallback(_stash_uri, "sick")
4440 # this tests that deep-check and stream-manifest will ignore
4441 # UnknownNode instances. Hopefully this will also cover deep-stats.
4442 future_node = UnknownNode(unknown_rwcap, unknown_rocap)
4443 d.addCallback(lambda ign: self.rootnode.set_node(u"future", future_node))
4445 def _clobber_shares(ignored):
4446 self.delete_shares_numbered(self.uris["sick"], [0,1])
4447 d.addCallback(_clobber_shares)
4455 d.addCallback(self.CHECK, "root", "t=stream-deep-check")
4458 units = [simplejson.loads(line)
4459 for line in res.splitlines()
4462 print "response is:", res
4463 print "undecodeable line was '%s'" % line
4465 self.failUnlessReallyEqual(len(units), 5+1)
4466 # should be parent-first
4468 self.failUnlessEqual(u0["path"], [])
4469 self.failUnlessEqual(u0["type"], "directory")
4470 self.failUnlessReallyEqual(to_str(u0["cap"]), self.rootnode.get_uri())
4471 u0cr = u0["check-results"]
4472 self.failUnlessReallyEqual(u0cr["results"]["count-shares-good"], 10)
4474 ugood = [u for u in units
4475 if u["type"] == "file" and u["path"] == [u"good"]][0]
4476 self.failUnlessReallyEqual(to_str(ugood["cap"]), self.uris["good"])
4477 ugoodcr = ugood["check-results"]
4478 self.failUnlessReallyEqual(ugoodcr["results"]["count-shares-good"], 10)
4481 self.failUnlessEqual(stats["type"], "stats")
4483 self.failUnlessReallyEqual(s["count-immutable-files"], 2)
4484 self.failUnlessReallyEqual(s["count-literal-files"], 1)
4485 self.failUnlessReallyEqual(s["count-directories"], 1)
4486 self.failUnlessReallyEqual(s["count-unknown"], 1)
4487 d.addCallback(_done)
4489 d.addCallback(self.CHECK, "root", "t=stream-manifest")
4490 def _check_manifest(res):
4491 self.failUnless(res.endswith("\n"))
4492 units = [simplejson.loads(t) for t in res[:-1].split("\n")]
4493 self.failUnlessReallyEqual(len(units), 5+1)
4494 self.failUnlessEqual(units[-1]["type"], "stats")
4496 self.failUnlessEqual(first["path"], [])
4497 self.failUnlessEqual(to_str(first["cap"]), self.rootnode.get_uri())
4498 self.failUnlessEqual(first["type"], "directory")
4499 stats = units[-1]["stats"]
4500 self.failUnlessReallyEqual(stats["count-immutable-files"], 2)
4501 self.failUnlessReallyEqual(stats["count-literal-files"], 1)
4502 self.failUnlessReallyEqual(stats["count-mutable-files"], 0)
4503 self.failUnlessReallyEqual(stats["count-immutable-files"], 2)
4504 self.failUnlessReallyEqual(stats["count-unknown"], 1)
4505 d.addCallback(_check_manifest)
4507 # now add root/subdir and root/subdir/grandchild, then make subdir
4508 # unrecoverable, then see what happens
4510 d.addCallback(lambda ign:
4511 self.rootnode.create_subdirectory(u"subdir"))
4512 d.addCallback(_stash_uri, "subdir")
4513 d.addCallback(lambda subdir_node:
4514 subdir_node.add_file(u"grandchild",
4515 upload.Data(DATA+"2",
4517 d.addCallback(_stash_uri, "grandchild")
4519 d.addCallback(lambda ign:
4520 self.delete_shares_numbered(self.uris["subdir"],
4528 # root/subdir [unrecoverable]
4529 # root/subdir/grandchild
4531 # how should a streaming-JSON API indicate fatal error?
4532 # answer: emit ERROR: instead of a JSON string
4534 d.addCallback(self.CHECK, "root", "t=stream-manifest")
4535 def _check_broken_manifest(res):
4536 lines = res.splitlines()
4538 for (i,line) in enumerate(lines)
4539 if line.startswith("ERROR:")]
4541 self.fail("no ERROR: in output: %s" % (res,))
4542 first_error = error_lines[0]
4543 error_line = lines[first_error]
4544 error_msg = lines[first_error+1:]
4545 error_msg_s = "\n".join(error_msg) + "\n"
4546 self.failUnlessIn("ERROR: UnrecoverableFileError(no recoverable versions)",
4548 self.failUnless(len(error_msg) > 2, error_msg_s) # some traceback
4549 units = [simplejson.loads(line) for line in lines[:first_error]]
4550 self.failUnlessReallyEqual(len(units), 6) # includes subdir
4551 last_unit = units[-1]
4552 self.failUnlessEqual(last_unit["path"], ["subdir"])
4553 d.addCallback(_check_broken_manifest)
4555 d.addCallback(self.CHECK, "root", "t=stream-deep-check")
4556 def _check_broken_deepcheck(res):
4557 lines = res.splitlines()
4559 for (i,line) in enumerate(lines)
4560 if line.startswith("ERROR:")]
4562 self.fail("no ERROR: in output: %s" % (res,))
4563 first_error = error_lines[0]
4564 error_line = lines[first_error]
4565 error_msg = lines[first_error+1:]
4566 error_msg_s = "\n".join(error_msg) + "\n"
4567 self.failUnlessIn("ERROR: UnrecoverableFileError(no recoverable versions)",
4569 self.failUnless(len(error_msg) > 2, error_msg_s) # some traceback
4570 units = [simplejson.loads(line) for line in lines[:first_error]]
4571 self.failUnlessReallyEqual(len(units), 6) # includes subdir
4572 last_unit = units[-1]
4573 self.failUnlessEqual(last_unit["path"], ["subdir"])
4574 r = last_unit["check-results"]["results"]
4575 self.failUnlessReallyEqual(r["count-recoverable-versions"], 0)
4576 self.failUnlessReallyEqual(r["count-shares-good"], 1)
4577 self.failUnlessReallyEqual(r["recoverable"], False)
4578 d.addCallback(_check_broken_deepcheck)
4580 d.addErrback(self.explain_web_error)
4583 def test_deep_check_and_repair(self):
4584 self.basedir = "web/Grid/deep_check_and_repair"
4586 c0 = self.g.clients[0]
4590 d = c0.create_dirnode()
4591 def _stash_root_and_create_file(n):
4593 self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri()) + "/"
4594 return n.add_file(u"good", upload.Data(DATA, convergence=""))
4595 d.addCallback(_stash_root_and_create_file)
4596 def _stash_uri(fn, which):
4597 self.uris[which] = fn.get_uri()
4598 d.addCallback(_stash_uri, "good")
4599 d.addCallback(lambda ign:
4600 self.rootnode.add_file(u"small",
4601 upload.Data("literal",
4603 d.addCallback(_stash_uri, "small")
4604 d.addCallback(lambda ign:
4605 self.rootnode.add_file(u"sick",
4606 upload.Data(DATA+"1",
4608 d.addCallback(_stash_uri, "sick")
4609 #d.addCallback(lambda ign:
4610 # self.rootnode.add_file(u"dead",
4611 # upload.Data(DATA+"2",
4613 #d.addCallback(_stash_uri, "dead")
4615 #d.addCallback(lambda ign: c0.create_mutable_file("mutable"))
4616 #d.addCallback(lambda fn: self.rootnode.set_node(u"corrupt", fn))
4617 #d.addCallback(_stash_uri, "corrupt")
4619 def _clobber_shares(ignored):
4620 good_shares = self.find_uri_shares(self.uris["good"])
4621 self.failUnlessReallyEqual(len(good_shares), 10)
4622 sick_shares = self.find_uri_shares(self.uris["sick"])
4623 os.unlink(sick_shares[0][2])
4624 #dead_shares = self.find_uri_shares(self.uris["dead"])
4625 #for i in range(1, 10):
4626 # os.unlink(dead_shares[i][2])
4628 #c_shares = self.find_uri_shares(self.uris["corrupt"])
4629 #cso = CorruptShareOptions()
4630 #cso.stdout = StringIO()
4631 #cso.parseOptions([c_shares[0][2]])
4633 d.addCallback(_clobber_shares)
4636 # root/good CHK, 10 shares
4638 # root/sick CHK, 9 shares
4640 d.addCallback(self.CHECK, "root", "t=stream-deep-check&repair=true")
4642 units = [simplejson.loads(line)
4643 for line in res.splitlines()
4645 self.failUnlessReallyEqual(len(units), 4+1)
4646 # should be parent-first
4648 self.failUnlessEqual(u0["path"], [])
4649 self.failUnlessEqual(u0["type"], "directory")
4650 self.failUnlessReallyEqual(to_str(u0["cap"]), self.rootnode.get_uri())
4651 u0crr = u0["check-and-repair-results"]
4652 self.failUnlessReallyEqual(u0crr["repair-attempted"], False)
4653 self.failUnlessReallyEqual(u0crr["pre-repair-results"]["results"]["count-shares-good"], 10)
4655 ugood = [u for u in units
4656 if u["type"] == "file" and u["path"] == [u"good"]][0]
4657 self.failUnlessEqual(to_str(ugood["cap"]), self.uris["good"])
4658 ugoodcrr = ugood["check-and-repair-results"]
4659 self.failUnlessReallyEqual(ugoodcrr["repair-attempted"], False)
4660 self.failUnlessReallyEqual(ugoodcrr["pre-repair-results"]["results"]["count-shares-good"], 10)
4662 usick = [u for u in units
4663 if u["type"] == "file" and u["path"] == [u"sick"]][0]
4664 self.failUnlessReallyEqual(to_str(usick["cap"]), self.uris["sick"])
4665 usickcrr = usick["check-and-repair-results"]
4666 self.failUnlessReallyEqual(usickcrr["repair-attempted"], True)
4667 self.failUnlessReallyEqual(usickcrr["repair-successful"], True)
4668 self.failUnlessReallyEqual(usickcrr["pre-repair-results"]["results"]["count-shares-good"], 9)
4669 self.failUnlessReallyEqual(usickcrr["post-repair-results"]["results"]["count-shares-good"], 10)
4672 self.failUnlessEqual(stats["type"], "stats")
4674 self.failUnlessReallyEqual(s["count-immutable-files"], 2)
4675 self.failUnlessReallyEqual(s["count-literal-files"], 1)
4676 self.failUnlessReallyEqual(s["count-directories"], 1)
4677 d.addCallback(_done)
4679 d.addErrback(self.explain_web_error)
4682 def _count_leases(self, ignored, which):
4683 u = self.uris[which]
4684 shares = self.find_uri_shares(u)
4686 for shnum, serverid, fn in shares:
4687 sf = get_share_file(fn)
4688 num_leases = len(list(sf.get_leases()))
4689 lease_counts.append( (fn, num_leases) )
4692 def _assert_leasecount(self, lease_counts, expected):
4693 for (fn, num_leases) in lease_counts:
4694 if num_leases != expected:
4695 self.fail("expected %d leases, have %d, on %s" %
4696 (expected, num_leases, fn))
4698 def test_add_lease(self):
4699 self.basedir = "web/Grid/add_lease"
4700 self.set_up_grid(num_clients=2)
4701 c0 = self.g.clients[0]
4704 d = c0.upload(upload.Data(DATA, convergence=""))
4705 def _stash_uri(ur, which):
4706 self.uris[which] = ur.uri
4707 d.addCallback(_stash_uri, "one")
4708 d.addCallback(lambda ign:
4709 c0.upload(upload.Data(DATA+"1", convergence="")))
4710 d.addCallback(_stash_uri, "two")
4711 def _stash_mutable_uri(n, which):
4712 self.uris[which] = n.get_uri()
4713 assert isinstance(self.uris[which], str)
4714 d.addCallback(lambda ign:
4715 c0.create_mutable_file(publish.MutableData(DATA+"2")))
4716 d.addCallback(_stash_mutable_uri, "mutable")
4718 def _compute_fileurls(ignored):
4720 for which in self.uris:
4721 self.fileurls[which] = "uri/" + urllib.quote(self.uris[which])
4722 d.addCallback(_compute_fileurls)
4724 d.addCallback(self._count_leases, "one")
4725 d.addCallback(self._assert_leasecount, 1)
4726 d.addCallback(self._count_leases, "two")
4727 d.addCallback(self._assert_leasecount, 1)
4728 d.addCallback(self._count_leases, "mutable")
4729 d.addCallback(self._assert_leasecount, 1)
4731 d.addCallback(self.CHECK, "one", "t=check") # no add-lease
4732 def _got_html_good(res):
4733 self.failUnlessIn("Healthy", res)
4734 self.failIfIn("Not Healthy", res)
4735 d.addCallback(_got_html_good)
4737 d.addCallback(self._count_leases, "one")
4738 d.addCallback(self._assert_leasecount, 1)
4739 d.addCallback(self._count_leases, "two")
4740 d.addCallback(self._assert_leasecount, 1)
4741 d.addCallback(self._count_leases, "mutable")
4742 d.addCallback(self._assert_leasecount, 1)
4744 # this CHECK uses the original client, which uses the same
4745 # lease-secrets, so it will just renew the original lease
4746 d.addCallback(self.CHECK, "one", "t=check&add-lease=true")
4747 d.addCallback(_got_html_good)
4749 d.addCallback(self._count_leases, "one")
4750 d.addCallback(self._assert_leasecount, 1)
4751 d.addCallback(self._count_leases, "two")
4752 d.addCallback(self._assert_leasecount, 1)
4753 d.addCallback(self._count_leases, "mutable")
4754 d.addCallback(self._assert_leasecount, 1)
4756 # this CHECK uses an alternate client, which adds a second lease
4757 d.addCallback(self.CHECK, "one", "t=check&add-lease=true", clientnum=1)
4758 d.addCallback(_got_html_good)
4760 d.addCallback(self._count_leases, "one")
4761 d.addCallback(self._assert_leasecount, 2)
4762 d.addCallback(self._count_leases, "two")
4763 d.addCallback(self._assert_leasecount, 1)
4764 d.addCallback(self._count_leases, "mutable")
4765 d.addCallback(self._assert_leasecount, 1)
4767 d.addCallback(self.CHECK, "mutable", "t=check&add-lease=true")
4768 d.addCallback(_got_html_good)
4770 d.addCallback(self._count_leases, "one")
4771 d.addCallback(self._assert_leasecount, 2)
4772 d.addCallback(self._count_leases, "two")
4773 d.addCallback(self._assert_leasecount, 1)
4774 d.addCallback(self._count_leases, "mutable")
4775 d.addCallback(self._assert_leasecount, 1)
4777 d.addCallback(self.CHECK, "mutable", "t=check&add-lease=true",
4779 d.addCallback(_got_html_good)
4781 d.addCallback(self._count_leases, "one")
4782 d.addCallback(self._assert_leasecount, 2)
4783 d.addCallback(self._count_leases, "two")
4784 d.addCallback(self._assert_leasecount, 1)
4785 d.addCallback(self._count_leases, "mutable")
4786 d.addCallback(self._assert_leasecount, 2)
4788 d.addErrback(self.explain_web_error)
4791 def test_deep_add_lease(self):
4792 self.basedir = "web/Grid/deep_add_lease"
4793 self.set_up_grid(num_clients=2)
4794 c0 = self.g.clients[0]
4798 d = c0.create_dirnode()
4799 def _stash_root_and_create_file(n):
4801 self.uris["root"] = n.get_uri()
4802 self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri()) + "/"
4803 return n.add_file(u"one", upload.Data(DATA, convergence=""))
4804 d.addCallback(_stash_root_and_create_file)
4805 def _stash_uri(fn, which):
4806 self.uris[which] = fn.get_uri()
4807 d.addCallback(_stash_uri, "one")
4808 d.addCallback(lambda ign:
4809 self.rootnode.add_file(u"small",
4810 upload.Data("literal",
4812 d.addCallback(_stash_uri, "small")
4814 d.addCallback(lambda ign:
4815 c0.create_mutable_file(publish.MutableData("mutable")))
4816 d.addCallback(lambda fn: self.rootnode.set_node(u"mutable", fn))
4817 d.addCallback(_stash_uri, "mutable")
4819 d.addCallback(self.CHECK, "root", "t=stream-deep-check") # no add-lease
4821 units = [simplejson.loads(line)
4822 for line in res.splitlines()
4824 # root, one, small, mutable, stats
4825 self.failUnlessReallyEqual(len(units), 4+1)
4826 d.addCallback(_done)
4828 d.addCallback(self._count_leases, "root")
4829 d.addCallback(self._assert_leasecount, 1)
4830 d.addCallback(self._count_leases, "one")
4831 d.addCallback(self._assert_leasecount, 1)
4832 d.addCallback(self._count_leases, "mutable")
4833 d.addCallback(self._assert_leasecount, 1)
4835 d.addCallback(self.CHECK, "root", "t=stream-deep-check&add-lease=true")
4836 d.addCallback(_done)
4838 d.addCallback(self._count_leases, "root")
4839 d.addCallback(self._assert_leasecount, 1)
4840 d.addCallback(self._count_leases, "one")
4841 d.addCallback(self._assert_leasecount, 1)
4842 d.addCallback(self._count_leases, "mutable")
4843 d.addCallback(self._assert_leasecount, 1)
4845 d.addCallback(self.CHECK, "root", "t=stream-deep-check&add-lease=true",
4847 d.addCallback(_done)
4849 d.addCallback(self._count_leases, "root")
4850 d.addCallback(self._assert_leasecount, 2)
4851 d.addCallback(self._count_leases, "one")
4852 d.addCallback(self._assert_leasecount, 2)
4853 d.addCallback(self._count_leases, "mutable")
4854 d.addCallback(self._assert_leasecount, 2)
4856 d.addErrback(self.explain_web_error)
4860 def test_exceptions(self):
4861 self.basedir = "web/Grid/exceptions"
4862 self.set_up_grid(num_clients=1, num_servers=2)
4863 c0 = self.g.clients[0]
4864 c0.DEFAULT_ENCODING_PARAMETERS['happy'] = 2
4867 d = c0.create_dirnode()
4869 self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri()) + "/"
4870 self.fileurls["imaginary"] = self.fileurls["root"] + "imaginary"
4872 d.addCallback(_stash_root)
4873 d.addCallback(lambda ign: c0.upload(upload.Data(DATA, convergence="")))
4875 self.fileurls["1share"] = "uri/" + urllib.quote(ur.uri)
4876 self.delete_shares_numbered(ur.uri, range(1,10))
4878 u = uri.from_string(ur.uri)
4879 u.key = testutil.flip_bit(u.key, 0)
4880 baduri = u.to_string()
4881 self.fileurls["0shares"] = "uri/" + urllib.quote(baduri)
4882 d.addCallback(_stash_bad)
4883 d.addCallback(lambda ign: c0.create_dirnode())
4884 def _mangle_dirnode_1share(n):
4886 url = self.fileurls["dir-1share"] = "uri/" + urllib.quote(u) + "/"
4887 self.fileurls["dir-1share-json"] = url + "?t=json"
4888 self.delete_shares_numbered(u, range(1,10))
4889 d.addCallback(_mangle_dirnode_1share)
4890 d.addCallback(lambda ign: c0.create_dirnode())
4891 def _mangle_dirnode_0share(n):
4893 url = self.fileurls["dir-0share"] = "uri/" + urllib.quote(u) + "/"
4894 self.fileurls["dir-0share-json"] = url + "?t=json"
4895 self.delete_shares_numbered(u, range(0,10))
4896 d.addCallback(_mangle_dirnode_0share)
4898 # NotEnoughSharesError should be reported sensibly, with a
4899 # text/plain explanation of the problem, and perhaps some
4900 # information on which shares *could* be found.
4902 d.addCallback(lambda ignored:
4903 self.shouldHTTPError("GET unrecoverable",
4904 410, "Gone", "NoSharesError",
4905 self.GET, self.fileurls["0shares"]))
4906 def _check_zero_shares(body):
4907 self.failIfIn("<html>", body)
4908 body = " ".join(body.strip().split())
4909 exp = ("NoSharesError: no shares could be found. "
4910 "Zero shares usually indicates a corrupt URI, or that "
4911 "no servers were connected, but it might also indicate "
4912 "severe corruption. You should perform a filecheck on "
4913 "this object to learn more. The full error message is: "
4914 "no shares (need 3). Last failure: None")
4915 self.failUnlessReallyEqual(exp, body)
4916 d.addCallback(_check_zero_shares)
4919 d.addCallback(lambda ignored:
4920 self.shouldHTTPError("GET 1share",
4921 410, "Gone", "NotEnoughSharesError",
4922 self.GET, self.fileurls["1share"]))
4923 def _check_one_share(body):
4924 self.failIfIn("<html>", body)
4925 body = " ".join(body.strip().split())
4926 msgbase = ("NotEnoughSharesError: This indicates that some "
4927 "servers were unavailable, or that shares have been "
4928 "lost to server departure, hard drive failure, or disk "
4929 "corruption. You should perform a filecheck on "
4930 "this object to learn more. The full error message is:"
4932 msg1 = msgbase + (" ran out of shares:"
4935 " overdue= unused= need 3. Last failure: None")
4936 msg2 = msgbase + (" ran out of shares:"
4938 " pending=Share(sh0-on-xgru5)"
4939 " overdue= unused= need 3. Last failure: None")
4940 self.failUnless(body == msg1 or body == msg2, body)
4941 d.addCallback(_check_one_share)
4943 d.addCallback(lambda ignored:
4944 self.shouldHTTPError("GET imaginary",
4945 404, "Not Found", None,
4946 self.GET, self.fileurls["imaginary"]))
4947 def _missing_child(body):
4948 self.failUnlessIn("No such child: imaginary", body)
4949 d.addCallback(_missing_child)
4951 d.addCallback(lambda ignored: self.GET(self.fileurls["dir-0share"]))
4952 def _check_0shares_dir_html(body):
4953 self.failUnlessIn("<html>", body)
4954 # we should see the regular page, but without the child table or
4956 body = " ".join(body.strip().split())
4957 self.failUnlessIn('href="?t=info">More info on this directory',
4959 exp = ("UnrecoverableFileError: the directory (or mutable file) "
4960 "could not be retrieved, because there were insufficient "
4961 "good shares. This might indicate that no servers were "
4962 "connected, insufficient servers were connected, the URI "
4963 "was corrupt, or that shares have been lost due to server "
4964 "departure, hard drive failure, or disk corruption. You "
4965 "should perform a filecheck on this object to learn more.")
4966 self.failUnlessIn(exp, body)
4967 self.failUnlessIn("No upload forms: directory is unreadable", body)
4968 d.addCallback(_check_0shares_dir_html)
4970 d.addCallback(lambda ignored: self.GET(self.fileurls["dir-1share"]))
4971 def _check_1shares_dir_html(body):
4972 # at some point, we'll split UnrecoverableFileError into 0-shares
4973 # and some-shares like we did for immutable files (since there
4974 # are different sorts of advice to offer in each case). For now,
4975 # they present the same way.
4976 self.failUnlessIn("<html>", body)
4977 body = " ".join(body.strip().split())
4978 self.failUnlessIn('href="?t=info">More info on this directory',
4980 exp = ("UnrecoverableFileError: the directory (or mutable file) "
4981 "could not be retrieved, because there were insufficient "
4982 "good shares. This might indicate that no servers were "
4983 "connected, insufficient servers were connected, the URI "
4984 "was corrupt, or that shares have been lost due to server "
4985 "departure, hard drive failure, or disk corruption. You "
4986 "should perform a filecheck on this object to learn more.")
4987 self.failUnlessIn(exp, body)
4988 self.failUnlessIn("No upload forms: directory is unreadable", body)
4989 d.addCallback(_check_1shares_dir_html)
4991 d.addCallback(lambda ignored:
4992 self.shouldHTTPError("GET dir-0share-json",
4993 410, "Gone", "UnrecoverableFileError",
4995 self.fileurls["dir-0share-json"]))
4996 def _check_unrecoverable_file(body):
4997 self.failIfIn("<html>", body)
4998 body = " ".join(body.strip().split())
4999 exp = ("UnrecoverableFileError: the directory (or mutable file) "
5000 "could not be retrieved, because there were insufficient "
5001 "good shares. This might indicate that no servers were "
5002 "connected, insufficient servers were connected, the URI "
5003 "was corrupt, or that shares have been lost due to server "
5004 "departure, hard drive failure, or disk corruption. You "
5005 "should perform a filecheck on this object to learn more.")
5006 self.failUnlessReallyEqual(exp, body)
5007 d.addCallback(_check_unrecoverable_file)
5009 d.addCallback(lambda ignored:
5010 self.shouldHTTPError("GET dir-1share-json",
5011 410, "Gone", "UnrecoverableFileError",
5013 self.fileurls["dir-1share-json"]))
5014 d.addCallback(_check_unrecoverable_file)
5016 d.addCallback(lambda ignored:
5017 self.shouldHTTPError("GET imaginary",
5018 404, "Not Found", None,
5019 self.GET, self.fileurls["imaginary"]))
5021 # attach a webapi child that throws a random error, to test how it
5023 w = c0.getServiceNamed("webish")
5024 w.root.putChild("ERRORBOOM", ErrorBoom())
5026 # "Accept: */*" : should get a text/html stack trace
5027 # "Accept: text/plain" : should get a text/plain stack trace
5028 # "Accept: text/plain, application/octet-stream" : text/plain (CLI)
5029 # no Accept header: should get a text/html stack trace
5031 d.addCallback(lambda ignored:
5032 self.shouldHTTPError("GET errorboom_html",
5033 500, "Internal Server Error", None,
5034 self.GET, "ERRORBOOM",
5035 headers={"accept": ["*/*"]}))
5036 def _internal_error_html1(body):
5037 self.failUnlessIn("<html>", "expected HTML, not '%s'" % body)
5038 d.addCallback(_internal_error_html1)
5040 d.addCallback(lambda ignored:
5041 self.shouldHTTPError("GET errorboom_text",
5042 500, "Internal Server Error", None,
5043 self.GET, "ERRORBOOM",
5044 headers={"accept": ["text/plain"]}))
5045 def _internal_error_text2(body):
5046 self.failIfIn("<html>", body)
5047 self.failUnless(body.startswith("Traceback "), body)
5048 d.addCallback(_internal_error_text2)
5050 CLI_accepts = "text/plain, application/octet-stream"
5051 d.addCallback(lambda ignored:
5052 self.shouldHTTPError("GET errorboom_text",
5053 500, "Internal Server Error", None,
5054 self.GET, "ERRORBOOM",
5055 headers={"accept": [CLI_accepts]}))
5056 def _internal_error_text3(body):
5057 self.failIfIn("<html>", body)
5058 self.failUnless(body.startswith("Traceback "), body)
5059 d.addCallback(_internal_error_text3)
5061 d.addCallback(lambda ignored:
5062 self.shouldHTTPError("GET errorboom_text",
5063 500, "Internal Server Error", None,
5064 self.GET, "ERRORBOOM"))
5065 def _internal_error_html4(body):
5066 self.failUnlessIn("<html>", body)
5067 d.addCallback(_internal_error_html4)
5069 def _flush_errors(res):
5070 # Trial: please ignore the CompletelyUnhandledError in the logs
5071 self.flushLoggedErrors(CompletelyUnhandledError)
5073 d.addBoth(_flush_errors)
5077 def test_blacklist(self):
5078 # download from a blacklisted URI, get an error
5079 self.basedir = "web/Grid/blacklist"
5081 c0 = self.g.clients[0]
5082 c0_basedir = c0.basedir
5083 fn = os.path.join(c0_basedir, "access.blacklist")
5085 DATA = "off-limits " * 50
5087 d = c0.upload(upload.Data(DATA, convergence=""))
5088 def _stash_uri_and_create_dir(ur):
5090 self.url = "uri/"+self.uri
5091 u = uri.from_string_filenode(self.uri)
5092 self.si = u.get_storage_index()
5093 childnode = c0.create_node_from_uri(self.uri, None)
5094 return c0.create_dirnode({u"blacklisted.txt": (childnode,{}) })
5095 d.addCallback(_stash_uri_and_create_dir)
5096 def _stash_dir(node):
5097 self.dir_node = node
5098 self.dir_uri = node.get_uri()
5099 self.dir_url = "uri/"+self.dir_uri
5100 d.addCallback(_stash_dir)
5101 d.addCallback(lambda ign: self.GET(self.dir_url, followRedirect=True))
5102 def _check_dir_html(body):
5103 self.failUnlessIn("<html>", body)
5104 self.failUnlessIn("blacklisted.txt</a>", body)
5105 d.addCallback(_check_dir_html)
5106 d.addCallback(lambda ign: self.GET(self.url))
5107 d.addCallback(lambda body: self.failUnlessEqual(DATA, body))
5109 def _blacklist(ign):
5111 f.write(" # this is a comment\n")
5113 f.write("\n") # also exercise blank lines
5114 f.write("%s %s\n" % (base32.b2a(self.si), "off-limits to you"))
5116 # clients should be checking the blacklist each time, so we don't
5117 # need to restart the client
5118 d.addCallback(_blacklist)
5119 d.addCallback(lambda ign: self.shouldHTTPError("get_from_blacklisted_uri",
5121 "Access Prohibited: off-limits",
5122 self.GET, self.url))
5124 # We should still be able to list the parent directory, in HTML...
5125 d.addCallback(lambda ign: self.GET(self.dir_url, followRedirect=True))
5126 def _check_dir_html2(body):
5127 self.failUnlessIn("<html>", body)
5128 self.failUnlessIn("blacklisted.txt</strike>", body)
5129 d.addCallback(_check_dir_html2)
5131 # ... and in JSON (used by CLI).
5132 d.addCallback(lambda ign: self.GET(self.dir_url+"?t=json", followRedirect=True))
5133 def _check_dir_json(res):
5134 data = simplejson.loads(res)
5135 self.failUnless(isinstance(data, list), data)
5136 self.failUnlessEqual(data[0], "dirnode")
5137 self.failUnless(isinstance(data[1], dict), data)
5138 self.failUnlessIn("children", data[1])
5139 self.failUnlessIn("blacklisted.txt", data[1]["children"])
5140 childdata = data[1]["children"]["blacklisted.txt"]
5141 self.failUnless(isinstance(childdata, list), data)
5142 self.failUnlessEqual(childdata[0], "filenode")
5143 self.failUnless(isinstance(childdata[1], dict), data)
5144 d.addCallback(_check_dir_json)
5146 def _unblacklist(ign):
5147 open(fn, "w").close()
5148 # the Blacklist object watches mtime to tell when the file has
5149 # changed, but on windows this test will run faster than the
5150 # filesystem's mtime resolution. So we edit Blacklist.last_mtime
5151 # to force a reload.
5152 self.g.clients[0].blacklist.last_mtime -= 2.0
5153 d.addCallback(_unblacklist)
5155 # now a read should work
5156 d.addCallback(lambda ign: self.GET(self.url))
5157 d.addCallback(lambda body: self.failUnlessEqual(DATA, body))
5159 # read again to exercise the blacklist-is-unchanged logic
5160 d.addCallback(lambda ign: self.GET(self.url))
5161 d.addCallback(lambda body: self.failUnlessEqual(DATA, body))
5163 # now add a blacklisted directory, and make sure files under it are
5166 childnode = c0.create_node_from_uri(self.uri, None)
5167 return c0.create_dirnode({u"child": (childnode,{}) })
5168 d.addCallback(_add_dir)
5169 def _get_dircap(dn):
5170 self.dir_si_b32 = base32.b2a(dn.get_storage_index())
5171 self.dir_url_base = "uri/"+dn.get_write_uri()
5172 self.dir_url_json1 = "uri/"+dn.get_write_uri()+"?t=json"
5173 self.dir_url_json2 = "uri/"+dn.get_write_uri()+"/?t=json"
5174 self.dir_url_json_ro = "uri/"+dn.get_readonly_uri()+"/?t=json"
5175 self.child_url = "uri/"+dn.get_readonly_uri()+"/child"
5176 d.addCallback(_get_dircap)
5177 d.addCallback(lambda ign: self.GET(self.dir_url_base, followRedirect=True))
5178 d.addCallback(lambda body: self.failUnlessIn("<html>", body))
5179 d.addCallback(lambda ign: self.GET(self.dir_url_json1))
5180 d.addCallback(lambda res: simplejson.loads(res)) # just check it decodes
5181 d.addCallback(lambda ign: self.GET(self.dir_url_json2))
5182 d.addCallback(lambda res: simplejson.loads(res)) # just check it decodes
5183 d.addCallback(lambda ign: self.GET(self.dir_url_json_ro))
5184 d.addCallback(lambda res: simplejson.loads(res)) # just check it decodes
5185 d.addCallback(lambda ign: self.GET(self.child_url))
5186 d.addCallback(lambda body: self.failUnlessEqual(DATA, body))
5188 def _block_dir(ign):
5190 f.write("%s %s\n" % (self.dir_si_b32, "dir-off-limits to you"))
5192 self.g.clients[0].blacklist.last_mtime -= 2.0
5193 d.addCallback(_block_dir)
5194 d.addCallback(lambda ign: self.shouldHTTPError("get_from_blacklisted_dir base",
5196 "Access Prohibited: dir-off-limits",
5197 self.GET, self.dir_url_base))
5198 d.addCallback(lambda ign: self.shouldHTTPError("get_from_blacklisted_dir json1",
5200 "Access Prohibited: dir-off-limits",
5201 self.GET, self.dir_url_json1))
5202 d.addCallback(lambda ign: self.shouldHTTPError("get_from_blacklisted_dir json2",
5204 "Access Prohibited: dir-off-limits",
5205 self.GET, self.dir_url_json2))
5206 d.addCallback(lambda ign: self.shouldHTTPError("get_from_blacklisted_dir json_ro",
5208 "Access Prohibited: dir-off-limits",
5209 self.GET, self.dir_url_json_ro))
5210 d.addCallback(lambda ign: self.shouldHTTPError("get_from_blacklisted_dir child",
5212 "Access Prohibited: dir-off-limits",
5213 self.GET, self.child_url))
5217 class CompletelyUnhandledError(Exception):
5219 class ErrorBoom(rend.Page):
5220 def beforeRender(self, ctx):
5221 raise CompletelyUnhandledError("whoops")