1 import os.path, re, urllib, time
3 from StringIO import StringIO
5 from twisted.application import service
6 from twisted.trial import unittest
7 from twisted.internet import defer, reactor
8 from twisted.internet.task import Clock
9 from twisted.web import client, error, http
10 from twisted.python import failure, log
12 from foolscap.api import fireEventually, flushEventualQueue
14 from nevow import rend
16 from allmydata import interfaces, uri, webish, dirnode
17 from allmydata.storage.shares import get_share_file
18 from allmydata.storage_client import StorageFarmBroker
19 from allmydata.immutable import upload
20 from allmydata.immutable.downloader.status import DownloadStatus
21 from allmydata.dirnode import DirectoryNode
22 from allmydata.nodemaker import NodeMaker
23 from allmydata.unknown import UnknownNode
24 from allmydata.web import status, common
25 from allmydata.scripts.debug import CorruptShareOptions, corrupt_share
26 from allmydata.util import fileutil, base32, hashutil
27 from allmydata.util.consumer import download_to_data
28 from allmydata.util.netstring import split_netstring
29 from allmydata.util.encodingutil import to_str
30 from allmydata.test.common import FakeCHKFileNode, FakeMutableFileNode, \
31 create_chk_filenode, WebErrorMixin, ShouldFailMixin, \
32 make_mutable_file_uri, create_mutable_filenode
33 from allmydata.interfaces import IMutableFileNode, SDMF_VERSION, MDMF_VERSION
34 from allmydata.mutable import servermap, publish, retrieve
35 import allmydata.test.common_util as testutil
36 from allmydata.test.no_network import GridTestMixin
37 from allmydata.test.common_web import HTTPClientGETFactory, \
39 from allmydata.client import Client, SecretHolder
40 from allmydata.introducer import IntroducerNode
42 # create a fake uploader/downloader, and a couple of fake dirnodes, then
43 # create a webserver that works against them
45 timeout = 480 # Most of these take longer than 240 seconds on Francois's arm box.
47 unknown_rwcap = u"lafs://from_the_future_rw_\u263A".encode('utf-8')
48 unknown_rocap = u"ro.lafs://readonly_from_the_future_ro_\u263A".encode('utf-8')
49 unknown_immcap = u"imm.lafs://immutable_from_the_future_imm_\u263A".encode('utf-8')
51 FAVICON_MARKUP = '<link href="/icon.png" rel="shortcut icon" />'
54 class FakeStatsProvider:
56 stats = {'stats': {}, 'counters': {}}
59 class FakeNodeMaker(NodeMaker):
64 'max_segment_size':128*1024 # 1024=KiB
66 def _create_lit(self, cap):
67 return FakeCHKFileNode(cap)
68 def _create_immutable(self, cap):
69 return FakeCHKFileNode(cap)
70 def _create_mutable(self, cap):
71 return FakeMutableFileNode(None,
73 self.encoding_params, None).init_from_cap(cap)
74 def create_mutable_file(self, contents="", keysize=None,
75 version=SDMF_VERSION):
76 n = FakeMutableFileNode(None, None, self.encoding_params, None)
77 return n.create(contents, version=version)
79 class FakeUploader(service.Service):
81 def upload(self, uploadable):
82 d = uploadable.get_size()
83 d.addCallback(lambda size: uploadable.read(size))
86 n = create_chk_filenode(data)
87 results = upload.UploadResults()
88 results.uri = n.get_uri()
90 d.addCallback(_got_data)
92 def get_helper_info(self):
96 def __init__(self, binaryserverid):
97 self.binaryserverid = binaryserverid
98 def get_name(self): return "short"
99 def get_longname(self): return "long"
100 def get_serverid(self): return self.binaryserverid
103 ds = DownloadStatus("storage_index", 1234)
106 serverA = FakeIServer(hashutil.tagged_hash("foo", "serverid_a")[:20])
107 serverB = FakeIServer(hashutil.tagged_hash("foo", "serverid_b")[:20])
108 storage_index = hashutil.storage_index_hash("SI")
109 e0 = ds.add_segment_request(0, now)
111 e0.deliver(now+1, 0, 100, 0.5) # when, start,len, decodetime
112 e1 = ds.add_segment_request(1, now+2)
114 # two outstanding requests
115 e2 = ds.add_segment_request(2, now+4)
116 e3 = ds.add_segment_request(3, now+5)
117 del e2,e3 # hush pyflakes
119 # simulate a segment which gets delivered faster than a system clock tick (ticket #1166)
120 e = ds.add_segment_request(4, now)
122 e.deliver(now, 0, 140, 0.5)
124 e = ds.add_dyhb_request(serverA, now)
125 e.finished([1,2], now+1)
126 e = ds.add_dyhb_request(serverB, now+2) # left unfinished
128 e = ds.add_read_event(0, 120, now)
129 e.update(60, 0.5, 0.1) # bytes, decrypttime, pausetime
131 e = ds.add_read_event(120, 30, now+2) # left unfinished
133 e = ds.add_block_request(serverA, 1, 100, 20, now)
134 e.finished(20, now+1)
135 e = ds.add_block_request(serverB, 1, 120, 30, now+1) # left unfinished
137 # make sure that add_read_event() can come first too
138 ds1 = DownloadStatus(storage_index, 1234)
139 e = ds1.add_read_event(0, 120, now)
140 e.update(60, 0.5, 0.1) # bytes, decrypttime, pausetime
146 _all_upload_status = [upload.UploadStatus()]
147 _all_download_status = [build_one_ds()]
148 _all_mapupdate_statuses = [servermap.UpdateStatus()]
149 _all_publish_statuses = [publish.PublishStatus()]
150 _all_retrieve_statuses = [retrieve.RetrieveStatus()]
152 def list_all_upload_statuses(self):
153 return self._all_upload_status
154 def list_all_download_statuses(self):
155 return self._all_download_status
156 def list_all_mapupdate_statuses(self):
157 return self._all_mapupdate_statuses
158 def list_all_publish_statuses(self):
159 return self._all_publish_statuses
160 def list_all_retrieve_statuses(self):
161 return self._all_retrieve_statuses
162 def list_all_helper_statuses(self):
165 class FakeClient(Client):
167 # don't upcall to Client.__init__, since we only want to initialize a
169 service.MultiService.__init__(self)
170 self.nodeid = "fake_nodeid"
171 self.nickname = "fake_nickname"
172 self.introducer_furl = "None"
173 self.stats_provider = FakeStatsProvider()
174 self._secret_holder = SecretHolder("lease secret", "convergence secret")
176 self.convergence = "some random string"
177 self.storage_broker = StorageFarmBroker(None, permute_peers=True)
178 self.introducer_client = None
179 self.history = FakeHistory()
180 self.uploader = FakeUploader()
181 self.uploader.setServiceParent(self)
182 self.blacklist = None
183 self.nodemaker = FakeNodeMaker(None, self._secret_holder, None,
186 self.mutable_file_default = SDMF_VERSION
188 def startService(self):
189 return service.MultiService.startService(self)
190 def stopService(self):
191 return service.MultiService.stopService(self)
193 MUTABLE_SIZELIMIT = FakeMutableFileNode.MUTABLE_SIZELIMIT
195 class WebMixin(object):
197 self.s = FakeClient()
198 self.s.startService()
199 self.staticdir = self.mktemp()
201 self.ws = webish.WebishServer(self.s, "0", staticdir=self.staticdir,
203 self.ws.setServiceParent(self.s)
204 self.webish_port = self.ws.getPortnum()
205 self.webish_url = self.ws.getURL()
206 assert self.webish_url.endswith("/")
207 self.webish_url = self.webish_url[:-1] # these tests add their own /
209 l = [ self.s.create_dirnode() for x in range(6) ]
210 d = defer.DeferredList(l)
212 self.public_root = res[0][1]
213 assert interfaces.IDirectoryNode.providedBy(self.public_root), res
214 self.public_url = "/uri/" + self.public_root.get_uri()
215 self.private_root = res[1][1]
219 self._foo_uri = foo.get_uri()
220 self._foo_readonly_uri = foo.get_readonly_uri()
221 self._foo_verifycap = foo.get_verify_cap().to_string()
222 # NOTE: we ignore the deferred on all set_uri() calls, because we
223 # know the fake nodes do these synchronously
224 self.public_root.set_uri(u"foo", foo.get_uri(),
225 foo.get_readonly_uri())
227 self.BAR_CONTENTS, n, self._bar_txt_uri = self.makefile(0)
228 foo.set_uri(u"bar.txt", self._bar_txt_uri, self._bar_txt_uri)
229 self._bar_txt_verifycap = n.get_verify_cap().to_string()
232 # XXX: Do we ever use this?
233 self.BAZ_CONTENTS, n, self._baz_txt_uri, self._baz_txt_readonly_uri = self.makefile_mutable(0)
235 foo.set_uri(u"baz.txt", self._baz_txt_uri, self._baz_txt_readonly_uri)
238 self.QUUX_CONTENTS, n, self._quux_txt_uri, self._quux_txt_readonly_uri = self.makefile_mutable(0, mdmf=True)
239 assert self._quux_txt_uri.startswith("URI:MDMF")
240 foo.set_uri(u"quux.txt", self._quux_txt_uri, self._quux_txt_readonly_uri)
242 foo.set_uri(u"empty", res[3][1].get_uri(),
243 res[3][1].get_readonly_uri())
244 sub_uri = res[4][1].get_uri()
245 self._sub_uri = sub_uri
246 foo.set_uri(u"sub", sub_uri, sub_uri)
247 sub = self.s.create_node_from_uri(sub_uri)
249 _ign, n, blocking_uri = self.makefile(1)
250 foo.set_uri(u"blockingfile", blocking_uri, blocking_uri)
252 unicode_filename = u"n\u00fc.txt" # n u-umlaut . t x t
253 # ok, unicode calls it LATIN SMALL LETTER U WITH DIAERESIS but I
254 # still think of it as an umlaut
255 foo.set_uri(unicode_filename, self._bar_txt_uri, self._bar_txt_uri)
257 _ign, n, baz_file = self.makefile(2)
258 self._baz_file_uri = baz_file
259 sub.set_uri(u"baz.txt", baz_file, baz_file)
261 _ign, n, self._bad_file_uri = self.makefile(3)
262 # this uri should not be downloadable
263 del FakeCHKFileNode.all_contents[self._bad_file_uri]
266 self.public_root.set_uri(u"reedownlee", rodir.get_readonly_uri(),
267 rodir.get_readonly_uri())
268 rodir.set_uri(u"nor", baz_file, baz_file)
274 # public/foo/quux.txt
275 # public/foo/blockingfile
278 # public/foo/sub/baz.txt
280 # public/reedownlee/nor
281 self.NEWFILE_CONTENTS = "newfile contents\n"
283 return foo.get_metadata_for(u"bar.txt")
285 def _got_metadata(metadata):
286 self._bar_txt_metadata = metadata
287 d.addCallback(_got_metadata)
290 def makefile(self, number):
291 contents = "contents of file %s\n" % number
292 n = create_chk_filenode(contents)
293 return contents, n, n.get_uri()
295 def makefile_mutable(self, number, mdmf=False):
296 contents = "contents of mutable file %s\n" % number
297 n = create_mutable_filenode(contents, mdmf)
298 return contents, n, n.get_uri(), n.get_readonly_uri()
301 return self.s.stopService()
303 def failUnlessIsBarDotTxt(self, res):
304 self.failUnlessReallyEqual(res, self.BAR_CONTENTS, res)
306 def failUnlessIsQuuxDotTxt(self, res):
307 self.failUnlessReallyEqual(res, self.QUUX_CONTENTS, res)
309 def failUnlessIsBazDotTxt(self, res):
310 self.failUnlessReallyEqual(res, self.BAZ_CONTENTS, res)
312 def failUnlessIsBarJSON(self, res):
313 data = simplejson.loads(res)
314 self.failUnless(isinstance(data, list))
315 self.failUnlessEqual(data[0], "filenode")
316 self.failUnless(isinstance(data[1], dict))
317 self.failIf(data[1]["mutable"])
318 self.failIfIn("rw_uri", data[1]) # immutable
319 self.failUnlessReallyEqual(to_str(data[1]["ro_uri"]), self._bar_txt_uri)
320 self.failUnlessReallyEqual(to_str(data[1]["verify_uri"]), self._bar_txt_verifycap)
321 self.failUnlessReallyEqual(data[1]["size"], len(self.BAR_CONTENTS))
323 def failUnlessIsQuuxJSON(self, res, readonly=False):
324 data = simplejson.loads(res)
325 self.failUnless(isinstance(data, list))
326 self.failUnlessEqual(data[0], "filenode")
327 self.failUnless(isinstance(data[1], dict))
329 return self.failUnlessIsQuuxDotTxtMetadata(metadata, readonly)
331 def failUnlessIsQuuxDotTxtMetadata(self, metadata, readonly):
332 self.failUnless(metadata['mutable'])
334 self.failIfIn("rw_uri", metadata)
336 self.failUnlessIn("rw_uri", metadata)
337 self.failUnlessEqual(metadata['rw_uri'], self._quux_txt_uri)
338 self.failUnlessIn("ro_uri", metadata)
339 self.failUnlessEqual(metadata['ro_uri'], self._quux_txt_readonly_uri)
340 self.failUnlessReallyEqual(metadata['size'], len(self.QUUX_CONTENTS))
342 def failUnlessIsFooJSON(self, res):
343 data = simplejson.loads(res)
344 self.failUnless(isinstance(data, list))
345 self.failUnlessEqual(data[0], "dirnode", res)
346 self.failUnless(isinstance(data[1], dict))
347 self.failUnless(data[1]["mutable"])
348 self.failUnlessIn("rw_uri", data[1]) # mutable
349 self.failUnlessReallyEqual(to_str(data[1]["rw_uri"]), self._foo_uri)
350 self.failUnlessReallyEqual(to_str(data[1]["ro_uri"]), self._foo_readonly_uri)
351 self.failUnlessReallyEqual(to_str(data[1]["verify_uri"]), self._foo_verifycap)
353 kidnames = sorted([unicode(n) for n in data[1]["children"]])
354 self.failUnlessEqual(kidnames,
355 [u"bar.txt", u"baz.txt", u"blockingfile",
356 u"empty", u"n\u00fc.txt", u"quux.txt", u"sub"])
357 kids = dict( [(unicode(name),value)
359 in data[1]["children"].iteritems()] )
360 self.failUnlessEqual(kids[u"sub"][0], "dirnode")
361 self.failUnlessIn("metadata", kids[u"sub"][1])
362 self.failUnlessIn("tahoe", kids[u"sub"][1]["metadata"])
363 tahoe_md = kids[u"sub"][1]["metadata"]["tahoe"]
364 self.failUnlessIn("linkcrtime", tahoe_md)
365 self.failUnlessIn("linkmotime", tahoe_md)
366 self.failUnlessEqual(kids[u"bar.txt"][0], "filenode")
367 self.failUnlessReallyEqual(kids[u"bar.txt"][1]["size"], len(self.BAR_CONTENTS))
368 self.failUnlessReallyEqual(to_str(kids[u"bar.txt"][1]["ro_uri"]), self._bar_txt_uri)
369 self.failUnlessReallyEqual(to_str(kids[u"bar.txt"][1]["verify_uri"]),
370 self._bar_txt_verifycap)
371 self.failUnlessIn("metadata", kids[u"bar.txt"][1])
372 self.failUnlessIn("tahoe", kids[u"bar.txt"][1]["metadata"])
373 self.failUnlessReallyEqual(kids[u"bar.txt"][1]["metadata"]["tahoe"]["linkcrtime"],
374 self._bar_txt_metadata["tahoe"]["linkcrtime"])
375 self.failUnlessReallyEqual(to_str(kids[u"n\u00fc.txt"][1]["ro_uri"]),
377 self.failUnlessIn("quux.txt", kids)
378 self.failUnlessReallyEqual(to_str(kids[u"quux.txt"][1]["rw_uri"]),
380 self.failUnlessReallyEqual(to_str(kids[u"quux.txt"][1]["ro_uri"]),
381 self._quux_txt_readonly_uri)
383 def GET(self, urlpath, followRedirect=False, return_response=False,
385 # if return_response=True, this fires with (data, statuscode,
386 # respheaders) instead of just data.
387 assert not isinstance(urlpath, unicode)
388 url = self.webish_url + urlpath
389 factory = HTTPClientGETFactory(url, method="GET",
390 followRedirect=followRedirect, **kwargs)
391 reactor.connectTCP("localhost", self.webish_port, factory)
394 return (data, factory.status, factory.response_headers)
396 d.addCallback(_got_data)
397 return factory.deferred
399 def HEAD(self, urlpath, return_response=False, **kwargs):
400 # this requires some surgery, because twisted.web.client doesn't want
401 # to give us back the response headers.
402 factory = HTTPClientHEADFactory(urlpath, method="HEAD", **kwargs)
403 reactor.connectTCP("localhost", self.webish_port, factory)
406 return (data, factory.status, factory.response_headers)
408 d.addCallback(_got_data)
409 return factory.deferred
411 def PUT(self, urlpath, data, **kwargs):
412 url = self.webish_url + urlpath
413 return client.getPage(url, method="PUT", postdata=data, **kwargs)
415 def DELETE(self, urlpath):
416 url = self.webish_url + urlpath
417 return client.getPage(url, method="DELETE")
419 def POST(self, urlpath, followRedirect=False, **fields):
420 sepbase = "boogabooga"
424 form.append('Content-Disposition: form-data; name="_charset"')
428 for name, value in fields.iteritems():
429 if isinstance(value, tuple):
430 filename, value = value
431 form.append('Content-Disposition: form-data; name="%s"; '
432 'filename="%s"' % (name, filename.encode("utf-8")))
434 form.append('Content-Disposition: form-data; name="%s"' % name)
436 if isinstance(value, unicode):
437 value = value.encode("utf-8")
440 assert isinstance(value, str)
447 body = "\r\n".join(form) + "\r\n"
448 headers["content-type"] = "multipart/form-data; boundary=%s" % sepbase
449 return self.POST2(urlpath, body, headers, followRedirect)
451 def POST2(self, urlpath, body="", headers={}, followRedirect=False):
452 url = self.webish_url + urlpath
453 return client.getPage(url, method="POST", postdata=body,
454 headers=headers, followRedirect=followRedirect)
456 def shouldFail(self, res, expected_failure, which,
457 substring=None, response_substring=None):
458 if isinstance(res, failure.Failure):
459 res.trap(expected_failure)
461 self.failUnlessIn(substring, str(res), which)
462 if response_substring:
463 self.failUnlessIn(response_substring, res.value.response, which)
465 self.fail("%s was supposed to raise %s, not get '%s'" %
466 (which, expected_failure, res))
468 def shouldFail2(self, expected_failure, which, substring,
470 callable, *args, **kwargs):
471 assert substring is None or isinstance(substring, str)
472 assert response_substring is None or isinstance(response_substring, str)
473 d = defer.maybeDeferred(callable, *args, **kwargs)
475 if isinstance(res, failure.Failure):
476 res.trap(expected_failure)
478 self.failUnlessIn(substring, str(res), which)
479 if response_substring:
480 self.failUnlessIn(response_substring, res.value.response, which)
482 self.fail("%s was supposed to raise %s, not get '%s'" %
483 (which, expected_failure, res))
487 def should404(self, res, which):
488 if isinstance(res, failure.Failure):
489 res.trap(error.Error)
490 self.failUnlessReallyEqual(res.value.status, "404")
492 self.fail("%s was supposed to Error(404), not get '%s'" %
495 def should302(self, res, which):
496 if isinstance(res, failure.Failure):
497 res.trap(error.Error)
498 self.failUnlessReallyEqual(res.value.status, "302")
500 self.fail("%s was supposed to Error(302), not get '%s'" %
504 class Web(WebMixin, WebErrorMixin, testutil.StallMixin, testutil.ReallyEqualMixin, unittest.TestCase):
505 def test_create(self):
508 def test_welcome(self):
511 self.failUnlessIn('Welcome to Tahoe-LAFS', res)
512 self.failUnlessIn(FAVICON_MARKUP, res)
513 self.failUnlessIn('href="https://tahoe-lafs.org/"', res)
515 self.s.basedir = 'web/test_welcome'
516 fileutil.make_dirs("web/test_welcome")
517 fileutil.make_dirs("web/test_welcome/private")
519 d.addCallback(_check)
522 def test_status(self):
523 h = self.s.get_history()
524 dl_num = h.list_all_download_statuses()[0].get_counter()
525 ul_num = h.list_all_upload_statuses()[0].get_counter()
526 mu_num = h.list_all_mapupdate_statuses()[0].get_counter()
527 pub_num = h.list_all_publish_statuses()[0].get_counter()
528 ret_num = h.list_all_retrieve_statuses()[0].get_counter()
529 d = self.GET("/status", followRedirect=True)
531 self.failUnlessIn('Upload and Download Status', res)
532 self.failUnlessIn('"down-%d"' % dl_num, res)
533 self.failUnlessIn('"up-%d"' % ul_num, res)
534 self.failUnlessIn('"mapupdate-%d"' % mu_num, res)
535 self.failUnlessIn('"publish-%d"' % pub_num, res)
536 self.failUnlessIn('"retrieve-%d"' % ret_num, res)
537 d.addCallback(_check)
538 d.addCallback(lambda res: self.GET("/status/?t=json"))
539 def _check_json(res):
540 data = simplejson.loads(res)
541 self.failUnless(isinstance(data, dict))
542 #active = data["active"]
543 # TODO: test more. We need a way to fake an active operation
545 d.addCallback(_check_json)
547 d.addCallback(lambda res: self.GET("/status/down-%d" % dl_num))
549 self.failUnlessIn("File Download Status", res)
550 d.addCallback(_check_dl)
551 d.addCallback(lambda res: self.GET("/status/down-%d/event_json" % dl_num))
552 def _check_dl_json(res):
553 data = simplejson.loads(res)
554 self.failUnless(isinstance(data, dict))
555 self.failUnlessIn("read", data)
556 self.failUnlessEqual(data["read"][0]["length"], 120)
557 self.failUnlessEqual(data["segment"][0]["segment_length"], 100)
558 self.failUnlessEqual(data["segment"][2]["segment_number"], 2)
559 self.failUnlessEqual(data["segment"][2]["finish_time"], None)
560 phwr_id = base32.b2a(hashutil.tagged_hash("foo", "serverid_a")[:20])
561 cmpu_id = base32.b2a(hashutil.tagged_hash("foo", "serverid_b")[:20])
562 # serverids[] keys are strings, since that's what JSON does, but
563 # we'd really like them to be ints
564 self.failUnlessEqual(data["serverids"]["0"], "phwr")
565 self.failUnless(data["serverids"].has_key("1"), data["serverids"])
566 self.failUnlessEqual(data["serverids"]["1"], "cmpu", data["serverids"])
567 self.failUnlessEqual(data["server_info"][phwr_id]["short"], "phwr")
568 self.failUnlessEqual(data["server_info"][cmpu_id]["short"], "cmpu")
569 self.failUnlessIn("dyhb", data)
570 self.failUnlessIn("misc", data)
571 d.addCallback(_check_dl_json)
572 d.addCallback(lambda res: self.GET("/status/up-%d" % ul_num))
574 self.failUnlessIn("File Upload Status", res)
575 d.addCallback(_check_ul)
576 d.addCallback(lambda res: self.GET("/status/mapupdate-%d" % mu_num))
577 def _check_mapupdate(res):
578 self.failUnlessIn("Mutable File Servermap Update Status", res)
579 d.addCallback(_check_mapupdate)
580 d.addCallback(lambda res: self.GET("/status/publish-%d" % pub_num))
581 def _check_publish(res):
582 self.failUnlessIn("Mutable File Publish Status", res)
583 d.addCallback(_check_publish)
584 d.addCallback(lambda res: self.GET("/status/retrieve-%d" % ret_num))
585 def _check_retrieve(res):
586 self.failUnlessIn("Mutable File Retrieve Status", res)
587 d.addCallback(_check_retrieve)
591 def test_status_numbers(self):
592 drrm = status.DownloadResultsRendererMixin()
593 self.failUnlessReallyEqual(drrm.render_time(None, None), "")
594 self.failUnlessReallyEqual(drrm.render_time(None, 2.5), "2.50s")
595 self.failUnlessReallyEqual(drrm.render_time(None, 0.25), "250ms")
596 self.failUnlessReallyEqual(drrm.render_time(None, 0.0021), "2.1ms")
597 self.failUnlessReallyEqual(drrm.render_time(None, 0.000123), "123us")
598 self.failUnlessReallyEqual(drrm.render_rate(None, None), "")
599 self.failUnlessReallyEqual(drrm.render_rate(None, 2500000), "2.50MBps")
600 self.failUnlessReallyEqual(drrm.render_rate(None, 30100), "30.1kBps")
601 self.failUnlessReallyEqual(drrm.render_rate(None, 123), "123Bps")
603 urrm = status.UploadResultsRendererMixin()
604 self.failUnlessReallyEqual(urrm.render_time(None, None), "")
605 self.failUnlessReallyEqual(urrm.render_time(None, 2.5), "2.50s")
606 self.failUnlessReallyEqual(urrm.render_time(None, 0.25), "250ms")
607 self.failUnlessReallyEqual(urrm.render_time(None, 0.0021), "2.1ms")
608 self.failUnlessReallyEqual(urrm.render_time(None, 0.000123), "123us")
609 self.failUnlessReallyEqual(urrm.render_rate(None, None), "")
610 self.failUnlessReallyEqual(urrm.render_rate(None, 2500000), "2.50MBps")
611 self.failUnlessReallyEqual(urrm.render_rate(None, 30100), "30.1kBps")
612 self.failUnlessReallyEqual(urrm.render_rate(None, 123), "123Bps")
614 def test_GET_FILEURL(self):
615 d = self.GET(self.public_url + "/foo/bar.txt")
616 d.addCallback(self.failUnlessIsBarDotTxt)
619 def test_GET_FILEURL_range(self):
620 headers = {"range": "bytes=1-10"}
621 d = self.GET(self.public_url + "/foo/bar.txt", headers=headers,
622 return_response=True)
623 def _got((res, status, headers)):
624 self.failUnlessReallyEqual(int(status), 206)
625 self.failUnless(headers.has_key("content-range"))
626 self.failUnlessReallyEqual(headers["content-range"][0],
627 "bytes 1-10/%d" % len(self.BAR_CONTENTS))
628 self.failUnlessReallyEqual(res, self.BAR_CONTENTS[1:11])
632 def test_GET_FILEURL_partial_range(self):
633 headers = {"range": "bytes=5-"}
634 length = len(self.BAR_CONTENTS)
635 d = self.GET(self.public_url + "/foo/bar.txt", headers=headers,
636 return_response=True)
637 def _got((res, status, headers)):
638 self.failUnlessReallyEqual(int(status), 206)
639 self.failUnless(headers.has_key("content-range"))
640 self.failUnlessReallyEqual(headers["content-range"][0],
641 "bytes 5-%d/%d" % (length-1, length))
642 self.failUnlessReallyEqual(res, self.BAR_CONTENTS[5:])
646 def test_GET_FILEURL_partial_end_range(self):
647 headers = {"range": "bytes=-5"}
648 length = len(self.BAR_CONTENTS)
649 d = self.GET(self.public_url + "/foo/bar.txt", headers=headers,
650 return_response=True)
651 def _got((res, status, headers)):
652 self.failUnlessReallyEqual(int(status), 206)
653 self.failUnless(headers.has_key("content-range"))
654 self.failUnlessReallyEqual(headers["content-range"][0],
655 "bytes %d-%d/%d" % (length-5, length-1, length))
656 self.failUnlessReallyEqual(res, self.BAR_CONTENTS[-5:])
660 def test_GET_FILEURL_partial_range_overrun(self):
661 headers = {"range": "bytes=100-200"}
662 d = self.shouldFail2(error.Error, "test_GET_FILEURL_range_overrun",
663 "416 Requested Range not satisfiable",
664 "First beyond end of file",
665 self.GET, self.public_url + "/foo/bar.txt",
669 def test_HEAD_FILEURL_range(self):
670 headers = {"range": "bytes=1-10"}
671 d = self.HEAD(self.public_url + "/foo/bar.txt", headers=headers,
672 return_response=True)
673 def _got((res, status, headers)):
674 self.failUnlessReallyEqual(res, "")
675 self.failUnlessReallyEqual(int(status), 206)
676 self.failUnless(headers.has_key("content-range"))
677 self.failUnlessReallyEqual(headers["content-range"][0],
678 "bytes 1-10/%d" % len(self.BAR_CONTENTS))
682 def test_HEAD_FILEURL_partial_range(self):
683 headers = {"range": "bytes=5-"}
684 length = len(self.BAR_CONTENTS)
685 d = self.HEAD(self.public_url + "/foo/bar.txt", headers=headers,
686 return_response=True)
687 def _got((res, status, headers)):
688 self.failUnlessReallyEqual(int(status), 206)
689 self.failUnless(headers.has_key("content-range"))
690 self.failUnlessReallyEqual(headers["content-range"][0],
691 "bytes 5-%d/%d" % (length-1, length))
695 def test_HEAD_FILEURL_partial_end_range(self):
696 headers = {"range": "bytes=-5"}
697 length = len(self.BAR_CONTENTS)
698 d = self.HEAD(self.public_url + "/foo/bar.txt", headers=headers,
699 return_response=True)
700 def _got((res, status, headers)):
701 self.failUnlessReallyEqual(int(status), 206)
702 self.failUnless(headers.has_key("content-range"))
703 self.failUnlessReallyEqual(headers["content-range"][0],
704 "bytes %d-%d/%d" % (length-5, length-1, length))
708 def test_HEAD_FILEURL_partial_range_overrun(self):
709 headers = {"range": "bytes=100-200"}
710 d = self.shouldFail2(error.Error, "test_HEAD_FILEURL_range_overrun",
711 "416 Requested Range not satisfiable",
713 self.HEAD, self.public_url + "/foo/bar.txt",
717 def test_GET_FILEURL_range_bad(self):
718 headers = {"range": "BOGUS=fizbop-quarnak"}
719 d = self.GET(self.public_url + "/foo/bar.txt", headers=headers,
720 return_response=True)
721 def _got((res, status, headers)):
722 self.failUnlessReallyEqual(int(status), 200)
723 self.failUnless(not headers.has_key("content-range"))
724 self.failUnlessReallyEqual(res, self.BAR_CONTENTS)
728 def test_HEAD_FILEURL(self):
729 d = self.HEAD(self.public_url + "/foo/bar.txt", return_response=True)
730 def _got((res, status, headers)):
731 self.failUnlessReallyEqual(res, "")
732 self.failUnlessReallyEqual(headers["content-length"][0],
733 str(len(self.BAR_CONTENTS)))
734 self.failUnlessReallyEqual(headers["content-type"], ["text/plain"])
738 def test_GET_FILEURL_named(self):
739 base = "/file/%s" % urllib.quote(self._bar_txt_uri)
740 base2 = "/named/%s" % urllib.quote(self._bar_txt_uri)
741 d = self.GET(base + "/@@name=/blah.txt")
742 d.addCallback(self.failUnlessIsBarDotTxt)
743 d.addCallback(lambda res: self.GET(base + "/blah.txt"))
744 d.addCallback(self.failUnlessIsBarDotTxt)
745 d.addCallback(lambda res: self.GET(base + "/ignore/lots/blah.txt"))
746 d.addCallback(self.failUnlessIsBarDotTxt)
747 d.addCallback(lambda res: self.GET(base2 + "/@@name=/blah.txt"))
748 d.addCallback(self.failUnlessIsBarDotTxt)
749 save_url = base + "?save=true&filename=blah.txt"
750 d.addCallback(lambda res: self.GET(save_url))
751 d.addCallback(self.failUnlessIsBarDotTxt) # TODO: check headers
752 u_filename = u"n\u00e9wer.txt" # n e-acute w e r . t x t
753 u_fn_e = urllib.quote(u_filename.encode("utf-8"))
754 u_url = base + "?save=true&filename=" + u_fn_e
755 d.addCallback(lambda res: self.GET(u_url))
756 d.addCallback(self.failUnlessIsBarDotTxt) # TODO: check headers
759 def test_PUT_FILEURL_named_bad(self):
760 base = "/file/%s" % urllib.quote(self._bar_txt_uri)
761 d = self.shouldFail2(error.Error, "test_PUT_FILEURL_named_bad",
763 "/file can only be used with GET or HEAD",
764 self.PUT, base + "/@@name=/blah.txt", "")
768 def test_GET_DIRURL_named_bad(self):
769 base = "/file/%s" % urllib.quote(self._foo_uri)
770 d = self.shouldFail2(error.Error, "test_PUT_DIRURL_named_bad",
773 self.GET, base + "/@@name=/blah.txt")
776 def test_GET_slash_file_bad(self):
777 d = self.shouldFail2(error.Error, "test_GET_slash_file_bad",
779 "/file must be followed by a file-cap and a name",
783 def test_GET_unhandled_URI_named(self):
784 contents, n, newuri = self.makefile(12)
785 verifier_cap = n.get_verify_cap().to_string()
786 base = "/file/%s" % urllib.quote(verifier_cap)
787 # client.create_node_from_uri() can't handle verify-caps
788 d = self.shouldFail2(error.Error, "GET_unhandled_URI_named",
789 "400 Bad Request", "is not a file-cap",
793 def test_GET_unhandled_URI(self):
794 contents, n, newuri = self.makefile(12)
795 verifier_cap = n.get_verify_cap().to_string()
796 base = "/uri/%s" % urllib.quote(verifier_cap)
797 # client.create_node_from_uri() can't handle verify-caps
798 d = self.shouldFail2(error.Error, "test_GET_unhandled_URI",
800 "GET unknown URI type: can only do t=info",
804 def test_GET_FILE_URI(self):
805 base = "/uri/%s" % urllib.quote(self._bar_txt_uri)
807 d.addCallback(self.failUnlessIsBarDotTxt)
810 def test_GET_FILE_URI_mdmf(self):
811 base = "/uri/%s" % urllib.quote(self._quux_txt_uri)
813 d.addCallback(self.failUnlessIsQuuxDotTxt)
816 def test_GET_FILE_URI_mdmf_extensions(self):
817 base = "/uri/%s" % urllib.quote("%s:RANDOMSTUFF" % self._quux_txt_uri)
819 d.addCallback(self.failUnlessIsQuuxDotTxt)
822 def test_GET_FILE_URI_mdmf_readonly(self):
823 base = "/uri/%s" % urllib.quote(self._quux_txt_readonly_uri)
825 d.addCallback(self.failUnlessIsQuuxDotTxt)
828 def test_GET_FILE_URI_badchild(self):
829 base = "/uri/%s/boguschild" % urllib.quote(self._bar_txt_uri)
830 errmsg = "Files have no children, certainly not named 'boguschild'"
831 d = self.shouldFail2(error.Error, "test_GET_FILE_URI_badchild",
832 "400 Bad Request", errmsg,
836 def test_PUT_FILE_URI_badchild(self):
837 base = "/uri/%s/boguschild" % urllib.quote(self._bar_txt_uri)
838 errmsg = "Cannot create directory 'boguschild', because its parent is a file, not a directory"
839 d = self.shouldFail2(error.Error, "test_GET_FILE_URI_badchild",
840 "400 Bad Request", errmsg,
844 def test_PUT_FILE_URI_mdmf(self):
845 base = "/uri/%s" % urllib.quote(self._quux_txt_uri)
846 self._quux_new_contents = "new_contents"
848 d.addCallback(lambda res:
849 self.failUnlessIsQuuxDotTxt(res))
850 d.addCallback(lambda ignored:
851 self.PUT(base, self._quux_new_contents))
852 d.addCallback(lambda ignored:
854 d.addCallback(lambda res:
855 self.failUnlessReallyEqual(res, self._quux_new_contents))
858 def test_PUT_FILE_URI_mdmf_extensions(self):
859 base = "/uri/%s" % urllib.quote("%s:EXTENSIONSTUFF" % self._quux_txt_uri)
860 self._quux_new_contents = "new_contents"
862 d.addCallback(lambda res: self.failUnlessIsQuuxDotTxt(res))
863 d.addCallback(lambda ignored: self.PUT(base, self._quux_new_contents))
864 d.addCallback(lambda ignored: self.GET(base))
865 d.addCallback(lambda res: self.failUnlessEqual(self._quux_new_contents,
869 def test_PUT_FILE_URI_mdmf_readonly(self):
870 # We're not allowed to PUT things to a readonly cap.
871 base = "/uri/%s" % self._quux_txt_readonly_uri
873 d.addCallback(lambda res:
874 self.failUnlessIsQuuxDotTxt(res))
875 # What should we get here? We get a 500 error now; that's not right.
876 d.addCallback(lambda ignored:
877 self.shouldFail2(error.Error, "test_PUT_FILE_URI_mdmf_readonly",
878 "400 Bad Request", "read-only cap",
879 self.PUT, base, "new data"))
882 def test_PUT_FILE_URI_sdmf_readonly(self):
883 # We're not allowed to put things to a readonly cap.
884 base = "/uri/%s" % self._baz_txt_readonly_uri
886 d.addCallback(lambda res:
887 self.failUnlessIsBazDotTxt(res))
888 d.addCallback(lambda ignored:
889 self.shouldFail2(error.Error, "test_PUT_FILE_URI_sdmf_readonly",
890 "400 Bad Request", "read-only cap",
891 self.PUT, base, "new_data"))
894 # TODO: version of this with a Unicode filename
895 def test_GET_FILEURL_save(self):
896 d = self.GET(self.public_url + "/foo/bar.txt?filename=bar.txt&save=true",
897 return_response=True)
898 def _got((res, statuscode, headers)):
899 content_disposition = headers["content-disposition"][0]
900 self.failUnless(content_disposition == 'attachment; filename="bar.txt"', content_disposition)
901 self.failUnlessIsBarDotTxt(res)
905 def test_GET_FILEURL_missing(self):
906 d = self.GET(self.public_url + "/foo/missing")
907 d.addBoth(self.should404, "test_GET_FILEURL_missing")
910 def test_GET_FILEURL_info_mdmf(self):
911 d = self.GET("/uri/%s?t=info" % self._quux_txt_uri)
913 self.failUnlessIn("mutable file (mdmf)", res)
914 self.failUnlessIn(self._quux_txt_uri, res)
915 self.failUnlessIn(self._quux_txt_readonly_uri, res)
919 def test_GET_FILEURL_info_mdmf_readonly(self):
920 d = self.GET("/uri/%s?t=info" % self._quux_txt_readonly_uri)
922 self.failUnlessIn("mutable file (mdmf)", res)
923 self.failIfIn(self._quux_txt_uri, res)
924 self.failUnlessIn(self._quux_txt_readonly_uri, res)
928 def test_GET_FILEURL_info_sdmf(self):
929 d = self.GET("/uri/%s?t=info" % self._baz_txt_uri)
931 self.failUnlessIn("mutable file (sdmf)", res)
932 self.failUnlessIn(self._baz_txt_uri, res)
936 def test_GET_FILEURL_info_mdmf_extensions(self):
937 d = self.GET("/uri/%s:STUFF?t=info" % self._quux_txt_uri)
939 self.failUnlessIn("mutable file (mdmf)", res)
940 self.failUnlessIn(self._quux_txt_uri, res)
941 self.failUnlessIn(self._quux_txt_readonly_uri, res)
945 def test_PUT_overwrite_only_files(self):
946 # create a directory, put a file in that directory.
947 contents, n, filecap = self.makefile(8)
948 d = self.PUT(self.public_url + "/foo/dir?t=mkdir", "")
949 d.addCallback(lambda res:
950 self.PUT(self.public_url + "/foo/dir/file1.txt",
951 self.NEWFILE_CONTENTS))
952 # try to overwrite the file with replace=only-files
954 d.addCallback(lambda res:
955 self.PUT(self.public_url + "/foo/dir/file1.txt?t=uri&replace=only-files",
957 d.addCallback(lambda res:
958 self.shouldFail2(error.Error, "PUT_bad_t", "409 Conflict",
959 "There was already a child by that name, and you asked me "
961 self.PUT, self.public_url + "/foo/dir?t=uri&replace=only-files",
965 def test_PUT_NEWFILEURL(self):
966 d = self.PUT(self.public_url + "/foo/new.txt", self.NEWFILE_CONTENTS)
967 # TODO: we lose the response code, so we can't check this
968 #self.failUnlessReallyEqual(responsecode, 201)
969 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"new.txt")
970 d.addCallback(lambda res:
971 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
972 self.NEWFILE_CONTENTS))
975 def test_PUT_NEWFILEURL_not_mutable(self):
976 d = self.PUT(self.public_url + "/foo/new.txt?mutable=false",
977 self.NEWFILE_CONTENTS)
978 # TODO: we lose the response code, so we can't check this
979 #self.failUnlessReallyEqual(responsecode, 201)
980 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"new.txt")
981 d.addCallback(lambda res:
982 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
983 self.NEWFILE_CONTENTS))
986 def test_PUT_NEWFILEURL_unlinked_mdmf(self):
987 # this should get us a few segments of an MDMF mutable file,
988 # which we can then test for.
989 contents = self.NEWFILE_CONTENTS * 300000
990 d = self.PUT("/uri?format=mdmf",
992 def _got_filecap(filecap):
993 self.failUnless(filecap.startswith("URI:MDMF"))
995 d.addCallback(_got_filecap)
996 d.addCallback(lambda filecap: self.GET("/uri/%s?t=json" % filecap))
997 d.addCallback(lambda json: self.failUnlessIn("MDMF", json))
1000 def test_PUT_NEWFILEURL_unlinked_sdmf(self):
1001 contents = self.NEWFILE_CONTENTS * 300000
1002 d = self.PUT("/uri?format=sdmf",
1004 d.addCallback(lambda filecap: self.GET("/uri/%s?t=json" % filecap))
1005 d.addCallback(lambda json: self.failUnlessIn("SDMF", json))
1008 def test_PUT_NEWFILEURL_unlinked_bad_format(self):
1009 contents = self.NEWFILE_CONTENTS * 300000
1010 return self.shouldHTTPError("PUT_NEWFILEURL_unlinked_bad_format",
1011 400, "Bad Request", "Unknown format: foo",
1012 self.PUT, "/uri?format=foo",
1015 def test_PUT_NEWFILEURL_range_bad(self):
1016 headers = {"content-range": "bytes 1-10/%d" % len(self.NEWFILE_CONTENTS)}
1017 target = self.public_url + "/foo/new.txt"
1018 d = self.shouldFail2(error.Error, "test_PUT_NEWFILEURL_range_bad",
1019 "501 Not Implemented",
1020 "Content-Range in PUT not yet supported",
1021 # (and certainly not for immutable files)
1022 self.PUT, target, self.NEWFILE_CONTENTS[1:11],
1024 d.addCallback(lambda res:
1025 self.failIfNodeHasChild(self._foo_node, u"new.txt"))
1028 def test_PUT_NEWFILEURL_mutable(self):
1029 d = self.PUT(self.public_url + "/foo/new.txt?mutable=true",
1030 self.NEWFILE_CONTENTS)
1031 # TODO: we lose the response code, so we can't check this
1032 #self.failUnlessReallyEqual(responsecode, 201)
1033 def _check_uri(res):
1034 u = uri.from_string_mutable_filenode(res)
1035 self.failUnless(u.is_mutable())
1036 self.failIf(u.is_readonly())
1038 d.addCallback(_check_uri)
1039 d.addCallback(self.failUnlessURIMatchesRWChild, self._foo_node, u"new.txt")
1040 d.addCallback(lambda res:
1041 self.failUnlessMutableChildContentsAre(self._foo_node,
1043 self.NEWFILE_CONTENTS))
1046 def test_PUT_NEWFILEURL_mutable_toobig(self):
1047 # It is okay to upload large mutable files, so we should be able
1049 d = self.PUT(self.public_url + "/foo/new.txt?mutable=true",
1050 "b" * (self.s.MUTABLE_SIZELIMIT + 1))
1053 def test_PUT_NEWFILEURL_replace(self):
1054 d = self.PUT(self.public_url + "/foo/bar.txt", self.NEWFILE_CONTENTS)
1055 # TODO: we lose the response code, so we can't check this
1056 #self.failUnlessReallyEqual(responsecode, 200)
1057 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"bar.txt")
1058 d.addCallback(lambda res:
1059 self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
1060 self.NEWFILE_CONTENTS))
1063 def test_PUT_NEWFILEURL_bad_t(self):
1064 d = self.shouldFail2(error.Error, "PUT_bad_t", "400 Bad Request",
1065 "PUT to a file: bad t=bogus",
1066 self.PUT, self.public_url + "/foo/bar.txt?t=bogus",
1070 def test_PUT_NEWFILEURL_no_replace(self):
1071 d = self.PUT(self.public_url + "/foo/bar.txt?replace=false",
1072 self.NEWFILE_CONTENTS)
1073 d.addBoth(self.shouldFail, error.Error, "PUT_NEWFILEURL_no_replace",
1075 "There was already a child by that name, and you asked me "
1076 "to not replace it")
1079 def test_PUT_NEWFILEURL_mkdirs(self):
1080 d = self.PUT(self.public_url + "/foo/newdir/new.txt", self.NEWFILE_CONTENTS)
1082 d.addCallback(self.failUnlessURIMatchesROChild, fn, u"newdir/new.txt")
1083 d.addCallback(lambda res: self.failIfNodeHasChild(fn, u"new.txt"))
1084 d.addCallback(lambda res: self.failUnlessNodeHasChild(fn, u"newdir"))
1085 d.addCallback(lambda res:
1086 self.failUnlessChildContentsAre(fn, u"newdir/new.txt",
1087 self.NEWFILE_CONTENTS))
1090 def test_PUT_NEWFILEURL_blocked(self):
1091 d = self.PUT(self.public_url + "/foo/blockingfile/new.txt",
1092 self.NEWFILE_CONTENTS)
1093 d.addBoth(self.shouldFail, error.Error, "PUT_NEWFILEURL_blocked",
1095 "Unable to create directory 'blockingfile': a file was in the way")
1098 def test_PUT_NEWFILEURL_emptyname(self):
1099 # an empty pathname component (i.e. a double-slash) is disallowed
1100 d = self.shouldFail2(error.Error, "test_PUT_NEWFILEURL_emptyname",
1102 "The webapi does not allow empty pathname components",
1103 self.PUT, self.public_url + "/foo//new.txt", "")
1106 def test_DELETE_FILEURL(self):
1107 d = self.DELETE(self.public_url + "/foo/bar.txt")
1108 d.addCallback(lambda res:
1109 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
1112 def test_DELETE_FILEURL_missing(self):
1113 d = self.DELETE(self.public_url + "/foo/missing")
1114 d.addBoth(self.should404, "test_DELETE_FILEURL_missing")
1117 def test_DELETE_FILEURL_missing2(self):
1118 d = self.DELETE(self.public_url + "/missing/missing")
1119 d.addBoth(self.should404, "test_DELETE_FILEURL_missing2")
1122 def failUnlessHasBarDotTxtMetadata(self, res):
1123 data = simplejson.loads(res)
1124 self.failUnless(isinstance(data, list))
1125 self.failUnlessIn("metadata", data[1])
1126 self.failUnlessIn("tahoe", data[1]["metadata"])
1127 self.failUnlessIn("linkcrtime", data[1]["metadata"]["tahoe"])
1128 self.failUnlessIn("linkmotime", data[1]["metadata"]["tahoe"])
1129 self.failUnlessReallyEqual(data[1]["metadata"]["tahoe"]["linkcrtime"],
1130 self._bar_txt_metadata["tahoe"]["linkcrtime"])
1132 def test_GET_FILEURL_json(self):
1133 # twisted.web.http.parse_qs ignores any query args without an '=', so
1134 # I can't do "GET /path?json", I have to do "GET /path/t=json"
1135 # instead. This may make it tricky to emulate the S3 interface
1137 d = self.GET(self.public_url + "/foo/bar.txt?t=json")
1139 self.failUnlessIsBarJSON(data)
1140 self.failUnlessHasBarDotTxtMetadata(data)
1142 d.addCallback(_check1)
1145 def test_GET_FILEURL_json_mutable_type(self):
1146 # The JSON should include format, which says whether the
1147 # file is SDMF or MDMF
1148 d = self.PUT("/uri?format=mdmf",
1149 self.NEWFILE_CONTENTS * 300000)
1150 d.addCallback(lambda filecap: self.GET("/uri/%s?t=json" % filecap))
1151 def _got_json(json, version):
1152 data = simplejson.loads(json)
1153 assert "filenode" == data[0]
1155 assert isinstance(data, dict)
1157 self.failUnlessIn("format", data)
1158 self.failUnlessEqual(data["format"], version)
1160 d.addCallback(_got_json, "MDMF")
1161 # Now make an SDMF file and check that it is reported correctly.
1162 d.addCallback(lambda ignored:
1163 self.PUT("/uri?format=sdmf",
1164 self.NEWFILE_CONTENTS * 300000))
1165 d.addCallback(lambda filecap: self.GET("/uri/%s?t=json" % filecap))
1166 d.addCallback(_got_json, "SDMF")
1169 def test_GET_FILEURL_json_mdmf(self):
1170 d = self.GET("/uri/%s?t=json" % urllib.quote(self._quux_txt_uri))
1171 d.addCallback(self.failUnlessIsQuuxJSON)
1174 def test_GET_FILEURL_json_missing(self):
1175 d = self.GET(self.public_url + "/foo/missing?json")
1176 d.addBoth(self.should404, "test_GET_FILEURL_json_missing")
1179 def test_GET_FILEURL_uri(self):
1180 d = self.GET(self.public_url + "/foo/bar.txt?t=uri")
1182 self.failUnlessReallyEqual(res, self._bar_txt_uri)
1183 d.addCallback(_check)
1184 d.addCallback(lambda res:
1185 self.GET(self.public_url + "/foo/bar.txt?t=readonly-uri"))
1187 # for now, for files, uris and readonly-uris are the same
1188 self.failUnlessReallyEqual(res, self._bar_txt_uri)
1189 d.addCallback(_check2)
1192 def test_GET_FILEURL_badtype(self):
1193 d = self.shouldHTTPError("GET t=bogus", 400, "Bad Request",
1196 self.public_url + "/foo/bar.txt?t=bogus")
1199 def test_CSS_FILE(self):
1200 d = self.GET("/tahoe.css", followRedirect=True)
1202 CSS_STYLE=re.compile('toolbar\s{.+text-align:\scenter.+toolbar-item.+display:\sinline',re.DOTALL)
1203 self.failUnless(CSS_STYLE.search(res), res)
1204 d.addCallback(_check)
1207 def test_GET_FILEURL_uri_missing(self):
1208 d = self.GET(self.public_url + "/foo/missing?t=uri")
1209 d.addBoth(self.should404, "test_GET_FILEURL_uri_missing")
1212 def _check_upload_and_mkdir_forms(self, html):
1213 # We should have a form to create a file, with radio buttons that allow
1214 # the user to toggle whether it is a CHK/LIT (default), SDMF, or MDMF file.
1215 self.failUnlessIn('name="t" value="upload"', html)
1216 self.failUnlessIn('input checked="checked" type="radio" id="upload-chk" value="chk" name="format"', html)
1217 self.failUnlessIn('input type="radio" id="upload-sdmf" value="sdmf" name="format"', html)
1218 self.failUnlessIn('input type="radio" id="upload-mdmf" value="mdmf" name="format"', html)
1220 # We should also have the ability to create a mutable directory, with
1221 # radio buttons that allow the user to toggle whether it is an SDMF (default)
1222 # or MDMF directory.
1223 self.failUnlessIn('name="t" value="mkdir"', html)
1224 self.failUnlessIn('input checked="checked" type="radio" id="mkdir-sdmf" value="sdmf" name="format"', html)
1225 self.failUnlessIn('input type="radio" id="mkdir-mdmf" value="mdmf" name="format"', html)
1227 self.failUnlessIn(FAVICON_MARKUP, html)
1229 def test_GET_DIRECTORY_html(self):
1230 d = self.GET(self.public_url + "/foo", followRedirect=True)
1232 self.failUnlessIn('<div class="toolbar-item"><a href="../../..">Return to Welcome page</a></div>', html)
1233 self._check_upload_and_mkdir_forms(html)
1234 self.failUnlessIn("quux", html)
1235 d.addCallback(_check)
1238 def test_GET_root_html(self):
1240 d.addCallback(self._check_upload_and_mkdir_forms)
1243 def test_GET_DIRURL(self):
1244 # the addSlash means we get a redirect here
1245 # from /uri/$URI/foo/ , we need ../../../ to get back to the root
1247 d = self.GET(self.public_url + "/foo", followRedirect=True)
1249 self.failUnlessIn('<a href="%s">Return to Welcome page' % ROOT, res)
1251 # the FILE reference points to a URI, but it should end in bar.txt
1252 bar_url = ("%s/file/%s/@@named=/bar.txt" %
1253 (ROOT, urllib.quote(self._bar_txt_uri)))
1254 get_bar = "".join([r'<td>FILE</td>',
1256 r'<a href="%s">bar.txt</a>' % bar_url,
1258 r'\s+<td align="right">%d</td>' % len(self.BAR_CONTENTS),
1260 self.failUnless(re.search(get_bar, res), res)
1261 for label in ['unlink', 'rename']:
1262 for line in res.split("\n"):
1263 # find the line that contains the relevant button for bar.txt
1264 if ("form action" in line and
1265 ('value="%s"' % (label,)) in line and
1266 'value="bar.txt"' in line):
1267 # the form target should use a relative URL
1268 foo_url = urllib.quote("%s/uri/%s/" % (ROOT, self._foo_uri))
1269 self.failUnlessIn('action="%s"' % foo_url, line)
1270 # and the when_done= should too
1271 #done_url = urllib.quote(???)
1272 #self.failUnlessIn('name="when_done" value="%s"' % done_url, line)
1274 # 'unlink' needs to use POST because it directly has a side effect
1275 if label == 'unlink':
1276 self.failUnlessIn('method="post"', line)
1279 self.fail("unable to find '%s bar.txt' line" % (label,), res)
1281 # the DIR reference just points to a URI
1282 sub_url = ("%s/uri/%s/" % (ROOT, urllib.quote(self._sub_uri)))
1283 get_sub = ((r'<td>DIR</td>')
1284 +r'\s+<td><a href="%s">sub</a></td>' % sub_url)
1285 self.failUnless(re.search(get_sub, res), res)
1286 d.addCallback(_check)
1288 # look at a readonly directory
1289 d.addCallback(lambda res:
1290 self.GET(self.public_url + "/reedownlee", followRedirect=True))
1292 self.failUnlessIn("(read-only)", res)
1293 self.failIfIn("Upload a file", res)
1294 d.addCallback(_check2)
1296 # and at a directory that contains a readonly directory
1297 d.addCallback(lambda res:
1298 self.GET(self.public_url, followRedirect=True))
1300 self.failUnless(re.search('<td>DIR-RO</td>'
1301 r'\s+<td><a href="[\.\/]+/uri/URI%3ADIR2-RO%3A[^"]+">reedownlee</a></td>', res), res)
1302 d.addCallback(_check3)
1304 # and an empty directory
1305 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty/"))
1307 self.failUnlessIn("directory is empty", res)
1308 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)
1309 self.failUnless(MKDIR_BUTTON_RE.search(res), res)
1310 d.addCallback(_check4)
1312 # and at a literal directory
1313 tiny_litdir_uri = "URI:DIR2-LIT:gqytunj2onug64tufqzdcosvkjetutcjkq5gw4tvm5vwszdgnz5hgyzufqydulbshj5x2lbm" # contains one child which is itself also LIT
1314 d.addCallback(lambda res:
1315 self.GET("/uri/" + tiny_litdir_uri + "/", followRedirect=True))
1317 self.failUnlessIn('(immutable)', res)
1318 self.failUnless(re.search('<td>FILE</td>'
1319 r'\s+<td><a href="[\.\/]+/file/URI%3ALIT%3Akrugkidfnzsc4/@@named=/short">short</a></td>', res), res)
1320 d.addCallback(_check5)
1323 def test_GET_DIRURL_badtype(self):
1324 d = self.shouldHTTPError("test_GET_DIRURL_badtype",
1328 self.public_url + "/foo?t=bogus")
1331 def test_GET_DIRURL_json(self):
1332 d = self.GET(self.public_url + "/foo?t=json")
1333 d.addCallback(self.failUnlessIsFooJSON)
1336 def test_GET_DIRURL_json_format(self):
1337 d = self.PUT(self.public_url + \
1338 "/foo/sdmf.txt?format=sdmf",
1339 self.NEWFILE_CONTENTS * 300000)
1340 d.addCallback(lambda ignored:
1341 self.PUT(self.public_url + \
1342 "/foo/mdmf.txt?format=mdmf",
1343 self.NEWFILE_CONTENTS * 300000))
1344 # Now we have an MDMF and SDMF file in the directory. If we GET
1345 # its JSON, we should see their encodings.
1346 d.addCallback(lambda ignored:
1347 self.GET(self.public_url + "/foo?t=json"))
1348 def _got_json(json):
1349 data = simplejson.loads(json)
1350 assert data[0] == "dirnode"
1353 kids = data['children']
1355 mdmf_data = kids['mdmf.txt'][1]
1356 self.failUnlessIn("format", mdmf_data)
1357 self.failUnlessEqual(mdmf_data["format"], "MDMF")
1359 sdmf_data = kids['sdmf.txt'][1]
1360 self.failUnlessIn("format", sdmf_data)
1361 self.failUnlessEqual(sdmf_data["format"], "SDMF")
1362 d.addCallback(_got_json)
1366 def test_POST_DIRURL_manifest_no_ophandle(self):
1367 d = self.shouldFail2(error.Error,
1368 "test_POST_DIRURL_manifest_no_ophandle",
1370 "slow operation requires ophandle=",
1371 self.POST, self.public_url, t="start-manifest")
1374 def test_POST_DIRURL_manifest(self):
1375 d = defer.succeed(None)
1376 def getman(ignored, output):
1377 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=125",
1378 followRedirect=True)
1379 d.addCallback(self.wait_for_operation, "125")
1380 d.addCallback(self.get_operation_results, "125", output)
1382 d.addCallback(getman, None)
1383 def _got_html(manifest):
1384 self.failUnlessIn("Manifest of SI=", manifest)
1385 self.failUnlessIn("<td>sub</td>", manifest)
1386 self.failUnlessIn(self._sub_uri, manifest)
1387 self.failUnlessIn("<td>sub/baz.txt</td>", manifest)
1388 self.failUnlessIn(FAVICON_MARKUP, manifest)
1389 d.addCallback(_got_html)
1391 # both t=status and unadorned GET should be identical
1392 d.addCallback(lambda res: self.GET("/operations/125"))
1393 d.addCallback(_got_html)
1395 d.addCallback(getman, "html")
1396 d.addCallback(_got_html)
1397 d.addCallback(getman, "text")
1398 def _got_text(manifest):
1399 self.failUnlessIn("\nsub " + self._sub_uri + "\n", manifest)
1400 self.failUnlessIn("\nsub/baz.txt URI:CHK:", manifest)
1401 d.addCallback(_got_text)
1402 d.addCallback(getman, "JSON")
1404 data = res["manifest"]
1406 for (path_list, cap) in data:
1407 got[tuple(path_list)] = cap
1408 self.failUnlessReallyEqual(to_str(got[(u"sub",)]), self._sub_uri)
1409 self.failUnlessIn((u"sub", u"baz.txt"), got)
1410 self.failUnlessIn("finished", res)
1411 self.failUnlessIn("origin", res)
1412 self.failUnlessIn("storage-index", res)
1413 self.failUnlessIn("verifycaps", res)
1414 self.failUnlessIn("stats", res)
1415 d.addCallback(_got_json)
1418 def test_POST_DIRURL_deepsize_no_ophandle(self):
1419 d = self.shouldFail2(error.Error,
1420 "test_POST_DIRURL_deepsize_no_ophandle",
1422 "slow operation requires ophandle=",
1423 self.POST, self.public_url, t="start-deep-size")
1426 def test_POST_DIRURL_deepsize(self):
1427 d = self.POST(self.public_url + "/foo/?t=start-deep-size&ophandle=126",
1428 followRedirect=True)
1429 d.addCallback(self.wait_for_operation, "126")
1430 d.addCallback(self.get_operation_results, "126", "json")
1431 def _got_json(data):
1432 self.failUnlessReallyEqual(data["finished"], True)
1434 self.failUnless(size > 1000)
1435 d.addCallback(_got_json)
1436 d.addCallback(self.get_operation_results, "126", "text")
1438 mo = re.search(r'^size: (\d+)$', res, re.M)
1439 self.failUnless(mo, res)
1440 size = int(mo.group(1))
1441 # with directories, the size varies.
1442 self.failUnless(size > 1000)
1443 d.addCallback(_got_text)
1446 def test_POST_DIRURL_deepstats_no_ophandle(self):
1447 d = self.shouldFail2(error.Error,
1448 "test_POST_DIRURL_deepstats_no_ophandle",
1450 "slow operation requires ophandle=",
1451 self.POST, self.public_url, t="start-deep-stats")
1454 def test_POST_DIRURL_deepstats(self):
1455 d = self.POST(self.public_url + "/foo/?t=start-deep-stats&ophandle=127",
1456 followRedirect=True)
1457 d.addCallback(self.wait_for_operation, "127")
1458 d.addCallback(self.get_operation_results, "127", "json")
1459 def _got_json(stats):
1460 expected = {"count-immutable-files": 3,
1461 "count-mutable-files": 2,
1462 "count-literal-files": 0,
1464 "count-directories": 3,
1465 "size-immutable-files": 57,
1466 "size-literal-files": 0,
1467 #"size-directories": 1912, # varies
1468 #"largest-directory": 1590,
1469 "largest-directory-children": 7,
1470 "largest-immutable-file": 19,
1472 for k,v in expected.iteritems():
1473 self.failUnlessReallyEqual(stats[k], v,
1474 "stats[%s] was %s, not %s" %
1476 self.failUnlessReallyEqual(stats["size-files-histogram"],
1478 d.addCallback(_got_json)
1481 def test_POST_DIRURL_stream_manifest(self):
1482 d = self.POST(self.public_url + "/foo/?t=stream-manifest")
1484 self.failUnless(res.endswith("\n"))
1485 units = [simplejson.loads(t) for t in res[:-1].split("\n")]
1486 self.failUnlessReallyEqual(len(units), 9)
1487 self.failUnlessEqual(units[-1]["type"], "stats")
1489 self.failUnlessEqual(first["path"], [])
1490 self.failUnlessReallyEqual(to_str(first["cap"]), self._foo_uri)
1491 self.failUnlessEqual(first["type"], "directory")
1492 baz = [u for u in units[:-1] if to_str(u["cap"]) == self._baz_file_uri][0]
1493 self.failUnlessEqual(baz["path"], ["sub", "baz.txt"])
1494 self.failIfEqual(baz["storage-index"], None)
1495 self.failIfEqual(baz["verifycap"], None)
1496 self.failIfEqual(baz["repaircap"], None)
1497 # XXX: Add quux and baz to this test.
1499 d.addCallback(_check)
1502 def test_GET_DIRURL_uri(self):
1503 d = self.GET(self.public_url + "/foo?t=uri")
1505 self.failUnlessReallyEqual(to_str(res), self._foo_uri)
1506 d.addCallback(_check)
1509 def test_GET_DIRURL_readonly_uri(self):
1510 d = self.GET(self.public_url + "/foo?t=readonly-uri")
1512 self.failUnlessReallyEqual(to_str(res), self._foo_readonly_uri)
1513 d.addCallback(_check)
1516 def test_PUT_NEWDIRURL(self):
1517 d = self.PUT(self.public_url + "/foo/newdir?t=mkdir", "")
1518 d.addCallback(lambda res:
1519 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1520 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1521 d.addCallback(self.failUnlessNodeKeysAre, [])
1524 def test_PUT_NEWDIRURL_mdmf(self):
1525 d = self.PUT(self.public_url + "/foo/newdir?t=mkdir&format=mdmf", "")
1526 d.addCallback(lambda res:
1527 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1528 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1529 d.addCallback(lambda node:
1530 self.failUnlessEqual(node._node.get_version(), MDMF_VERSION))
1533 def test_PUT_NEWDIRURL_sdmf(self):
1534 d = self.PUT(self.public_url + "/foo/newdir?t=mkdir&format=sdmf",
1536 d.addCallback(lambda res:
1537 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1538 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1539 d.addCallback(lambda node:
1540 self.failUnlessEqual(node._node.get_version(), SDMF_VERSION))
1543 def test_PUT_NEWDIRURL_bad_format(self):
1544 return self.shouldHTTPError("PUT_NEWDIRURL_bad_format",
1545 400, "Bad Request", "Unknown format: foo",
1546 self.PUT, self.public_url +
1547 "/foo/newdir=?t=mkdir&format=foo", "")
1549 def test_POST_NEWDIRURL(self):
1550 d = self.POST2(self.public_url + "/foo/newdir?t=mkdir", "")
1551 d.addCallback(lambda res:
1552 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1553 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1554 d.addCallback(self.failUnlessNodeKeysAre, [])
1557 def test_POST_NEWDIRURL_mdmf(self):
1558 d = self.POST2(self.public_url + "/foo/newdir?t=mkdir&format=mdmf", "")
1559 d.addCallback(lambda res:
1560 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1561 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1562 d.addCallback(lambda node:
1563 self.failUnlessEqual(node._node.get_version(), MDMF_VERSION))
1566 def test_POST_NEWDIRURL_sdmf(self):
1567 d = self.POST2(self.public_url + "/foo/newdir?t=mkdir&format=sdmf", "")
1568 d.addCallback(lambda res:
1569 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1570 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1571 d.addCallback(lambda node:
1572 self.failUnlessEqual(node._node.get_version(), SDMF_VERSION))
1575 def test_POST_NEWDIRURL_bad_format(self):
1576 return self.shouldHTTPError("POST_NEWDIRURL_bad_format",
1577 400, "Bad Request", "Unknown format: foo",
1578 self.POST2, self.public_url + \
1579 "/foo/newdir?t=mkdir&format=foo", "")
1581 def test_POST_NEWDIRURL_emptyname(self):
1582 # an empty pathname component (i.e. a double-slash) is disallowed
1583 d = self.shouldFail2(error.Error, "POST_NEWDIRURL_emptyname",
1585 "The webapi does not allow empty pathname components, i.e. a double slash",
1586 self.POST, self.public_url + "//?t=mkdir")
1589 def _do_POST_NEWDIRURL_initial_children_test(self, version=None):
1590 (newkids, caps) = self._create_initial_children()
1591 query = "/foo/newdir?t=mkdir-with-children"
1592 if version == MDMF_VERSION:
1593 query += "&format=mdmf"
1594 elif version == SDMF_VERSION:
1595 query += "&format=sdmf"
1597 version = SDMF_VERSION # for later
1598 d = self.POST2(self.public_url + query,
1599 simplejson.dumps(newkids))
1601 n = self.s.create_node_from_uri(uri.strip())
1602 d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
1603 self.failUnlessEqual(n._node.get_version(), version)
1604 d2.addCallback(lambda ign:
1605 self.failUnlessROChildURIIs(n, u"child-imm",
1607 d2.addCallback(lambda ign:
1608 self.failUnlessRWChildURIIs(n, u"child-mutable",
1610 d2.addCallback(lambda ign:
1611 self.failUnlessROChildURIIs(n, u"child-mutable-ro",
1613 d2.addCallback(lambda ign:
1614 self.failUnlessROChildURIIs(n, u"unknownchild-ro",
1615 caps['unknown_rocap']))
1616 d2.addCallback(lambda ign:
1617 self.failUnlessRWChildURIIs(n, u"unknownchild-rw",
1618 caps['unknown_rwcap']))
1619 d2.addCallback(lambda ign:
1620 self.failUnlessROChildURIIs(n, u"unknownchild-imm",
1621 caps['unknown_immcap']))
1622 d2.addCallback(lambda ign:
1623 self.failUnlessRWChildURIIs(n, u"dirchild",
1625 d2.addCallback(lambda ign:
1626 self.failUnlessROChildURIIs(n, u"dirchild-lit",
1628 d2.addCallback(lambda ign:
1629 self.failUnlessROChildURIIs(n, u"dirchild-empty",
1630 caps['emptydircap']))
1632 d.addCallback(_check)
1633 d.addCallback(lambda res:
1634 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1635 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1636 d.addCallback(self.failUnlessNodeKeysAre, newkids.keys())
1637 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1638 d.addCallback(self.failUnlessROChildURIIs, u"child-imm", caps['filecap1'])
1641 def test_POST_NEWDIRURL_initial_children(self):
1642 return self._do_POST_NEWDIRURL_initial_children_test()
1644 def test_POST_NEWDIRURL_initial_children_mdmf(self):
1645 return self._do_POST_NEWDIRURL_initial_children_test(MDMF_VERSION)
1647 def test_POST_NEWDIRURL_initial_children_sdmf(self):
1648 return self._do_POST_NEWDIRURL_initial_children_test(SDMF_VERSION)
1650 def test_POST_NEWDIRURL_initial_children_bad_format(self):
1651 (newkids, caps) = self._create_initial_children()
1652 return self.shouldHTTPError("POST_NEWDIRURL_initial_children_bad_format",
1653 400, "Bad Request", "Unknown format: foo",
1654 self.POST2, self.public_url + \
1655 "/foo/newdir?t=mkdir-with-children&format=foo",
1656 simplejson.dumps(newkids))
1658 def test_POST_NEWDIRURL_immutable(self):
1659 (newkids, caps) = self._create_immutable_children()
1660 d = self.POST2(self.public_url + "/foo/newdir?t=mkdir-immutable",
1661 simplejson.dumps(newkids))
1663 n = self.s.create_node_from_uri(uri.strip())
1664 d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
1665 d2.addCallback(lambda ign:
1666 self.failUnlessROChildURIIs(n, u"child-imm",
1668 d2.addCallback(lambda ign:
1669 self.failUnlessROChildURIIs(n, u"unknownchild-imm",
1670 caps['unknown_immcap']))
1671 d2.addCallback(lambda ign:
1672 self.failUnlessROChildURIIs(n, u"dirchild-imm",
1674 d2.addCallback(lambda ign:
1675 self.failUnlessROChildURIIs(n, u"dirchild-lit",
1677 d2.addCallback(lambda ign:
1678 self.failUnlessROChildURIIs(n, u"dirchild-empty",
1679 caps['emptydircap']))
1681 d.addCallback(_check)
1682 d.addCallback(lambda res:
1683 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1684 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1685 d.addCallback(self.failUnlessNodeKeysAre, newkids.keys())
1686 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1687 d.addCallback(self.failUnlessROChildURIIs, u"child-imm", caps['filecap1'])
1688 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1689 d.addCallback(self.failUnlessROChildURIIs, u"unknownchild-imm", caps['unknown_immcap'])
1690 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1691 d.addCallback(self.failUnlessROChildURIIs, u"dirchild-imm", caps['immdircap'])
1692 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1693 d.addCallback(self.failUnlessROChildURIIs, u"dirchild-lit", caps['litdircap'])
1694 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1695 d.addCallback(self.failUnlessROChildURIIs, u"dirchild-empty", caps['emptydircap'])
1696 d.addErrback(self.explain_web_error)
1699 def test_POST_NEWDIRURL_immutable_bad(self):
1700 (newkids, caps) = self._create_initial_children()
1701 d = self.shouldFail2(error.Error, "test_POST_NEWDIRURL_immutable_bad",
1703 "needed to be immutable but was not",
1705 self.public_url + "/foo/newdir?t=mkdir-immutable",
1706 simplejson.dumps(newkids))
1709 def test_PUT_NEWDIRURL_exists(self):
1710 d = self.PUT(self.public_url + "/foo/sub?t=mkdir", "")
1711 d.addCallback(lambda res:
1712 self.failUnlessNodeHasChild(self._foo_node, u"sub"))
1713 d.addCallback(lambda res: self._foo_node.get(u"sub"))
1714 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
1717 def test_PUT_NEWDIRURL_blocked(self):
1718 d = self.shouldFail2(error.Error, "PUT_NEWDIRURL_blocked",
1719 "409 Conflict", "Unable to create directory 'bar.txt': a file was in the way",
1721 self.public_url + "/foo/bar.txt/sub?t=mkdir", "")
1722 d.addCallback(lambda res:
1723 self.failUnlessNodeHasChild(self._foo_node, u"sub"))
1724 d.addCallback(lambda res: self._foo_node.get(u"sub"))
1725 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
1728 def test_PUT_NEWDIRURL_mkdir_p(self):
1729 d = defer.succeed(None)
1730 d.addCallback(lambda res: self.POST(self.public_url + "/foo", t='mkdir', name='mkp'))
1731 d.addCallback(lambda res: self.failUnlessNodeHasChild(self._foo_node, u"mkp"))
1732 d.addCallback(lambda res: self._foo_node.get(u"mkp"))
1733 def mkdir_p(mkpnode):
1734 url = '/uri/%s?t=mkdir-p&path=/sub1/sub2' % urllib.quote(mkpnode.get_uri())
1736 def made_subsub(ssuri):
1737 d = self._foo_node.get_child_at_path(u"mkp/sub1/sub2")
1738 d.addCallback(lambda ssnode: self.failUnlessReallyEqual(ssnode.get_uri(), ssuri))
1740 d.addCallback(lambda uri2: self.failUnlessReallyEqual(uri2, ssuri))
1742 d.addCallback(made_subsub)
1744 d.addCallback(mkdir_p)
1747 def test_PUT_NEWDIRURL_mkdirs(self):
1748 d = self.PUT(self.public_url + "/foo/subdir/newdir?t=mkdir", "")
1749 d.addCallback(lambda res:
1750 self.failIfNodeHasChild(self._foo_node, u"newdir"))
1751 d.addCallback(lambda res:
1752 self.failUnlessNodeHasChild(self._foo_node, u"subdir"))
1753 d.addCallback(lambda res:
1754 self._foo_node.get_child_at_path(u"subdir/newdir"))
1755 d.addCallback(self.failUnlessNodeKeysAre, [])
1758 def test_PUT_NEWDIRURL_mkdirs_mdmf(self):
1759 d = self.PUT(self.public_url + "/foo/subdir/newdir?t=mkdir&format=mdmf", "")
1760 d.addCallback(lambda ignored:
1761 self.failUnlessNodeHasChild(self._foo_node, u"subdir"))
1762 d.addCallback(lambda ignored:
1763 self.failIfNodeHasChild(self._foo_node, u"newdir"))
1764 d.addCallback(lambda ignored:
1765 self._foo_node.get_child_at_path(u"subdir"))
1766 def _got_subdir(subdir):
1767 # XXX: What we want?
1768 #self.failUnlessEqual(subdir._node.get_version(), MDMF_VERSION)
1769 self.failUnlessNodeHasChild(subdir, u"newdir")
1770 return subdir.get_child_at_path(u"newdir")
1771 d.addCallback(_got_subdir)
1772 d.addCallback(lambda newdir:
1773 self.failUnlessEqual(newdir._node.get_version(), MDMF_VERSION))
1776 def test_PUT_NEWDIRURL_mkdirs_sdmf(self):
1777 d = self.PUT(self.public_url + "/foo/subdir/newdir?t=mkdir&format=sdmf", "")
1778 d.addCallback(lambda ignored:
1779 self.failUnlessNodeHasChild(self._foo_node, u"subdir"))
1780 d.addCallback(lambda ignored:
1781 self.failIfNodeHasChild(self._foo_node, u"newdir"))
1782 d.addCallback(lambda ignored:
1783 self._foo_node.get_child_at_path(u"subdir"))
1784 def _got_subdir(subdir):
1785 # XXX: What we want?
1786 #self.failUnlessEqual(subdir._node.get_version(), MDMF_VERSION)
1787 self.failUnlessNodeHasChild(subdir, u"newdir")
1788 return subdir.get_child_at_path(u"newdir")
1789 d.addCallback(_got_subdir)
1790 d.addCallback(lambda newdir:
1791 self.failUnlessEqual(newdir._node.get_version(), SDMF_VERSION))
1794 def test_PUT_NEWDIRURL_mkdirs_bad_format(self):
1795 return self.shouldHTTPError("PUT_NEWDIRURL_mkdirs_bad_format",
1796 400, "Bad Request", "Unknown format: foo",
1797 self.PUT, self.public_url + \
1798 "/foo/subdir/newdir?t=mkdir&format=foo",
1801 def test_DELETE_DIRURL(self):
1802 d = self.DELETE(self.public_url + "/foo")
1803 d.addCallback(lambda res:
1804 self.failIfNodeHasChild(self.public_root, u"foo"))
1807 def test_DELETE_DIRURL_missing(self):
1808 d = self.DELETE(self.public_url + "/foo/missing")
1809 d.addBoth(self.should404, "test_DELETE_DIRURL_missing")
1810 d.addCallback(lambda res:
1811 self.failUnlessNodeHasChild(self.public_root, u"foo"))
1814 def test_DELETE_DIRURL_missing2(self):
1815 d = self.DELETE(self.public_url + "/missing")
1816 d.addBoth(self.should404, "test_DELETE_DIRURL_missing2")
1819 def dump_root(self):
1821 w = webish.DirnodeWalkerMixin()
1822 def visitor(childpath, childnode, metadata):
1824 d = w.walk(self.public_root, visitor)
1827 def failUnlessNodeKeysAre(self, node, expected_keys):
1828 for k in expected_keys:
1829 assert isinstance(k, unicode)
1831 def _check(children):
1832 self.failUnlessReallyEqual(sorted(children.keys()), sorted(expected_keys))
1833 d.addCallback(_check)
1835 def failUnlessNodeHasChild(self, node, name):
1836 assert isinstance(name, unicode)
1838 def _check(children):
1839 self.failUnlessIn(name, children)
1840 d.addCallback(_check)
1842 def failIfNodeHasChild(self, node, name):
1843 assert isinstance(name, unicode)
1845 def _check(children):
1846 self.failIfIn(name, children)
1847 d.addCallback(_check)
1850 def failUnlessChildContentsAre(self, node, name, expected_contents):
1851 assert isinstance(name, unicode)
1852 d = node.get_child_at_path(name)
1853 d.addCallback(lambda node: download_to_data(node))
1854 def _check(contents):
1855 self.failUnlessReallyEqual(contents, expected_contents)
1856 d.addCallback(_check)
1859 def failUnlessMutableChildContentsAre(self, node, name, expected_contents):
1860 assert isinstance(name, unicode)
1861 d = node.get_child_at_path(name)
1862 d.addCallback(lambda node: node.download_best_version())
1863 def _check(contents):
1864 self.failUnlessReallyEqual(contents, expected_contents)
1865 d.addCallback(_check)
1868 def failUnlessRWChildURIIs(self, node, name, expected_uri):
1869 assert isinstance(name, unicode)
1870 d = node.get_child_at_path(name)
1872 self.failUnless(child.is_unknown() or not child.is_readonly())
1873 self.failUnlessReallyEqual(child.get_uri(), expected_uri.strip())
1874 self.failUnlessReallyEqual(child.get_write_uri(), expected_uri.strip())
1875 expected_ro_uri = self._make_readonly(expected_uri)
1877 self.failUnlessReallyEqual(child.get_readonly_uri(), expected_ro_uri.strip())
1878 d.addCallback(_check)
1881 def failUnlessROChildURIIs(self, node, name, expected_uri):
1882 assert isinstance(name, unicode)
1883 d = node.get_child_at_path(name)
1885 self.failUnless(child.is_unknown() or child.is_readonly())
1886 self.failUnlessReallyEqual(child.get_write_uri(), None)
1887 self.failUnlessReallyEqual(child.get_uri(), expected_uri.strip())
1888 self.failUnlessReallyEqual(child.get_readonly_uri(), expected_uri.strip())
1889 d.addCallback(_check)
1892 def failUnlessURIMatchesRWChild(self, got_uri, node, name):
1893 assert isinstance(name, unicode)
1894 d = node.get_child_at_path(name)
1896 self.failUnless(child.is_unknown() or not child.is_readonly())
1897 self.failUnlessReallyEqual(child.get_uri(), got_uri.strip())
1898 self.failUnlessReallyEqual(child.get_write_uri(), got_uri.strip())
1899 expected_ro_uri = self._make_readonly(got_uri)
1901 self.failUnlessReallyEqual(child.get_readonly_uri(), expected_ro_uri.strip())
1902 d.addCallback(_check)
1905 def failUnlessURIMatchesROChild(self, got_uri, node, name):
1906 assert isinstance(name, unicode)
1907 d = node.get_child_at_path(name)
1909 self.failUnless(child.is_unknown() or child.is_readonly())
1910 self.failUnlessReallyEqual(child.get_write_uri(), None)
1911 self.failUnlessReallyEqual(got_uri.strip(), child.get_uri())
1912 self.failUnlessReallyEqual(got_uri.strip(), child.get_readonly_uri())
1913 d.addCallback(_check)
1916 def failUnlessCHKURIHasContents(self, got_uri, contents):
1917 self.failUnless(FakeCHKFileNode.all_contents[got_uri] == contents)
1919 def test_POST_upload(self):
1920 d = self.POST(self.public_url + "/foo", t="upload",
1921 file=("new.txt", self.NEWFILE_CONTENTS))
1923 d.addCallback(self.failUnlessURIMatchesROChild, fn, u"new.txt")
1924 d.addCallback(lambda res:
1925 self.failUnlessChildContentsAre(fn, u"new.txt",
1926 self.NEWFILE_CONTENTS))
1929 def test_POST_upload_unicode(self):
1930 filename = u"n\u00e9wer.txt" # n e-acute w e r . t x t
1931 d = self.POST(self.public_url + "/foo", t="upload",
1932 file=(filename, self.NEWFILE_CONTENTS))
1934 d.addCallback(self.failUnlessURIMatchesROChild, fn, filename)
1935 d.addCallback(lambda res:
1936 self.failUnlessChildContentsAre(fn, filename,
1937 self.NEWFILE_CONTENTS))
1938 target_url = self.public_url + "/foo/" + filename.encode("utf-8")
1939 d.addCallback(lambda res: self.GET(target_url))
1940 d.addCallback(lambda contents: self.failUnlessReallyEqual(contents,
1941 self.NEWFILE_CONTENTS,
1945 def test_POST_upload_unicode_named(self):
1946 filename = u"n\u00e9wer.txt" # n e-acute w e r . t x t
1947 d = self.POST(self.public_url + "/foo", t="upload",
1949 file=("overridden", self.NEWFILE_CONTENTS))
1951 d.addCallback(self.failUnlessURIMatchesROChild, fn, filename)
1952 d.addCallback(lambda res:
1953 self.failUnlessChildContentsAre(fn, filename,
1954 self.NEWFILE_CONTENTS))
1955 target_url = self.public_url + "/foo/" + filename.encode("utf-8")
1956 d.addCallback(lambda res: self.GET(target_url))
1957 d.addCallback(lambda contents: self.failUnlessReallyEqual(contents,
1958 self.NEWFILE_CONTENTS,
1962 def test_POST_upload_no_link(self):
1963 d = self.POST("/uri", t="upload",
1964 file=("new.txt", self.NEWFILE_CONTENTS))
1965 def _check_upload_results(page):
1966 # this should be a page which describes the results of the upload
1967 # that just finished.
1968 self.failUnlessIn("Upload Results:", page)
1969 self.failUnlessIn("URI:", page)
1970 uri_re = re.compile("URI: <tt><span>(.*)</span>")
1971 mo = uri_re.search(page)
1972 self.failUnless(mo, page)
1973 new_uri = mo.group(1)
1975 d.addCallback(_check_upload_results)
1976 d.addCallback(self.failUnlessCHKURIHasContents, self.NEWFILE_CONTENTS)
1979 def test_POST_upload_no_link_whendone(self):
1980 d = self.POST("/uri", t="upload", when_done="/",
1981 file=("new.txt", self.NEWFILE_CONTENTS))
1982 d.addBoth(self.shouldRedirect, "/")
1985 def shouldRedirect2(self, which, checker, callable, *args, **kwargs):
1986 d = defer.maybeDeferred(callable, *args, **kwargs)
1988 if isinstance(res, failure.Failure):
1989 res.trap(error.PageRedirect)
1990 statuscode = res.value.status
1991 target = res.value.location
1992 return checker(statuscode, target)
1993 self.fail("%s: callable was supposed to redirect, not return '%s'"
1998 def test_POST_upload_no_link_whendone_results(self):
1999 def check(statuscode, target):
2000 self.failUnlessReallyEqual(statuscode, str(http.FOUND))
2001 self.failUnless(target.startswith(self.webish_url), target)
2002 return client.getPage(target, method="GET")
2003 d = self.shouldRedirect2("test_POST_upload_no_link_whendone_results",
2005 self.POST, "/uri", t="upload",
2006 when_done="/uri/%(uri)s",
2007 file=("new.txt", self.NEWFILE_CONTENTS))
2008 d.addCallback(lambda res:
2009 self.failUnlessReallyEqual(res, self.NEWFILE_CONTENTS))
2012 def test_POST_upload_no_link_mutable(self):
2013 d = self.POST("/uri", t="upload", mutable="true",
2014 file=("new.txt", self.NEWFILE_CONTENTS))
2015 def _check(filecap):
2016 filecap = filecap.strip()
2017 self.failUnless(filecap.startswith("URI:SSK:"), filecap)
2018 self.filecap = filecap
2019 u = uri.WriteableSSKFileURI.init_from_string(filecap)
2020 self.failUnlessIn(u.get_storage_index(), FakeMutableFileNode.all_contents)
2021 n = self.s.create_node_from_uri(filecap)
2022 return n.download_best_version()
2023 d.addCallback(_check)
2025 self.failUnlessReallyEqual(data, self.NEWFILE_CONTENTS)
2026 return self.GET("/uri/%s" % urllib.quote(self.filecap))
2027 d.addCallback(_check2)
2029 self.failUnlessReallyEqual(data, self.NEWFILE_CONTENTS)
2030 return self.GET("/file/%s" % urllib.quote(self.filecap))
2031 d.addCallback(_check3)
2033 self.failUnlessReallyEqual(data, self.NEWFILE_CONTENTS)
2034 d.addCallback(_check4)
2037 def test_POST_upload_no_link_mutable_toobig(self):
2038 # The SDMF size limit is no longer in place, so we should be
2039 # able to upload mutable files that are as large as we want them
2041 d = self.POST("/uri", t="upload", mutable="true",
2042 file=("new.txt", "b" * (self.s.MUTABLE_SIZELIMIT + 1)))
2046 def test_POST_upload_format_unlinked(self):
2047 def _check_upload_unlinked(ign, format, uri_prefix):
2048 filename = format + ".txt"
2049 d = self.POST("/uri?t=upload&format=" + format,
2050 file=(filename, self.NEWFILE_CONTENTS * 300000))
2051 def _got_results(results):
2052 if format.upper() in ("SDMF", "MDMF"):
2053 # webapi.rst says this returns a filecap
2056 # for immutable, it returns an "upload results page", and
2057 # the filecap is buried inside
2058 line = [l for l in results.split("\n") if "URI: " in l][0]
2059 mo = re.search(r'<span>([^<]+)</span>', line)
2060 filecap = mo.group(1)
2061 self.failUnless(filecap.startswith(uri_prefix),
2062 (uri_prefix, filecap))
2063 return self.GET("/uri/%s?t=json" % filecap)
2064 d.addCallback(_got_results)
2065 def _got_json(json):
2066 data = simplejson.loads(json)
2068 self.failUnlessIn("format", data)
2069 self.failUnlessEqual(data["format"], format.upper())
2070 d.addCallback(_got_json)
2072 d = defer.succeed(None)
2073 d.addCallback(_check_upload_unlinked, "chk", "URI:CHK")
2074 d.addCallback(_check_upload_unlinked, "CHK", "URI:CHK")
2075 d.addCallback(_check_upload_unlinked, "sdmf", "URI:SSK")
2076 d.addCallback(_check_upload_unlinked, "mdmf", "URI:MDMF")
2079 def test_POST_upload_bad_format_unlinked(self):
2080 return self.shouldHTTPError("POST_upload_bad_format_unlinked",
2081 400, "Bad Request", "Unknown format: foo",
2083 "/uri?t=upload&format=foo",
2084 file=("foo.txt", self.NEWFILE_CONTENTS * 300000))
2086 def test_POST_upload_format(self):
2087 def _check_upload(ign, format, uri_prefix, fn=None):
2088 filename = format + ".txt"
2089 d = self.POST(self.public_url +
2090 "/foo?t=upload&format=" + format,
2091 file=(filename, self.NEWFILE_CONTENTS * 300000))
2092 def _got_filecap(filecap):
2094 filenameu = unicode(filename)
2095 self.failUnlessURIMatchesRWChild(filecap, fn, filenameu)
2096 self.failUnless(filecap.startswith(uri_prefix))
2097 return self.GET(self.public_url + "/foo/%s?t=json" % filename)
2098 d.addCallback(_got_filecap)
2099 def _got_json(json):
2100 data = simplejson.loads(json)
2102 self.failUnlessIn("format", data)
2103 self.failUnlessEqual(data["format"], format.upper())
2104 d.addCallback(_got_json)
2107 d = defer.succeed(None)
2108 d.addCallback(_check_upload, "chk", "URI:CHK")
2109 d.addCallback(_check_upload, "sdmf", "URI:SSK", self._foo_node)
2110 d.addCallback(_check_upload, "mdmf", "URI:MDMF")
2111 d.addCallback(_check_upload, "MDMF", "URI:MDMF")
2114 def test_POST_upload_bad_format(self):
2115 return self.shouldHTTPError("POST_upload_bad_format",
2116 400, "Bad Request", "Unknown format: foo",
2117 self.POST, self.public_url + \
2118 "/foo?t=upload&format=foo",
2119 file=("foo.txt", self.NEWFILE_CONTENTS * 300000))
2121 def test_POST_upload_mutable(self):
2122 # this creates a mutable file
2123 d = self.POST(self.public_url + "/foo", t="upload", mutable="true",
2124 file=("new.txt", self.NEWFILE_CONTENTS))
2126 d.addCallback(self.failUnlessURIMatchesRWChild, fn, u"new.txt")
2127 d.addCallback(lambda res:
2128 self.failUnlessMutableChildContentsAre(fn, u"new.txt",
2129 self.NEWFILE_CONTENTS))
2130 d.addCallback(lambda res: self._foo_node.get(u"new.txt"))
2132 self.failUnless(IMutableFileNode.providedBy(newnode))
2133 self.failUnless(newnode.is_mutable())
2134 self.failIf(newnode.is_readonly())
2135 self._mutable_node = newnode
2136 self._mutable_uri = newnode.get_uri()
2139 # now upload it again and make sure that the URI doesn't change
2140 NEWER_CONTENTS = self.NEWFILE_CONTENTS + "newer\n"
2141 d.addCallback(lambda res:
2142 self.POST(self.public_url + "/foo", t="upload",
2144 file=("new.txt", NEWER_CONTENTS)))
2145 d.addCallback(self.failUnlessURIMatchesRWChild, fn, u"new.txt")
2146 d.addCallback(lambda res:
2147 self.failUnlessMutableChildContentsAre(fn, u"new.txt",
2149 d.addCallback(lambda res: self._foo_node.get(u"new.txt"))
2151 self.failUnless(IMutableFileNode.providedBy(newnode))
2152 self.failUnless(newnode.is_mutable())
2153 self.failIf(newnode.is_readonly())
2154 self.failUnlessReallyEqual(self._mutable_uri, newnode.get_uri())
2155 d.addCallback(_got2)
2157 # upload a second time, using PUT instead of POST
2158 NEW2_CONTENTS = NEWER_CONTENTS + "overwrite with PUT\n"
2159 d.addCallback(lambda res:
2160 self.PUT(self.public_url + "/foo/new.txt", NEW2_CONTENTS))
2161 d.addCallback(self.failUnlessURIMatchesRWChild, fn, u"new.txt")
2162 d.addCallback(lambda res:
2163 self.failUnlessMutableChildContentsAre(fn, u"new.txt",
2166 # finally list the directory, since mutable files are displayed
2167 # slightly differently
2169 d.addCallback(lambda res:
2170 self.GET(self.public_url + "/foo/",
2171 followRedirect=True))
2172 def _check_page(res):
2173 # TODO: assert more about the contents
2174 self.failUnlessIn("SSK", res)
2176 d.addCallback(_check_page)
2178 d.addCallback(lambda res: self._foo_node.get(u"new.txt"))
2180 self.failUnless(IMutableFileNode.providedBy(newnode))
2181 self.failUnless(newnode.is_mutable())
2182 self.failIf(newnode.is_readonly())
2183 self.failUnlessReallyEqual(self._mutable_uri, newnode.get_uri())
2184 d.addCallback(_got3)
2186 # look at the JSON form of the enclosing directory
2187 d.addCallback(lambda res:
2188 self.GET(self.public_url + "/foo/?t=json",
2189 followRedirect=True))
2190 def _check_page_json(res):
2191 parsed = simplejson.loads(res)
2192 self.failUnlessEqual(parsed[0], "dirnode")
2193 children = dict( [(unicode(name),value)
2195 in parsed[1]["children"].iteritems()] )
2196 self.failUnlessIn(u"new.txt", children)
2197 new_json = children[u"new.txt"]
2198 self.failUnlessEqual(new_json[0], "filenode")
2199 self.failUnless(new_json[1]["mutable"])
2200 self.failUnlessReallyEqual(to_str(new_json[1]["rw_uri"]), self._mutable_uri)
2201 ro_uri = self._mutable_node.get_readonly().to_string()
2202 self.failUnlessReallyEqual(to_str(new_json[1]["ro_uri"]), ro_uri)
2203 d.addCallback(_check_page_json)
2205 # and the JSON form of the file
2206 d.addCallback(lambda res:
2207 self.GET(self.public_url + "/foo/new.txt?t=json"))
2208 def _check_file_json(res):
2209 parsed = simplejson.loads(res)
2210 self.failUnlessEqual(parsed[0], "filenode")
2211 self.failUnless(parsed[1]["mutable"])
2212 self.failUnlessReallyEqual(to_str(parsed[1]["rw_uri"]), self._mutable_uri)
2213 ro_uri = self._mutable_node.get_readonly().to_string()
2214 self.failUnlessReallyEqual(to_str(parsed[1]["ro_uri"]), ro_uri)
2215 d.addCallback(_check_file_json)
2217 # and look at t=uri and t=readonly-uri
2218 d.addCallback(lambda res:
2219 self.GET(self.public_url + "/foo/new.txt?t=uri"))
2220 d.addCallback(lambda res: self.failUnlessReallyEqual(res, self._mutable_uri))
2221 d.addCallback(lambda res:
2222 self.GET(self.public_url + "/foo/new.txt?t=readonly-uri"))
2223 def _check_ro_uri(res):
2224 ro_uri = self._mutable_node.get_readonly().to_string()
2225 self.failUnlessReallyEqual(res, ro_uri)
2226 d.addCallback(_check_ro_uri)
2228 # make sure we can get to it from /uri/URI
2229 d.addCallback(lambda res:
2230 self.GET("/uri/%s" % urllib.quote(self._mutable_uri)))
2231 d.addCallback(lambda res:
2232 self.failUnlessReallyEqual(res, NEW2_CONTENTS))
2234 # and that HEAD computes the size correctly
2235 d.addCallback(lambda res:
2236 self.HEAD(self.public_url + "/foo/new.txt",
2237 return_response=True))
2238 def _got_headers((res, status, headers)):
2239 self.failUnlessReallyEqual(res, "")
2240 self.failUnlessReallyEqual(headers["content-length"][0],
2241 str(len(NEW2_CONTENTS)))
2242 self.failUnlessReallyEqual(headers["content-type"], ["text/plain"])
2243 d.addCallback(_got_headers)
2245 # make sure that outdated size limits aren't enforced anymore.
2246 d.addCallback(lambda ignored:
2247 self.POST(self.public_url + "/foo", t="upload",
2250 "b" * (self.s.MUTABLE_SIZELIMIT+1))))
2251 d.addErrback(self.dump_error)
2254 def test_POST_upload_mutable_toobig(self):
2255 # SDMF had a size limti that was removed a while ago. MDMF has
2256 # never had a size limit. Test to make sure that we do not
2257 # encounter errors when trying to upload large mutable files,
2258 # since there should be no coded prohibitions regarding large
2260 d = self.POST(self.public_url + "/foo",
2261 t="upload", mutable="true",
2262 file=("new.txt", "b" * (self.s.MUTABLE_SIZELIMIT + 1)))
2265 def dump_error(self, f):
2266 # if the web server returns an error code (like 400 Bad Request),
2267 # web.client.getPage puts the HTTP response body into the .response
2268 # attribute of the exception object that it gives back. It does not
2269 # appear in the Failure's repr(), so the ERROR that trial displays
2270 # will be rather terse and unhelpful. addErrback this method to the
2271 # end of your chain to get more information out of these errors.
2272 if f.check(error.Error):
2273 print "web.error.Error:"
2275 print f.value.response
2278 def test_POST_upload_replace(self):
2279 d = self.POST(self.public_url + "/foo", t="upload",
2280 file=("bar.txt", self.NEWFILE_CONTENTS))
2282 d.addCallback(self.failUnlessURIMatchesROChild, fn, u"bar.txt")
2283 d.addCallback(lambda res:
2284 self.failUnlessChildContentsAre(fn, u"bar.txt",
2285 self.NEWFILE_CONTENTS))
2288 def test_POST_upload_no_replace_ok(self):
2289 d = self.POST(self.public_url + "/foo?replace=false", t="upload",
2290 file=("new.txt", self.NEWFILE_CONTENTS))
2291 d.addCallback(lambda res: self.GET(self.public_url + "/foo/new.txt"))
2292 d.addCallback(lambda res: self.failUnlessReallyEqual(res,
2293 self.NEWFILE_CONTENTS))
2296 def test_POST_upload_no_replace_queryarg(self):
2297 d = self.POST(self.public_url + "/foo?replace=false", t="upload",
2298 file=("bar.txt", self.NEWFILE_CONTENTS))
2299 d.addBoth(self.shouldFail, error.Error,
2300 "POST_upload_no_replace_queryarg",
2302 "There was already a child by that name, and you asked me "
2303 "to not replace it")
2304 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
2305 d.addCallback(self.failUnlessIsBarDotTxt)
2308 def test_POST_upload_no_replace_field(self):
2309 d = self.POST(self.public_url + "/foo", t="upload", replace="false",
2310 file=("bar.txt", self.NEWFILE_CONTENTS))
2311 d.addBoth(self.shouldFail, error.Error, "POST_upload_no_replace_field",
2313 "There was already a child by that name, and you asked me "
2314 "to not replace it")
2315 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
2316 d.addCallback(self.failUnlessIsBarDotTxt)
2319 def test_POST_upload_whendone(self):
2320 d = self.POST(self.public_url + "/foo", t="upload", when_done="/THERE",
2321 file=("new.txt", self.NEWFILE_CONTENTS))
2322 d.addBoth(self.shouldRedirect, "/THERE")
2324 d.addCallback(lambda res:
2325 self.failUnlessChildContentsAre(fn, u"new.txt",
2326 self.NEWFILE_CONTENTS))
2329 def test_POST_upload_named(self):
2331 d = self.POST(self.public_url + "/foo", t="upload",
2332 name="new.txt", file=self.NEWFILE_CONTENTS)
2333 d.addCallback(self.failUnlessURIMatchesROChild, fn, u"new.txt")
2334 d.addCallback(lambda res:
2335 self.failUnlessChildContentsAre(fn, u"new.txt",
2336 self.NEWFILE_CONTENTS))
2339 def test_POST_upload_named_badfilename(self):
2340 d = self.POST(self.public_url + "/foo", t="upload",
2341 name="slashes/are/bad.txt", file=self.NEWFILE_CONTENTS)
2342 d.addBoth(self.shouldFail, error.Error,
2343 "test_POST_upload_named_badfilename",
2345 "name= may not contain a slash",
2347 # make sure that nothing was added
2348 d.addCallback(lambda res:
2349 self.failUnlessNodeKeysAre(self._foo_node,
2350 [u"bar.txt", u"baz.txt", u"blockingfile",
2351 u"empty", u"n\u00fc.txt", u"quux.txt",
2355 def test_POST_FILEURL_check(self):
2356 bar_url = self.public_url + "/foo/bar.txt"
2357 d = self.POST(bar_url, t="check")
2359 self.failUnlessIn("Healthy :", res)
2360 d.addCallback(_check)
2361 redir_url = "http://allmydata.org/TARGET"
2362 def _check2(statuscode, target):
2363 self.failUnlessReallyEqual(statuscode, str(http.FOUND))
2364 self.failUnlessReallyEqual(target, redir_url)
2365 d.addCallback(lambda res:
2366 self.shouldRedirect2("test_POST_FILEURL_check",
2370 when_done=redir_url))
2371 d.addCallback(lambda res:
2372 self.POST(bar_url, t="check", return_to=redir_url))
2374 self.failUnlessIn("Healthy :", res)
2375 self.failUnlessIn("Return to file", res)
2376 self.failUnlessIn(redir_url, res)
2377 d.addCallback(_check3)
2379 d.addCallback(lambda res:
2380 self.POST(bar_url, t="check", output="JSON"))
2381 def _check_json(res):
2382 data = simplejson.loads(res)
2383 self.failUnlessIn("storage-index", data)
2384 self.failUnless(data["results"]["healthy"])
2385 d.addCallback(_check_json)
2389 def test_POST_FILEURL_check_and_repair(self):
2390 bar_url = self.public_url + "/foo/bar.txt"
2391 d = self.POST(bar_url, t="check", repair="true")
2393 self.failUnlessIn("Healthy :", res)
2394 d.addCallback(_check)
2395 redir_url = "http://allmydata.org/TARGET"
2396 def _check2(statuscode, target):
2397 self.failUnlessReallyEqual(statuscode, str(http.FOUND))
2398 self.failUnlessReallyEqual(target, redir_url)
2399 d.addCallback(lambda res:
2400 self.shouldRedirect2("test_POST_FILEURL_check_and_repair",
2403 t="check", repair="true",
2404 when_done=redir_url))
2405 d.addCallback(lambda res:
2406 self.POST(bar_url, t="check", return_to=redir_url))
2408 self.failUnlessIn("Healthy :", res)
2409 self.failUnlessIn("Return to file", res)
2410 self.failUnlessIn(redir_url, res)
2411 d.addCallback(_check3)
2414 def test_POST_DIRURL_check(self):
2415 foo_url = self.public_url + "/foo/"
2416 d = self.POST(foo_url, t="check")
2418 self.failUnlessIn("Healthy :", res)
2419 d.addCallback(_check)
2420 redir_url = "http://allmydata.org/TARGET"
2421 def _check2(statuscode, target):
2422 self.failUnlessReallyEqual(statuscode, str(http.FOUND))
2423 self.failUnlessReallyEqual(target, redir_url)
2424 d.addCallback(lambda res:
2425 self.shouldRedirect2("test_POST_DIRURL_check",
2429 when_done=redir_url))
2430 d.addCallback(lambda res:
2431 self.POST(foo_url, t="check", return_to=redir_url))
2433 self.failUnlessIn("Healthy :", res)
2434 self.failUnlessIn("Return to file/directory", res)
2435 self.failUnlessIn(redir_url, res)
2436 d.addCallback(_check3)
2438 d.addCallback(lambda res:
2439 self.POST(foo_url, t="check", output="JSON"))
2440 def _check_json(res):
2441 data = simplejson.loads(res)
2442 self.failUnlessIn("storage-index", data)
2443 self.failUnless(data["results"]["healthy"])
2444 d.addCallback(_check_json)
2448 def test_POST_DIRURL_check_and_repair(self):
2449 foo_url = self.public_url + "/foo/"
2450 d = self.POST(foo_url, t="check", repair="true")
2452 self.failUnlessIn("Healthy :", res)
2453 d.addCallback(_check)
2454 redir_url = "http://allmydata.org/TARGET"
2455 def _check2(statuscode, target):
2456 self.failUnlessReallyEqual(statuscode, str(http.FOUND))
2457 self.failUnlessReallyEqual(target, redir_url)
2458 d.addCallback(lambda res:
2459 self.shouldRedirect2("test_POST_DIRURL_check_and_repair",
2462 t="check", repair="true",
2463 when_done=redir_url))
2464 d.addCallback(lambda res:
2465 self.POST(foo_url, t="check", return_to=redir_url))
2467 self.failUnlessIn("Healthy :", res)
2468 self.failUnlessIn("Return to file/directory", res)
2469 self.failUnlessIn(redir_url, res)
2470 d.addCallback(_check3)
2473 def test_POST_FILEURL_mdmf_check(self):
2474 quux_url = "/uri/%s" % urllib.quote(self._quux_txt_uri)
2475 d = self.POST(quux_url, t="check")
2477 self.failUnlessIn("Healthy", res)
2478 d.addCallback(_check)
2479 quux_extension_url = "/uri/%s" % urllib.quote("%s:3:131073" % self._quux_txt_uri)
2480 d.addCallback(lambda ignored:
2481 self.POST(quux_extension_url, t="check"))
2482 d.addCallback(_check)
2485 def test_POST_FILEURL_mdmf_check_and_repair(self):
2486 quux_url = "/uri/%s" % urllib.quote(self._quux_txt_uri)
2487 d = self.POST(quux_url, t="check", repair="true")
2489 self.failUnlessIn("Healthy", res)
2490 d.addCallback(_check)
2491 quux_extension_url = "/uri/%s" % urllib.quote("%s:3:131073" % self._quux_txt_uri)
2492 d.addCallback(lambda ignored:
2493 self.POST(quux_extension_url, t="check", repair="true"))
2494 d.addCallback(_check)
2497 def wait_for_operation(self, ignored, ophandle):
2498 url = "/operations/" + ophandle
2499 url += "?t=status&output=JSON"
2502 data = simplejson.loads(res)
2503 if not data["finished"]:
2504 d = self.stall(delay=1.0)
2505 d.addCallback(self.wait_for_operation, ophandle)
2511 def get_operation_results(self, ignored, ophandle, output=None):
2512 url = "/operations/" + ophandle
2515 url += "&output=" + output
2518 if output and output.lower() == "json":
2519 return simplejson.loads(res)
2524 def test_POST_DIRURL_deepcheck_no_ophandle(self):
2525 d = self.shouldFail2(error.Error,
2526 "test_POST_DIRURL_deepcheck_no_ophandle",
2528 "slow operation requires ophandle=",
2529 self.POST, self.public_url, t="start-deep-check")
2532 def test_POST_DIRURL_deepcheck(self):
2533 def _check_redirect(statuscode, target):
2534 self.failUnlessReallyEqual(statuscode, str(http.FOUND))
2535 self.failUnless(target.endswith("/operations/123"))
2536 d = self.shouldRedirect2("test_POST_DIRURL_deepcheck", _check_redirect,
2537 self.POST, self.public_url,
2538 t="start-deep-check", ophandle="123")
2539 d.addCallback(self.wait_for_operation, "123")
2540 def _check_json(data):
2541 self.failUnlessReallyEqual(data["finished"], True)
2542 self.failUnlessReallyEqual(data["count-objects-checked"], 10)
2543 self.failUnlessReallyEqual(data["count-objects-healthy"], 10)
2544 d.addCallback(_check_json)
2545 d.addCallback(self.get_operation_results, "123", "html")
2546 def _check_html(res):
2547 self.failUnlessIn("Objects Checked: <span>10</span>", res)
2548 self.failUnlessIn("Objects Healthy: <span>10</span>", res)
2549 self.failUnlessIn(FAVICON_MARKUP, res)
2550 d.addCallback(_check_html)
2552 d.addCallback(lambda res:
2553 self.GET("/operations/123/"))
2554 d.addCallback(_check_html) # should be the same as without the slash
2556 d.addCallback(lambda res:
2557 self.shouldFail2(error.Error, "one", "404 Not Found",
2558 "No detailed results for SI bogus",
2559 self.GET, "/operations/123/bogus"))
2561 foo_si = self._foo_node.get_storage_index()
2562 foo_si_s = base32.b2a(foo_si)
2563 d.addCallback(lambda res:
2564 self.GET("/operations/123/%s?output=JSON" % foo_si_s))
2565 def _check_foo_json(res):
2566 data = simplejson.loads(res)
2567 self.failUnlessEqual(data["storage-index"], foo_si_s)
2568 self.failUnless(data["results"]["healthy"])
2569 d.addCallback(_check_foo_json)
2572 def test_POST_DIRURL_deepcheck_and_repair(self):
2573 d = self.POST(self.public_url, t="start-deep-check", repair="true",
2574 ophandle="124", output="json", followRedirect=True)
2575 d.addCallback(self.wait_for_operation, "124")
2576 def _check_json(data):
2577 self.failUnlessReallyEqual(data["finished"], True)
2578 self.failUnlessReallyEqual(data["count-objects-checked"], 10)
2579 self.failUnlessReallyEqual(data["count-objects-healthy-pre-repair"], 10)
2580 self.failUnlessReallyEqual(data["count-objects-unhealthy-pre-repair"], 0)
2581 self.failUnlessReallyEqual(data["count-corrupt-shares-pre-repair"], 0)
2582 self.failUnlessReallyEqual(data["count-repairs-attempted"], 0)
2583 self.failUnlessReallyEqual(data["count-repairs-successful"], 0)
2584 self.failUnlessReallyEqual(data["count-repairs-unsuccessful"], 0)
2585 self.failUnlessReallyEqual(data["count-objects-healthy-post-repair"], 10)
2586 self.failUnlessReallyEqual(data["count-objects-unhealthy-post-repair"], 0)
2587 self.failUnlessReallyEqual(data["count-corrupt-shares-post-repair"], 0)
2588 d.addCallback(_check_json)
2589 d.addCallback(self.get_operation_results, "124", "html")
2590 def _check_html(res):
2591 self.failUnlessIn("Objects Checked: <span>10</span>", res)
2593 self.failUnlessIn("Objects Healthy (before repair): <span>10</span>", res)
2594 self.failUnlessIn("Objects Unhealthy (before repair): <span>0</span>", res)
2595 self.failUnlessIn("Corrupt Shares (before repair): <span>0</span>", res)
2597 self.failUnlessIn("Repairs Attempted: <span>0</span>", res)
2598 self.failUnlessIn("Repairs Successful: <span>0</span>", res)
2599 self.failUnlessIn("Repairs Unsuccessful: <span>0</span>", res)
2601 self.failUnlessIn("Objects Healthy (after repair): <span>10</span>", res)
2602 self.failUnlessIn("Objects Unhealthy (after repair): <span>0</span>", res)
2603 self.failUnlessIn("Corrupt Shares (after repair): <span>0</span>", res)
2605 self.failUnlessIn(FAVICON_MARKUP, res)
2606 d.addCallback(_check_html)
2609 def test_POST_FILEURL_bad_t(self):
2610 d = self.shouldFail2(error.Error, "POST_bad_t", "400 Bad Request",
2611 "POST to file: bad t=bogus",
2612 self.POST, self.public_url + "/foo/bar.txt",
2616 def test_POST_mkdir(self): # return value?
2617 d = self.POST(self.public_url + "/foo", t="mkdir", name="newdir")
2618 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2619 d.addCallback(self.failUnlessNodeKeysAre, [])
2622 def test_POST_mkdir_mdmf(self):
2623 d = self.POST(self.public_url + "/foo?t=mkdir&name=newdir&format=mdmf")
2624 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2625 d.addCallback(lambda node:
2626 self.failUnlessEqual(node._node.get_version(), MDMF_VERSION))
2629 def test_POST_mkdir_sdmf(self):
2630 d = self.POST(self.public_url + "/foo?t=mkdir&name=newdir&format=sdmf")
2631 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2632 d.addCallback(lambda node:
2633 self.failUnlessEqual(node._node.get_version(), SDMF_VERSION))
2636 def test_POST_mkdir_bad_format(self):
2637 return self.shouldHTTPError("POST_mkdir_bad_format",
2638 400, "Bad Request", "Unknown format: foo",
2639 self.POST, self.public_url +
2640 "/foo?t=mkdir&name=newdir&format=foo")
2642 def test_POST_mkdir_initial_children(self):
2643 (newkids, caps) = self._create_initial_children()
2644 d = self.POST2(self.public_url +
2645 "/foo?t=mkdir-with-children&name=newdir",
2646 simplejson.dumps(newkids))
2647 d.addCallback(lambda res:
2648 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
2649 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2650 d.addCallback(self.failUnlessNodeKeysAre, newkids.keys())
2651 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2652 d.addCallback(self.failUnlessROChildURIIs, u"child-imm", caps['filecap1'])
2655 def test_POST_mkdir_initial_children_mdmf(self):
2656 (newkids, caps) = self._create_initial_children()
2657 d = self.POST2(self.public_url +
2658 "/foo?t=mkdir-with-children&name=newdir&format=mdmf",
2659 simplejson.dumps(newkids))
2660 d.addCallback(lambda res:
2661 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
2662 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2663 d.addCallback(lambda node:
2664 self.failUnlessEqual(node._node.get_version(), MDMF_VERSION))
2665 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2666 d.addCallback(self.failUnlessROChildURIIs, u"child-imm",
2671 def test_POST_mkdir_initial_children_sdmf(self):
2672 (newkids, caps) = self._create_initial_children()
2673 d = self.POST2(self.public_url +
2674 "/foo?t=mkdir-with-children&name=newdir&format=sdmf",
2675 simplejson.dumps(newkids))
2676 d.addCallback(lambda res:
2677 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
2678 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2679 d.addCallback(lambda node:
2680 self.failUnlessEqual(node._node.get_version(), SDMF_VERSION))
2681 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2682 d.addCallback(self.failUnlessROChildURIIs, u"child-imm",
2686 def test_POST_mkdir_initial_children_bad_format(self):
2687 (newkids, caps) = self._create_initial_children()
2688 return self.shouldHTTPError("POST_mkdir_initial_children_bad_format",
2689 400, "Bad Request", "Unknown format: foo",
2690 self.POST, self.public_url + \
2691 "/foo?t=mkdir-with-children&name=newdir&format=foo",
2692 simplejson.dumps(newkids))
2694 def test_POST_mkdir_immutable(self):
2695 (newkids, caps) = self._create_immutable_children()
2696 d = self.POST2(self.public_url +
2697 "/foo?t=mkdir-immutable&name=newdir",
2698 simplejson.dumps(newkids))
2699 d.addCallback(lambda res:
2700 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
2701 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2702 d.addCallback(self.failUnlessNodeKeysAre, newkids.keys())
2703 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2704 d.addCallback(self.failUnlessROChildURIIs, u"child-imm", caps['filecap1'])
2705 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2706 d.addCallback(self.failUnlessROChildURIIs, u"unknownchild-imm", caps['unknown_immcap'])
2707 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2708 d.addCallback(self.failUnlessROChildURIIs, u"dirchild-imm", caps['immdircap'])
2709 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2710 d.addCallback(self.failUnlessROChildURIIs, u"dirchild-lit", caps['litdircap'])
2711 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2712 d.addCallback(self.failUnlessROChildURIIs, u"dirchild-empty", caps['emptydircap'])
2715 def test_POST_mkdir_immutable_bad(self):
2716 (newkids, caps) = self._create_initial_children()
2717 d = self.shouldFail2(error.Error, "POST_mkdir_immutable_bad",
2719 "needed to be immutable but was not",
2722 "/foo?t=mkdir-immutable&name=newdir",
2723 simplejson.dumps(newkids))
2726 def test_POST_mkdir_2(self):
2727 d = self.POST(self.public_url + "/foo/newdir?t=mkdir", "")
2728 d.addCallback(lambda res:
2729 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
2730 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2731 d.addCallback(self.failUnlessNodeKeysAre, [])
2734 def test_POST_mkdirs_2(self):
2735 d = self.POST(self.public_url + "/foo/bardir/newdir?t=mkdir", "")
2736 d.addCallback(lambda res:
2737 self.failUnlessNodeHasChild(self._foo_node, u"bardir"))
2738 d.addCallback(lambda res: self._foo_node.get(u"bardir"))
2739 d.addCallback(lambda bardirnode: bardirnode.get(u"newdir"))
2740 d.addCallback(self.failUnlessNodeKeysAre, [])
2743 def test_POST_mkdir_no_parentdir_noredirect(self):
2744 d = self.POST("/uri?t=mkdir")
2745 def _after_mkdir(res):
2746 uri.DirectoryURI.init_from_string(res)
2747 d.addCallback(_after_mkdir)
2750 def test_POST_mkdir_no_parentdir_noredirect_mdmf(self):
2751 d = self.POST("/uri?t=mkdir&format=mdmf")
2752 def _after_mkdir(res):
2753 u = uri.from_string(res)
2754 # Check that this is an MDMF writecap
2755 self.failUnlessIsInstance(u, uri.MDMFDirectoryURI)
2756 d.addCallback(_after_mkdir)
2759 def test_POST_mkdir_no_parentdir_noredirect_sdmf(self):
2760 d = self.POST("/uri?t=mkdir&format=sdmf")
2761 def _after_mkdir(res):
2762 u = uri.from_string(res)
2763 self.failUnlessIsInstance(u, uri.DirectoryURI)
2764 d.addCallback(_after_mkdir)
2767 def test_POST_mkdir_no_parentdir_noredirect_bad_format(self):
2768 return self.shouldHTTPError("POST_mkdir_no_parentdir_noredirect_bad_format",
2769 400, "Bad Request", "Unknown format: foo",
2770 self.POST, self.public_url +
2771 "/uri?t=mkdir&format=foo")
2773 def test_POST_mkdir_no_parentdir_noredirect2(self):
2774 # make sure form-based arguments (as on the welcome page) still work
2775 d = self.POST("/uri", t="mkdir")
2776 def _after_mkdir(res):
2777 uri.DirectoryURI.init_from_string(res)
2778 d.addCallback(_after_mkdir)
2779 d.addErrback(self.explain_web_error)
2782 def test_POST_mkdir_no_parentdir_redirect(self):
2783 d = self.POST("/uri?t=mkdir&redirect_to_result=true")
2784 d.addBoth(self.shouldRedirect, None, statuscode='303')
2785 def _check_target(target):
2786 target = urllib.unquote(target)
2787 self.failUnless(target.startswith("uri/URI:DIR2:"), target)
2788 d.addCallback(_check_target)
2791 def test_POST_mkdir_no_parentdir_redirect2(self):
2792 d = self.POST("/uri", t="mkdir", redirect_to_result="true")
2793 d.addBoth(self.shouldRedirect, None, statuscode='303')
2794 def _check_target(target):
2795 target = urllib.unquote(target)
2796 self.failUnless(target.startswith("uri/URI:DIR2:"), target)
2797 d.addCallback(_check_target)
2798 d.addErrback(self.explain_web_error)
2801 def _make_readonly(self, u):
2802 ro_uri = uri.from_string(u).get_readonly()
2805 return ro_uri.to_string()
2807 def _create_initial_children(self):
2808 contents, n, filecap1 = self.makefile(12)
2809 md1 = {"metakey1": "metavalue1"}
2810 filecap2 = make_mutable_file_uri()
2811 node3 = self.s.create_node_from_uri(make_mutable_file_uri())
2812 filecap3 = node3.get_readonly_uri()
2813 node4 = self.s.create_node_from_uri(make_mutable_file_uri())
2814 dircap = DirectoryNode(node4, None, None).get_uri()
2815 mdmfcap = make_mutable_file_uri(mdmf=True)
2816 litdircap = "URI:DIR2-LIT:ge3dumj2mewdcotyfqydulbshj5x2lbm"
2817 emptydircap = "URI:DIR2-LIT:"
2818 newkids = {u"child-imm": ["filenode", {"rw_uri": filecap1,
2819 "ro_uri": self._make_readonly(filecap1),
2820 "metadata": md1, }],
2821 u"child-mutable": ["filenode", {"rw_uri": filecap2,
2822 "ro_uri": self._make_readonly(filecap2)}],
2823 u"child-mutable-ro": ["filenode", {"ro_uri": filecap3}],
2824 u"unknownchild-rw": ["unknown", {"rw_uri": unknown_rwcap,
2825 "ro_uri": unknown_rocap}],
2826 u"unknownchild-ro": ["unknown", {"ro_uri": unknown_rocap}],
2827 u"unknownchild-imm": ["unknown", {"ro_uri": unknown_immcap}],
2828 u"dirchild": ["dirnode", {"rw_uri": dircap,
2829 "ro_uri": self._make_readonly(dircap)}],
2830 u"dirchild-lit": ["dirnode", {"ro_uri": litdircap}],
2831 u"dirchild-empty": ["dirnode", {"ro_uri": emptydircap}],
2832 u"child-mutable-mdmf": ["filenode", {"rw_uri": mdmfcap,
2833 "ro_uri": self._make_readonly(mdmfcap)}],
2835 return newkids, {'filecap1': filecap1,
2836 'filecap2': filecap2,
2837 'filecap3': filecap3,
2838 'unknown_rwcap': unknown_rwcap,
2839 'unknown_rocap': unknown_rocap,
2840 'unknown_immcap': unknown_immcap,
2842 'litdircap': litdircap,
2843 'emptydircap': emptydircap,
2846 def _create_immutable_children(self):
2847 contents, n, filecap1 = self.makefile(12)
2848 md1 = {"metakey1": "metavalue1"}
2849 tnode = create_chk_filenode("immutable directory contents\n"*10)
2850 dnode = DirectoryNode(tnode, None, None)
2851 assert not dnode.is_mutable()
2852 immdircap = dnode.get_uri()
2853 litdircap = "URI:DIR2-LIT:ge3dumj2mewdcotyfqydulbshj5x2lbm"
2854 emptydircap = "URI:DIR2-LIT:"
2855 newkids = {u"child-imm": ["filenode", {"ro_uri": filecap1,
2856 "metadata": md1, }],
2857 u"unknownchild-imm": ["unknown", {"ro_uri": unknown_immcap}],
2858 u"dirchild-imm": ["dirnode", {"ro_uri": immdircap}],
2859 u"dirchild-lit": ["dirnode", {"ro_uri": litdircap}],
2860 u"dirchild-empty": ["dirnode", {"ro_uri": emptydircap}],
2862 return newkids, {'filecap1': filecap1,
2863 'unknown_immcap': unknown_immcap,
2864 'immdircap': immdircap,
2865 'litdircap': litdircap,
2866 'emptydircap': emptydircap}
2868 def test_POST_mkdir_no_parentdir_initial_children(self):
2869 (newkids, caps) = self._create_initial_children()
2870 d = self.POST2("/uri?t=mkdir-with-children", simplejson.dumps(newkids))
2871 def _after_mkdir(res):
2872 self.failUnless(res.startswith("URI:DIR"), res)
2873 n = self.s.create_node_from_uri(res)
2874 d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
2875 d2.addCallback(lambda ign:
2876 self.failUnlessROChildURIIs(n, u"child-imm",
2878 d2.addCallback(lambda ign:
2879 self.failUnlessRWChildURIIs(n, u"child-mutable",
2881 d2.addCallback(lambda ign:
2882 self.failUnlessROChildURIIs(n, u"child-mutable-ro",
2884 d2.addCallback(lambda ign:
2885 self.failUnlessRWChildURIIs(n, u"unknownchild-rw",
2886 caps['unknown_rwcap']))
2887 d2.addCallback(lambda ign:
2888 self.failUnlessROChildURIIs(n, u"unknownchild-ro",
2889 caps['unknown_rocap']))
2890 d2.addCallback(lambda ign:
2891 self.failUnlessROChildURIIs(n, u"unknownchild-imm",
2892 caps['unknown_immcap']))
2893 d2.addCallback(lambda ign:
2894 self.failUnlessRWChildURIIs(n, u"dirchild",
2897 d.addCallback(_after_mkdir)
2900 def test_POST_mkdir_no_parentdir_unexpected_children(self):
2901 # the regular /uri?t=mkdir operation is specified to ignore its body.
2902 # Only t=mkdir-with-children pays attention to it.
2903 (newkids, caps) = self._create_initial_children()
2904 d = self.shouldHTTPError("POST_mkdir_no_parentdir_unexpected_children",
2906 "t=mkdir does not accept children=, "
2907 "try t=mkdir-with-children instead",
2908 self.POST2, "/uri?t=mkdir", # without children
2909 simplejson.dumps(newkids))
2912 def test_POST_noparent_bad(self):
2913 d = self.shouldHTTPError("POST_noparent_bad",
2915 "/uri accepts only PUT, PUT?t=mkdir, "
2916 "POST?t=upload, and POST?t=mkdir",
2917 self.POST, "/uri?t=bogus")
2920 def test_POST_mkdir_no_parentdir_immutable(self):
2921 (newkids, caps) = self._create_immutable_children()
2922 d = self.POST2("/uri?t=mkdir-immutable", simplejson.dumps(newkids))
2923 def _after_mkdir(res):
2924 self.failUnless(res.startswith("URI:DIR"), res)
2925 n = self.s.create_node_from_uri(res)
2926 d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
2927 d2.addCallback(lambda ign:
2928 self.failUnlessROChildURIIs(n, u"child-imm",
2930 d2.addCallback(lambda ign:
2931 self.failUnlessROChildURIIs(n, u"unknownchild-imm",
2932 caps['unknown_immcap']))
2933 d2.addCallback(lambda ign:
2934 self.failUnlessROChildURIIs(n, u"dirchild-imm",
2936 d2.addCallback(lambda ign:
2937 self.failUnlessROChildURIIs(n, u"dirchild-lit",
2939 d2.addCallback(lambda ign:
2940 self.failUnlessROChildURIIs(n, u"dirchild-empty",
2941 caps['emptydircap']))
2943 d.addCallback(_after_mkdir)
2946 def test_POST_mkdir_no_parentdir_immutable_bad(self):
2947 (newkids, caps) = self._create_initial_children()
2948 d = self.shouldFail2(error.Error,
2949 "test_POST_mkdir_no_parentdir_immutable_bad",
2951 "needed to be immutable but was not",
2953 "/uri?t=mkdir-immutable",
2954 simplejson.dumps(newkids))
2957 def test_welcome_page_mkdir_button(self):
2958 # Fetch the welcome page.
2960 def _after_get_welcome_page(res):
2961 MKDIR_BUTTON_RE = re.compile(
2962 '<form action="([^"]*)" method="post".*?'
2963 '<input type="hidden" name="t" value="([^"]*)" />'
2964 '<input type="hidden" name="([^"]*)" value="([^"]*)" />'
2965 '<input type="submit" value="Create a directory" />',
2967 mo = MKDIR_BUTTON_RE.search(res)
2968 formaction = mo.group(1)
2970 formaname = mo.group(3)
2971 formavalue = mo.group(4)
2972 return (formaction, formt, formaname, formavalue)
2973 d.addCallback(_after_get_welcome_page)
2974 def _after_parse_form(res):
2975 (formaction, formt, formaname, formavalue) = res
2976 return self.POST("/%s?t=%s&%s=%s" % (formaction, formt, formaname, formavalue))
2977 d.addCallback(_after_parse_form)
2978 d.addBoth(self.shouldRedirect, None, statuscode='303')
2981 def test_POST_mkdir_replace(self): # return value?
2982 d = self.POST(self.public_url + "/foo", t="mkdir", name="sub")
2983 d.addCallback(lambda res: self._foo_node.get(u"sub"))
2984 d.addCallback(self.failUnlessNodeKeysAre, [])
2987 def test_POST_mkdir_no_replace_queryarg(self): # return value?
2988 d = self.POST(self.public_url + "/foo?replace=false", t="mkdir", name="sub")
2989 d.addBoth(self.shouldFail, error.Error,
2990 "POST_mkdir_no_replace_queryarg",
2992 "There was already a child by that name, and you asked me "
2993 "to not replace it")
2994 d.addCallback(lambda res: self._foo_node.get(u"sub"))
2995 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
2998 def test_POST_mkdir_no_replace_field(self): # return value?
2999 d = self.POST(self.public_url + "/foo", t="mkdir", name="sub",
3001 d.addBoth(self.shouldFail, error.Error, "POST_mkdir_no_replace_field",
3003 "There was already a child by that name, and you asked me "
3004 "to not replace it")
3005 d.addCallback(lambda res: self._foo_node.get(u"sub"))
3006 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
3009 def test_POST_mkdir_whendone_field(self):
3010 d = self.POST(self.public_url + "/foo",
3011 t="mkdir", name="newdir", when_done="/THERE")
3012 d.addBoth(self.shouldRedirect, "/THERE")
3013 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
3014 d.addCallback(self.failUnlessNodeKeysAre, [])
3017 def test_POST_mkdir_whendone_queryarg(self):
3018 d = self.POST(self.public_url + "/foo?when_done=/THERE",
3019 t="mkdir", name="newdir")
3020 d.addBoth(self.shouldRedirect, "/THERE")
3021 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
3022 d.addCallback(self.failUnlessNodeKeysAre, [])
3025 def test_POST_bad_t(self):
3026 d = self.shouldFail2(error.Error, "POST_bad_t",
3028 "POST to a directory with bad t=BOGUS",
3029 self.POST, self.public_url + "/foo", t="BOGUS")
3032 def test_POST_set_children(self, command_name="set_children"):
3033 contents9, n9, newuri9 = self.makefile(9)
3034 contents10, n10, newuri10 = self.makefile(10)
3035 contents11, n11, newuri11 = self.makefile(11)
3038 "atomic_added_1": [ "filenode", { "rw_uri": "%s",
3041 "ctime": 1002777696.7564139,
3042 "mtime": 1002777696.7564139
3045 "atomic_added_2": [ "filenode", { "rw_uri": "%s",
3048 "ctime": 1002777696.7564139,
3049 "mtime": 1002777696.7564139
3052 "atomic_added_3": [ "filenode", { "rw_uri": "%s",
3055 "ctime": 1002777696.7564139,
3056 "mtime": 1002777696.7564139
3059 }""" % (newuri9, newuri10, newuri11)
3061 url = self.webish_url + self.public_url + "/foo" + "?t=" + command_name
3063 d = client.getPage(url, method="POST", postdata=reqbody)
3065 self.failUnlessURIMatchesROChild(newuri9, self._foo_node, u"atomic_added_1")
3066 self.failUnlessURIMatchesROChild(newuri10, self._foo_node, u"atomic_added_2")
3067 self.failUnlessURIMatchesROChild(newuri11, self._foo_node, u"atomic_added_3")
3069 d.addCallback(_then)
3070 d.addErrback(self.dump_error)
3073 def test_POST_set_children_with_hyphen(self):
3074 return self.test_POST_set_children(command_name="set-children")
3076 def test_POST_link_uri(self):
3077 contents, n, newuri = self.makefile(8)
3078 d = self.POST(self.public_url + "/foo", t="uri", name="new.txt", uri=newuri)
3079 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"new.txt")
3080 d.addCallback(lambda res:
3081 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
3085 def test_POST_link_uri_replace(self):
3086 contents, n, newuri = self.makefile(8)
3087 d = self.POST(self.public_url + "/foo", t="uri", name="bar.txt", uri=newuri)
3088 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"bar.txt")
3089 d.addCallback(lambda res:
3090 self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
3094 def test_POST_link_uri_unknown_bad(self):
3095 d = self.POST(self.public_url + "/foo", t="uri", name="future.txt", uri=unknown_rwcap)
3096 d.addBoth(self.shouldFail, error.Error,
3097 "POST_link_uri_unknown_bad",
3099 "unknown cap in a write slot")
3102 def test_POST_link_uri_unknown_ro_good(self):
3103 d = self.POST(self.public_url + "/foo", t="uri", name="future-ro.txt", uri=unknown_rocap)
3104 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"future-ro.txt")
3107 def test_POST_link_uri_unknown_imm_good(self):
3108 d = self.POST(self.public_url + "/foo", t="uri", name="future-imm.txt", uri=unknown_immcap)
3109 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"future-imm.txt")
3112 def test_POST_link_uri_no_replace_queryarg(self):
3113 contents, n, newuri = self.makefile(8)
3114 d = self.POST(self.public_url + "/foo?replace=false", t="uri",
3115 name="bar.txt", uri=newuri)
3116 d.addBoth(self.shouldFail, error.Error,
3117 "POST_link_uri_no_replace_queryarg",
3119 "There was already a child by that name, and you asked me "
3120 "to not replace it")
3121 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
3122 d.addCallback(self.failUnlessIsBarDotTxt)
3125 def test_POST_link_uri_no_replace_field(self):
3126 contents, n, newuri = self.makefile(8)
3127 d = self.POST(self.public_url + "/foo", t="uri", replace="false",
3128 name="bar.txt", uri=newuri)
3129 d.addBoth(self.shouldFail, error.Error,
3130 "POST_link_uri_no_replace_field",
3132 "There was already a child by that name, and you asked me "
3133 "to not replace it")
3134 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
3135 d.addCallback(self.failUnlessIsBarDotTxt)
3138 def test_POST_delete(self, command_name='delete'):
3139 d = self._foo_node.list()
3140 def _check_before(children):
3141 self.failUnlessIn(u"bar.txt", children)
3142 d.addCallback(_check_before)
3143 d.addCallback(lambda res: self.POST(self.public_url + "/foo", t=command_name, name="bar.txt"))
3144 d.addCallback(lambda res: self._foo_node.list())
3145 def _check_after(children):
3146 self.failIfIn(u"bar.txt", children)
3147 d.addCallback(_check_after)
3150 def test_POST_unlink(self):
3151 return self.test_POST_delete(command_name='unlink')
3153 def test_POST_rename_file(self):
3154 d = self.POST(self.public_url + "/foo", t="rename",
3155 from_name="bar.txt", to_name='wibble.txt')
3156 d.addCallback(lambda res:
3157 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
3158 d.addCallback(lambda res:
3159 self.failUnlessNodeHasChild(self._foo_node, u"wibble.txt"))
3160 d.addCallback(lambda res: self.GET(self.public_url + "/foo/wibble.txt"))
3161 d.addCallback(self.failUnlessIsBarDotTxt)
3162 d.addCallback(lambda res: self.GET(self.public_url + "/foo/wibble.txt?t=json"))
3163 d.addCallback(self.failUnlessIsBarJSON)
3166 def test_POST_rename_file_redundant(self):
3167 d = self.POST(self.public_url + "/foo", t="rename",
3168 from_name="bar.txt", to_name='bar.txt')
3169 d.addCallback(lambda res:
3170 self.failUnlessNodeHasChild(self._foo_node, u"bar.txt"))
3171 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
3172 d.addCallback(self.failUnlessIsBarDotTxt)
3173 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt?t=json"))
3174 d.addCallback(self.failUnlessIsBarJSON)
3177 def test_POST_rename_file_replace(self):
3178 # rename a file and replace a directory with it
3179 d = self.POST(self.public_url + "/foo", t="rename",
3180 from_name="bar.txt", to_name='empty')
3181 d.addCallback(lambda res:
3182 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
3183 d.addCallback(lambda res:
3184 self.failUnlessNodeHasChild(self._foo_node, u"empty"))
3185 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty"))
3186 d.addCallback(self.failUnlessIsBarDotTxt)
3187 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
3188 d.addCallback(self.failUnlessIsBarJSON)
3191 def test_POST_rename_file_no_replace_queryarg(self):
3192 # rename a file and replace a directory with it
3193 d = self.POST(self.public_url + "/foo?replace=false", t="rename",
3194 from_name="bar.txt", to_name='empty')
3195 d.addBoth(self.shouldFail, error.Error,
3196 "POST_rename_file_no_replace_queryarg",
3198 "There was already a child by that name, and you asked me "
3199 "to not replace it")
3200 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
3201 d.addCallback(self.failUnlessIsEmptyJSON)
3204 def test_POST_rename_file_no_replace_field(self):
3205 # rename a file and replace a directory with it
3206 d = self.POST(self.public_url + "/foo", t="rename", replace="false",
3207 from_name="bar.txt", to_name='empty')
3208 d.addBoth(self.shouldFail, error.Error,
3209 "POST_rename_file_no_replace_field",
3211 "There was already a child by that name, and you asked me "
3212 "to not replace it")
3213 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
3214 d.addCallback(self.failUnlessIsEmptyJSON)
3217 def failUnlessIsEmptyJSON(self, res):
3218 data = simplejson.loads(res)
3219 self.failUnlessEqual(data[0], "dirnode", data)
3220 self.failUnlessReallyEqual(len(data[1]["children"]), 0)
3222 def test_POST_rename_file_slash_fail(self):
3223 d = self.POST(self.public_url + "/foo", t="rename",
3224 from_name="bar.txt", to_name='kirk/spock.txt')
3225 d.addBoth(self.shouldFail, error.Error,
3226 "test_POST_rename_file_slash_fail",
3228 "to_name= may not contain a slash",
3230 d.addCallback(lambda res:
3231 self.failUnlessNodeHasChild(self._foo_node, u"bar.txt"))
3234 def test_POST_rename_dir(self):
3235 d = self.POST(self.public_url, t="rename",
3236 from_name="foo", to_name='plunk')
3237 d.addCallback(lambda res:
3238 self.failIfNodeHasChild(self.public_root, u"foo"))
3239 d.addCallback(lambda res:
3240 self.failUnlessNodeHasChild(self.public_root, u"plunk"))
3241 d.addCallback(lambda res: self.GET(self.public_url + "/plunk?t=json"))
3242 d.addCallback(self.failUnlessIsFooJSON)
3245 def shouldRedirect(self, res, target=None, statuscode=None, which=""):
3246 """ If target is not None then the redirection has to go to target. If
3247 statuscode is not None then the redirection has to be accomplished with
3248 that HTTP status code."""
3249 if not isinstance(res, failure.Failure):
3250 to_where = (target is None) and "somewhere" or ("to " + target)
3251 self.fail("%s: we were expecting to get redirected %s, not get an"
3252 " actual page: %s" % (which, to_where, res))
3253 res.trap(error.PageRedirect)
3254 if statuscode is not None:
3255 self.failUnlessReallyEqual(res.value.status, statuscode,
3256 "%s: not a redirect" % which)
3257 if target is not None:
3258 # the PageRedirect does not seem to capture the uri= query arg
3259 # properly, so we can't check for it.
3260 realtarget = self.webish_url + target
3261 self.failUnlessReallyEqual(res.value.location, realtarget,
3262 "%s: wrong target" % which)
3263 return res.value.location
3265 def test_GET_URI_form(self):
3266 base = "/uri?uri=%s" % self._bar_txt_uri
3267 # this is supposed to give us a redirect to /uri/$URI, plus arguments
3268 targetbase = "/uri/%s" % urllib.quote(self._bar_txt_uri)
3270 d.addBoth(self.shouldRedirect, targetbase)
3271 d.addCallback(lambda res: self.GET(base+"&filename=bar.txt"))
3272 d.addBoth(self.shouldRedirect, targetbase+"?filename=bar.txt")
3273 d.addCallback(lambda res: self.GET(base+"&t=json"))
3274 d.addBoth(self.shouldRedirect, targetbase+"?t=json")
3275 d.addCallback(self.log, "about to get file by uri")
3276 d.addCallback(lambda res: self.GET(base, followRedirect=True))
3277 d.addCallback(self.failUnlessIsBarDotTxt)
3278 d.addCallback(self.log, "got file by uri, about to get dir by uri")
3279 d.addCallback(lambda res: self.GET("/uri?uri=%s&t=json" % self._foo_uri,
3280 followRedirect=True))
3281 d.addCallback(self.failUnlessIsFooJSON)
3282 d.addCallback(self.log, "got dir by uri")
3286 def test_GET_URI_form_bad(self):
3287 d = self.shouldFail2(error.Error, "test_GET_URI_form_bad",
3288 "400 Bad Request", "GET /uri requires uri=",
3292 def test_GET_rename_form(self):
3293 d = self.GET(self.public_url + "/foo?t=rename-form&name=bar.txt",
3294 followRedirect=True)
3296 self.failUnlessIn('name="when_done" value="."', res)
3297 self.failUnless(re.search(r'name="from_name" value="bar\.txt"', res))
3298 self.failUnlessIn(FAVICON_MARKUP, res)
3299 d.addCallback(_check)
3302 def log(self, res, msg):
3303 #print "MSG: %s RES: %s" % (msg, res)
3307 def test_GET_URI_URL(self):
3308 base = "/uri/%s" % self._bar_txt_uri
3310 d.addCallback(self.failUnlessIsBarDotTxt)
3311 d.addCallback(lambda res: self.GET(base+"?filename=bar.txt"))
3312 d.addCallback(self.failUnlessIsBarDotTxt)
3313 d.addCallback(lambda res: self.GET(base+"?filename=bar.txt&save=true"))
3314 d.addCallback(self.failUnlessIsBarDotTxt)
3317 def test_GET_URI_URL_dir(self):
3318 base = "/uri/%s?t=json" % self._foo_uri
3320 d.addCallback(self.failUnlessIsFooJSON)
3323 def test_GET_URI_URL_missing(self):
3324 base = "/uri/%s" % self._bad_file_uri
3325 d = self.shouldHTTPError("test_GET_URI_URL_missing",
3326 http.GONE, None, "NotEnoughSharesError",
3328 # TODO: how can we exercise both sides of WebDownloadTarget.fail
3329 # here? we must arrange for a download to fail after target.open()
3330 # has been called, and then inspect the response to see that it is
3331 # shorter than we expected.
3334 def test_PUT_DIRURL_uri(self):
3335 d = self.s.create_dirnode()
3337 new_uri = dn.get_uri()
3338 # replace /foo with a new (empty) directory
3339 d = self.PUT(self.public_url + "/foo?t=uri", new_uri)
3340 d.addCallback(lambda res:
3341 self.failUnlessReallyEqual(res.strip(), new_uri))
3342 d.addCallback(lambda res:
3343 self.failUnlessRWChildURIIs(self.public_root,
3347 d.addCallback(_made_dir)
3350 def test_PUT_DIRURL_uri_noreplace(self):
3351 d = self.s.create_dirnode()
3353 new_uri = dn.get_uri()
3354 # replace /foo with a new (empty) directory, but ask that
3355 # replace=false, so it should fail
3356 d = self.shouldFail2(error.Error, "test_PUT_DIRURL_uri_noreplace",
3357 "409 Conflict", "There was already a child by that name, and you asked me to not replace it",
3359 self.public_url + "/foo?t=uri&replace=false",
3361 d.addCallback(lambda res:
3362 self.failUnlessRWChildURIIs(self.public_root,
3366 d.addCallback(_made_dir)
3369 def test_PUT_DIRURL_bad_t(self):
3370 d = self.shouldFail2(error.Error, "test_PUT_DIRURL_bad_t",
3371 "400 Bad Request", "PUT to a directory",
3372 self.PUT, self.public_url + "/foo?t=BOGUS", "")
3373 d.addCallback(lambda res:
3374 self.failUnlessRWChildURIIs(self.public_root,
3379 def test_PUT_NEWFILEURL_uri(self):
3380 contents, n, new_uri = self.makefile(8)
3381 d = self.PUT(self.public_url + "/foo/new.txt?t=uri", new_uri)
3382 d.addCallback(lambda res: self.failUnlessReallyEqual(res.strip(), new_uri))
3383 d.addCallback(lambda res:
3384 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
3388 def test_PUT_NEWFILEURL_mdmf(self):
3389 new_contents = self.NEWFILE_CONTENTS * 300000
3390 d = self.PUT(self.public_url + \
3391 "/foo/mdmf.txt?format=mdmf",
3393 d.addCallback(lambda ignored:
3394 self.GET(self.public_url + "/foo/mdmf.txt?t=json"))
3395 def _got_json(json):
3396 data = simplejson.loads(json)
3398 self.failUnlessIn("format", data)
3399 self.failUnlessEqual(data["format"], "MDMF")
3400 self.failUnless(data['rw_uri'].startswith("URI:MDMF"))
3401 self.failUnless(data['ro_uri'].startswith("URI:MDMF"))
3402 d.addCallback(_got_json)
3405 def test_PUT_NEWFILEURL_sdmf(self):
3406 new_contents = self.NEWFILE_CONTENTS * 300000
3407 d = self.PUT(self.public_url + \
3408 "/foo/sdmf.txt?format=sdmf",
3410 d.addCallback(lambda ignored:
3411 self.GET(self.public_url + "/foo/sdmf.txt?t=json"))
3412 def _got_json(json):
3413 data = simplejson.loads(json)
3415 self.failUnlessIn("format", data)
3416 self.failUnlessEqual(data["format"], "SDMF")
3417 d.addCallback(_got_json)
3420 def test_PUT_NEWFILEURL_bad_format(self):
3421 new_contents = self.NEWFILE_CONTENTS * 300000
3422 return self.shouldHTTPError("PUT_NEWFILEURL_bad_format",
3423 400, "Bad Request", "Unknown format: foo",
3424 self.PUT, self.public_url + \
3425 "/foo/foo.txt?format=foo",
3428 def test_PUT_NEWFILEURL_uri_replace(self):
3429 contents, n, new_uri = self.makefile(8)
3430 d = self.PUT(self.public_url + "/foo/bar.txt?t=uri", new_uri)
3431 d.addCallback(lambda res: self.failUnlessReallyEqual(res.strip(), new_uri))
3432 d.addCallback(lambda res:
3433 self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
3437 def test_PUT_NEWFILEURL_uri_no_replace(self):
3438 contents, n, new_uri = self.makefile(8)
3439 d = self.PUT(self.public_url + "/foo/bar.txt?t=uri&replace=false", new_uri)
3440 d.addBoth(self.shouldFail, error.Error,
3441 "PUT_NEWFILEURL_uri_no_replace",
3443 "There was already a child by that name, and you asked me "
3444 "to not replace it")
3447 def test_PUT_NEWFILEURL_uri_unknown_bad(self):
3448 d = self.PUT(self.public_url + "/foo/put-future.txt?t=uri", unknown_rwcap)
3449 d.addBoth(self.shouldFail, error.Error,
3450 "POST_put_uri_unknown_bad",
3452 "unknown cap in a write slot")
3455 def test_PUT_NEWFILEURL_uri_unknown_ro_good(self):
3456 d = self.PUT(self.public_url + "/foo/put-future-ro.txt?t=uri", unknown_rocap)
3457 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node,
3458 u"put-future-ro.txt")
3461 def test_PUT_NEWFILEURL_uri_unknown_imm_good(self):
3462 d = self.PUT(self.public_url + "/foo/put-future-imm.txt?t=uri", unknown_immcap)
3463 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node,
3464 u"put-future-imm.txt")
3467 def test_PUT_NEWFILE_URI(self):
3468 file_contents = "New file contents here\n"
3469 d = self.PUT("/uri", file_contents)
3471 assert isinstance(uri, str), uri
3472 self.failUnlessIn(uri, FakeCHKFileNode.all_contents)
3473 self.failUnlessReallyEqual(FakeCHKFileNode.all_contents[uri],
3475 return self.GET("/uri/%s" % uri)
3476 d.addCallback(_check)
3478 self.failUnlessReallyEqual(res, file_contents)
3479 d.addCallback(_check2)
3482 def test_PUT_NEWFILE_URI_not_mutable(self):
3483 file_contents = "New file contents here\n"
3484 d = self.PUT("/uri?mutable=false", file_contents)
3486 assert isinstance(uri, str), uri
3487 self.failUnlessIn(uri, FakeCHKFileNode.all_contents)
3488 self.failUnlessReallyEqual(FakeCHKFileNode.all_contents[uri],
3490 return self.GET("/uri/%s" % uri)
3491 d.addCallback(_check)
3493 self.failUnlessReallyEqual(res, file_contents)
3494 d.addCallback(_check2)
3497 def test_PUT_NEWFILE_URI_only_PUT(self):
3498 d = self.PUT("/uri?t=bogus", "")
3499 d.addBoth(self.shouldFail, error.Error,
3500 "PUT_NEWFILE_URI_only_PUT",
3502 "/uri accepts only PUT, PUT?t=mkdir, POST?t=upload, and POST?t=mkdir")
3505 def test_PUT_NEWFILE_URI_mutable(self):
3506 file_contents = "New file contents here\n"
3507 d = self.PUT("/uri?mutable=true", file_contents)
3508 def _check1(filecap):
3509 filecap = filecap.strip()
3510 self.failUnless(filecap.startswith("URI:SSK:"), filecap)
3511 self.filecap = filecap
3512 u = uri.WriteableSSKFileURI.init_from_string(filecap)
3513 self.failUnlessIn(u.get_storage_index(), FakeMutableFileNode.all_contents)
3514 n = self.s.create_node_from_uri(filecap)
3515 return n.download_best_version()
3516 d.addCallback(_check1)
3518 self.failUnlessReallyEqual(data, file_contents)
3519 return self.GET("/uri/%s" % urllib.quote(self.filecap))
3520 d.addCallback(_check2)
3522 self.failUnlessReallyEqual(res, file_contents)
3523 d.addCallback(_check3)
3526 def test_PUT_mkdir(self):
3527 d = self.PUT("/uri?t=mkdir", "")
3529 n = self.s.create_node_from_uri(uri.strip())
3530 d2 = self.failUnlessNodeKeysAre(n, [])
3531 d2.addCallback(lambda res:
3532 self.GET("/uri/%s?t=json" % uri))
3534 d.addCallback(_check)
3535 d.addCallback(self.failUnlessIsEmptyJSON)
3538 def test_PUT_mkdir_mdmf(self):
3539 d = self.PUT("/uri?t=mkdir&format=mdmf", "")
3541 u = uri.from_string(res)
3542 # Check that this is an MDMF writecap
3543 self.failUnlessIsInstance(u, uri.MDMFDirectoryURI)
3547 def test_PUT_mkdir_sdmf(self):
3548 d = self.PUT("/uri?t=mkdir&format=sdmf", "")
3550 u = uri.from_string(res)
3551 self.failUnlessIsInstance(u, uri.DirectoryURI)
3555 def test_PUT_mkdir_bad_format(self):
3556 return self.shouldHTTPError("PUT_mkdir_bad_format",
3557 400, "Bad Request", "Unknown format: foo",
3558 self.PUT, "/uri?t=mkdir&format=foo",
3561 def test_POST_check(self):
3562 d = self.POST(self.public_url + "/foo", t="check", name="bar.txt")
3564 # this returns a string form of the results, which are probably
3565 # None since we're using fake filenodes.
3566 # TODO: verify that the check actually happened, by changing
3567 # FakeCHKFileNode to count how many times .check() has been
3570 d.addCallback(_done)
3574 def test_PUT_update_at_offset(self):
3575 file_contents = "test file" * 100000 # about 900 KiB
3576 d = self.PUT("/uri?mutable=true", file_contents)
3578 self.filecap = filecap
3579 new_data = file_contents[:100]
3580 new = "replaced and so on"
3582 new_data += file_contents[len(new_data):]
3583 assert len(new_data) == len(file_contents)
3584 self.new_data = new_data
3585 d.addCallback(_then)
3586 d.addCallback(lambda ignored:
3587 self.PUT("/uri/%s?replace=True&offset=100" % self.filecap,
3588 "replaced and so on"))
3589 def _get_data(filecap):
3590 n = self.s.create_node_from_uri(filecap)
3591 return n.download_best_version()
3592 d.addCallback(_get_data)
3593 d.addCallback(lambda results:
3594 self.failUnlessEqual(results, self.new_data))
3595 # Now try appending things to the file
3596 d.addCallback(lambda ignored:
3597 self.PUT("/uri/%s?offset=%d" % (self.filecap, len(self.new_data)),
3599 d.addCallback(_get_data)
3600 d.addCallback(lambda results:
3601 self.failUnlessEqual(results, self.new_data + ("puppies" * 100)))
3602 # and try replacing the beginning of the file
3603 d.addCallback(lambda ignored:
3604 self.PUT("/uri/%s?offset=0" % self.filecap, "begin"))
3605 d.addCallback(_get_data)
3606 d.addCallback(lambda results:
3607 self.failUnlessEqual(results, "begin"+self.new_data[len("begin"):]+("puppies"*100)))
3610 def test_PUT_update_at_invalid_offset(self):
3611 file_contents = "test file" * 100000 # about 900 KiB
3612 d = self.PUT("/uri?mutable=true", file_contents)
3614 self.filecap = filecap
3615 d.addCallback(_then)
3616 # Negative offsets should cause an error.
3617 d.addCallback(lambda ignored:
3618 self.shouldHTTPError("PUT_update_at_invalid_offset",
3622 "/uri/%s?offset=-1" % self.filecap,
3626 def test_PUT_update_at_offset_immutable(self):
3627 file_contents = "Test file" * 100000
3628 d = self.PUT("/uri", file_contents)
3630 self.filecap = filecap
3631 d.addCallback(_then)
3632 d.addCallback(lambda ignored:
3633 self.shouldHTTPError("PUT_update_at_offset_immutable",
3637 "/uri/%s?offset=50" % self.filecap,
3642 def test_bad_method(self):
3643 url = self.webish_url + self.public_url + "/foo/bar.txt"
3644 d = self.shouldHTTPError("bad_method",
3645 501, "Not Implemented",
3646 "I don't know how to treat a BOGUS request.",
3647 client.getPage, url, method="BOGUS")
3650 def test_short_url(self):
3651 url = self.webish_url + "/uri"
3652 d = self.shouldHTTPError("short_url", 501, "Not Implemented",
3653 "I don't know how to treat a DELETE request.",
3654 client.getPage, url, method="DELETE")
3657 def test_ophandle_bad(self):
3658 url = self.webish_url + "/operations/bogus?t=status"
3659 d = self.shouldHTTPError("ophandle_bad", 404, "404 Not Found",
3660 "unknown/expired handle 'bogus'",
3661 client.getPage, url)
3664 def test_ophandle_cancel(self):
3665 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=128",
3666 followRedirect=True)
3667 d.addCallback(lambda ignored:
3668 self.GET("/operations/128?t=status&output=JSON"))
3670 data = simplejson.loads(res)
3671 self.failUnless("finished" in data, res)
3672 monitor = self.ws.root.child_operations.handles["128"][0]
3673 d = self.POST("/operations/128?t=cancel&output=JSON")
3675 data = simplejson.loads(res)
3676 self.failUnless("finished" in data, res)
3677 # t=cancel causes the handle to be forgotten
3678 self.failUnless(monitor.is_cancelled())
3679 d.addCallback(_check2)
3681 d.addCallback(_check1)
3682 d.addCallback(lambda ignored:
3683 self.shouldHTTPError("ophandle_cancel",
3684 404, "404 Not Found",
3685 "unknown/expired handle '128'",
3687 "/operations/128?t=status&output=JSON"))
3690 def test_ophandle_retainfor(self):
3691 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=129&retain-for=60",
3692 followRedirect=True)
3693 d.addCallback(lambda ignored:
3694 self.GET("/operations/129?t=status&output=JSON&retain-for=0"))
3696 data = simplejson.loads(res)
3697 self.failUnless("finished" in data, res)
3698 d.addCallback(_check1)
3699 # the retain-for=0 will cause the handle to be expired very soon
3700 d.addCallback(lambda ign:
3701 self.clock.advance(2.0))
3702 d.addCallback(lambda ignored:
3703 self.shouldHTTPError("ophandle_retainfor",
3704 404, "404 Not Found",
3705 "unknown/expired handle '129'",
3707 "/operations/129?t=status&output=JSON"))
3710 def test_ophandle_release_after_complete(self):
3711 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=130",
3712 followRedirect=True)
3713 d.addCallback(self.wait_for_operation, "130")
3714 d.addCallback(lambda ignored:
3715 self.GET("/operations/130?t=status&output=JSON&release-after-complete=true"))
3716 # the release-after-complete=true will cause the handle to be expired
3717 d.addCallback(lambda ignored:
3718 self.shouldHTTPError("ophandle_release_after_complete",
3719 404, "404 Not Found",
3720 "unknown/expired handle '130'",
3722 "/operations/130?t=status&output=JSON"))
3725 def test_uncollected_ophandle_expiration(self):
3726 # uncollected ophandles should expire after 4 days
3727 def _make_uncollected_ophandle(ophandle):
3728 d = self.POST(self.public_url +
3729 "/foo/?t=start-manifest&ophandle=%d" % ophandle,
3730 followRedirect=False)
3731 # When we start the operation, the webapi server will want
3732 # to redirect us to the page for the ophandle, so we get
3733 # confirmation that the operation has started. If the
3734 # manifest operation has finished by the time we get there,
3735 # following that redirect (by setting followRedirect=True
3736 # above) has the side effect of collecting the ophandle that
3737 # we've just created, which means that we can't use the
3738 # ophandle to test the uncollected timeout anymore. So,
3739 # instead, catch the 302 here and don't follow it.
3740 d.addBoth(self.should302, "uncollected_ophandle_creation")
3742 # Create an ophandle, don't collect it, then advance the clock by
3743 # 4 days - 1 second and make sure that the ophandle is still there.
3744 d = _make_uncollected_ophandle(131)
3745 d.addCallback(lambda ign:
3746 self.clock.advance((96*60*60) - 1)) # 96 hours = 4 days
3747 d.addCallback(lambda ign:
3748 self.GET("/operations/131?t=status&output=JSON"))
3750 data = simplejson.loads(res)
3751 self.failUnless("finished" in data, res)
3752 d.addCallback(_check1)
3753 # Create an ophandle, don't collect it, then try to collect it
3754 # after 4 days. It should be gone.
3755 d.addCallback(lambda ign:
3756 _make_uncollected_ophandle(132))
3757 d.addCallback(lambda ign:
3758 self.clock.advance(96*60*60))
3759 d.addCallback(lambda ign:
3760 self.shouldHTTPError("uncollected_ophandle_expired_after_100_hours",
3761 404, "404 Not Found",
3762 "unknown/expired handle '132'",
3764 "/operations/132?t=status&output=JSON"))
3767 def test_collected_ophandle_expiration(self):
3768 # collected ophandles should expire after 1 day
3769 def _make_collected_ophandle(ophandle):
3770 d = self.POST(self.public_url +
3771 "/foo/?t=start-manifest&ophandle=%d" % ophandle,
3772 followRedirect=True)
3773 # By following the initial redirect, we collect the ophandle
3774 # we've just created.
3776 # Create a collected ophandle, then collect it after 23 hours
3777 # and 59 seconds to make sure that it is still there.
3778 d = _make_collected_ophandle(133)
3779 d.addCallback(lambda ign:
3780 self.clock.advance((24*60*60) - 1))
3781 d.addCallback(lambda ign:
3782 self.GET("/operations/133?t=status&output=JSON"))
3784 data = simplejson.loads(res)
3785 self.failUnless("finished" in data, res)
3786 d.addCallback(_check1)
3787 # Create another uncollected ophandle, then try to collect it
3788 # after 24 hours to make sure that it is gone.
3789 d.addCallback(lambda ign:
3790 _make_collected_ophandle(134))
3791 d.addCallback(lambda ign:
3792 self.clock.advance(24*60*60))
3793 d.addCallback(lambda ign:
3794 self.shouldHTTPError("collected_ophandle_expired_after_1_day",
3795 404, "404 Not Found",
3796 "unknown/expired handle '134'",
3798 "/operations/134?t=status&output=JSON"))
3801 def test_incident(self):
3802 d = self.POST("/report_incident", details="eek")
3804 self.failIfIn("<html>", res)
3805 self.failUnlessIn("Thank you for your report!", res)
3806 d.addCallback(_done)
3809 def test_static(self):
3810 webdir = os.path.join(self.staticdir, "subdir")
3811 fileutil.make_dirs(webdir)
3812 f = open(os.path.join(webdir, "hello.txt"), "wb")
3816 d = self.GET("/static/subdir/hello.txt")
3818 self.failUnlessReallyEqual(res, "hello")
3819 d.addCallback(_check)
3823 class IntroducerWeb(unittest.TestCase):
3828 d = defer.succeed(None)
3830 d.addCallback(lambda ign: self.node.stopService())
3831 d.addCallback(flushEventualQueue)
3834 def test_welcome(self):
3835 basedir = "web.IntroducerWeb.test_welcome"
3837 fileutil.write(os.path.join(basedir, "tahoe.cfg"), "[node]\nweb.port = tcp:0\n")
3838 self.node = IntroducerNode(basedir)
3839 self.ws = self.node.getServiceNamed("webish")
3841 d = fireEventually(None)
3842 d.addCallback(lambda ign: self.node.startService())
3843 d.addCallback(lambda ign: self.node.when_tub_ready())
3845 d.addCallback(lambda ign: self.GET("/"))
3847 self.failUnlessIn('Welcome to the Tahoe-LAFS Introducer', res)
3848 self.failUnlessIn(FAVICON_MARKUP, res)
3849 d.addCallback(_check)
3852 def GET(self, urlpath, followRedirect=False, return_response=False,
3854 # if return_response=True, this fires with (data, statuscode,
3855 # respheaders) instead of just data.
3856 assert not isinstance(urlpath, unicode)
3857 url = self.ws.getURL().rstrip('/') + urlpath
3858 factory = HTTPClientGETFactory(url, method="GET",
3859 followRedirect=followRedirect, **kwargs)
3860 reactor.connectTCP("localhost", self.ws.getPortnum(), factory)
3861 d = factory.deferred
3862 def _got_data(data):
3863 return (data, factory.status, factory.response_headers)
3865 d.addCallback(_got_data)
3866 return factory.deferred
3869 class Util(ShouldFailMixin, testutil.ReallyEqualMixin, unittest.TestCase):
3870 def test_load_file(self):
3871 # This will raise an exception unless a well-formed XML file is found under that name.
3872 common.getxmlfile('directory.xhtml').load()
3874 def test_parse_replace_arg(self):
3875 self.failUnlessReallyEqual(common.parse_replace_arg("true"), True)
3876 self.failUnlessReallyEqual(common.parse_replace_arg("false"), False)
3877 self.failUnlessReallyEqual(common.parse_replace_arg("only-files"),
3879 self.shouldFail(AssertionError, "test_parse_replace_arg", "",
3880 common.parse_replace_arg, "only_fles")
3882 def test_abbreviate_time(self):
3883 self.failUnlessReallyEqual(common.abbreviate_time(None), "")
3884 self.failUnlessReallyEqual(common.abbreviate_time(1.234), "1.23s")
3885 self.failUnlessReallyEqual(common.abbreviate_time(0.123), "123ms")
3886 self.failUnlessReallyEqual(common.abbreviate_time(0.00123), "1.2ms")
3887 self.failUnlessReallyEqual(common.abbreviate_time(0.000123), "123us")
3888 self.failUnlessReallyEqual(common.abbreviate_time(-123000), "-123000000000us")
3890 def test_compute_rate(self):
3891 self.failUnlessReallyEqual(common.compute_rate(None, None), None)
3892 self.failUnlessReallyEqual(common.compute_rate(None, 1), None)
3893 self.failUnlessReallyEqual(common.compute_rate(250000, None), None)
3894 self.failUnlessReallyEqual(common.compute_rate(250000, 0), None)
3895 self.failUnlessReallyEqual(common.compute_rate(250000, 10), 25000.0)
3896 self.failUnlessReallyEqual(common.compute_rate(0, 10), 0.0)
3897 self.shouldFail(AssertionError, "test_compute_rate", "",
3898 common.compute_rate, -100, 10)
3899 self.shouldFail(AssertionError, "test_compute_rate", "",
3900 common.compute_rate, 100, -10)
3903 rate = common.compute_rate(10*1000*1000, 1)
3904 self.failUnlessReallyEqual(common.abbreviate_rate(rate), "10.00MBps")
3906 def test_abbreviate_rate(self):
3907 self.failUnlessReallyEqual(common.abbreviate_rate(None), "")
3908 self.failUnlessReallyEqual(common.abbreviate_rate(1234000), "1.23MBps")
3909 self.failUnlessReallyEqual(common.abbreviate_rate(12340), "12.3kBps")
3910 self.failUnlessReallyEqual(common.abbreviate_rate(123), "123Bps")
3912 def test_abbreviate_size(self):
3913 self.failUnlessReallyEqual(common.abbreviate_size(None), "")
3914 self.failUnlessReallyEqual(common.abbreviate_size(1.23*1000*1000*1000), "1.23GB")
3915 self.failUnlessReallyEqual(common.abbreviate_size(1.23*1000*1000), "1.23MB")
3916 self.failUnlessReallyEqual(common.abbreviate_size(1230), "1.2kB")
3917 self.failUnlessReallyEqual(common.abbreviate_size(123), "123B")
3919 def test_plural(self):
3921 return "%d second%s" % (s, status.plural(s))
3922 self.failUnlessReallyEqual(convert(0), "0 seconds")
3923 self.failUnlessReallyEqual(convert(1), "1 second")
3924 self.failUnlessReallyEqual(convert(2), "2 seconds")
3926 return "has share%s: %s" % (status.plural(s), ",".join(s))
3927 self.failUnlessReallyEqual(convert2([]), "has shares: ")
3928 self.failUnlessReallyEqual(convert2(["1"]), "has share: 1")
3929 self.failUnlessReallyEqual(convert2(["1","2"]), "has shares: 1,2")
3932 class Grid(GridTestMixin, WebErrorMixin, ShouldFailMixin, testutil.ReallyEqualMixin, unittest.TestCase):
3934 def CHECK(self, ign, which, args, clientnum=0):
3935 fileurl = self.fileurls[which]
3936 url = fileurl + "?" + args
3937 return self.GET(url, method="POST", clientnum=clientnum)
3939 def test_filecheck(self):
3940 self.basedir = "web/Grid/filecheck"
3942 c0 = self.g.clients[0]
3945 d = c0.upload(upload.Data(DATA, convergence=""))
3946 def _stash_uri(ur, which):
3947 self.uris[which] = ur.uri
3948 d.addCallback(_stash_uri, "good")
3949 d.addCallback(lambda ign:
3950 c0.upload(upload.Data(DATA+"1", convergence="")))
3951 d.addCallback(_stash_uri, "sick")
3952 d.addCallback(lambda ign:
3953 c0.upload(upload.Data(DATA+"2", convergence="")))
3954 d.addCallback(_stash_uri, "dead")
3955 def _stash_mutable_uri(n, which):
3956 self.uris[which] = n.get_uri()
3957 assert isinstance(self.uris[which], str)
3958 d.addCallback(lambda ign:
3959 c0.create_mutable_file(publish.MutableData(DATA+"3")))
3960 d.addCallback(_stash_mutable_uri, "corrupt")
3961 d.addCallback(lambda ign:
3962 c0.upload(upload.Data("literal", convergence="")))
3963 d.addCallback(_stash_uri, "small")
3964 d.addCallback(lambda ign: c0.create_immutable_dirnode({}))
3965 d.addCallback(_stash_mutable_uri, "smalldir")
3967 def _compute_fileurls(ignored):
3969 for which in self.uris:
3970 self.fileurls[which] = "uri/" + urllib.quote(self.uris[which])
3971 d.addCallback(_compute_fileurls)
3973 def _clobber_shares(ignored):
3974 good_shares = self.find_uri_shares(self.uris["good"])
3975 self.failUnlessReallyEqual(len(good_shares), 10)
3976 sick_shares = self.find_uri_shares(self.uris["sick"])
3977 os.unlink(sick_shares[0][2])
3978 dead_shares = self.find_uri_shares(self.uris["dead"])
3979 for i in range(1, 10):
3980 os.unlink(dead_shares[i][2])
3981 c_shares = self.find_uri_shares(self.uris["corrupt"])
3982 cso = CorruptShareOptions()
3983 cso.stdout = StringIO()
3984 cso.parseOptions([c_shares[0][2]])
3986 d.addCallback(_clobber_shares)
3988 d.addCallback(self.CHECK, "good", "t=check")
3989 def _got_html_good(res):
3990 self.failUnlessIn("Healthy", res)
3991 self.failIfIn("Not Healthy", res)
3992 self.failUnlessIn(FAVICON_MARKUP, res)
3993 d.addCallback(_got_html_good)
3994 d.addCallback(self.CHECK, "good", "t=check&return_to=somewhere")
3995 def _got_html_good_return_to(res):
3996 self.failUnlessIn("Healthy", res)
3997 self.failIfIn("Not Healthy", res)
3998 self.failUnlessIn('<a href="somewhere">Return to file', res)
3999 d.addCallback(_got_html_good_return_to)
4000 d.addCallback(self.CHECK, "good", "t=check&output=json")
4001 def _got_json_good(res):
4002 r = simplejson.loads(res)
4003 self.failUnlessEqual(r["summary"], "Healthy")
4004 self.failUnless(r["results"]["healthy"])
4005 self.failIf(r["results"]["needs-rebalancing"])
4006 self.failUnless(r["results"]["recoverable"])
4007 d.addCallback(_got_json_good)
4009 d.addCallback(self.CHECK, "small", "t=check")
4010 def _got_html_small(res):
4011 self.failUnlessIn("Literal files are always healthy", res)
4012 self.failIfIn("Not Healthy", res)
4013 d.addCallback(_got_html_small)
4014 d.addCallback(self.CHECK, "small", "t=check&return_to=somewhere")
4015 def _got_html_small_return_to(res):
4016 self.failUnlessIn("Literal files are always healthy", res)
4017 self.failIfIn("Not Healthy", res)
4018 self.failUnlessIn('<a href="somewhere">Return to file', res)
4019 d.addCallback(_got_html_small_return_to)
4020 d.addCallback(self.CHECK, "small", "t=check&output=json")
4021 def _got_json_small(res):
4022 r = simplejson.loads(res)
4023 self.failUnlessEqual(r["storage-index"], "")
4024 self.failUnless(r["results"]["healthy"])
4025 d.addCallback(_got_json_small)
4027 d.addCallback(self.CHECK, "smalldir", "t=check")
4028 def _got_html_smalldir(res):
4029 self.failUnlessIn("Literal files are always healthy", res)
4030 self.failIfIn("Not Healthy", res)
4031 d.addCallback(_got_html_smalldir)
4032 d.addCallback(self.CHECK, "smalldir", "t=check&output=json")
4033 def _got_json_smalldir(res):
4034 r = simplejson.loads(res)
4035 self.failUnlessEqual(r["storage-index"], "")
4036 self.failUnless(r["results"]["healthy"])
4037 d.addCallback(_got_json_smalldir)
4039 d.addCallback(self.CHECK, "sick", "t=check")
4040 def _got_html_sick(res):
4041 self.failUnlessIn("Not Healthy", res)
4042 d.addCallback(_got_html_sick)
4043 d.addCallback(self.CHECK, "sick", "t=check&output=json")
4044 def _got_json_sick(res):
4045 r = simplejson.loads(res)
4046 self.failUnlessEqual(r["summary"],
4047 "Not Healthy: 9 shares (enc 3-of-10)")
4048 self.failIf(r["results"]["healthy"])
4049 self.failIf(r["results"]["needs-rebalancing"])
4050 self.failUnless(r["results"]["recoverable"])
4051 d.addCallback(_got_json_sick)
4053 d.addCallback(self.CHECK, "dead", "t=check")
4054 def _got_html_dead(res):
4055 self.failUnlessIn("Not Healthy", res)
4056 d.addCallback(_got_html_dead)
4057 d.addCallback(self.CHECK, "dead", "t=check&output=json")
4058 def _got_json_dead(res):
4059 r = simplejson.loads(res)
4060 self.failUnlessEqual(r["summary"],
4061 "Not Healthy: 1 shares (enc 3-of-10)")
4062 self.failIf(r["results"]["healthy"])
4063 self.failIf(r["results"]["needs-rebalancing"])
4064 self.failIf(r["results"]["recoverable"])
4065 d.addCallback(_got_json_dead)
4067 d.addCallback(self.CHECK, "corrupt", "t=check&verify=true")
4068 def _got_html_corrupt(res):
4069 self.failUnlessIn("Not Healthy! : Unhealthy", res)
4070 d.addCallback(_got_html_corrupt)
4071 d.addCallback(self.CHECK, "corrupt", "t=check&verify=true&output=json")
4072 def _got_json_corrupt(res):
4073 r = simplejson.loads(res)
4074 self.failUnlessIn("Unhealthy: 9 shares (enc 3-of-10)", r["summary"])
4075 self.failIf(r["results"]["healthy"])
4076 self.failUnless(r["results"]["recoverable"])
4077 self.failUnlessReallyEqual(r["results"]["count-shares-good"], 9)
4078 self.failUnlessReallyEqual(r["results"]["count-corrupt-shares"], 1)
4079 d.addCallback(_got_json_corrupt)
4081 d.addErrback(self.explain_web_error)
4084 def test_repair_html(self):
4085 self.basedir = "web/Grid/repair_html"
4087 c0 = self.g.clients[0]
4090 d = c0.upload(upload.Data(DATA, convergence=""))
4091 def _stash_uri(ur, which):
4092 self.uris[which] = ur.uri
4093 d.addCallback(_stash_uri, "good")
4094 d.addCallback(lambda ign:
4095 c0.upload(upload.Data(DATA+"1", convergence="")))
4096 d.addCallback(_stash_uri, "sick")
4097 d.addCallback(lambda ign:
4098 c0.upload(upload.Data(DATA+"2", convergence="")))
4099 d.addCallback(_stash_uri, "dead")
4100 def _stash_mutable_uri(n, which):
4101 self.uris[which] = n.get_uri()
4102 assert isinstance(self.uris[which], str)
4103 d.addCallback(lambda ign:
4104 c0.create_mutable_file(publish.MutableData(DATA+"3")))
4105 d.addCallback(_stash_mutable_uri, "corrupt")
4107 def _compute_fileurls(ignored):
4109 for which in self.uris:
4110 self.fileurls[which] = "uri/" + urllib.quote(self.uris[which])
4111 d.addCallback(_compute_fileurls)
4113 def _clobber_shares(ignored):
4114 good_shares = self.find_uri_shares(self.uris["good"])
4115 self.failUnlessReallyEqual(len(good_shares), 10)
4116 sick_shares = self.find_uri_shares(self.uris["sick"])
4117 os.unlink(sick_shares[0][2])
4118 dead_shares = self.find_uri_shares(self.uris["dead"])
4119 for i in range(1, 10):
4120 os.unlink(dead_shares[i][2])
4121 c_shares = self.find_uri_shares(self.uris["corrupt"])
4122 cso = CorruptShareOptions()
4123 cso.stdout = StringIO()
4124 cso.parseOptions([c_shares[0][2]])
4126 d.addCallback(_clobber_shares)
4128 d.addCallback(self.CHECK, "good", "t=check&repair=true")
4129 def _got_html_good(res):
4130 self.failUnlessIn("Healthy", res)
4131 self.failIfIn("Not Healthy", res)
4132 self.failUnlessIn("No repair necessary", res)
4133 self.failUnlessIn(FAVICON_MARKUP, res)
4134 d.addCallback(_got_html_good)
4136 d.addCallback(self.CHECK, "sick", "t=check&repair=true")
4137 def _got_html_sick(res):
4138 self.failUnlessIn("Healthy : healthy", res)
4139 self.failIfIn("Not Healthy", res)
4140 self.failUnlessIn("Repair successful", res)
4141 d.addCallback(_got_html_sick)
4143 # repair of a dead file will fail, of course, but it isn't yet
4144 # clear how this should be reported. Right now it shows up as
4147 #d.addCallback(self.CHECK, "dead", "t=check&repair=true")
4148 #def _got_html_dead(res):
4150 # self.failUnlessIn("Healthy : healthy", res)
4151 # self.failIfIn("Not Healthy", res)
4152 # self.failUnlessIn("No repair necessary", res)
4153 #d.addCallback(_got_html_dead)
4155 d.addCallback(self.CHECK, "corrupt", "t=check&verify=true&repair=true")
4156 def _got_html_corrupt(res):
4157 self.failUnlessIn("Healthy : Healthy", res)
4158 self.failIfIn("Not Healthy", res)
4159 self.failUnlessIn("Repair successful", res)
4160 d.addCallback(_got_html_corrupt)
4162 d.addErrback(self.explain_web_error)
4165 def test_repair_json(self):
4166 self.basedir = "web/Grid/repair_json"
4168 c0 = self.g.clients[0]
4171 d = c0.upload(upload.Data(DATA+"1", convergence=""))
4172 def _stash_uri(ur, which):
4173 self.uris[which] = ur.uri
4174 d.addCallback(_stash_uri, "sick")
4176 def _compute_fileurls(ignored):
4178 for which in self.uris:
4179 self.fileurls[which] = "uri/" + urllib.quote(self.uris[which])
4180 d.addCallback(_compute_fileurls)
4182 def _clobber_shares(ignored):
4183 sick_shares = self.find_uri_shares(self.uris["sick"])
4184 os.unlink(sick_shares[0][2])
4185 d.addCallback(_clobber_shares)
4187 d.addCallback(self.CHECK, "sick", "t=check&repair=true&output=json")
4188 def _got_json_sick(res):
4189 r = simplejson.loads(res)
4190 self.failUnlessReallyEqual(r["repair-attempted"], True)
4191 self.failUnlessReallyEqual(r["repair-successful"], True)
4192 self.failUnlessEqual(r["pre-repair-results"]["summary"],
4193 "Not Healthy: 9 shares (enc 3-of-10)")
4194 self.failIf(r["pre-repair-results"]["results"]["healthy"])
4195 self.failUnlessEqual(r["post-repair-results"]["summary"], "healthy")
4196 self.failUnless(r["post-repair-results"]["results"]["healthy"])
4197 d.addCallback(_got_json_sick)
4199 d.addErrback(self.explain_web_error)
4202 def test_unknown(self, immutable=False):
4203 self.basedir = "web/Grid/unknown"
4205 self.basedir = "web/Grid/unknown-immutable"
4208 c0 = self.g.clients[0]
4212 # the future cap format may contain slashes, which must be tolerated
4213 expected_info_url = "uri/%s?t=info" % urllib.quote(unknown_rwcap,
4217 name = u"future-imm"
4218 future_node = UnknownNode(None, unknown_immcap, deep_immutable=True)
4219 d = c0.create_immutable_dirnode({name: (future_node, {})})
4222 future_node = UnknownNode(unknown_rwcap, unknown_rocap)
4223 d = c0.create_dirnode()
4225 def _stash_root_and_create_file(n):
4227 self.rooturl = "uri/" + urllib.quote(n.get_uri()) + "/"
4228 self.rourl = "uri/" + urllib.quote(n.get_readonly_uri()) + "/"
4230 return self.rootnode.set_node(name, future_node)
4231 d.addCallback(_stash_root_and_create_file)
4233 # make sure directory listing tolerates unknown nodes
4234 d.addCallback(lambda ign: self.GET(self.rooturl))
4235 def _check_directory_html(res, expected_type_suffix):
4236 pattern = re.compile(r'<td>\?%s</td>[ \t\n\r]*'
4237 '<td>%s</td>' % (expected_type_suffix, str(name)),
4239 self.failUnless(re.search(pattern, res), res)
4240 # find the More Info link for name, should be relative
4241 mo = re.search(r'<a href="([^"]+)">More Info</a>', res)
4242 info_url = mo.group(1)
4243 self.failUnlessReallyEqual(info_url, "%s?t=info" % (str(name),))
4245 d.addCallback(_check_directory_html, "-IMM")
4247 d.addCallback(_check_directory_html, "")
4249 d.addCallback(lambda ign: self.GET(self.rooturl+"?t=json"))
4250 def _check_directory_json(res, expect_rw_uri):
4251 data = simplejson.loads(res)
4252 self.failUnlessEqual(data[0], "dirnode")
4253 f = data[1]["children"][name]
4254 self.failUnlessEqual(f[0], "unknown")
4256 self.failUnlessReallyEqual(to_str(f[1]["rw_uri"]), unknown_rwcap, data)
4258 self.failIfIn("rw_uri", f[1])
4260 self.failUnlessReallyEqual(to_str(f[1]["ro_uri"]), unknown_immcap, data)
4262 self.failUnlessReallyEqual(to_str(f[1]["ro_uri"]), unknown_rocap, data)
4263 self.failUnlessIn("metadata", f[1])
4264 d.addCallback(_check_directory_json, expect_rw_uri=not immutable)
4266 def _check_info(res, expect_rw_uri, expect_ro_uri):
4267 self.failUnlessIn("Object Type: <span>unknown</span>", res)
4269 self.failUnlessIn(unknown_rwcap, res)
4272 self.failUnlessIn(unknown_immcap, res)
4274 self.failUnlessIn(unknown_rocap, res)
4276 self.failIfIn(unknown_rocap, res)
4277 self.failIfIn("Raw data as", res)
4278 self.failIfIn("Directory writecap", res)
4279 self.failIfIn("Checker Operations", res)
4280 self.failIfIn("Mutable File Operations", res)
4281 self.failIfIn("Directory Operations", res)
4283 # FIXME: these should have expect_rw_uri=not immutable; I don't know
4284 # why they fail. Possibly related to ticket #922.
4286 d.addCallback(lambda ign: self.GET(expected_info_url))
4287 d.addCallback(_check_info, expect_rw_uri=False, expect_ro_uri=False)
4288 d.addCallback(lambda ign: self.GET("%s%s?t=info" % (self.rooturl, str(name))))
4289 d.addCallback(_check_info, expect_rw_uri=False, expect_ro_uri=True)
4291 def _check_json(res, expect_rw_uri):
4292 data = simplejson.loads(res)
4293 self.failUnlessEqual(data[0], "unknown")
4295 self.failUnlessReallyEqual(to_str(data[1]["rw_uri"]), unknown_rwcap, data)
4297 self.failIfIn("rw_uri", data[1])
4300 self.failUnlessReallyEqual(to_str(data[1]["ro_uri"]), unknown_immcap, data)
4301 self.failUnlessReallyEqual(data[1]["mutable"], False)
4303 self.failUnlessReallyEqual(to_str(data[1]["ro_uri"]), unknown_rocap, data)
4304 self.failUnlessReallyEqual(data[1]["mutable"], True)
4306 self.failUnlessReallyEqual(to_str(data[1]["ro_uri"]), unknown_rocap, data)
4307 self.failIfIn("mutable", data[1])
4309 # TODO: check metadata contents
4310 self.failUnlessIn("metadata", data[1])
4312 d.addCallback(lambda ign: self.GET("%s%s?t=json" % (self.rooturl, str(name))))
4313 d.addCallback(_check_json, expect_rw_uri=not immutable)
4315 # and make sure that a read-only version of the directory can be
4316 # rendered too. This version will not have unknown_rwcap, whether
4317 # or not future_node was immutable.
4318 d.addCallback(lambda ign: self.GET(self.rourl))
4320 d.addCallback(_check_directory_html, "-IMM")
4322 d.addCallback(_check_directory_html, "-RO")
4324 d.addCallback(lambda ign: self.GET(self.rourl+"?t=json"))
4325 d.addCallback(_check_directory_json, expect_rw_uri=False)
4327 d.addCallback(lambda ign: self.GET("%s%s?t=json" % (self.rourl, str(name))))
4328 d.addCallback(_check_json, expect_rw_uri=False)
4330 # TODO: check that getting t=info from the Info link in the ro directory
4331 # works, and does not include the writecap URI.
4334 def test_immutable_unknown(self):
4335 return self.test_unknown(immutable=True)
4337 def test_mutant_dirnodes_are_omitted(self):
4338 self.basedir = "web/Grid/mutant_dirnodes_are_omitted"
4341 c = self.g.clients[0]
4346 lonely_uri = "URI:LIT:n5xgk" # LIT for "one"
4347 mut_write_uri = "URI:SSK:vfvcbdfbszyrsaxchgevhmmlii:euw4iw7bbnkrrwpzuburbhppuxhc3gwxv26f6imekhz7zyw2ojnq"
4348 mut_read_uri = "URI:SSK-RO:e3mdrzfwhoq42hy5ubcz6rp3o4:ybyibhnp3vvwuq2vaw2ckjmesgkklfs6ghxleztqidihjyofgw7q"
4350 # This method tests mainly dirnode, but we'd have to duplicate code in order to
4351 # test the dirnode and web layers separately.
4353 # 'lonely' is a valid LIT child, 'ro' is a mutant child with an SSK-RO readcap,
4354 # and 'write-in-ro' is a mutant child with an SSK writecap in the ro_uri field.
4355 # When the directory is read, the mutants should be silently disposed of, leaving
4356 # their lonely sibling.
4357 # We don't test the case of a retrieving a cap from the encrypted rw_uri field,
4358 # because immutable directories don't have a writecap and therefore that field
4359 # isn't (and can't be) decrypted.
4360 # TODO: The field still exists in the netstring. Technically we should check what
4361 # happens if something is put there (_unpack_contents should raise ValueError),
4362 # but that can wait.
4364 lonely_child = nm.create_from_cap(lonely_uri)
4365 mutant_ro_child = nm.create_from_cap(mut_read_uri)
4366 mutant_write_in_ro_child = nm.create_from_cap(mut_write_uri)
4368 def _by_hook_or_by_crook():
4370 for n in [mutant_ro_child, mutant_write_in_ro_child]:
4371 n.is_allowed_in_immutable_directory = _by_hook_or_by_crook
4373 mutant_write_in_ro_child.get_write_uri = lambda: None
4374 mutant_write_in_ro_child.get_readonly_uri = lambda: mut_write_uri
4376 kids = {u"lonely": (lonely_child, {}),
4377 u"ro": (mutant_ro_child, {}),
4378 u"write-in-ro": (mutant_write_in_ro_child, {}),
4380 d = c.create_immutable_dirnode(kids)
4383 self.failUnless(isinstance(dn, dirnode.DirectoryNode))
4384 self.failIf(dn.is_mutable())
4385 self.failUnless(dn.is_readonly())
4386 # This checks that if we somehow ended up calling dn._decrypt_rwcapdata, it would fail.
4387 self.failIf(hasattr(dn._node, 'get_writekey'))
4389 self.failUnlessIn("RO-IMM", rep)
4391 self.failUnlessIn("CHK", cap.to_string())
4394 self.rooturl = "uri/" + urllib.quote(dn.get_uri()) + "/"
4395 return download_to_data(dn._node)
4396 d.addCallback(_created)
4398 def _check_data(data):
4399 # Decode the netstring representation of the directory to check that all children
4400 # are present. This is a bit of an abstraction violation, but there's not really
4401 # any other way to do it given that the real DirectoryNode._unpack_contents would
4402 # strip the mutant children out (which is what we're trying to test, later).
4405 while position < len(data):
4406 entries, position = split_netstring(data, 1, position)
4408 (name_utf8, ro_uri, rwcapdata, metadata_s), subpos = split_netstring(entry, 4)
4409 name = name_utf8.decode("utf-8")
4410 self.failUnlessEqual(rwcapdata, "")
4411 self.failUnlessIn(name, kids)
4412 (expected_child, ign) = kids[name]
4413 self.failUnlessReallyEqual(ro_uri, expected_child.get_readonly_uri())
4416 self.failUnlessReallyEqual(numkids, 3)
4417 return self.rootnode.list()
4418 d.addCallback(_check_data)
4420 # Now when we use the real directory listing code, the mutants should be absent.
4421 def _check_kids(children):
4422 self.failUnlessReallyEqual(sorted(children.keys()), [u"lonely"])
4423 lonely_node, lonely_metadata = children[u"lonely"]
4425 self.failUnlessReallyEqual(lonely_node.get_write_uri(), None)
4426 self.failUnlessReallyEqual(lonely_node.get_readonly_uri(), lonely_uri)
4427 d.addCallback(_check_kids)
4429 d.addCallback(lambda ign: nm.create_from_cap(self.cap.to_string()))
4430 d.addCallback(lambda n: n.list())
4431 d.addCallback(_check_kids) # again with dirnode recreated from cap
4433 # Make sure the lonely child can be listed in HTML...
4434 d.addCallback(lambda ign: self.GET(self.rooturl))
4435 def _check_html(res):
4436 self.failIfIn("URI:SSK", res)
4437 get_lonely = "".join([r'<td>FILE</td>',
4439 r'<a href="[^"]+%s[^"]+">lonely</a>' % (urllib.quote(lonely_uri),),
4441 r'\s+<td align="right">%d</td>' % len("one"),
4443 self.failUnless(re.search(get_lonely, res), res)
4445 # find the More Info link for name, should be relative
4446 mo = re.search(r'<a href="([^"]+)">More Info</a>', res)
4447 info_url = mo.group(1)
4448 self.failUnless(info_url.endswith(urllib.quote(lonely_uri) + "?t=info"), info_url)
4449 d.addCallback(_check_html)
4452 d.addCallback(lambda ign: self.GET(self.rooturl+"?t=json"))
4453 def _check_json(res):
4454 data = simplejson.loads(res)
4455 self.failUnlessEqual(data[0], "dirnode")
4456 listed_children = data[1]["children"]
4457 self.failUnlessReallyEqual(sorted(listed_children.keys()), [u"lonely"])
4458 ll_type, ll_data = listed_children[u"lonely"]
4459 self.failUnlessEqual(ll_type, "filenode")
4460 self.failIfIn("rw_uri", ll_data)
4461 self.failUnlessReallyEqual(to_str(ll_data["ro_uri"]), lonely_uri)
4462 d.addCallback(_check_json)
4465 def test_deep_check(self):
4466 self.basedir = "web/Grid/deep_check"
4468 c0 = self.g.clients[0]
4472 d = c0.create_dirnode()
4473 def _stash_root_and_create_file(n):
4475 self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri()) + "/"
4476 return n.add_file(u"good", upload.Data(DATA, convergence=""))
4477 d.addCallback(_stash_root_and_create_file)
4478 def _stash_uri(fn, which):
4479 self.uris[which] = fn.get_uri()
4481 d.addCallback(_stash_uri, "good")
4482 d.addCallback(lambda ign:
4483 self.rootnode.add_file(u"small",
4484 upload.Data("literal",
4486 d.addCallback(_stash_uri, "small")
4487 d.addCallback(lambda ign:
4488 self.rootnode.add_file(u"sick",
4489 upload.Data(DATA+"1",
4491 d.addCallback(_stash_uri, "sick")
4493 # this tests that deep-check and stream-manifest will ignore
4494 # UnknownNode instances. Hopefully this will also cover deep-stats.
4495 future_node = UnknownNode(unknown_rwcap, unknown_rocap)
4496 d.addCallback(lambda ign: self.rootnode.set_node(u"future", future_node))
4498 def _clobber_shares(ignored):
4499 self.delete_shares_numbered(self.uris["sick"], [0,1])
4500 d.addCallback(_clobber_shares)
4508 d.addCallback(self.CHECK, "root", "t=stream-deep-check")
4511 units = [simplejson.loads(line)
4512 for line in res.splitlines()
4515 print "response is:", res
4516 print "undecodeable line was '%s'" % line
4518 self.failUnlessReallyEqual(len(units), 5+1)
4519 # should be parent-first
4521 self.failUnlessEqual(u0["path"], [])
4522 self.failUnlessEqual(u0["type"], "directory")
4523 self.failUnlessReallyEqual(to_str(u0["cap"]), self.rootnode.get_uri())
4524 u0cr = u0["check-results"]
4525 self.failUnlessReallyEqual(u0cr["results"]["count-shares-good"], 10)
4527 ugood = [u for u in units
4528 if u["type"] == "file" and u["path"] == [u"good"]][0]
4529 self.failUnlessReallyEqual(to_str(ugood["cap"]), self.uris["good"])
4530 ugoodcr = ugood["check-results"]
4531 self.failUnlessReallyEqual(ugoodcr["results"]["count-shares-good"], 10)
4534 self.failUnlessEqual(stats["type"], "stats")
4536 self.failUnlessReallyEqual(s["count-immutable-files"], 2)
4537 self.failUnlessReallyEqual(s["count-literal-files"], 1)
4538 self.failUnlessReallyEqual(s["count-directories"], 1)
4539 self.failUnlessReallyEqual(s["count-unknown"], 1)
4540 d.addCallback(_done)
4542 d.addCallback(self.CHECK, "root", "t=stream-manifest")
4543 def _check_manifest(res):
4544 self.failUnless(res.endswith("\n"))
4545 units = [simplejson.loads(t) for t in res[:-1].split("\n")]
4546 self.failUnlessReallyEqual(len(units), 5+1)
4547 self.failUnlessEqual(units[-1]["type"], "stats")
4549 self.failUnlessEqual(first["path"], [])
4550 self.failUnlessEqual(to_str(first["cap"]), self.rootnode.get_uri())
4551 self.failUnlessEqual(first["type"], "directory")
4552 stats = units[-1]["stats"]
4553 self.failUnlessReallyEqual(stats["count-immutable-files"], 2)
4554 self.failUnlessReallyEqual(stats["count-literal-files"], 1)
4555 self.failUnlessReallyEqual(stats["count-mutable-files"], 0)
4556 self.failUnlessReallyEqual(stats["count-immutable-files"], 2)
4557 self.failUnlessReallyEqual(stats["count-unknown"], 1)
4558 d.addCallback(_check_manifest)
4560 # now add root/subdir and root/subdir/grandchild, then make subdir
4561 # unrecoverable, then see what happens
4563 d.addCallback(lambda ign:
4564 self.rootnode.create_subdirectory(u"subdir"))
4565 d.addCallback(_stash_uri, "subdir")
4566 d.addCallback(lambda subdir_node:
4567 subdir_node.add_file(u"grandchild",
4568 upload.Data(DATA+"2",
4570 d.addCallback(_stash_uri, "grandchild")
4572 d.addCallback(lambda ign:
4573 self.delete_shares_numbered(self.uris["subdir"],
4581 # root/subdir [unrecoverable]
4582 # root/subdir/grandchild
4584 # how should a streaming-JSON API indicate fatal error?
4585 # answer: emit ERROR: instead of a JSON string
4587 d.addCallback(self.CHECK, "root", "t=stream-manifest")
4588 def _check_broken_manifest(res):
4589 lines = res.splitlines()
4591 for (i,line) in enumerate(lines)
4592 if line.startswith("ERROR:")]
4594 self.fail("no ERROR: in output: %s" % (res,))
4595 first_error = error_lines[0]
4596 error_line = lines[first_error]
4597 error_msg = lines[first_error+1:]
4598 error_msg_s = "\n".join(error_msg) + "\n"
4599 self.failUnlessIn("ERROR: UnrecoverableFileError(no recoverable versions)",
4601 self.failUnless(len(error_msg) > 2, error_msg_s) # some traceback
4602 units = [simplejson.loads(line) for line in lines[:first_error]]
4603 self.failUnlessReallyEqual(len(units), 6) # includes subdir
4604 last_unit = units[-1]
4605 self.failUnlessEqual(last_unit["path"], ["subdir"])
4606 d.addCallback(_check_broken_manifest)
4608 d.addCallback(self.CHECK, "root", "t=stream-deep-check")
4609 def _check_broken_deepcheck(res):
4610 lines = res.splitlines()
4612 for (i,line) in enumerate(lines)
4613 if line.startswith("ERROR:")]
4615 self.fail("no ERROR: in output: %s" % (res,))
4616 first_error = error_lines[0]
4617 error_line = lines[first_error]
4618 error_msg = lines[first_error+1:]
4619 error_msg_s = "\n".join(error_msg) + "\n"
4620 self.failUnlessIn("ERROR: UnrecoverableFileError(no recoverable versions)",
4622 self.failUnless(len(error_msg) > 2, error_msg_s) # some traceback
4623 units = [simplejson.loads(line) for line in lines[:first_error]]
4624 self.failUnlessReallyEqual(len(units), 6) # includes subdir
4625 last_unit = units[-1]
4626 self.failUnlessEqual(last_unit["path"], ["subdir"])
4627 r = last_unit["check-results"]["results"]
4628 self.failUnlessReallyEqual(r["count-recoverable-versions"], 0)
4629 self.failUnlessReallyEqual(r["count-shares-good"], 1)
4630 self.failUnlessReallyEqual(r["recoverable"], False)
4631 d.addCallback(_check_broken_deepcheck)
4633 d.addErrback(self.explain_web_error)
4636 def test_deep_check_and_repair(self):
4637 self.basedir = "web/Grid/deep_check_and_repair"
4639 c0 = self.g.clients[0]
4643 d = c0.create_dirnode()
4644 def _stash_root_and_create_file(n):
4646 self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri()) + "/"
4647 return n.add_file(u"good", upload.Data(DATA, convergence=""))
4648 d.addCallback(_stash_root_and_create_file)
4649 def _stash_uri(fn, which):
4650 self.uris[which] = fn.get_uri()
4651 d.addCallback(_stash_uri, "good")
4652 d.addCallback(lambda ign:
4653 self.rootnode.add_file(u"small",
4654 upload.Data("literal",
4656 d.addCallback(_stash_uri, "small")
4657 d.addCallback(lambda ign:
4658 self.rootnode.add_file(u"sick",
4659 upload.Data(DATA+"1",
4661 d.addCallback(_stash_uri, "sick")
4662 #d.addCallback(lambda ign:
4663 # self.rootnode.add_file(u"dead",
4664 # upload.Data(DATA+"2",
4666 #d.addCallback(_stash_uri, "dead")
4668 #d.addCallback(lambda ign: c0.create_mutable_file("mutable"))
4669 #d.addCallback(lambda fn: self.rootnode.set_node(u"corrupt", fn))
4670 #d.addCallback(_stash_uri, "corrupt")
4672 def _clobber_shares(ignored):
4673 good_shares = self.find_uri_shares(self.uris["good"])
4674 self.failUnlessReallyEqual(len(good_shares), 10)
4675 sick_shares = self.find_uri_shares(self.uris["sick"])
4676 os.unlink(sick_shares[0][2])
4677 #dead_shares = self.find_uri_shares(self.uris["dead"])
4678 #for i in range(1, 10):
4679 # os.unlink(dead_shares[i][2])
4681 #c_shares = self.find_uri_shares(self.uris["corrupt"])
4682 #cso = CorruptShareOptions()
4683 #cso.stdout = StringIO()
4684 #cso.parseOptions([c_shares[0][2]])
4686 d.addCallback(_clobber_shares)
4689 # root/good CHK, 10 shares
4691 # root/sick CHK, 9 shares
4693 d.addCallback(self.CHECK, "root", "t=stream-deep-check&repair=true")
4695 units = [simplejson.loads(line)
4696 for line in res.splitlines()
4698 self.failUnlessReallyEqual(len(units), 4+1)
4699 # should be parent-first
4701 self.failUnlessEqual(u0["path"], [])
4702 self.failUnlessEqual(u0["type"], "directory")
4703 self.failUnlessReallyEqual(to_str(u0["cap"]), self.rootnode.get_uri())
4704 u0crr = u0["check-and-repair-results"]
4705 self.failUnlessReallyEqual(u0crr["repair-attempted"], False)
4706 self.failUnlessReallyEqual(u0crr["pre-repair-results"]["results"]["count-shares-good"], 10)
4708 ugood = [u for u in units
4709 if u["type"] == "file" and u["path"] == [u"good"]][0]
4710 self.failUnlessEqual(to_str(ugood["cap"]), self.uris["good"])
4711 ugoodcrr = ugood["check-and-repair-results"]
4712 self.failUnlessReallyEqual(ugoodcrr["repair-attempted"], False)
4713 self.failUnlessReallyEqual(ugoodcrr["pre-repair-results"]["results"]["count-shares-good"], 10)
4715 usick = [u for u in units
4716 if u["type"] == "file" and u["path"] == [u"sick"]][0]
4717 self.failUnlessReallyEqual(to_str(usick["cap"]), self.uris["sick"])
4718 usickcrr = usick["check-and-repair-results"]
4719 self.failUnlessReallyEqual(usickcrr["repair-attempted"], True)
4720 self.failUnlessReallyEqual(usickcrr["repair-successful"], True)
4721 self.failUnlessReallyEqual(usickcrr["pre-repair-results"]["results"]["count-shares-good"], 9)
4722 self.failUnlessReallyEqual(usickcrr["post-repair-results"]["results"]["count-shares-good"], 10)
4725 self.failUnlessEqual(stats["type"], "stats")
4727 self.failUnlessReallyEqual(s["count-immutable-files"], 2)
4728 self.failUnlessReallyEqual(s["count-literal-files"], 1)
4729 self.failUnlessReallyEqual(s["count-directories"], 1)
4730 d.addCallback(_done)
4732 d.addErrback(self.explain_web_error)
4735 def _count_leases(self, ignored, which):
4736 u = self.uris[which]
4737 shares = self.find_uri_shares(u)
4739 for shnum, serverid, fn in shares:
4740 sf = get_share_file(fn)
4741 num_leases = len(list(sf.get_leases()))
4742 lease_counts.append( (fn, num_leases) )
4745 def _assert_leasecount(self, lease_counts, expected):
4746 for (fn, num_leases) in lease_counts:
4747 if num_leases != expected:
4748 self.fail("expected %d leases, have %d, on %s" %
4749 (expected, num_leases, fn))
4751 def test_add_lease(self):
4752 self.basedir = "web/Grid/add_lease"
4753 self.set_up_grid(num_clients=2)
4754 c0 = self.g.clients[0]
4757 d = c0.upload(upload.Data(DATA, convergence=""))
4758 def _stash_uri(ur, which):
4759 self.uris[which] = ur.uri
4760 d.addCallback(_stash_uri, "one")
4761 d.addCallback(lambda ign:
4762 c0.upload(upload.Data(DATA+"1", convergence="")))
4763 d.addCallback(_stash_uri, "two")
4764 def _stash_mutable_uri(n, which):
4765 self.uris[which] = n.get_uri()
4766 assert isinstance(self.uris[which], str)
4767 d.addCallback(lambda ign:
4768 c0.create_mutable_file(publish.MutableData(DATA+"2")))
4769 d.addCallback(_stash_mutable_uri, "mutable")
4771 def _compute_fileurls(ignored):
4773 for which in self.uris:
4774 self.fileurls[which] = "uri/" + urllib.quote(self.uris[which])
4775 d.addCallback(_compute_fileurls)
4777 d.addCallback(self._count_leases, "one")
4778 d.addCallback(self._assert_leasecount, 1)
4779 d.addCallback(self._count_leases, "two")
4780 d.addCallback(self._assert_leasecount, 1)
4781 d.addCallback(self._count_leases, "mutable")
4782 d.addCallback(self._assert_leasecount, 1)
4784 d.addCallback(self.CHECK, "one", "t=check") # no add-lease
4785 def _got_html_good(res):
4786 self.failUnlessIn("Healthy", res)
4787 self.failIfIn("Not Healthy", res)
4788 d.addCallback(_got_html_good)
4790 d.addCallback(self._count_leases, "one")
4791 d.addCallback(self._assert_leasecount, 1)
4792 d.addCallback(self._count_leases, "two")
4793 d.addCallback(self._assert_leasecount, 1)
4794 d.addCallback(self._count_leases, "mutable")
4795 d.addCallback(self._assert_leasecount, 1)
4797 # this CHECK uses the original client, which uses the same
4798 # lease-secrets, so it will just renew the original lease
4799 d.addCallback(self.CHECK, "one", "t=check&add-lease=true")
4800 d.addCallback(_got_html_good)
4802 d.addCallback(self._count_leases, "one")
4803 d.addCallback(self._assert_leasecount, 1)
4804 d.addCallback(self._count_leases, "two")
4805 d.addCallback(self._assert_leasecount, 1)
4806 d.addCallback(self._count_leases, "mutable")
4807 d.addCallback(self._assert_leasecount, 1)
4809 # this CHECK uses an alternate client, which adds a second lease
4810 d.addCallback(self.CHECK, "one", "t=check&add-lease=true", clientnum=1)
4811 d.addCallback(_got_html_good)
4813 d.addCallback(self._count_leases, "one")
4814 d.addCallback(self._assert_leasecount, 2)
4815 d.addCallback(self._count_leases, "two")
4816 d.addCallback(self._assert_leasecount, 1)
4817 d.addCallback(self._count_leases, "mutable")
4818 d.addCallback(self._assert_leasecount, 1)
4820 d.addCallback(self.CHECK, "mutable", "t=check&add-lease=true")
4821 d.addCallback(_got_html_good)
4823 d.addCallback(self._count_leases, "one")
4824 d.addCallback(self._assert_leasecount, 2)
4825 d.addCallback(self._count_leases, "two")
4826 d.addCallback(self._assert_leasecount, 1)
4827 d.addCallback(self._count_leases, "mutable")
4828 d.addCallback(self._assert_leasecount, 1)
4830 d.addCallback(self.CHECK, "mutable", "t=check&add-lease=true",
4832 d.addCallback(_got_html_good)
4834 d.addCallback(self._count_leases, "one")
4835 d.addCallback(self._assert_leasecount, 2)
4836 d.addCallback(self._count_leases, "two")
4837 d.addCallback(self._assert_leasecount, 1)
4838 d.addCallback(self._count_leases, "mutable")
4839 d.addCallback(self._assert_leasecount, 2)
4841 d.addErrback(self.explain_web_error)
4844 def test_deep_add_lease(self):
4845 self.basedir = "web/Grid/deep_add_lease"
4846 self.set_up_grid(num_clients=2)
4847 c0 = self.g.clients[0]
4851 d = c0.create_dirnode()
4852 def _stash_root_and_create_file(n):
4854 self.uris["root"] = n.get_uri()
4855 self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri()) + "/"
4856 return n.add_file(u"one", upload.Data(DATA, convergence=""))
4857 d.addCallback(_stash_root_and_create_file)
4858 def _stash_uri(fn, which):
4859 self.uris[which] = fn.get_uri()
4860 d.addCallback(_stash_uri, "one")
4861 d.addCallback(lambda ign:
4862 self.rootnode.add_file(u"small",
4863 upload.Data("literal",
4865 d.addCallback(_stash_uri, "small")
4867 d.addCallback(lambda ign:
4868 c0.create_mutable_file(publish.MutableData("mutable")))
4869 d.addCallback(lambda fn: self.rootnode.set_node(u"mutable", fn))
4870 d.addCallback(_stash_uri, "mutable")
4872 d.addCallback(self.CHECK, "root", "t=stream-deep-check") # no add-lease
4874 units = [simplejson.loads(line)
4875 for line in res.splitlines()
4877 # root, one, small, mutable, stats
4878 self.failUnlessReallyEqual(len(units), 4+1)
4879 d.addCallback(_done)
4881 d.addCallback(self._count_leases, "root")
4882 d.addCallback(self._assert_leasecount, 1)
4883 d.addCallback(self._count_leases, "one")
4884 d.addCallback(self._assert_leasecount, 1)
4885 d.addCallback(self._count_leases, "mutable")
4886 d.addCallback(self._assert_leasecount, 1)
4888 d.addCallback(self.CHECK, "root", "t=stream-deep-check&add-lease=true")
4889 d.addCallback(_done)
4891 d.addCallback(self._count_leases, "root")
4892 d.addCallback(self._assert_leasecount, 1)
4893 d.addCallback(self._count_leases, "one")
4894 d.addCallback(self._assert_leasecount, 1)
4895 d.addCallback(self._count_leases, "mutable")
4896 d.addCallback(self._assert_leasecount, 1)
4898 d.addCallback(self.CHECK, "root", "t=stream-deep-check&add-lease=true",
4900 d.addCallback(_done)
4902 d.addCallback(self._count_leases, "root")
4903 d.addCallback(self._assert_leasecount, 2)
4904 d.addCallback(self._count_leases, "one")
4905 d.addCallback(self._assert_leasecount, 2)
4906 d.addCallback(self._count_leases, "mutable")
4907 d.addCallback(self._assert_leasecount, 2)
4909 d.addErrback(self.explain_web_error)
4913 def test_exceptions(self):
4914 self.basedir = "web/Grid/exceptions"
4915 self.set_up_grid(num_clients=1, num_servers=2)
4916 c0 = self.g.clients[0]
4917 c0.DEFAULT_ENCODING_PARAMETERS['happy'] = 2
4920 d = c0.create_dirnode()
4922 self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri()) + "/"
4923 self.fileurls["imaginary"] = self.fileurls["root"] + "imaginary"
4925 d.addCallback(_stash_root)
4926 d.addCallback(lambda ign: c0.upload(upload.Data(DATA, convergence="")))
4928 self.fileurls["1share"] = "uri/" + urllib.quote(ur.uri)
4929 self.delete_shares_numbered(ur.uri, range(1,10))
4931 u = uri.from_string(ur.uri)
4932 u.key = testutil.flip_bit(u.key, 0)
4933 baduri = u.to_string()
4934 self.fileurls["0shares"] = "uri/" + urllib.quote(baduri)
4935 d.addCallback(_stash_bad)
4936 d.addCallback(lambda ign: c0.create_dirnode())
4937 def _mangle_dirnode_1share(n):
4939 url = self.fileurls["dir-1share"] = "uri/" + urllib.quote(u) + "/"
4940 self.fileurls["dir-1share-json"] = url + "?t=json"
4941 self.delete_shares_numbered(u, range(1,10))
4942 d.addCallback(_mangle_dirnode_1share)
4943 d.addCallback(lambda ign: c0.create_dirnode())
4944 def _mangle_dirnode_0share(n):
4946 url = self.fileurls["dir-0share"] = "uri/" + urllib.quote(u) + "/"
4947 self.fileurls["dir-0share-json"] = url + "?t=json"
4948 self.delete_shares_numbered(u, range(0,10))
4949 d.addCallback(_mangle_dirnode_0share)
4951 # NotEnoughSharesError should be reported sensibly, with a
4952 # text/plain explanation of the problem, and perhaps some
4953 # information on which shares *could* be found.
4955 d.addCallback(lambda ignored:
4956 self.shouldHTTPError("GET unrecoverable",
4957 410, "Gone", "NoSharesError",
4958 self.GET, self.fileurls["0shares"]))
4959 def _check_zero_shares(body):
4960 self.failIfIn("<html>", body)
4961 body = " ".join(body.strip().split())
4962 exp = ("NoSharesError: no shares could be found. "
4963 "Zero shares usually indicates a corrupt URI, or that "
4964 "no servers were connected, but it might also indicate "
4965 "severe corruption. You should perform a filecheck on "
4966 "this object to learn more. The full error message is: "
4967 "no shares (need 3). Last failure: None")
4968 self.failUnlessReallyEqual(exp, body)
4969 d.addCallback(_check_zero_shares)
4972 d.addCallback(lambda ignored:
4973 self.shouldHTTPError("GET 1share",
4974 410, "Gone", "NotEnoughSharesError",
4975 self.GET, self.fileurls["1share"]))
4976 def _check_one_share(body):
4977 self.failIfIn("<html>", body)
4978 body = " ".join(body.strip().split())
4979 msgbase = ("NotEnoughSharesError: This indicates that some "
4980 "servers were unavailable, or that shares have been "
4981 "lost to server departure, hard drive failure, or disk "
4982 "corruption. You should perform a filecheck on "
4983 "this object to learn more. The full error message is:"
4985 msg1 = msgbase + (" ran out of shares:"
4988 " overdue= unused= need 3. Last failure: None")
4989 msg2 = msgbase + (" ran out of shares:"
4991 " pending=Share(sh0-on-xgru5)"
4992 " overdue= unused= need 3. Last failure: None")
4993 self.failUnless(body == msg1 or body == msg2, body)
4994 d.addCallback(_check_one_share)
4996 d.addCallback(lambda ignored:
4997 self.shouldHTTPError("GET imaginary",
4998 404, "Not Found", None,
4999 self.GET, self.fileurls["imaginary"]))
5000 def _missing_child(body):
5001 self.failUnlessIn("No such child: imaginary", body)
5002 d.addCallback(_missing_child)
5004 d.addCallback(lambda ignored: self.GET(self.fileurls["dir-0share"]))
5005 def _check_0shares_dir_html(body):
5006 self.failUnlessIn("<html>", body)
5007 # we should see the regular page, but without the child table or
5009 body = " ".join(body.strip().split())
5010 self.failUnlessIn('href="?t=info">More info on this directory',
5012 exp = ("UnrecoverableFileError: the directory (or mutable file) "
5013 "could not be retrieved, because there were insufficient "
5014 "good shares. This might indicate that no servers were "
5015 "connected, insufficient servers were connected, the URI "
5016 "was corrupt, or that shares have been lost due to server "
5017 "departure, hard drive failure, or disk corruption. You "
5018 "should perform a filecheck on this object to learn more.")
5019 self.failUnlessIn(exp, body)
5020 self.failUnlessIn("No upload forms: directory is unreadable", body)
5021 d.addCallback(_check_0shares_dir_html)
5023 d.addCallback(lambda ignored: self.GET(self.fileurls["dir-1share"]))
5024 def _check_1shares_dir_html(body):
5025 # at some point, we'll split UnrecoverableFileError into 0-shares
5026 # and some-shares like we did for immutable files (since there
5027 # are different sorts of advice to offer in each case). For now,
5028 # they present the same way.
5029 self.failUnlessIn("<html>", body)
5030 body = " ".join(body.strip().split())
5031 self.failUnlessIn('href="?t=info">More info on this directory',
5033 exp = ("UnrecoverableFileError: the directory (or mutable file) "
5034 "could not be retrieved, because there were insufficient "
5035 "good shares. This might indicate that no servers were "
5036 "connected, insufficient servers were connected, the URI "
5037 "was corrupt, or that shares have been lost due to server "
5038 "departure, hard drive failure, or disk corruption. You "
5039 "should perform a filecheck on this object to learn more.")
5040 self.failUnlessIn(exp, body)
5041 self.failUnlessIn("No upload forms: directory is unreadable", body)
5042 d.addCallback(_check_1shares_dir_html)
5044 d.addCallback(lambda ignored:
5045 self.shouldHTTPError("GET dir-0share-json",
5046 410, "Gone", "UnrecoverableFileError",
5048 self.fileurls["dir-0share-json"]))
5049 def _check_unrecoverable_file(body):
5050 self.failIfIn("<html>", body)
5051 body = " ".join(body.strip().split())
5052 exp = ("UnrecoverableFileError: the directory (or mutable file) "
5053 "could not be retrieved, because there were insufficient "
5054 "good shares. This might indicate that no servers were "
5055 "connected, insufficient servers were connected, the URI "
5056 "was corrupt, or that shares have been lost due to server "
5057 "departure, hard drive failure, or disk corruption. You "
5058 "should perform a filecheck on this object to learn more.")
5059 self.failUnlessReallyEqual(exp, body)
5060 d.addCallback(_check_unrecoverable_file)
5062 d.addCallback(lambda ignored:
5063 self.shouldHTTPError("GET dir-1share-json",
5064 410, "Gone", "UnrecoverableFileError",
5066 self.fileurls["dir-1share-json"]))
5067 d.addCallback(_check_unrecoverable_file)
5069 d.addCallback(lambda ignored:
5070 self.shouldHTTPError("GET imaginary",
5071 404, "Not Found", None,
5072 self.GET, self.fileurls["imaginary"]))
5074 # attach a webapi child that throws a random error, to test how it
5076 w = c0.getServiceNamed("webish")
5077 w.root.putChild("ERRORBOOM", ErrorBoom())
5079 # "Accept: */*" : should get a text/html stack trace
5080 # "Accept: text/plain" : should get a text/plain stack trace
5081 # "Accept: text/plain, application/octet-stream" : text/plain (CLI)
5082 # no Accept header: should get a text/html stack trace
5084 d.addCallback(lambda ignored:
5085 self.shouldHTTPError("GET errorboom_html",
5086 500, "Internal Server Error", None,
5087 self.GET, "ERRORBOOM",
5088 headers={"accept": ["*/*"]}))
5089 def _internal_error_html1(body):
5090 self.failUnlessIn("<html>", "expected HTML, not '%s'" % body)
5091 d.addCallback(_internal_error_html1)
5093 d.addCallback(lambda ignored:
5094 self.shouldHTTPError("GET errorboom_text",
5095 500, "Internal Server Error", None,
5096 self.GET, "ERRORBOOM",
5097 headers={"accept": ["text/plain"]}))
5098 def _internal_error_text2(body):
5099 self.failIfIn("<html>", body)
5100 self.failUnless(body.startswith("Traceback "), body)
5101 d.addCallback(_internal_error_text2)
5103 CLI_accepts = "text/plain, application/octet-stream"
5104 d.addCallback(lambda ignored:
5105 self.shouldHTTPError("GET errorboom_text",
5106 500, "Internal Server Error", None,
5107 self.GET, "ERRORBOOM",
5108 headers={"accept": [CLI_accepts]}))
5109 def _internal_error_text3(body):
5110 self.failIfIn("<html>", body)
5111 self.failUnless(body.startswith("Traceback "), body)
5112 d.addCallback(_internal_error_text3)
5114 d.addCallback(lambda ignored:
5115 self.shouldHTTPError("GET errorboom_text",
5116 500, "Internal Server Error", None,
5117 self.GET, "ERRORBOOM"))
5118 def _internal_error_html4(body):
5119 self.failUnlessIn("<html>", body)
5120 d.addCallback(_internal_error_html4)
5122 def _flush_errors(res):
5123 # Trial: please ignore the CompletelyUnhandledError in the logs
5124 self.flushLoggedErrors(CompletelyUnhandledError)
5126 d.addBoth(_flush_errors)
5130 def test_blacklist(self):
5131 # download from a blacklisted URI, get an error
5132 self.basedir = "web/Grid/blacklist"
5134 c0 = self.g.clients[0]
5135 c0_basedir = c0.basedir
5136 fn = os.path.join(c0_basedir, "access.blacklist")
5138 DATA = "off-limits " * 50
5140 d = c0.upload(upload.Data(DATA, convergence=""))
5141 def _stash_uri_and_create_dir(ur):
5143 self.url = "uri/"+self.uri
5144 u = uri.from_string_filenode(self.uri)
5145 self.si = u.get_storage_index()
5146 childnode = c0.create_node_from_uri(self.uri, None)
5147 return c0.create_dirnode({u"blacklisted.txt": (childnode,{}) })
5148 d.addCallback(_stash_uri_and_create_dir)
5149 def _stash_dir(node):
5150 self.dir_node = node
5151 self.dir_uri = node.get_uri()
5152 self.dir_url = "uri/"+self.dir_uri
5153 d.addCallback(_stash_dir)
5154 d.addCallback(lambda ign: self.GET(self.dir_url, followRedirect=True))
5155 def _check_dir_html(body):
5156 self.failUnlessIn("<html>", body)
5157 self.failUnlessIn("blacklisted.txt</a>", body)
5158 d.addCallback(_check_dir_html)
5159 d.addCallback(lambda ign: self.GET(self.url))
5160 d.addCallback(lambda body: self.failUnlessEqual(DATA, body))
5162 def _blacklist(ign):
5164 f.write(" # this is a comment\n")
5166 f.write("\n") # also exercise blank lines
5167 f.write("%s %s\n" % (base32.b2a(self.si), "off-limits to you"))
5169 # clients should be checking the blacklist each time, so we don't
5170 # need to restart the client
5171 d.addCallback(_blacklist)
5172 d.addCallback(lambda ign: self.shouldHTTPError("get_from_blacklisted_uri",
5174 "Access Prohibited: off-limits",
5175 self.GET, self.url))
5177 # We should still be able to list the parent directory, in HTML...
5178 d.addCallback(lambda ign: self.GET(self.dir_url, followRedirect=True))
5179 def _check_dir_html2(body):
5180 self.failUnlessIn("<html>", body)
5181 self.failUnlessIn("blacklisted.txt</strike>", body)
5182 d.addCallback(_check_dir_html2)
5184 # ... and in JSON (used by CLI).
5185 d.addCallback(lambda ign: self.GET(self.dir_url+"?t=json", followRedirect=True))
5186 def _check_dir_json(res):
5187 data = simplejson.loads(res)
5188 self.failUnless(isinstance(data, list), data)
5189 self.failUnlessEqual(data[0], "dirnode")
5190 self.failUnless(isinstance(data[1], dict), data)
5191 self.failUnlessIn("children", data[1])
5192 self.failUnlessIn("blacklisted.txt", data[1]["children"])
5193 childdata = data[1]["children"]["blacklisted.txt"]
5194 self.failUnless(isinstance(childdata, list), data)
5195 self.failUnlessEqual(childdata[0], "filenode")
5196 self.failUnless(isinstance(childdata[1], dict), data)
5197 d.addCallback(_check_dir_json)
5199 def _unblacklist(ign):
5200 open(fn, "w").close()
5201 # the Blacklist object watches mtime to tell when the file has
5202 # changed, but on windows this test will run faster than the
5203 # filesystem's mtime resolution. So we edit Blacklist.last_mtime
5204 # to force a reload.
5205 self.g.clients[0].blacklist.last_mtime -= 2.0
5206 d.addCallback(_unblacklist)
5208 # now a read should work
5209 d.addCallback(lambda ign: self.GET(self.url))
5210 d.addCallback(lambda body: self.failUnlessEqual(DATA, body))
5212 # read again to exercise the blacklist-is-unchanged logic
5213 d.addCallback(lambda ign: self.GET(self.url))
5214 d.addCallback(lambda body: self.failUnlessEqual(DATA, body))
5216 # now add a blacklisted directory, and make sure files under it are
5219 childnode = c0.create_node_from_uri(self.uri, None)
5220 return c0.create_dirnode({u"child": (childnode,{}) })
5221 d.addCallback(_add_dir)
5222 def _get_dircap(dn):
5223 self.dir_si_b32 = base32.b2a(dn.get_storage_index())
5224 self.dir_url_base = "uri/"+dn.get_write_uri()
5225 self.dir_url_json1 = "uri/"+dn.get_write_uri()+"?t=json"
5226 self.dir_url_json2 = "uri/"+dn.get_write_uri()+"/?t=json"
5227 self.dir_url_json_ro = "uri/"+dn.get_readonly_uri()+"/?t=json"
5228 self.child_url = "uri/"+dn.get_readonly_uri()+"/child"
5229 d.addCallback(_get_dircap)
5230 d.addCallback(lambda ign: self.GET(self.dir_url_base, followRedirect=True))
5231 d.addCallback(lambda body: self.failUnlessIn("<html>", body))
5232 d.addCallback(lambda ign: self.GET(self.dir_url_json1))
5233 d.addCallback(lambda res: simplejson.loads(res)) # just check it decodes
5234 d.addCallback(lambda ign: self.GET(self.dir_url_json2))
5235 d.addCallback(lambda res: simplejson.loads(res)) # just check it decodes
5236 d.addCallback(lambda ign: self.GET(self.dir_url_json_ro))
5237 d.addCallback(lambda res: simplejson.loads(res)) # just check it decodes
5238 d.addCallback(lambda ign: self.GET(self.child_url))
5239 d.addCallback(lambda body: self.failUnlessEqual(DATA, body))
5241 def _block_dir(ign):
5243 f.write("%s %s\n" % (self.dir_si_b32, "dir-off-limits to you"))
5245 self.g.clients[0].blacklist.last_mtime -= 2.0
5246 d.addCallback(_block_dir)
5247 d.addCallback(lambda ign: self.shouldHTTPError("get_from_blacklisted_dir base",
5249 "Access Prohibited: dir-off-limits",
5250 self.GET, self.dir_url_base))
5251 d.addCallback(lambda ign: self.shouldHTTPError("get_from_blacklisted_dir json1",
5253 "Access Prohibited: dir-off-limits",
5254 self.GET, self.dir_url_json1))
5255 d.addCallback(lambda ign: self.shouldHTTPError("get_from_blacklisted_dir json2",
5257 "Access Prohibited: dir-off-limits",
5258 self.GET, self.dir_url_json2))
5259 d.addCallback(lambda ign: self.shouldHTTPError("get_from_blacklisted_dir json_ro",
5261 "Access Prohibited: dir-off-limits",
5262 self.GET, self.dir_url_json_ro))
5263 d.addCallback(lambda ign: self.shouldHTTPError("get_from_blacklisted_dir child",
5265 "Access Prohibited: dir-off-limits",
5266 self.GET, self.child_url))
5270 class CompletelyUnhandledError(Exception):
5272 class ErrorBoom(rend.Page):
5273 def beforeRender(self, ctx):
5274 raise CompletelyUnhandledError("whoops")