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)
250 _ign, n, blocking_uri = self.makefile(1)
251 foo.set_uri(u"blockingfile", blocking_uri, blocking_uri)
253 unicode_filename = u"n\u00fc.txt" # n u-umlaut . t x t
254 # ok, unicode calls it LATIN SMALL LETTER U WITH DIAERESIS but I
255 # still think of it as an umlaut
256 foo.set_uri(unicode_filename, self._bar_txt_uri, self._bar_txt_uri)
258 self.SUBBAZ_CONTENTS, n, baz_file = self.makefile(2)
259 self._baz_file_uri = baz_file
260 sub.set_uri(u"baz.txt", baz_file, baz_file)
262 _ign, n, self._bad_file_uri = self.makefile(3)
263 # this uri should not be downloadable
264 del FakeCHKFileNode.all_contents[self._bad_file_uri]
267 self.public_root.set_uri(u"reedownlee", rodir.get_readonly_uri(),
268 rodir.get_readonly_uri())
269 rodir.set_uri(u"nor", baz_file, baz_file)
275 # public/foo/quux.txt
276 # public/foo/blockingfile
279 # public/foo/sub/baz.txt
281 # public/reedownlee/nor
282 self.NEWFILE_CONTENTS = "newfile contents\n"
284 return foo.get_metadata_for(u"bar.txt")
286 def _got_metadata(metadata):
287 self._bar_txt_metadata = metadata
288 d.addCallback(_got_metadata)
291 def makefile(self, number):
292 contents = "contents of file %s\n" % number
293 n = create_chk_filenode(contents)
294 return contents, n, n.get_uri()
296 def makefile_mutable(self, number, mdmf=False):
297 contents = "contents of mutable file %s\n" % number
298 n = create_mutable_filenode(contents, mdmf)
299 return contents, n, n.get_uri(), n.get_readonly_uri()
302 return self.s.stopService()
304 def failUnlessIsBarDotTxt(self, res):
305 self.failUnlessReallyEqual(res, self.BAR_CONTENTS, res)
307 def failUnlessIsQuuxDotTxt(self, res):
308 self.failUnlessReallyEqual(res, self.QUUX_CONTENTS, res)
310 def failUnlessIsBazDotTxt(self, res):
311 self.failUnlessReallyEqual(res, self.BAZ_CONTENTS, res)
313 def failUnlessIsSubBazDotTxt(self, res):
314 self.failUnlessReallyEqual(res, self.SUBBAZ_CONTENTS, res)
316 def failUnlessIsBarJSON(self, res):
317 data = simplejson.loads(res)
318 self.failUnless(isinstance(data, list))
319 self.failUnlessEqual(data[0], "filenode")
320 self.failUnless(isinstance(data[1], dict))
321 self.failIf(data[1]["mutable"])
322 self.failIfIn("rw_uri", data[1]) # immutable
323 self.failUnlessReallyEqual(to_str(data[1]["ro_uri"]), self._bar_txt_uri)
324 self.failUnlessReallyEqual(to_str(data[1]["verify_uri"]), self._bar_txt_verifycap)
325 self.failUnlessReallyEqual(data[1]["size"], len(self.BAR_CONTENTS))
327 def failUnlessIsQuuxJSON(self, res, readonly=False):
328 data = simplejson.loads(res)
329 self.failUnless(isinstance(data, list))
330 self.failUnlessEqual(data[0], "filenode")
331 self.failUnless(isinstance(data[1], dict))
333 return self.failUnlessIsQuuxDotTxtMetadata(metadata, readonly)
335 def failUnlessIsQuuxDotTxtMetadata(self, metadata, readonly):
336 self.failUnless(metadata['mutable'])
338 self.failIfIn("rw_uri", metadata)
340 self.failUnlessIn("rw_uri", metadata)
341 self.failUnlessEqual(metadata['rw_uri'], self._quux_txt_uri)
342 self.failUnlessIn("ro_uri", metadata)
343 self.failUnlessEqual(metadata['ro_uri'], self._quux_txt_readonly_uri)
344 self.failUnlessReallyEqual(metadata['size'], len(self.QUUX_CONTENTS))
346 def failUnlessIsFooJSON(self, res):
347 data = simplejson.loads(res)
348 self.failUnless(isinstance(data, list))
349 self.failUnlessEqual(data[0], "dirnode", res)
350 self.failUnless(isinstance(data[1], dict))
351 self.failUnless(data[1]["mutable"])
352 self.failUnlessIn("rw_uri", data[1]) # mutable
353 self.failUnlessReallyEqual(to_str(data[1]["rw_uri"]), self._foo_uri)
354 self.failUnlessReallyEqual(to_str(data[1]["ro_uri"]), self._foo_readonly_uri)
355 self.failUnlessReallyEqual(to_str(data[1]["verify_uri"]), self._foo_verifycap)
357 kidnames = sorted([unicode(n) for n in data[1]["children"]])
358 self.failUnlessEqual(kidnames,
359 [u"bar.txt", u"baz.txt", u"blockingfile",
360 u"empty", u"n\u00fc.txt", u"quux.txt", u"sub"])
361 kids = dict( [(unicode(name),value)
363 in data[1]["children"].iteritems()] )
364 self.failUnlessEqual(kids[u"sub"][0], "dirnode")
365 self.failUnlessIn("metadata", kids[u"sub"][1])
366 self.failUnlessIn("tahoe", kids[u"sub"][1]["metadata"])
367 tahoe_md = kids[u"sub"][1]["metadata"]["tahoe"]
368 self.failUnlessIn("linkcrtime", tahoe_md)
369 self.failUnlessIn("linkmotime", tahoe_md)
370 self.failUnlessEqual(kids[u"bar.txt"][0], "filenode")
371 self.failUnlessReallyEqual(kids[u"bar.txt"][1]["size"], len(self.BAR_CONTENTS))
372 self.failUnlessReallyEqual(to_str(kids[u"bar.txt"][1]["ro_uri"]), self._bar_txt_uri)
373 self.failUnlessReallyEqual(to_str(kids[u"bar.txt"][1]["verify_uri"]),
374 self._bar_txt_verifycap)
375 self.failUnlessIn("metadata", kids[u"bar.txt"][1])
376 self.failUnlessIn("tahoe", kids[u"bar.txt"][1]["metadata"])
377 self.failUnlessReallyEqual(kids[u"bar.txt"][1]["metadata"]["tahoe"]["linkcrtime"],
378 self._bar_txt_metadata["tahoe"]["linkcrtime"])
379 self.failUnlessReallyEqual(to_str(kids[u"n\u00fc.txt"][1]["ro_uri"]),
381 self.failUnlessIn("quux.txt", kids)
382 self.failUnlessReallyEqual(to_str(kids[u"quux.txt"][1]["rw_uri"]),
384 self.failUnlessReallyEqual(to_str(kids[u"quux.txt"][1]["ro_uri"]),
385 self._quux_txt_readonly_uri)
387 def GET(self, urlpath, followRedirect=False, return_response=False,
389 # if return_response=True, this fires with (data, statuscode,
390 # respheaders) instead of just data.
391 assert not isinstance(urlpath, unicode)
392 url = self.webish_url + urlpath
393 factory = HTTPClientGETFactory(url, method="GET",
394 followRedirect=followRedirect, **kwargs)
395 reactor.connectTCP("localhost", self.webish_port, factory)
398 return (data, factory.status, factory.response_headers)
400 d.addCallback(_got_data)
401 return factory.deferred
403 def HEAD(self, urlpath, return_response=False, **kwargs):
404 # this requires some surgery, because twisted.web.client doesn't want
405 # to give us back the response headers.
406 factory = HTTPClientHEADFactory(urlpath, method="HEAD", **kwargs)
407 reactor.connectTCP("localhost", self.webish_port, factory)
410 return (data, factory.status, factory.response_headers)
412 d.addCallback(_got_data)
413 return factory.deferred
415 def PUT(self, urlpath, data, **kwargs):
416 url = self.webish_url + urlpath
417 return client.getPage(url, method="PUT", postdata=data, **kwargs)
419 def DELETE(self, urlpath):
420 url = self.webish_url + urlpath
421 return client.getPage(url, method="DELETE")
423 def POST(self, urlpath, followRedirect=False, **fields):
424 sepbase = "boogabooga"
428 form.append('Content-Disposition: form-data; name="_charset"')
432 for name, value in fields.iteritems():
433 if isinstance(value, tuple):
434 filename, value = value
435 form.append('Content-Disposition: form-data; name="%s"; '
436 'filename="%s"' % (name, filename.encode("utf-8")))
438 form.append('Content-Disposition: form-data; name="%s"' % name)
440 if isinstance(value, unicode):
441 value = value.encode("utf-8")
444 assert isinstance(value, str)
451 body = "\r\n".join(form) + "\r\n"
452 headers["content-type"] = "multipart/form-data; boundary=%s" % sepbase
453 return self.POST2(urlpath, body, headers, followRedirect)
455 def POST2(self, urlpath, body="", headers={}, followRedirect=False):
456 url = self.webish_url + urlpath
457 return client.getPage(url, method="POST", postdata=body,
458 headers=headers, followRedirect=followRedirect)
460 def shouldFail(self, res, expected_failure, which,
461 substring=None, response_substring=None):
462 if isinstance(res, failure.Failure):
463 res.trap(expected_failure)
465 self.failUnlessIn(substring, str(res), which)
466 if response_substring:
467 self.failUnlessIn(response_substring, res.value.response, which)
469 self.fail("%s was supposed to raise %s, not get '%s'" %
470 (which, expected_failure, res))
472 def shouldFail2(self, expected_failure, which, substring,
474 callable, *args, **kwargs):
475 assert substring is None or isinstance(substring, str)
476 assert response_substring is None or isinstance(response_substring, str)
477 d = defer.maybeDeferred(callable, *args, **kwargs)
479 if isinstance(res, failure.Failure):
480 res.trap(expected_failure)
482 self.failUnlessIn(substring, str(res),
483 "'%s' not in '%s' for test '%s'" % \
484 (substring, str(res), which))
485 if response_substring:
486 self.failUnlessIn(response_substring, res.value.response,
487 "'%s' not in '%s' for test '%s'" % \
488 (response_substring, res.value.response,
491 self.fail("%s was supposed to raise %s, not get '%s'" %
492 (which, expected_failure, res))
496 def should404(self, res, which):
497 if isinstance(res, failure.Failure):
498 res.trap(error.Error)
499 self.failUnlessReallyEqual(res.value.status, "404")
501 self.fail("%s was supposed to Error(404), not get '%s'" %
504 def should302(self, res, which):
505 if isinstance(res, failure.Failure):
506 res.trap(error.Error)
507 self.failUnlessReallyEqual(res.value.status, "302")
509 self.fail("%s was supposed to Error(302), not get '%s'" %
513 class Web(WebMixin, WebErrorMixin, testutil.StallMixin, testutil.ReallyEqualMixin, unittest.TestCase):
514 def test_create(self):
517 def test_welcome(self):
520 self.failUnlessIn('Welcome to Tahoe-LAFS', res)
521 self.failUnlessIn(FAVICON_MARKUP, res)
522 self.failUnlessIn('href="https://tahoe-lafs.org/"', res)
524 self.s.basedir = 'web/test_welcome'
525 fileutil.make_dirs("web/test_welcome")
526 fileutil.make_dirs("web/test_welcome/private")
528 d.addCallback(_check)
531 def test_status(self):
532 h = self.s.get_history()
533 dl_num = h.list_all_download_statuses()[0].get_counter()
534 ul_num = h.list_all_upload_statuses()[0].get_counter()
535 mu_num = h.list_all_mapupdate_statuses()[0].get_counter()
536 pub_num = h.list_all_publish_statuses()[0].get_counter()
537 ret_num = h.list_all_retrieve_statuses()[0].get_counter()
538 d = self.GET("/status", followRedirect=True)
540 self.failUnlessIn('Upload and Download Status', res)
541 self.failUnlessIn('"down-%d"' % dl_num, res)
542 self.failUnlessIn('"up-%d"' % ul_num, res)
543 self.failUnlessIn('"mapupdate-%d"' % mu_num, res)
544 self.failUnlessIn('"publish-%d"' % pub_num, res)
545 self.failUnlessIn('"retrieve-%d"' % ret_num, res)
546 d.addCallback(_check)
547 d.addCallback(lambda res: self.GET("/status/?t=json"))
548 def _check_json(res):
549 data = simplejson.loads(res)
550 self.failUnless(isinstance(data, dict))
551 #active = data["active"]
552 # TODO: test more. We need a way to fake an active operation
554 d.addCallback(_check_json)
556 d.addCallback(lambda res: self.GET("/status/down-%d" % dl_num))
558 self.failUnlessIn("File Download Status", res)
559 d.addCallback(_check_dl)
560 d.addCallback(lambda res: self.GET("/status/down-%d/event_json" % dl_num))
561 def _check_dl_json(res):
562 data = simplejson.loads(res)
563 self.failUnless(isinstance(data, dict))
564 self.failUnlessIn("read", data)
565 self.failUnlessEqual(data["read"][0]["length"], 120)
566 self.failUnlessEqual(data["segment"][0]["segment_length"], 100)
567 self.failUnlessEqual(data["segment"][2]["segment_number"], 2)
568 self.failUnlessEqual(data["segment"][2]["finish_time"], None)
569 phwr_id = base32.b2a(hashutil.tagged_hash("foo", "serverid_a")[:20])
570 cmpu_id = base32.b2a(hashutil.tagged_hash("foo", "serverid_b")[:20])
571 # serverids[] keys are strings, since that's what JSON does, but
572 # we'd really like them to be ints
573 self.failUnlessEqual(data["serverids"]["0"], "phwr")
574 self.failUnless(data["serverids"].has_key("1"), data["serverids"])
575 self.failUnlessEqual(data["serverids"]["1"], "cmpu", data["serverids"])
576 self.failUnlessEqual(data["server_info"][phwr_id]["short"], "phwr")
577 self.failUnlessEqual(data["server_info"][cmpu_id]["short"], "cmpu")
578 self.failUnlessIn("dyhb", data)
579 self.failUnlessIn("misc", data)
580 d.addCallback(_check_dl_json)
581 d.addCallback(lambda res: self.GET("/status/up-%d" % ul_num))
583 self.failUnlessIn("File Upload Status", res)
584 d.addCallback(_check_ul)
585 d.addCallback(lambda res: self.GET("/status/mapupdate-%d" % mu_num))
586 def _check_mapupdate(res):
587 self.failUnlessIn("Mutable File Servermap Update Status", res)
588 d.addCallback(_check_mapupdate)
589 d.addCallback(lambda res: self.GET("/status/publish-%d" % pub_num))
590 def _check_publish(res):
591 self.failUnlessIn("Mutable File Publish Status", res)
592 d.addCallback(_check_publish)
593 d.addCallback(lambda res: self.GET("/status/retrieve-%d" % ret_num))
594 def _check_retrieve(res):
595 self.failUnlessIn("Mutable File Retrieve Status", res)
596 d.addCallback(_check_retrieve)
600 def test_status_numbers(self):
601 drrm = status.DownloadResultsRendererMixin()
602 self.failUnlessReallyEqual(drrm.render_time(None, None), "")
603 self.failUnlessReallyEqual(drrm.render_time(None, 2.5), "2.50s")
604 self.failUnlessReallyEqual(drrm.render_time(None, 0.25), "250ms")
605 self.failUnlessReallyEqual(drrm.render_time(None, 0.0021), "2.1ms")
606 self.failUnlessReallyEqual(drrm.render_time(None, 0.000123), "123us")
607 self.failUnlessReallyEqual(drrm.render_rate(None, None), "")
608 self.failUnlessReallyEqual(drrm.render_rate(None, 2500000), "2.50MBps")
609 self.failUnlessReallyEqual(drrm.render_rate(None, 30100), "30.1kBps")
610 self.failUnlessReallyEqual(drrm.render_rate(None, 123), "123Bps")
612 urrm = status.UploadResultsRendererMixin()
613 self.failUnlessReallyEqual(urrm.render_time(None, None), "")
614 self.failUnlessReallyEqual(urrm.render_time(None, 2.5), "2.50s")
615 self.failUnlessReallyEqual(urrm.render_time(None, 0.25), "250ms")
616 self.failUnlessReallyEqual(urrm.render_time(None, 0.0021), "2.1ms")
617 self.failUnlessReallyEqual(urrm.render_time(None, 0.000123), "123us")
618 self.failUnlessReallyEqual(urrm.render_rate(None, None), "")
619 self.failUnlessReallyEqual(urrm.render_rate(None, 2500000), "2.50MBps")
620 self.failUnlessReallyEqual(urrm.render_rate(None, 30100), "30.1kBps")
621 self.failUnlessReallyEqual(urrm.render_rate(None, 123), "123Bps")
623 def test_GET_FILEURL(self):
624 d = self.GET(self.public_url + "/foo/bar.txt")
625 d.addCallback(self.failUnlessIsBarDotTxt)
628 def test_GET_FILEURL_range(self):
629 headers = {"range": "bytes=1-10"}
630 d = self.GET(self.public_url + "/foo/bar.txt", headers=headers,
631 return_response=True)
632 def _got((res, status, headers)):
633 self.failUnlessReallyEqual(int(status), 206)
634 self.failUnless(headers.has_key("content-range"))
635 self.failUnlessReallyEqual(headers["content-range"][0],
636 "bytes 1-10/%d" % len(self.BAR_CONTENTS))
637 self.failUnlessReallyEqual(res, self.BAR_CONTENTS[1:11])
641 def test_GET_FILEURL_partial_range(self):
642 headers = {"range": "bytes=5-"}
643 length = len(self.BAR_CONTENTS)
644 d = self.GET(self.public_url + "/foo/bar.txt", headers=headers,
645 return_response=True)
646 def _got((res, status, headers)):
647 self.failUnlessReallyEqual(int(status), 206)
648 self.failUnless(headers.has_key("content-range"))
649 self.failUnlessReallyEqual(headers["content-range"][0],
650 "bytes 5-%d/%d" % (length-1, length))
651 self.failUnlessReallyEqual(res, self.BAR_CONTENTS[5:])
655 def test_GET_FILEURL_partial_end_range(self):
656 headers = {"range": "bytes=-5"}
657 length = len(self.BAR_CONTENTS)
658 d = self.GET(self.public_url + "/foo/bar.txt", headers=headers,
659 return_response=True)
660 def _got((res, status, headers)):
661 self.failUnlessReallyEqual(int(status), 206)
662 self.failUnless(headers.has_key("content-range"))
663 self.failUnlessReallyEqual(headers["content-range"][0],
664 "bytes %d-%d/%d" % (length-5, length-1, length))
665 self.failUnlessReallyEqual(res, self.BAR_CONTENTS[-5:])
669 def test_GET_FILEURL_partial_range_overrun(self):
670 headers = {"range": "bytes=100-200"}
671 d = self.shouldFail2(error.Error, "test_GET_FILEURL_range_overrun",
672 "416 Requested Range not satisfiable",
673 "First beyond end of file",
674 self.GET, self.public_url + "/foo/bar.txt",
678 def test_HEAD_FILEURL_range(self):
679 headers = {"range": "bytes=1-10"}
680 d = self.HEAD(self.public_url + "/foo/bar.txt", headers=headers,
681 return_response=True)
682 def _got((res, status, headers)):
683 self.failUnlessReallyEqual(res, "")
684 self.failUnlessReallyEqual(int(status), 206)
685 self.failUnless(headers.has_key("content-range"))
686 self.failUnlessReallyEqual(headers["content-range"][0],
687 "bytes 1-10/%d" % len(self.BAR_CONTENTS))
691 def test_HEAD_FILEURL_partial_range(self):
692 headers = {"range": "bytes=5-"}
693 length = len(self.BAR_CONTENTS)
694 d = self.HEAD(self.public_url + "/foo/bar.txt", headers=headers,
695 return_response=True)
696 def _got((res, status, headers)):
697 self.failUnlessReallyEqual(int(status), 206)
698 self.failUnless(headers.has_key("content-range"))
699 self.failUnlessReallyEqual(headers["content-range"][0],
700 "bytes 5-%d/%d" % (length-1, length))
704 def test_HEAD_FILEURL_partial_end_range(self):
705 headers = {"range": "bytes=-5"}
706 length = len(self.BAR_CONTENTS)
707 d = self.HEAD(self.public_url + "/foo/bar.txt", headers=headers,
708 return_response=True)
709 def _got((res, status, headers)):
710 self.failUnlessReallyEqual(int(status), 206)
711 self.failUnless(headers.has_key("content-range"))
712 self.failUnlessReallyEqual(headers["content-range"][0],
713 "bytes %d-%d/%d" % (length-5, length-1, length))
717 def test_HEAD_FILEURL_partial_range_overrun(self):
718 headers = {"range": "bytes=100-200"}
719 d = self.shouldFail2(error.Error, "test_HEAD_FILEURL_range_overrun",
720 "416 Requested Range not satisfiable",
722 self.HEAD, self.public_url + "/foo/bar.txt",
726 def test_GET_FILEURL_range_bad(self):
727 headers = {"range": "BOGUS=fizbop-quarnak"}
728 d = self.GET(self.public_url + "/foo/bar.txt", headers=headers,
729 return_response=True)
730 def _got((res, status, headers)):
731 self.failUnlessReallyEqual(int(status), 200)
732 self.failUnless(not headers.has_key("content-range"))
733 self.failUnlessReallyEqual(res, self.BAR_CONTENTS)
737 def test_HEAD_FILEURL(self):
738 d = self.HEAD(self.public_url + "/foo/bar.txt", return_response=True)
739 def _got((res, status, headers)):
740 self.failUnlessReallyEqual(res, "")
741 self.failUnlessReallyEqual(headers["content-length"][0],
742 str(len(self.BAR_CONTENTS)))
743 self.failUnlessReallyEqual(headers["content-type"], ["text/plain"])
747 def test_GET_FILEURL_named(self):
748 base = "/file/%s" % urllib.quote(self._bar_txt_uri)
749 base2 = "/named/%s" % urllib.quote(self._bar_txt_uri)
750 d = self.GET(base + "/@@name=/blah.txt")
751 d.addCallback(self.failUnlessIsBarDotTxt)
752 d.addCallback(lambda res: self.GET(base + "/blah.txt"))
753 d.addCallback(self.failUnlessIsBarDotTxt)
754 d.addCallback(lambda res: self.GET(base + "/ignore/lots/blah.txt"))
755 d.addCallback(self.failUnlessIsBarDotTxt)
756 d.addCallback(lambda res: self.GET(base2 + "/@@name=/blah.txt"))
757 d.addCallback(self.failUnlessIsBarDotTxt)
758 save_url = base + "?save=true&filename=blah.txt"
759 d.addCallback(lambda res: self.GET(save_url))
760 d.addCallback(self.failUnlessIsBarDotTxt) # TODO: check headers
761 u_filename = u"n\u00e9wer.txt" # n e-acute w e r . t x t
762 u_fn_e = urllib.quote(u_filename.encode("utf-8"))
763 u_url = base + "?save=true&filename=" + u_fn_e
764 d.addCallback(lambda res: self.GET(u_url))
765 d.addCallback(self.failUnlessIsBarDotTxt) # TODO: check headers
768 def test_PUT_FILEURL_named_bad(self):
769 base = "/file/%s" % urllib.quote(self._bar_txt_uri)
770 d = self.shouldFail2(error.Error, "test_PUT_FILEURL_named_bad",
772 "/file can only be used with GET or HEAD",
773 self.PUT, base + "/@@name=/blah.txt", "")
777 def test_GET_DIRURL_named_bad(self):
778 base = "/file/%s" % urllib.quote(self._foo_uri)
779 d = self.shouldFail2(error.Error, "test_PUT_DIRURL_named_bad",
782 self.GET, base + "/@@name=/blah.txt")
785 def test_GET_slash_file_bad(self):
786 d = self.shouldFail2(error.Error, "test_GET_slash_file_bad",
788 "/file must be followed by a file-cap and a name",
792 def test_GET_unhandled_URI_named(self):
793 contents, n, newuri = self.makefile(12)
794 verifier_cap = n.get_verify_cap().to_string()
795 base = "/file/%s" % urllib.quote(verifier_cap)
796 # client.create_node_from_uri() can't handle verify-caps
797 d = self.shouldFail2(error.Error, "GET_unhandled_URI_named",
798 "400 Bad Request", "is not a file-cap",
802 def test_GET_unhandled_URI(self):
803 contents, n, newuri = self.makefile(12)
804 verifier_cap = n.get_verify_cap().to_string()
805 base = "/uri/%s" % urllib.quote(verifier_cap)
806 # client.create_node_from_uri() can't handle verify-caps
807 d = self.shouldFail2(error.Error, "test_GET_unhandled_URI",
809 "GET unknown URI type: can only do t=info",
813 def test_GET_FILE_URI(self):
814 base = "/uri/%s" % urllib.quote(self._bar_txt_uri)
816 d.addCallback(self.failUnlessIsBarDotTxt)
819 def test_GET_FILE_URI_mdmf(self):
820 base = "/uri/%s" % urllib.quote(self._quux_txt_uri)
822 d.addCallback(self.failUnlessIsQuuxDotTxt)
825 def test_GET_FILE_URI_mdmf_extensions(self):
826 base = "/uri/%s" % urllib.quote("%s:RANDOMSTUFF" % self._quux_txt_uri)
828 d.addCallback(self.failUnlessIsQuuxDotTxt)
831 def test_GET_FILE_URI_mdmf_readonly(self):
832 base = "/uri/%s" % urllib.quote(self._quux_txt_readonly_uri)
834 d.addCallback(self.failUnlessIsQuuxDotTxt)
837 def test_GET_FILE_URI_badchild(self):
838 base = "/uri/%s/boguschild" % urllib.quote(self._bar_txt_uri)
839 errmsg = "Files have no children, certainly not named 'boguschild'"
840 d = self.shouldFail2(error.Error, "test_GET_FILE_URI_badchild",
841 "400 Bad Request", errmsg,
845 def test_PUT_FILE_URI_badchild(self):
846 base = "/uri/%s/boguschild" % urllib.quote(self._bar_txt_uri)
847 errmsg = "Cannot create directory 'boguschild', because its parent is a file, not a directory"
848 d = self.shouldFail2(error.Error, "test_GET_FILE_URI_badchild",
849 "400 Bad Request", errmsg,
853 def test_PUT_FILE_URI_mdmf(self):
854 base = "/uri/%s" % urllib.quote(self._quux_txt_uri)
855 self._quux_new_contents = "new_contents"
857 d.addCallback(lambda res:
858 self.failUnlessIsQuuxDotTxt(res))
859 d.addCallback(lambda ignored:
860 self.PUT(base, self._quux_new_contents))
861 d.addCallback(lambda ignored:
863 d.addCallback(lambda res:
864 self.failUnlessReallyEqual(res, self._quux_new_contents))
867 def test_PUT_FILE_URI_mdmf_extensions(self):
868 base = "/uri/%s" % urllib.quote("%s:EXTENSIONSTUFF" % self._quux_txt_uri)
869 self._quux_new_contents = "new_contents"
871 d.addCallback(lambda res: self.failUnlessIsQuuxDotTxt(res))
872 d.addCallback(lambda ignored: self.PUT(base, self._quux_new_contents))
873 d.addCallback(lambda ignored: self.GET(base))
874 d.addCallback(lambda res: self.failUnlessEqual(self._quux_new_contents,
878 def test_PUT_FILE_URI_mdmf_readonly(self):
879 # We're not allowed to PUT things to a readonly cap.
880 base = "/uri/%s" % self._quux_txt_readonly_uri
882 d.addCallback(lambda res:
883 self.failUnlessIsQuuxDotTxt(res))
884 # What should we get here? We get a 500 error now; that's not right.
885 d.addCallback(lambda ignored:
886 self.shouldFail2(error.Error, "test_PUT_FILE_URI_mdmf_readonly",
887 "400 Bad Request", "read-only cap",
888 self.PUT, base, "new data"))
891 def test_PUT_FILE_URI_sdmf_readonly(self):
892 # We're not allowed to put things to a readonly cap.
893 base = "/uri/%s" % self._baz_txt_readonly_uri
895 d.addCallback(lambda res:
896 self.failUnlessIsBazDotTxt(res))
897 d.addCallback(lambda ignored:
898 self.shouldFail2(error.Error, "test_PUT_FILE_URI_sdmf_readonly",
899 "400 Bad Request", "read-only cap",
900 self.PUT, base, "new_data"))
903 # TODO: version of this with a Unicode filename
904 def test_GET_FILEURL_save(self):
905 d = self.GET(self.public_url + "/foo/bar.txt?filename=bar.txt&save=true",
906 return_response=True)
907 def _got((res, statuscode, headers)):
908 content_disposition = headers["content-disposition"][0]
909 self.failUnless(content_disposition == 'attachment; filename="bar.txt"', content_disposition)
910 self.failUnlessIsBarDotTxt(res)
914 def test_GET_FILEURL_missing(self):
915 d = self.GET(self.public_url + "/foo/missing")
916 d.addBoth(self.should404, "test_GET_FILEURL_missing")
919 def test_GET_FILEURL_info_mdmf(self):
920 d = self.GET("/uri/%s?t=info" % self._quux_txt_uri)
922 self.failUnlessIn("mutable file (mdmf)", res)
923 self.failUnlessIn(self._quux_txt_uri, res)
924 self.failUnlessIn(self._quux_txt_readonly_uri, res)
928 def test_GET_FILEURL_info_mdmf_readonly(self):
929 d = self.GET("/uri/%s?t=info" % self._quux_txt_readonly_uri)
931 self.failUnlessIn("mutable file (mdmf)", res)
932 self.failIfIn(self._quux_txt_uri, res)
933 self.failUnlessIn(self._quux_txt_readonly_uri, res)
937 def test_GET_FILEURL_info_sdmf(self):
938 d = self.GET("/uri/%s?t=info" % self._baz_txt_uri)
940 self.failUnlessIn("mutable file (sdmf)", res)
941 self.failUnlessIn(self._baz_txt_uri, res)
945 def test_GET_FILEURL_info_mdmf_extensions(self):
946 d = self.GET("/uri/%s:STUFF?t=info" % self._quux_txt_uri)
948 self.failUnlessIn("mutable file (mdmf)", res)
949 self.failUnlessIn(self._quux_txt_uri, res)
950 self.failUnlessIn(self._quux_txt_readonly_uri, res)
954 def test_PUT_overwrite_only_files(self):
955 # create a directory, put a file in that directory.
956 contents, n, filecap = self.makefile(8)
957 d = self.PUT(self.public_url + "/foo/dir?t=mkdir", "")
958 d.addCallback(lambda res:
959 self.PUT(self.public_url + "/foo/dir/file1.txt",
960 self.NEWFILE_CONTENTS))
961 # try to overwrite the file with replace=only-files
963 d.addCallback(lambda res:
964 self.PUT(self.public_url + "/foo/dir/file1.txt?t=uri&replace=only-files",
966 d.addCallback(lambda res:
967 self.shouldFail2(error.Error, "PUT_bad_t", "409 Conflict",
968 "There was already a child by that name, and you asked me "
970 self.PUT, self.public_url + "/foo/dir?t=uri&replace=only-files",
974 def test_PUT_NEWFILEURL(self):
975 d = self.PUT(self.public_url + "/foo/new.txt", self.NEWFILE_CONTENTS)
976 # TODO: we lose the response code, so we can't check this
977 #self.failUnlessReallyEqual(responsecode, 201)
978 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"new.txt")
979 d.addCallback(lambda res:
980 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
981 self.NEWFILE_CONTENTS))
984 def test_PUT_NEWFILEURL_not_mutable(self):
985 d = self.PUT(self.public_url + "/foo/new.txt?mutable=false",
986 self.NEWFILE_CONTENTS)
987 # TODO: we lose the response code, so we can't check this
988 #self.failUnlessReallyEqual(responsecode, 201)
989 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"new.txt")
990 d.addCallback(lambda res:
991 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
992 self.NEWFILE_CONTENTS))
995 def test_PUT_NEWFILEURL_unlinked_mdmf(self):
996 # this should get us a few segments of an MDMF mutable file,
997 # which we can then test for.
998 contents = self.NEWFILE_CONTENTS * 300000
999 d = self.PUT("/uri?format=mdmf",
1001 def _got_filecap(filecap):
1002 self.failUnless(filecap.startswith("URI:MDMF"))
1004 d.addCallback(_got_filecap)
1005 d.addCallback(lambda filecap: self.GET("/uri/%s?t=json" % filecap))
1006 d.addCallback(lambda json: self.failUnlessIn("MDMF", json))
1009 def test_PUT_NEWFILEURL_unlinked_sdmf(self):
1010 contents = self.NEWFILE_CONTENTS * 300000
1011 d = self.PUT("/uri?format=sdmf",
1013 d.addCallback(lambda filecap: self.GET("/uri/%s?t=json" % filecap))
1014 d.addCallback(lambda json: self.failUnlessIn("SDMF", json))
1017 def test_PUT_NEWFILEURL_unlinked_bad_format(self):
1018 contents = self.NEWFILE_CONTENTS * 300000
1019 return self.shouldHTTPError("PUT_NEWFILEURL_unlinked_bad_format",
1020 400, "Bad Request", "Unknown format: foo",
1021 self.PUT, "/uri?format=foo",
1024 def test_PUT_NEWFILEURL_range_bad(self):
1025 headers = {"content-range": "bytes 1-10/%d" % len(self.NEWFILE_CONTENTS)}
1026 target = self.public_url + "/foo/new.txt"
1027 d = self.shouldFail2(error.Error, "test_PUT_NEWFILEURL_range_bad",
1028 "501 Not Implemented",
1029 "Content-Range in PUT not yet supported",
1030 # (and certainly not for immutable files)
1031 self.PUT, target, self.NEWFILE_CONTENTS[1:11],
1033 d.addCallback(lambda res:
1034 self.failIfNodeHasChild(self._foo_node, u"new.txt"))
1037 def test_PUT_NEWFILEURL_mutable(self):
1038 d = self.PUT(self.public_url + "/foo/new.txt?mutable=true",
1039 self.NEWFILE_CONTENTS)
1040 # TODO: we lose the response code, so we can't check this
1041 #self.failUnlessReallyEqual(responsecode, 201)
1042 def _check_uri(res):
1043 u = uri.from_string_mutable_filenode(res)
1044 self.failUnless(u.is_mutable())
1045 self.failIf(u.is_readonly())
1047 d.addCallback(_check_uri)
1048 d.addCallback(self.failUnlessURIMatchesRWChild, self._foo_node, u"new.txt")
1049 d.addCallback(lambda res:
1050 self.failUnlessMutableChildContentsAre(self._foo_node,
1052 self.NEWFILE_CONTENTS))
1055 def test_PUT_NEWFILEURL_mutable_toobig(self):
1056 # It is okay to upload large mutable files, so we should be able
1058 d = self.PUT(self.public_url + "/foo/new.txt?mutable=true",
1059 "b" * (self.s.MUTABLE_SIZELIMIT + 1))
1062 def test_PUT_NEWFILEURL_replace(self):
1063 d = self.PUT(self.public_url + "/foo/bar.txt", self.NEWFILE_CONTENTS)
1064 # TODO: we lose the response code, so we can't check this
1065 #self.failUnlessReallyEqual(responsecode, 200)
1066 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"bar.txt")
1067 d.addCallback(lambda res:
1068 self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
1069 self.NEWFILE_CONTENTS))
1072 def test_PUT_NEWFILEURL_bad_t(self):
1073 d = self.shouldFail2(error.Error, "PUT_bad_t", "400 Bad Request",
1074 "PUT to a file: bad t=bogus",
1075 self.PUT, self.public_url + "/foo/bar.txt?t=bogus",
1079 def test_PUT_NEWFILEURL_no_replace(self):
1080 d = self.PUT(self.public_url + "/foo/bar.txt?replace=false",
1081 self.NEWFILE_CONTENTS)
1082 d.addBoth(self.shouldFail, error.Error, "PUT_NEWFILEURL_no_replace",
1084 "There was already a child by that name, and you asked me "
1085 "to not replace it")
1088 def test_PUT_NEWFILEURL_mkdirs(self):
1089 d = self.PUT(self.public_url + "/foo/newdir/new.txt", self.NEWFILE_CONTENTS)
1091 d.addCallback(self.failUnlessURIMatchesROChild, fn, u"newdir/new.txt")
1092 d.addCallback(lambda res: self.failIfNodeHasChild(fn, u"new.txt"))
1093 d.addCallback(lambda res: self.failUnlessNodeHasChild(fn, u"newdir"))
1094 d.addCallback(lambda res:
1095 self.failUnlessChildContentsAre(fn, u"newdir/new.txt",
1096 self.NEWFILE_CONTENTS))
1099 def test_PUT_NEWFILEURL_blocked(self):
1100 d = self.PUT(self.public_url + "/foo/blockingfile/new.txt",
1101 self.NEWFILE_CONTENTS)
1102 d.addBoth(self.shouldFail, error.Error, "PUT_NEWFILEURL_blocked",
1104 "Unable to create directory 'blockingfile': a file was in the way")
1107 def test_PUT_NEWFILEURL_emptyname(self):
1108 # an empty pathname component (i.e. a double-slash) is disallowed
1109 d = self.shouldFail2(error.Error, "test_PUT_NEWFILEURL_emptyname",
1111 "The webapi does not allow empty pathname components",
1112 self.PUT, self.public_url + "/foo//new.txt", "")
1115 def test_DELETE_FILEURL(self):
1116 d = self.DELETE(self.public_url + "/foo/bar.txt")
1117 d.addCallback(lambda res:
1118 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
1121 def test_DELETE_FILEURL_missing(self):
1122 d = self.DELETE(self.public_url + "/foo/missing")
1123 d.addBoth(self.should404, "test_DELETE_FILEURL_missing")
1126 def test_DELETE_FILEURL_missing2(self):
1127 d = self.DELETE(self.public_url + "/missing/missing")
1128 d.addBoth(self.should404, "test_DELETE_FILEURL_missing2")
1131 def failUnlessHasBarDotTxtMetadata(self, res):
1132 data = simplejson.loads(res)
1133 self.failUnless(isinstance(data, list))
1134 self.failUnlessIn("metadata", data[1])
1135 self.failUnlessIn("tahoe", data[1]["metadata"])
1136 self.failUnlessIn("linkcrtime", data[1]["metadata"]["tahoe"])
1137 self.failUnlessIn("linkmotime", data[1]["metadata"]["tahoe"])
1138 self.failUnlessReallyEqual(data[1]["metadata"]["tahoe"]["linkcrtime"],
1139 self._bar_txt_metadata["tahoe"]["linkcrtime"])
1141 def test_GET_FILEURL_json(self):
1142 # twisted.web.http.parse_qs ignores any query args without an '=', so
1143 # I can't do "GET /path?json", I have to do "GET /path/t=json"
1144 # instead. This may make it tricky to emulate the S3 interface
1146 d = self.GET(self.public_url + "/foo/bar.txt?t=json")
1148 self.failUnlessIsBarJSON(data)
1149 self.failUnlessHasBarDotTxtMetadata(data)
1151 d.addCallback(_check1)
1154 def test_GET_FILEURL_json_mutable_type(self):
1155 # The JSON should include format, which says whether the
1156 # file is SDMF or MDMF
1157 d = self.PUT("/uri?format=mdmf",
1158 self.NEWFILE_CONTENTS * 300000)
1159 d.addCallback(lambda filecap: self.GET("/uri/%s?t=json" % filecap))
1160 def _got_json(json, version):
1161 data = simplejson.loads(json)
1162 assert "filenode" == data[0]
1164 assert isinstance(data, dict)
1166 self.failUnlessIn("format", data)
1167 self.failUnlessEqual(data["format"], version)
1169 d.addCallback(_got_json, "MDMF")
1170 # Now make an SDMF file and check that it is reported correctly.
1171 d.addCallback(lambda ignored:
1172 self.PUT("/uri?format=sdmf",
1173 self.NEWFILE_CONTENTS * 300000))
1174 d.addCallback(lambda filecap: self.GET("/uri/%s?t=json" % filecap))
1175 d.addCallback(_got_json, "SDMF")
1178 def test_GET_FILEURL_json_mdmf(self):
1179 d = self.GET("/uri/%s?t=json" % urllib.quote(self._quux_txt_uri))
1180 d.addCallback(self.failUnlessIsQuuxJSON)
1183 def test_GET_FILEURL_json_missing(self):
1184 d = self.GET(self.public_url + "/foo/missing?json")
1185 d.addBoth(self.should404, "test_GET_FILEURL_json_missing")
1188 def test_GET_FILEURL_uri(self):
1189 d = self.GET(self.public_url + "/foo/bar.txt?t=uri")
1191 self.failUnlessReallyEqual(res, self._bar_txt_uri)
1192 d.addCallback(_check)
1193 d.addCallback(lambda res:
1194 self.GET(self.public_url + "/foo/bar.txt?t=readonly-uri"))
1196 # for now, for files, uris and readonly-uris are the same
1197 self.failUnlessReallyEqual(res, self._bar_txt_uri)
1198 d.addCallback(_check2)
1201 def test_GET_FILEURL_badtype(self):
1202 d = self.shouldHTTPError("GET t=bogus", 400, "Bad Request",
1205 self.public_url + "/foo/bar.txt?t=bogus")
1208 def test_CSS_FILE(self):
1209 d = self.GET("/tahoe.css", followRedirect=True)
1211 CSS_STYLE=re.compile('toolbar\s{.+text-align:\scenter.+toolbar-item.+display:\sinline',re.DOTALL)
1212 self.failUnless(CSS_STYLE.search(res), res)
1213 d.addCallback(_check)
1216 def test_GET_FILEURL_uri_missing(self):
1217 d = self.GET(self.public_url + "/foo/missing?t=uri")
1218 d.addBoth(self.should404, "test_GET_FILEURL_uri_missing")
1221 def _check_upload_and_mkdir_forms(self, html):
1222 # We should have a form to create a file, with radio buttons that allow
1223 # the user to toggle whether it is a CHK/LIT (default), SDMF, or MDMF file.
1224 self.failUnlessIn('name="t" value="upload"', html)
1225 self.failUnlessIn('input checked="checked" type="radio" id="upload-chk" value="chk" name="format"', html)
1226 self.failUnlessIn('input type="radio" id="upload-sdmf" value="sdmf" name="format"', html)
1227 self.failUnlessIn('input type="radio" id="upload-mdmf" value="mdmf" name="format"', html)
1229 # We should also have the ability to create a mutable directory, with
1230 # radio buttons that allow the user to toggle whether it is an SDMF (default)
1231 # or MDMF directory.
1232 self.failUnlessIn('name="t" value="mkdir"', html)
1233 self.failUnlessIn('input checked="checked" type="radio" id="mkdir-sdmf" value="sdmf" name="format"', html)
1234 self.failUnlessIn('input type="radio" id="mkdir-mdmf" value="mdmf" name="format"', html)
1236 self.failUnlessIn(FAVICON_MARKUP, html)
1238 def test_GET_DIRECTORY_html(self):
1239 d = self.GET(self.public_url + "/foo", followRedirect=True)
1241 self.failUnlessIn('<div class="toolbar-item"><a href="../../..">Return to Welcome page</a></div>', html)
1242 self._check_upload_and_mkdir_forms(html)
1243 self.failUnlessIn("quux", html)
1244 d.addCallback(_check)
1247 def test_GET_root_html(self):
1249 d.addCallback(self._check_upload_and_mkdir_forms)
1252 def test_GET_DIRURL(self):
1253 # the addSlash means we get a redirect here
1254 # from /uri/$URI/foo/ , we need ../../../ to get back to the root
1256 d = self.GET(self.public_url + "/foo", followRedirect=True)
1258 self.failUnlessIn('<a href="%s">Return to Welcome page' % ROOT, res)
1260 # the FILE reference points to a URI, but it should end in bar.txt
1261 bar_url = ("%s/file/%s/@@named=/bar.txt" %
1262 (ROOT, urllib.quote(self._bar_txt_uri)))
1263 get_bar = "".join([r'<td>FILE</td>',
1265 r'<a href="%s">bar.txt</a>' % bar_url,
1267 r'\s+<td align="right">%d</td>' % len(self.BAR_CONTENTS),
1269 self.failUnless(re.search(get_bar, res), res)
1270 for label in ['unlink', 'rename/move']:
1271 for line in res.split("\n"):
1272 # find the line that contains the relevant button for bar.txt
1273 if ("form action" in line and
1274 ('value="%s"' % (label,)) in line and
1275 'value="bar.txt"' in line):
1276 # the form target should use a relative URL
1277 foo_url = urllib.quote("%s/uri/%s/" % (ROOT, self._foo_uri))
1278 self.failUnlessIn('action="%s"' % foo_url, line)
1279 # and the when_done= should too
1280 #done_url = urllib.quote(???)
1281 #self.failUnlessIn('name="when_done" value="%s"' % done_url, line)
1283 # 'unlink' needs to use POST because it directly has a side effect
1284 if label == 'unlink':
1285 self.failUnlessIn('method="post"', line)
1288 self.fail("unable to find '%s bar.txt' line" % (label,))
1290 # the DIR reference just points to a URI
1291 sub_url = ("%s/uri/%s/" % (ROOT, urllib.quote(self._sub_uri)))
1292 get_sub = ((r'<td>DIR</td>')
1293 +r'\s+<td><a href="%s">sub</a></td>' % sub_url)
1294 self.failUnless(re.search(get_sub, res), res)
1295 d.addCallback(_check)
1297 # look at a readonly directory
1298 d.addCallback(lambda res:
1299 self.GET(self.public_url + "/reedownlee", followRedirect=True))
1301 self.failUnlessIn("(read-only)", res)
1302 self.failIfIn("Upload a file", res)
1303 d.addCallback(_check2)
1305 # and at a directory that contains a readonly directory
1306 d.addCallback(lambda res:
1307 self.GET(self.public_url, followRedirect=True))
1309 self.failUnless(re.search('<td>DIR-RO</td>'
1310 r'\s+<td><a href="[\.\/]+/uri/URI%3ADIR2-RO%3A[^"]+">reedownlee</a></td>', res), res)
1311 d.addCallback(_check3)
1313 # and an empty directory
1314 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty/"))
1316 self.failUnlessIn("directory is empty", res)
1317 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)
1318 self.failUnless(MKDIR_BUTTON_RE.search(res), res)
1319 d.addCallback(_check4)
1321 # and at a literal directory
1322 tiny_litdir_uri = "URI:DIR2-LIT:gqytunj2onug64tufqzdcosvkjetutcjkq5gw4tvm5vwszdgnz5hgyzufqydulbshj5x2lbm" # contains one child which is itself also LIT
1323 d.addCallback(lambda res:
1324 self.GET("/uri/" + tiny_litdir_uri + "/", followRedirect=True))
1326 self.failUnlessIn('(immutable)', res)
1327 self.failUnless(re.search('<td>FILE</td>'
1328 r'\s+<td><a href="[\.\/]+/file/URI%3ALIT%3Akrugkidfnzsc4/@@named=/short">short</a></td>', res), res)
1329 d.addCallback(_check5)
1332 def test_GET_DIRURL_badtype(self):
1333 d = self.shouldHTTPError("test_GET_DIRURL_badtype",
1337 self.public_url + "/foo?t=bogus")
1340 def test_GET_DIRURL_json(self):
1341 d = self.GET(self.public_url + "/foo?t=json")
1342 d.addCallback(self.failUnlessIsFooJSON)
1345 def test_GET_DIRURL_json_format(self):
1346 d = self.PUT(self.public_url + \
1347 "/foo/sdmf.txt?format=sdmf",
1348 self.NEWFILE_CONTENTS * 300000)
1349 d.addCallback(lambda ignored:
1350 self.PUT(self.public_url + \
1351 "/foo/mdmf.txt?format=mdmf",
1352 self.NEWFILE_CONTENTS * 300000))
1353 # Now we have an MDMF and SDMF file in the directory. If we GET
1354 # its JSON, we should see their encodings.
1355 d.addCallback(lambda ignored:
1356 self.GET(self.public_url + "/foo?t=json"))
1357 def _got_json(json):
1358 data = simplejson.loads(json)
1359 assert data[0] == "dirnode"
1362 kids = data['children']
1364 mdmf_data = kids['mdmf.txt'][1]
1365 self.failUnlessIn("format", mdmf_data)
1366 self.failUnlessEqual(mdmf_data["format"], "MDMF")
1368 sdmf_data = kids['sdmf.txt'][1]
1369 self.failUnlessIn("format", sdmf_data)
1370 self.failUnlessEqual(sdmf_data["format"], "SDMF")
1371 d.addCallback(_got_json)
1375 def test_POST_DIRURL_manifest_no_ophandle(self):
1376 d = self.shouldFail2(error.Error,
1377 "test_POST_DIRURL_manifest_no_ophandle",
1379 "slow operation requires ophandle=",
1380 self.POST, self.public_url, t="start-manifest")
1383 def test_POST_DIRURL_manifest(self):
1384 d = defer.succeed(None)
1385 def getman(ignored, output):
1386 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=125",
1387 followRedirect=True)
1388 d.addCallback(self.wait_for_operation, "125")
1389 d.addCallback(self.get_operation_results, "125", output)
1391 d.addCallback(getman, None)
1392 def _got_html(manifest):
1393 self.failUnlessIn("Manifest of SI=", manifest)
1394 self.failUnlessIn("<td>sub</td>", manifest)
1395 self.failUnlessIn(self._sub_uri, manifest)
1396 self.failUnlessIn("<td>sub/baz.txt</td>", manifest)
1397 self.failUnlessIn(FAVICON_MARKUP, manifest)
1398 d.addCallback(_got_html)
1400 # both t=status and unadorned GET should be identical
1401 d.addCallback(lambda res: self.GET("/operations/125"))
1402 d.addCallback(_got_html)
1404 d.addCallback(getman, "html")
1405 d.addCallback(_got_html)
1406 d.addCallback(getman, "text")
1407 def _got_text(manifest):
1408 self.failUnlessIn("\nsub " + self._sub_uri + "\n", manifest)
1409 self.failUnlessIn("\nsub/baz.txt URI:CHK:", manifest)
1410 d.addCallback(_got_text)
1411 d.addCallback(getman, "JSON")
1413 data = res["manifest"]
1415 for (path_list, cap) in data:
1416 got[tuple(path_list)] = cap
1417 self.failUnlessReallyEqual(to_str(got[(u"sub",)]), self._sub_uri)
1418 self.failUnlessIn((u"sub", u"baz.txt"), got)
1419 self.failUnlessIn("finished", res)
1420 self.failUnlessIn("origin", res)
1421 self.failUnlessIn("storage-index", res)
1422 self.failUnlessIn("verifycaps", res)
1423 self.failUnlessIn("stats", res)
1424 d.addCallback(_got_json)
1427 def test_POST_DIRURL_deepsize_no_ophandle(self):
1428 d = self.shouldFail2(error.Error,
1429 "test_POST_DIRURL_deepsize_no_ophandle",
1431 "slow operation requires ophandle=",
1432 self.POST, self.public_url, t="start-deep-size")
1435 def test_POST_DIRURL_deepsize(self):
1436 d = self.POST(self.public_url + "/foo/?t=start-deep-size&ophandle=126",
1437 followRedirect=True)
1438 d.addCallback(self.wait_for_operation, "126")
1439 d.addCallback(self.get_operation_results, "126", "json")
1440 def _got_json(data):
1441 self.failUnlessReallyEqual(data["finished"], True)
1443 self.failUnless(size > 1000)
1444 d.addCallback(_got_json)
1445 d.addCallback(self.get_operation_results, "126", "text")
1447 mo = re.search(r'^size: (\d+)$', res, re.M)
1448 self.failUnless(mo, res)
1449 size = int(mo.group(1))
1450 # with directories, the size varies.
1451 self.failUnless(size > 1000)
1452 d.addCallback(_got_text)
1455 def test_POST_DIRURL_deepstats_no_ophandle(self):
1456 d = self.shouldFail2(error.Error,
1457 "test_POST_DIRURL_deepstats_no_ophandle",
1459 "slow operation requires ophandle=",
1460 self.POST, self.public_url, t="start-deep-stats")
1463 def test_POST_DIRURL_deepstats(self):
1464 d = self.POST(self.public_url + "/foo/?t=start-deep-stats&ophandle=127",
1465 followRedirect=True)
1466 d.addCallback(self.wait_for_operation, "127")
1467 d.addCallback(self.get_operation_results, "127", "json")
1468 def _got_json(stats):
1469 expected = {"count-immutable-files": 3,
1470 "count-mutable-files": 2,
1471 "count-literal-files": 0,
1473 "count-directories": 3,
1474 "size-immutable-files": 57,
1475 "size-literal-files": 0,
1476 #"size-directories": 1912, # varies
1477 #"largest-directory": 1590,
1478 "largest-directory-children": 7,
1479 "largest-immutable-file": 19,
1481 for k,v in expected.iteritems():
1482 self.failUnlessReallyEqual(stats[k], v,
1483 "stats[%s] was %s, not %s" %
1485 self.failUnlessReallyEqual(stats["size-files-histogram"],
1487 d.addCallback(_got_json)
1490 def test_POST_DIRURL_stream_manifest(self):
1491 d = self.POST(self.public_url + "/foo/?t=stream-manifest")
1493 self.failUnless(res.endswith("\n"))
1494 units = [simplejson.loads(t) for t in res[:-1].split("\n")]
1495 self.failUnlessReallyEqual(len(units), 9)
1496 self.failUnlessEqual(units[-1]["type"], "stats")
1498 self.failUnlessEqual(first["path"], [])
1499 self.failUnlessReallyEqual(to_str(first["cap"]), self._foo_uri)
1500 self.failUnlessEqual(first["type"], "directory")
1501 baz = [u for u in units[:-1] if to_str(u["cap"]) == self._baz_file_uri][0]
1502 self.failUnlessEqual(baz["path"], ["sub", "baz.txt"])
1503 self.failIfEqual(baz["storage-index"], None)
1504 self.failIfEqual(baz["verifycap"], None)
1505 self.failIfEqual(baz["repaircap"], None)
1506 # XXX: Add quux and baz to this test.
1508 d.addCallback(_check)
1511 def test_GET_DIRURL_uri(self):
1512 d = self.GET(self.public_url + "/foo?t=uri")
1514 self.failUnlessReallyEqual(to_str(res), self._foo_uri)
1515 d.addCallback(_check)
1518 def test_GET_DIRURL_readonly_uri(self):
1519 d = self.GET(self.public_url + "/foo?t=readonly-uri")
1521 self.failUnlessReallyEqual(to_str(res), self._foo_readonly_uri)
1522 d.addCallback(_check)
1525 def test_PUT_NEWDIRURL(self):
1526 d = self.PUT(self.public_url + "/foo/newdir?t=mkdir", "")
1527 d.addCallback(lambda res:
1528 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1529 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1530 d.addCallback(self.failUnlessNodeKeysAre, [])
1533 def test_PUT_NEWDIRURL_mdmf(self):
1534 d = self.PUT(self.public_url + "/foo/newdir?t=mkdir&format=mdmf", "")
1535 d.addCallback(lambda res:
1536 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1537 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1538 d.addCallback(lambda node:
1539 self.failUnlessEqual(node._node.get_version(), MDMF_VERSION))
1542 def test_PUT_NEWDIRURL_sdmf(self):
1543 d = self.PUT(self.public_url + "/foo/newdir?t=mkdir&format=sdmf",
1545 d.addCallback(lambda res:
1546 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1547 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1548 d.addCallback(lambda node:
1549 self.failUnlessEqual(node._node.get_version(), SDMF_VERSION))
1552 def test_PUT_NEWDIRURL_bad_format(self):
1553 return self.shouldHTTPError("PUT_NEWDIRURL_bad_format",
1554 400, "Bad Request", "Unknown format: foo",
1555 self.PUT, self.public_url +
1556 "/foo/newdir=?t=mkdir&format=foo", "")
1558 def test_POST_NEWDIRURL(self):
1559 d = self.POST2(self.public_url + "/foo/newdir?t=mkdir", "")
1560 d.addCallback(lambda res:
1561 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1562 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1563 d.addCallback(self.failUnlessNodeKeysAre, [])
1566 def test_POST_NEWDIRURL_mdmf(self):
1567 d = self.POST2(self.public_url + "/foo/newdir?t=mkdir&format=mdmf", "")
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(), MDMF_VERSION))
1575 def test_POST_NEWDIRURL_sdmf(self):
1576 d = self.POST2(self.public_url + "/foo/newdir?t=mkdir&format=sdmf", "")
1577 d.addCallback(lambda res:
1578 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1579 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1580 d.addCallback(lambda node:
1581 self.failUnlessEqual(node._node.get_version(), SDMF_VERSION))
1584 def test_POST_NEWDIRURL_bad_format(self):
1585 return self.shouldHTTPError("POST_NEWDIRURL_bad_format",
1586 400, "Bad Request", "Unknown format: foo",
1587 self.POST2, self.public_url + \
1588 "/foo/newdir?t=mkdir&format=foo", "")
1590 def test_POST_NEWDIRURL_emptyname(self):
1591 # an empty pathname component (i.e. a double-slash) is disallowed
1592 d = self.shouldFail2(error.Error, "POST_NEWDIRURL_emptyname",
1594 "The webapi does not allow empty pathname components, i.e. a double slash",
1595 self.POST, self.public_url + "//?t=mkdir")
1598 def _do_POST_NEWDIRURL_initial_children_test(self, version=None):
1599 (newkids, caps) = self._create_initial_children()
1600 query = "/foo/newdir?t=mkdir-with-children"
1601 if version == MDMF_VERSION:
1602 query += "&format=mdmf"
1603 elif version == SDMF_VERSION:
1604 query += "&format=sdmf"
1606 version = SDMF_VERSION # for later
1607 d = self.POST2(self.public_url + query,
1608 simplejson.dumps(newkids))
1610 n = self.s.create_node_from_uri(uri.strip())
1611 d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
1612 self.failUnlessEqual(n._node.get_version(), version)
1613 d2.addCallback(lambda ign:
1614 self.failUnlessROChildURIIs(n, u"child-imm",
1616 d2.addCallback(lambda ign:
1617 self.failUnlessRWChildURIIs(n, u"child-mutable",
1619 d2.addCallback(lambda ign:
1620 self.failUnlessROChildURIIs(n, u"child-mutable-ro",
1622 d2.addCallback(lambda ign:
1623 self.failUnlessROChildURIIs(n, u"unknownchild-ro",
1624 caps['unknown_rocap']))
1625 d2.addCallback(lambda ign:
1626 self.failUnlessRWChildURIIs(n, u"unknownchild-rw",
1627 caps['unknown_rwcap']))
1628 d2.addCallback(lambda ign:
1629 self.failUnlessROChildURIIs(n, u"unknownchild-imm",
1630 caps['unknown_immcap']))
1631 d2.addCallback(lambda ign:
1632 self.failUnlessRWChildURIIs(n, u"dirchild",
1634 d2.addCallback(lambda ign:
1635 self.failUnlessROChildURIIs(n, u"dirchild-lit",
1637 d2.addCallback(lambda ign:
1638 self.failUnlessROChildURIIs(n, u"dirchild-empty",
1639 caps['emptydircap']))
1641 d.addCallback(_check)
1642 d.addCallback(lambda res:
1643 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1644 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1645 d.addCallback(self.failUnlessNodeKeysAre, newkids.keys())
1646 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1647 d.addCallback(self.failUnlessROChildURIIs, u"child-imm", caps['filecap1'])
1650 def test_POST_NEWDIRURL_initial_children(self):
1651 return self._do_POST_NEWDIRURL_initial_children_test()
1653 def test_POST_NEWDIRURL_initial_children_mdmf(self):
1654 return self._do_POST_NEWDIRURL_initial_children_test(MDMF_VERSION)
1656 def test_POST_NEWDIRURL_initial_children_sdmf(self):
1657 return self._do_POST_NEWDIRURL_initial_children_test(SDMF_VERSION)
1659 def test_POST_NEWDIRURL_initial_children_bad_format(self):
1660 (newkids, caps) = self._create_initial_children()
1661 return self.shouldHTTPError("POST_NEWDIRURL_initial_children_bad_format",
1662 400, "Bad Request", "Unknown format: foo",
1663 self.POST2, self.public_url + \
1664 "/foo/newdir?t=mkdir-with-children&format=foo",
1665 simplejson.dumps(newkids))
1667 def test_POST_NEWDIRURL_immutable(self):
1668 (newkids, caps) = self._create_immutable_children()
1669 d = self.POST2(self.public_url + "/foo/newdir?t=mkdir-immutable",
1670 simplejson.dumps(newkids))
1672 n = self.s.create_node_from_uri(uri.strip())
1673 d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
1674 d2.addCallback(lambda ign:
1675 self.failUnlessROChildURIIs(n, u"child-imm",
1677 d2.addCallback(lambda ign:
1678 self.failUnlessROChildURIIs(n, u"unknownchild-imm",
1679 caps['unknown_immcap']))
1680 d2.addCallback(lambda ign:
1681 self.failUnlessROChildURIIs(n, u"dirchild-imm",
1683 d2.addCallback(lambda ign:
1684 self.failUnlessROChildURIIs(n, u"dirchild-lit",
1686 d2.addCallback(lambda ign:
1687 self.failUnlessROChildURIIs(n, u"dirchild-empty",
1688 caps['emptydircap']))
1690 d.addCallback(_check)
1691 d.addCallback(lambda res:
1692 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1693 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1694 d.addCallback(self.failUnlessNodeKeysAre, newkids.keys())
1695 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1696 d.addCallback(self.failUnlessROChildURIIs, u"child-imm", caps['filecap1'])
1697 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1698 d.addCallback(self.failUnlessROChildURIIs, u"unknownchild-imm", caps['unknown_immcap'])
1699 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1700 d.addCallback(self.failUnlessROChildURIIs, u"dirchild-imm", caps['immdircap'])
1701 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1702 d.addCallback(self.failUnlessROChildURIIs, u"dirchild-lit", caps['litdircap'])
1703 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1704 d.addCallback(self.failUnlessROChildURIIs, u"dirchild-empty", caps['emptydircap'])
1705 d.addErrback(self.explain_web_error)
1708 def test_POST_NEWDIRURL_immutable_bad(self):
1709 (newkids, caps) = self._create_initial_children()
1710 d = self.shouldFail2(error.Error, "test_POST_NEWDIRURL_immutable_bad",
1712 "needed to be immutable but was not",
1714 self.public_url + "/foo/newdir?t=mkdir-immutable",
1715 simplejson.dumps(newkids))
1718 def test_PUT_NEWDIRURL_exists(self):
1719 d = self.PUT(self.public_url + "/foo/sub?t=mkdir", "")
1720 d.addCallback(lambda res:
1721 self.failUnlessNodeHasChild(self._foo_node, u"sub"))
1722 d.addCallback(lambda res: self._foo_node.get(u"sub"))
1723 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
1726 def test_PUT_NEWDIRURL_blocked(self):
1727 d = self.shouldFail2(error.Error, "PUT_NEWDIRURL_blocked",
1728 "409 Conflict", "Unable to create directory 'bar.txt': a file was in the way",
1730 self.public_url + "/foo/bar.txt/sub?t=mkdir", "")
1731 d.addCallback(lambda res:
1732 self.failUnlessNodeHasChild(self._foo_node, u"sub"))
1733 d.addCallback(lambda res: self._foo_node.get(u"sub"))
1734 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
1737 def test_PUT_NEWDIRURL_mkdirs(self):
1738 d = self.PUT(self.public_url + "/foo/subdir/newdir?t=mkdir", "")
1739 d.addCallback(lambda res:
1740 self.failIfNodeHasChild(self._foo_node, u"newdir"))
1741 d.addCallback(lambda res:
1742 self.failUnlessNodeHasChild(self._foo_node, u"subdir"))
1743 d.addCallback(lambda res:
1744 self._foo_node.get_child_at_path(u"subdir/newdir"))
1745 d.addCallback(self.failUnlessNodeKeysAre, [])
1748 def test_PUT_NEWDIRURL_mkdirs_mdmf(self):
1749 d = self.PUT(self.public_url + "/foo/subdir/newdir?t=mkdir&format=mdmf", "")
1750 d.addCallback(lambda ignored:
1751 self.failUnlessNodeHasChild(self._foo_node, u"subdir"))
1752 d.addCallback(lambda ignored:
1753 self.failIfNodeHasChild(self._foo_node, u"newdir"))
1754 d.addCallback(lambda ignored:
1755 self._foo_node.get_child_at_path(u"subdir"))
1756 def _got_subdir(subdir):
1757 # XXX: What we want?
1758 #self.failUnlessEqual(subdir._node.get_version(), MDMF_VERSION)
1759 self.failUnlessNodeHasChild(subdir, u"newdir")
1760 return subdir.get_child_at_path(u"newdir")
1761 d.addCallback(_got_subdir)
1762 d.addCallback(lambda newdir:
1763 self.failUnlessEqual(newdir._node.get_version(), MDMF_VERSION))
1766 def test_PUT_NEWDIRURL_mkdirs_sdmf(self):
1767 d = self.PUT(self.public_url + "/foo/subdir/newdir?t=mkdir&format=sdmf", "")
1768 d.addCallback(lambda ignored:
1769 self.failUnlessNodeHasChild(self._foo_node, u"subdir"))
1770 d.addCallback(lambda ignored:
1771 self.failIfNodeHasChild(self._foo_node, u"newdir"))
1772 d.addCallback(lambda ignored:
1773 self._foo_node.get_child_at_path(u"subdir"))
1774 def _got_subdir(subdir):
1775 # XXX: What we want?
1776 #self.failUnlessEqual(subdir._node.get_version(), MDMF_VERSION)
1777 self.failUnlessNodeHasChild(subdir, u"newdir")
1778 return subdir.get_child_at_path(u"newdir")
1779 d.addCallback(_got_subdir)
1780 d.addCallback(lambda newdir:
1781 self.failUnlessEqual(newdir._node.get_version(), SDMF_VERSION))
1784 def test_PUT_NEWDIRURL_mkdirs_bad_format(self):
1785 return self.shouldHTTPError("PUT_NEWDIRURL_mkdirs_bad_format",
1786 400, "Bad Request", "Unknown format: foo",
1787 self.PUT, self.public_url + \
1788 "/foo/subdir/newdir?t=mkdir&format=foo",
1791 def test_DELETE_DIRURL(self):
1792 d = self.DELETE(self.public_url + "/foo")
1793 d.addCallback(lambda res:
1794 self.failIfNodeHasChild(self.public_root, u"foo"))
1797 def test_DELETE_DIRURL_missing(self):
1798 d = self.DELETE(self.public_url + "/foo/missing")
1799 d.addBoth(self.should404, "test_DELETE_DIRURL_missing")
1800 d.addCallback(lambda res:
1801 self.failUnlessNodeHasChild(self.public_root, u"foo"))
1804 def test_DELETE_DIRURL_missing2(self):
1805 d = self.DELETE(self.public_url + "/missing")
1806 d.addBoth(self.should404, "test_DELETE_DIRURL_missing2")
1809 def dump_root(self):
1811 w = webish.DirnodeWalkerMixin()
1812 def visitor(childpath, childnode, metadata):
1814 d = w.walk(self.public_root, visitor)
1817 def failUnlessNodeKeysAre(self, node, expected_keys):
1818 for k in expected_keys:
1819 assert isinstance(k, unicode)
1821 def _check(children):
1822 self.failUnlessReallyEqual(sorted(children.keys()), sorted(expected_keys))
1823 d.addCallback(_check)
1825 def failUnlessNodeHasChild(self, node, name):
1826 assert isinstance(name, unicode)
1828 def _check(children):
1829 self.failUnlessIn(name, children)
1830 d.addCallback(_check)
1832 def failIfNodeHasChild(self, node, name):
1833 assert isinstance(name, unicode)
1835 def _check(children):
1836 self.failIfIn(name, children)
1837 d.addCallback(_check)
1840 def failUnlessChildContentsAre(self, node, name, expected_contents):
1841 assert isinstance(name, unicode)
1842 d = node.get_child_at_path(name)
1843 d.addCallback(lambda node: download_to_data(node))
1844 def _check(contents):
1845 self.failUnlessReallyEqual(contents, expected_contents)
1846 d.addCallback(_check)
1849 def failUnlessMutableChildContentsAre(self, node, name, expected_contents):
1850 assert isinstance(name, unicode)
1851 d = node.get_child_at_path(name)
1852 d.addCallback(lambda node: node.download_best_version())
1853 def _check(contents):
1854 self.failUnlessReallyEqual(contents, expected_contents)
1855 d.addCallback(_check)
1858 def failUnlessRWChildURIIs(self, node, name, expected_uri):
1859 assert isinstance(name, unicode)
1860 d = node.get_child_at_path(name)
1862 self.failUnless(child.is_unknown() or not child.is_readonly())
1863 self.failUnlessReallyEqual(child.get_uri(), expected_uri.strip())
1864 self.failUnlessReallyEqual(child.get_write_uri(), expected_uri.strip())
1865 expected_ro_uri = self._make_readonly(expected_uri)
1867 self.failUnlessReallyEqual(child.get_readonly_uri(), expected_ro_uri.strip())
1868 d.addCallback(_check)
1871 def failUnlessROChildURIIs(self, node, name, expected_uri):
1872 assert isinstance(name, unicode)
1873 d = node.get_child_at_path(name)
1875 self.failUnless(child.is_unknown() or child.is_readonly())
1876 self.failUnlessReallyEqual(child.get_write_uri(), None)
1877 self.failUnlessReallyEqual(child.get_uri(), expected_uri.strip())
1878 self.failUnlessReallyEqual(child.get_readonly_uri(), expected_uri.strip())
1879 d.addCallback(_check)
1882 def failUnlessURIMatchesRWChild(self, got_uri, node, name):
1883 assert isinstance(name, unicode)
1884 d = node.get_child_at_path(name)
1886 self.failUnless(child.is_unknown() or not child.is_readonly())
1887 self.failUnlessReallyEqual(child.get_uri(), got_uri.strip())
1888 self.failUnlessReallyEqual(child.get_write_uri(), got_uri.strip())
1889 expected_ro_uri = self._make_readonly(got_uri)
1891 self.failUnlessReallyEqual(child.get_readonly_uri(), expected_ro_uri.strip())
1892 d.addCallback(_check)
1895 def failUnlessURIMatchesROChild(self, got_uri, node, name):
1896 assert isinstance(name, unicode)
1897 d = node.get_child_at_path(name)
1899 self.failUnless(child.is_unknown() or child.is_readonly())
1900 self.failUnlessReallyEqual(child.get_write_uri(), None)
1901 self.failUnlessReallyEqual(got_uri.strip(), child.get_uri())
1902 self.failUnlessReallyEqual(got_uri.strip(), child.get_readonly_uri())
1903 d.addCallback(_check)
1906 def failUnlessCHKURIHasContents(self, got_uri, contents):
1907 self.failUnless(FakeCHKFileNode.all_contents[got_uri] == contents)
1909 def test_POST_upload(self):
1910 d = self.POST(self.public_url + "/foo", t="upload",
1911 file=("new.txt", self.NEWFILE_CONTENTS))
1913 d.addCallback(self.failUnlessURIMatchesROChild, fn, u"new.txt")
1914 d.addCallback(lambda res:
1915 self.failUnlessChildContentsAre(fn, u"new.txt",
1916 self.NEWFILE_CONTENTS))
1919 def test_POST_upload_unicode(self):
1920 filename = u"n\u00e9wer.txt" # n e-acute w e r . t x t
1921 d = self.POST(self.public_url + "/foo", t="upload",
1922 file=(filename, self.NEWFILE_CONTENTS))
1924 d.addCallback(self.failUnlessURIMatchesROChild, fn, filename)
1925 d.addCallback(lambda res:
1926 self.failUnlessChildContentsAre(fn, filename,
1927 self.NEWFILE_CONTENTS))
1928 target_url = self.public_url + "/foo/" + filename.encode("utf-8")
1929 d.addCallback(lambda res: self.GET(target_url))
1930 d.addCallback(lambda contents: self.failUnlessReallyEqual(contents,
1931 self.NEWFILE_CONTENTS,
1935 def test_POST_upload_unicode_named(self):
1936 filename = u"n\u00e9wer.txt" # n e-acute w e r . t x t
1937 d = self.POST(self.public_url + "/foo", t="upload",
1939 file=("overridden", self.NEWFILE_CONTENTS))
1941 d.addCallback(self.failUnlessURIMatchesROChild, fn, filename)
1942 d.addCallback(lambda res:
1943 self.failUnlessChildContentsAre(fn, filename,
1944 self.NEWFILE_CONTENTS))
1945 target_url = self.public_url + "/foo/" + filename.encode("utf-8")
1946 d.addCallback(lambda res: self.GET(target_url))
1947 d.addCallback(lambda contents: self.failUnlessReallyEqual(contents,
1948 self.NEWFILE_CONTENTS,
1952 def test_POST_upload_no_link(self):
1953 d = self.POST("/uri", t="upload",
1954 file=("new.txt", self.NEWFILE_CONTENTS))
1955 def _check_upload_results(page):
1956 # this should be a page which describes the results of the upload
1957 # that just finished.
1958 self.failUnlessIn("Upload Results:", page)
1959 self.failUnlessIn("URI:", page)
1960 uri_re = re.compile("URI: <tt><span>(.*)</span>")
1961 mo = uri_re.search(page)
1962 self.failUnless(mo, page)
1963 new_uri = mo.group(1)
1965 d.addCallback(_check_upload_results)
1966 d.addCallback(self.failUnlessCHKURIHasContents, self.NEWFILE_CONTENTS)
1969 def test_POST_upload_no_link_whendone(self):
1970 d = self.POST("/uri", t="upload", when_done="/",
1971 file=("new.txt", self.NEWFILE_CONTENTS))
1972 d.addBoth(self.shouldRedirect, "/")
1975 def shouldRedirect2(self, which, checker, callable, *args, **kwargs):
1976 d = defer.maybeDeferred(callable, *args, **kwargs)
1978 if isinstance(res, failure.Failure):
1979 res.trap(error.PageRedirect)
1980 statuscode = res.value.status
1981 target = res.value.location
1982 return checker(statuscode, target)
1983 self.fail("%s: callable was supposed to redirect, not return '%s'"
1988 def test_POST_upload_no_link_whendone_results(self):
1989 def check(statuscode, target):
1990 self.failUnlessReallyEqual(statuscode, str(http.FOUND))
1991 self.failUnless(target.startswith(self.webish_url), target)
1992 return client.getPage(target, method="GET")
1993 d = self.shouldRedirect2("test_POST_upload_no_link_whendone_results",
1995 self.POST, "/uri", t="upload",
1996 when_done="/uri/%(uri)s",
1997 file=("new.txt", self.NEWFILE_CONTENTS))
1998 d.addCallback(lambda res:
1999 self.failUnlessReallyEqual(res, self.NEWFILE_CONTENTS))
2002 def test_POST_upload_no_link_mutable(self):
2003 d = self.POST("/uri", t="upload", mutable="true",
2004 file=("new.txt", self.NEWFILE_CONTENTS))
2005 def _check(filecap):
2006 filecap = filecap.strip()
2007 self.failUnless(filecap.startswith("URI:SSK:"), filecap)
2008 self.filecap = filecap
2009 u = uri.WriteableSSKFileURI.init_from_string(filecap)
2010 self.failUnlessIn(u.get_storage_index(), FakeMutableFileNode.all_contents)
2011 n = self.s.create_node_from_uri(filecap)
2012 return n.download_best_version()
2013 d.addCallback(_check)
2015 self.failUnlessReallyEqual(data, self.NEWFILE_CONTENTS)
2016 return self.GET("/uri/%s" % urllib.quote(self.filecap))
2017 d.addCallback(_check2)
2019 self.failUnlessReallyEqual(data, self.NEWFILE_CONTENTS)
2020 return self.GET("/file/%s" % urllib.quote(self.filecap))
2021 d.addCallback(_check3)
2023 self.failUnlessReallyEqual(data, self.NEWFILE_CONTENTS)
2024 d.addCallback(_check4)
2027 def test_POST_upload_no_link_mutable_toobig(self):
2028 # The SDMF size limit is no longer in place, so we should be
2029 # able to upload mutable files that are as large as we want them
2031 d = self.POST("/uri", t="upload", mutable="true",
2032 file=("new.txt", "b" * (self.s.MUTABLE_SIZELIMIT + 1)))
2036 def test_POST_upload_format_unlinked(self):
2037 def _check_upload_unlinked(ign, format, uri_prefix):
2038 filename = format + ".txt"
2039 d = self.POST("/uri?t=upload&format=" + format,
2040 file=(filename, self.NEWFILE_CONTENTS * 300000))
2041 def _got_results(results):
2042 if format.upper() in ("SDMF", "MDMF"):
2043 # webapi.rst says this returns a filecap
2046 # for immutable, it returns an "upload results page", and
2047 # the filecap is buried inside
2048 line = [l for l in results.split("\n") if "URI: " in l][0]
2049 mo = re.search(r'<span>([^<]+)</span>', line)
2050 filecap = mo.group(1)
2051 self.failUnless(filecap.startswith(uri_prefix),
2052 (uri_prefix, filecap))
2053 return self.GET("/uri/%s?t=json" % filecap)
2054 d.addCallback(_got_results)
2055 def _got_json(json):
2056 data = simplejson.loads(json)
2058 self.failUnlessIn("format", data)
2059 self.failUnlessEqual(data["format"], format.upper())
2060 d.addCallback(_got_json)
2062 d = defer.succeed(None)
2063 d.addCallback(_check_upload_unlinked, "chk", "URI:CHK")
2064 d.addCallback(_check_upload_unlinked, "CHK", "URI:CHK")
2065 d.addCallback(_check_upload_unlinked, "sdmf", "URI:SSK")
2066 d.addCallback(_check_upload_unlinked, "mdmf", "URI:MDMF")
2069 def test_POST_upload_bad_format_unlinked(self):
2070 return self.shouldHTTPError("POST_upload_bad_format_unlinked",
2071 400, "Bad Request", "Unknown format: foo",
2073 "/uri?t=upload&format=foo",
2074 file=("foo.txt", self.NEWFILE_CONTENTS * 300000))
2076 def test_POST_upload_format(self):
2077 def _check_upload(ign, format, uri_prefix, fn=None):
2078 filename = format + ".txt"
2079 d = self.POST(self.public_url +
2080 "/foo?t=upload&format=" + format,
2081 file=(filename, self.NEWFILE_CONTENTS * 300000))
2082 def _got_filecap(filecap):
2084 filenameu = unicode(filename)
2085 self.failUnlessURIMatchesRWChild(filecap, fn, filenameu)
2086 self.failUnless(filecap.startswith(uri_prefix))
2087 return self.GET(self.public_url + "/foo/%s?t=json" % filename)
2088 d.addCallback(_got_filecap)
2089 def _got_json(json):
2090 data = simplejson.loads(json)
2092 self.failUnlessIn("format", data)
2093 self.failUnlessEqual(data["format"], format.upper())
2094 d.addCallback(_got_json)
2097 d = defer.succeed(None)
2098 d.addCallback(_check_upload, "chk", "URI:CHK")
2099 d.addCallback(_check_upload, "sdmf", "URI:SSK", self._foo_node)
2100 d.addCallback(_check_upload, "mdmf", "URI:MDMF")
2101 d.addCallback(_check_upload, "MDMF", "URI:MDMF")
2104 def test_POST_upload_bad_format(self):
2105 return self.shouldHTTPError("POST_upload_bad_format",
2106 400, "Bad Request", "Unknown format: foo",
2107 self.POST, self.public_url + \
2108 "/foo?t=upload&format=foo",
2109 file=("foo.txt", self.NEWFILE_CONTENTS * 300000))
2111 def test_POST_upload_mutable(self):
2112 # this creates a mutable file
2113 d = self.POST(self.public_url + "/foo", t="upload", mutable="true",
2114 file=("new.txt", self.NEWFILE_CONTENTS))
2116 d.addCallback(self.failUnlessURIMatchesRWChild, fn, u"new.txt")
2117 d.addCallback(lambda res:
2118 self.failUnlessMutableChildContentsAre(fn, u"new.txt",
2119 self.NEWFILE_CONTENTS))
2120 d.addCallback(lambda res: self._foo_node.get(u"new.txt"))
2122 self.failUnless(IMutableFileNode.providedBy(newnode))
2123 self.failUnless(newnode.is_mutable())
2124 self.failIf(newnode.is_readonly())
2125 self._mutable_node = newnode
2126 self._mutable_uri = newnode.get_uri()
2129 # now upload it again and make sure that the URI doesn't change
2130 NEWER_CONTENTS = self.NEWFILE_CONTENTS + "newer\n"
2131 d.addCallback(lambda res:
2132 self.POST(self.public_url + "/foo", t="upload",
2134 file=("new.txt", NEWER_CONTENTS)))
2135 d.addCallback(self.failUnlessURIMatchesRWChild, fn, u"new.txt")
2136 d.addCallback(lambda res:
2137 self.failUnlessMutableChildContentsAre(fn, u"new.txt",
2139 d.addCallback(lambda res: self._foo_node.get(u"new.txt"))
2141 self.failUnless(IMutableFileNode.providedBy(newnode))
2142 self.failUnless(newnode.is_mutable())
2143 self.failIf(newnode.is_readonly())
2144 self.failUnlessReallyEqual(self._mutable_uri, newnode.get_uri())
2145 d.addCallback(_got2)
2147 # upload a second time, using PUT instead of POST
2148 NEW2_CONTENTS = NEWER_CONTENTS + "overwrite with PUT\n"
2149 d.addCallback(lambda res:
2150 self.PUT(self.public_url + "/foo/new.txt", NEW2_CONTENTS))
2151 d.addCallback(self.failUnlessURIMatchesRWChild, fn, u"new.txt")
2152 d.addCallback(lambda res:
2153 self.failUnlessMutableChildContentsAre(fn, u"new.txt",
2156 # finally list the directory, since mutable files are displayed
2157 # slightly differently
2159 d.addCallback(lambda res:
2160 self.GET(self.public_url + "/foo/",
2161 followRedirect=True))
2162 def _check_page(res):
2163 # TODO: assert more about the contents
2164 self.failUnlessIn("SSK", res)
2166 d.addCallback(_check_page)
2168 d.addCallback(lambda res: self._foo_node.get(u"new.txt"))
2170 self.failUnless(IMutableFileNode.providedBy(newnode))
2171 self.failUnless(newnode.is_mutable())
2172 self.failIf(newnode.is_readonly())
2173 self.failUnlessReallyEqual(self._mutable_uri, newnode.get_uri())
2174 d.addCallback(_got3)
2176 # look at the JSON form of the enclosing directory
2177 d.addCallback(lambda res:
2178 self.GET(self.public_url + "/foo/?t=json",
2179 followRedirect=True))
2180 def _check_page_json(res):
2181 parsed = simplejson.loads(res)
2182 self.failUnlessEqual(parsed[0], "dirnode")
2183 children = dict( [(unicode(name),value)
2185 in parsed[1]["children"].iteritems()] )
2186 self.failUnlessIn(u"new.txt", children)
2187 new_json = children[u"new.txt"]
2188 self.failUnlessEqual(new_json[0], "filenode")
2189 self.failUnless(new_json[1]["mutable"])
2190 self.failUnlessReallyEqual(to_str(new_json[1]["rw_uri"]), self._mutable_uri)
2191 ro_uri = self._mutable_node.get_readonly().to_string()
2192 self.failUnlessReallyEqual(to_str(new_json[1]["ro_uri"]), ro_uri)
2193 d.addCallback(_check_page_json)
2195 # and the JSON form of the file
2196 d.addCallback(lambda res:
2197 self.GET(self.public_url + "/foo/new.txt?t=json"))
2198 def _check_file_json(res):
2199 parsed = simplejson.loads(res)
2200 self.failUnlessEqual(parsed[0], "filenode")
2201 self.failUnless(parsed[1]["mutable"])
2202 self.failUnlessReallyEqual(to_str(parsed[1]["rw_uri"]), self._mutable_uri)
2203 ro_uri = self._mutable_node.get_readonly().to_string()
2204 self.failUnlessReallyEqual(to_str(parsed[1]["ro_uri"]), ro_uri)
2205 d.addCallback(_check_file_json)
2207 # and look at t=uri and t=readonly-uri
2208 d.addCallback(lambda res:
2209 self.GET(self.public_url + "/foo/new.txt?t=uri"))
2210 d.addCallback(lambda res: self.failUnlessReallyEqual(res, self._mutable_uri))
2211 d.addCallback(lambda res:
2212 self.GET(self.public_url + "/foo/new.txt?t=readonly-uri"))
2213 def _check_ro_uri(res):
2214 ro_uri = self._mutable_node.get_readonly().to_string()
2215 self.failUnlessReallyEqual(res, ro_uri)
2216 d.addCallback(_check_ro_uri)
2218 # make sure we can get to it from /uri/URI
2219 d.addCallback(lambda res:
2220 self.GET("/uri/%s" % urllib.quote(self._mutable_uri)))
2221 d.addCallback(lambda res:
2222 self.failUnlessReallyEqual(res, NEW2_CONTENTS))
2224 # and that HEAD computes the size correctly
2225 d.addCallback(lambda res:
2226 self.HEAD(self.public_url + "/foo/new.txt",
2227 return_response=True))
2228 def _got_headers((res, status, headers)):
2229 self.failUnlessReallyEqual(res, "")
2230 self.failUnlessReallyEqual(headers["content-length"][0],
2231 str(len(NEW2_CONTENTS)))
2232 self.failUnlessReallyEqual(headers["content-type"], ["text/plain"])
2233 d.addCallback(_got_headers)
2235 # make sure that outdated size limits aren't enforced anymore.
2236 d.addCallback(lambda ignored:
2237 self.POST(self.public_url + "/foo", t="upload",
2240 "b" * (self.s.MUTABLE_SIZELIMIT+1))))
2241 d.addErrback(self.dump_error)
2244 def test_POST_upload_mutable_toobig(self):
2245 # SDMF had a size limti that was removed a while ago. MDMF has
2246 # never had a size limit. Test to make sure that we do not
2247 # encounter errors when trying to upload large mutable files,
2248 # since there should be no coded prohibitions regarding large
2250 d = self.POST(self.public_url + "/foo",
2251 t="upload", mutable="true",
2252 file=("new.txt", "b" * (self.s.MUTABLE_SIZELIMIT + 1)))
2255 def dump_error(self, f):
2256 # if the web server returns an error code (like 400 Bad Request),
2257 # web.client.getPage puts the HTTP response body into the .response
2258 # attribute of the exception object that it gives back. It does not
2259 # appear in the Failure's repr(), so the ERROR that trial displays
2260 # will be rather terse and unhelpful. addErrback this method to the
2261 # end of your chain to get more information out of these errors.
2262 if f.check(error.Error):
2263 print "web.error.Error:"
2265 print f.value.response
2268 def test_POST_upload_replace(self):
2269 d = self.POST(self.public_url + "/foo", t="upload",
2270 file=("bar.txt", self.NEWFILE_CONTENTS))
2272 d.addCallback(self.failUnlessURIMatchesROChild, fn, u"bar.txt")
2273 d.addCallback(lambda res:
2274 self.failUnlessChildContentsAre(fn, u"bar.txt",
2275 self.NEWFILE_CONTENTS))
2278 def test_POST_upload_no_replace_ok(self):
2279 d = self.POST(self.public_url + "/foo?replace=false", t="upload",
2280 file=("new.txt", self.NEWFILE_CONTENTS))
2281 d.addCallback(lambda res: self.GET(self.public_url + "/foo/new.txt"))
2282 d.addCallback(lambda res: self.failUnlessReallyEqual(res,
2283 self.NEWFILE_CONTENTS))
2286 def test_POST_upload_no_replace_queryarg(self):
2287 d = self.POST(self.public_url + "/foo?replace=false", t="upload",
2288 file=("bar.txt", self.NEWFILE_CONTENTS))
2289 d.addBoth(self.shouldFail, error.Error,
2290 "POST_upload_no_replace_queryarg",
2292 "There was already a child by that name, and you asked me "
2293 "to not replace it")
2294 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
2295 d.addCallback(self.failUnlessIsBarDotTxt)
2298 def test_POST_upload_no_replace_field(self):
2299 d = self.POST(self.public_url + "/foo", t="upload", replace="false",
2300 file=("bar.txt", self.NEWFILE_CONTENTS))
2301 d.addBoth(self.shouldFail, error.Error, "POST_upload_no_replace_field",
2303 "There was already a child by that name, and you asked me "
2304 "to not replace it")
2305 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
2306 d.addCallback(self.failUnlessIsBarDotTxt)
2309 def test_POST_upload_whendone(self):
2310 d = self.POST(self.public_url + "/foo", t="upload", when_done="/THERE",
2311 file=("new.txt", self.NEWFILE_CONTENTS))
2312 d.addBoth(self.shouldRedirect, "/THERE")
2314 d.addCallback(lambda res:
2315 self.failUnlessChildContentsAre(fn, u"new.txt",
2316 self.NEWFILE_CONTENTS))
2319 def test_POST_upload_named(self):
2321 d = self.POST(self.public_url + "/foo", t="upload",
2322 name="new.txt", file=self.NEWFILE_CONTENTS)
2323 d.addCallback(self.failUnlessURIMatchesROChild, fn, u"new.txt")
2324 d.addCallback(lambda res:
2325 self.failUnlessChildContentsAre(fn, u"new.txt",
2326 self.NEWFILE_CONTENTS))
2329 def test_POST_upload_named_badfilename(self):
2330 d = self.POST(self.public_url + "/foo", t="upload",
2331 name="slashes/are/bad.txt", file=self.NEWFILE_CONTENTS)
2332 d.addBoth(self.shouldFail, error.Error,
2333 "test_POST_upload_named_badfilename",
2335 "name= may not contain a slash",
2337 # make sure that nothing was added
2338 d.addCallback(lambda res:
2339 self.failUnlessNodeKeysAre(self._foo_node,
2340 [u"bar.txt", u"baz.txt", u"blockingfile",
2341 u"empty", u"n\u00fc.txt", u"quux.txt",
2345 def test_POST_FILEURL_check(self):
2346 bar_url = self.public_url + "/foo/bar.txt"
2347 d = self.POST(bar_url, t="check")
2349 self.failUnlessIn("Healthy :", res)
2350 d.addCallback(_check)
2351 redir_url = "http://allmydata.org/TARGET"
2352 def _check2(statuscode, target):
2353 self.failUnlessReallyEqual(statuscode, str(http.FOUND))
2354 self.failUnlessReallyEqual(target, redir_url)
2355 d.addCallback(lambda res:
2356 self.shouldRedirect2("test_POST_FILEURL_check",
2360 when_done=redir_url))
2361 d.addCallback(lambda res:
2362 self.POST(bar_url, t="check", return_to=redir_url))
2364 self.failUnlessIn("Healthy :", res)
2365 self.failUnlessIn("Return to file", res)
2366 self.failUnlessIn(redir_url, res)
2367 d.addCallback(_check3)
2369 d.addCallback(lambda res:
2370 self.POST(bar_url, t="check", output="JSON"))
2371 def _check_json(res):
2372 data = simplejson.loads(res)
2373 self.failUnlessIn("storage-index", data)
2374 self.failUnless(data["results"]["healthy"])
2375 d.addCallback(_check_json)
2379 def test_POST_FILEURL_check_and_repair(self):
2380 bar_url = self.public_url + "/foo/bar.txt"
2381 d = self.POST(bar_url, t="check", repair="true")
2383 self.failUnlessIn("Healthy :", res)
2384 d.addCallback(_check)
2385 redir_url = "http://allmydata.org/TARGET"
2386 def _check2(statuscode, target):
2387 self.failUnlessReallyEqual(statuscode, str(http.FOUND))
2388 self.failUnlessReallyEqual(target, redir_url)
2389 d.addCallback(lambda res:
2390 self.shouldRedirect2("test_POST_FILEURL_check_and_repair",
2393 t="check", repair="true",
2394 when_done=redir_url))
2395 d.addCallback(lambda res:
2396 self.POST(bar_url, t="check", return_to=redir_url))
2398 self.failUnlessIn("Healthy :", res)
2399 self.failUnlessIn("Return to file", res)
2400 self.failUnlessIn(redir_url, res)
2401 d.addCallback(_check3)
2404 def test_POST_DIRURL_check(self):
2405 foo_url = self.public_url + "/foo/"
2406 d = self.POST(foo_url, t="check")
2408 self.failUnlessIn("Healthy :", res)
2409 d.addCallback(_check)
2410 redir_url = "http://allmydata.org/TARGET"
2411 def _check2(statuscode, target):
2412 self.failUnlessReallyEqual(statuscode, str(http.FOUND))
2413 self.failUnlessReallyEqual(target, redir_url)
2414 d.addCallback(lambda res:
2415 self.shouldRedirect2("test_POST_DIRURL_check",
2419 when_done=redir_url))
2420 d.addCallback(lambda res:
2421 self.POST(foo_url, t="check", return_to=redir_url))
2423 self.failUnlessIn("Healthy :", res)
2424 self.failUnlessIn("Return to file/directory", res)
2425 self.failUnlessIn(redir_url, res)
2426 d.addCallback(_check3)
2428 d.addCallback(lambda res:
2429 self.POST(foo_url, t="check", output="JSON"))
2430 def _check_json(res):
2431 data = simplejson.loads(res)
2432 self.failUnlessIn("storage-index", data)
2433 self.failUnless(data["results"]["healthy"])
2434 d.addCallback(_check_json)
2438 def test_POST_DIRURL_check_and_repair(self):
2439 foo_url = self.public_url + "/foo/"
2440 d = self.POST(foo_url, t="check", repair="true")
2442 self.failUnlessIn("Healthy :", res)
2443 d.addCallback(_check)
2444 redir_url = "http://allmydata.org/TARGET"
2445 def _check2(statuscode, target):
2446 self.failUnlessReallyEqual(statuscode, str(http.FOUND))
2447 self.failUnlessReallyEqual(target, redir_url)
2448 d.addCallback(lambda res:
2449 self.shouldRedirect2("test_POST_DIRURL_check_and_repair",
2452 t="check", repair="true",
2453 when_done=redir_url))
2454 d.addCallback(lambda res:
2455 self.POST(foo_url, t="check", return_to=redir_url))
2457 self.failUnlessIn("Healthy :", res)
2458 self.failUnlessIn("Return to file/directory", res)
2459 self.failUnlessIn(redir_url, res)
2460 d.addCallback(_check3)
2463 def test_POST_FILEURL_mdmf_check(self):
2464 quux_url = "/uri/%s" % urllib.quote(self._quux_txt_uri)
2465 d = self.POST(quux_url, t="check")
2467 self.failUnlessIn("Healthy", res)
2468 d.addCallback(_check)
2469 quux_extension_url = "/uri/%s" % urllib.quote("%s:3:131073" % self._quux_txt_uri)
2470 d.addCallback(lambda ignored:
2471 self.POST(quux_extension_url, t="check"))
2472 d.addCallback(_check)
2475 def test_POST_FILEURL_mdmf_check_and_repair(self):
2476 quux_url = "/uri/%s" % urllib.quote(self._quux_txt_uri)
2477 d = self.POST(quux_url, t="check", repair="true")
2479 self.failUnlessIn("Healthy", res)
2480 d.addCallback(_check)
2481 quux_extension_url = "/uri/%s" % urllib.quote("%s:3:131073" % self._quux_txt_uri)
2482 d.addCallback(lambda ignored:
2483 self.POST(quux_extension_url, t="check", repair="true"))
2484 d.addCallback(_check)
2487 def wait_for_operation(self, ignored, ophandle):
2488 url = "/operations/" + ophandle
2489 url += "?t=status&output=JSON"
2492 data = simplejson.loads(res)
2493 if not data["finished"]:
2494 d = self.stall(delay=1.0)
2495 d.addCallback(self.wait_for_operation, ophandle)
2501 def get_operation_results(self, ignored, ophandle, output=None):
2502 url = "/operations/" + ophandle
2505 url += "&output=" + output
2508 if output and output.lower() == "json":
2509 return simplejson.loads(res)
2514 def test_POST_DIRURL_deepcheck_no_ophandle(self):
2515 d = self.shouldFail2(error.Error,
2516 "test_POST_DIRURL_deepcheck_no_ophandle",
2518 "slow operation requires ophandle=",
2519 self.POST, self.public_url, t="start-deep-check")
2522 def test_POST_DIRURL_deepcheck(self):
2523 def _check_redirect(statuscode, target):
2524 self.failUnlessReallyEqual(statuscode, str(http.FOUND))
2525 self.failUnless(target.endswith("/operations/123"))
2526 d = self.shouldRedirect2("test_POST_DIRURL_deepcheck", _check_redirect,
2527 self.POST, self.public_url,
2528 t="start-deep-check", ophandle="123")
2529 d.addCallback(self.wait_for_operation, "123")
2530 def _check_json(data):
2531 self.failUnlessReallyEqual(data["finished"], True)
2532 self.failUnlessReallyEqual(data["count-objects-checked"], 10)
2533 self.failUnlessReallyEqual(data["count-objects-healthy"], 10)
2534 d.addCallback(_check_json)
2535 d.addCallback(self.get_operation_results, "123", "html")
2536 def _check_html(res):
2537 self.failUnlessIn("Objects Checked: <span>10</span>", res)
2538 self.failUnlessIn("Objects Healthy: <span>10</span>", res)
2539 self.failUnlessIn(FAVICON_MARKUP, res)
2540 d.addCallback(_check_html)
2542 d.addCallback(lambda res:
2543 self.GET("/operations/123/"))
2544 d.addCallback(_check_html) # should be the same as without the slash
2546 d.addCallback(lambda res:
2547 self.shouldFail2(error.Error, "one", "404 Not Found",
2548 "No detailed results for SI bogus",
2549 self.GET, "/operations/123/bogus"))
2551 foo_si = self._foo_node.get_storage_index()
2552 foo_si_s = base32.b2a(foo_si)
2553 d.addCallback(lambda res:
2554 self.GET("/operations/123/%s?output=JSON" % foo_si_s))
2555 def _check_foo_json(res):
2556 data = simplejson.loads(res)
2557 self.failUnlessEqual(data["storage-index"], foo_si_s)
2558 self.failUnless(data["results"]["healthy"])
2559 d.addCallback(_check_foo_json)
2562 def test_POST_DIRURL_deepcheck_and_repair(self):
2563 d = self.POST(self.public_url, t="start-deep-check", repair="true",
2564 ophandle="124", output="json", followRedirect=True)
2565 d.addCallback(self.wait_for_operation, "124")
2566 def _check_json(data):
2567 self.failUnlessReallyEqual(data["finished"], True)
2568 self.failUnlessReallyEqual(data["count-objects-checked"], 10)
2569 self.failUnlessReallyEqual(data["count-objects-healthy-pre-repair"], 10)
2570 self.failUnlessReallyEqual(data["count-objects-unhealthy-pre-repair"], 0)
2571 self.failUnlessReallyEqual(data["count-corrupt-shares-pre-repair"], 0)
2572 self.failUnlessReallyEqual(data["count-repairs-attempted"], 0)
2573 self.failUnlessReallyEqual(data["count-repairs-successful"], 0)
2574 self.failUnlessReallyEqual(data["count-repairs-unsuccessful"], 0)
2575 self.failUnlessReallyEqual(data["count-objects-healthy-post-repair"], 10)
2576 self.failUnlessReallyEqual(data["count-objects-unhealthy-post-repair"], 0)
2577 self.failUnlessReallyEqual(data["count-corrupt-shares-post-repair"], 0)
2578 d.addCallback(_check_json)
2579 d.addCallback(self.get_operation_results, "124", "html")
2580 def _check_html(res):
2581 self.failUnlessIn("Objects Checked: <span>10</span>", res)
2583 self.failUnlessIn("Objects Healthy (before repair): <span>10</span>", res)
2584 self.failUnlessIn("Objects Unhealthy (before repair): <span>0</span>", res)
2585 self.failUnlessIn("Corrupt Shares (before repair): <span>0</span>", res)
2587 self.failUnlessIn("Repairs Attempted: <span>0</span>", res)
2588 self.failUnlessIn("Repairs Successful: <span>0</span>", res)
2589 self.failUnlessIn("Repairs Unsuccessful: <span>0</span>", res)
2591 self.failUnlessIn("Objects Healthy (after repair): <span>10</span>", res)
2592 self.failUnlessIn("Objects Unhealthy (after repair): <span>0</span>", res)
2593 self.failUnlessIn("Corrupt Shares (after repair): <span>0</span>", res)
2595 self.failUnlessIn(FAVICON_MARKUP, res)
2596 d.addCallback(_check_html)
2599 def test_POST_FILEURL_bad_t(self):
2600 d = self.shouldFail2(error.Error, "POST_bad_t", "400 Bad Request",
2601 "POST to file: bad t=bogus",
2602 self.POST, self.public_url + "/foo/bar.txt",
2606 def test_POST_mkdir(self): # return value?
2607 d = self.POST(self.public_url + "/foo", t="mkdir", name="newdir")
2608 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2609 d.addCallback(self.failUnlessNodeKeysAre, [])
2612 def test_POST_mkdir_mdmf(self):
2613 d = self.POST(self.public_url + "/foo?t=mkdir&name=newdir&format=mdmf")
2614 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2615 d.addCallback(lambda node:
2616 self.failUnlessEqual(node._node.get_version(), MDMF_VERSION))
2619 def test_POST_mkdir_sdmf(self):
2620 d = self.POST(self.public_url + "/foo?t=mkdir&name=newdir&format=sdmf")
2621 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2622 d.addCallback(lambda node:
2623 self.failUnlessEqual(node._node.get_version(), SDMF_VERSION))
2626 def test_POST_mkdir_bad_format(self):
2627 return self.shouldHTTPError("POST_mkdir_bad_format",
2628 400, "Bad Request", "Unknown format: foo",
2629 self.POST, self.public_url +
2630 "/foo?t=mkdir&name=newdir&format=foo")
2632 def test_POST_mkdir_initial_children(self):
2633 (newkids, caps) = self._create_initial_children()
2634 d = self.POST2(self.public_url +
2635 "/foo?t=mkdir-with-children&name=newdir",
2636 simplejson.dumps(newkids))
2637 d.addCallback(lambda res:
2638 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
2639 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2640 d.addCallback(self.failUnlessNodeKeysAre, newkids.keys())
2641 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2642 d.addCallback(self.failUnlessROChildURIIs, u"child-imm", caps['filecap1'])
2645 def test_POST_mkdir_initial_children_mdmf(self):
2646 (newkids, caps) = self._create_initial_children()
2647 d = self.POST2(self.public_url +
2648 "/foo?t=mkdir-with-children&name=newdir&format=mdmf",
2649 simplejson.dumps(newkids))
2650 d.addCallback(lambda res:
2651 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
2652 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2653 d.addCallback(lambda node:
2654 self.failUnlessEqual(node._node.get_version(), MDMF_VERSION))
2655 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2656 d.addCallback(self.failUnlessROChildURIIs, u"child-imm",
2661 def test_POST_mkdir_initial_children_sdmf(self):
2662 (newkids, caps) = self._create_initial_children()
2663 d = self.POST2(self.public_url +
2664 "/foo?t=mkdir-with-children&name=newdir&format=sdmf",
2665 simplejson.dumps(newkids))
2666 d.addCallback(lambda res:
2667 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
2668 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2669 d.addCallback(lambda node:
2670 self.failUnlessEqual(node._node.get_version(), SDMF_VERSION))
2671 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2672 d.addCallback(self.failUnlessROChildURIIs, u"child-imm",
2676 def test_POST_mkdir_initial_children_bad_format(self):
2677 (newkids, caps) = self._create_initial_children()
2678 return self.shouldHTTPError("POST_mkdir_initial_children_bad_format",
2679 400, "Bad Request", "Unknown format: foo",
2680 self.POST, self.public_url + \
2681 "/foo?t=mkdir-with-children&name=newdir&format=foo",
2682 simplejson.dumps(newkids))
2684 def test_POST_mkdir_immutable(self):
2685 (newkids, caps) = self._create_immutable_children()
2686 d = self.POST2(self.public_url +
2687 "/foo?t=mkdir-immutable&name=newdir",
2688 simplejson.dumps(newkids))
2689 d.addCallback(lambda res:
2690 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
2691 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2692 d.addCallback(self.failUnlessNodeKeysAre, newkids.keys())
2693 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2694 d.addCallback(self.failUnlessROChildURIIs, u"child-imm", caps['filecap1'])
2695 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2696 d.addCallback(self.failUnlessROChildURIIs, u"unknownchild-imm", caps['unknown_immcap'])
2697 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2698 d.addCallback(self.failUnlessROChildURIIs, u"dirchild-imm", caps['immdircap'])
2699 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2700 d.addCallback(self.failUnlessROChildURIIs, u"dirchild-lit", caps['litdircap'])
2701 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2702 d.addCallback(self.failUnlessROChildURIIs, u"dirchild-empty", caps['emptydircap'])
2705 def test_POST_mkdir_immutable_bad(self):
2706 (newkids, caps) = self._create_initial_children()
2707 d = self.shouldFail2(error.Error, "POST_mkdir_immutable_bad",
2709 "needed to be immutable but was not",
2712 "/foo?t=mkdir-immutable&name=newdir",
2713 simplejson.dumps(newkids))
2716 def test_POST_mkdir_2(self):
2717 d = self.POST(self.public_url + "/foo/newdir?t=mkdir", "")
2718 d.addCallback(lambda res:
2719 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
2720 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2721 d.addCallback(self.failUnlessNodeKeysAre, [])
2724 def test_POST_mkdirs_2(self):
2725 d = self.POST(self.public_url + "/foo/bardir/newdir?t=mkdir", "")
2726 d.addCallback(lambda res:
2727 self.failUnlessNodeHasChild(self._foo_node, u"bardir"))
2728 d.addCallback(lambda res: self._foo_node.get(u"bardir"))
2729 d.addCallback(lambda bardirnode: bardirnode.get(u"newdir"))
2730 d.addCallback(self.failUnlessNodeKeysAre, [])
2733 def test_POST_mkdir_no_parentdir_noredirect(self):
2734 d = self.POST("/uri?t=mkdir")
2735 def _after_mkdir(res):
2736 uri.DirectoryURI.init_from_string(res)
2737 d.addCallback(_after_mkdir)
2740 def test_POST_mkdir_no_parentdir_noredirect_mdmf(self):
2741 d = self.POST("/uri?t=mkdir&format=mdmf")
2742 def _after_mkdir(res):
2743 u = uri.from_string(res)
2744 # Check that this is an MDMF writecap
2745 self.failUnlessIsInstance(u, uri.MDMFDirectoryURI)
2746 d.addCallback(_after_mkdir)
2749 def test_POST_mkdir_no_parentdir_noredirect_sdmf(self):
2750 d = self.POST("/uri?t=mkdir&format=sdmf")
2751 def _after_mkdir(res):
2752 u = uri.from_string(res)
2753 self.failUnlessIsInstance(u, uri.DirectoryURI)
2754 d.addCallback(_after_mkdir)
2757 def test_POST_mkdir_no_parentdir_noredirect_bad_format(self):
2758 return self.shouldHTTPError("POST_mkdir_no_parentdir_noredirect_bad_format",
2759 400, "Bad Request", "Unknown format: foo",
2760 self.POST, self.public_url +
2761 "/uri?t=mkdir&format=foo")
2763 def test_POST_mkdir_no_parentdir_noredirect2(self):
2764 # make sure form-based arguments (as on the welcome page) still work
2765 d = self.POST("/uri", t="mkdir")
2766 def _after_mkdir(res):
2767 uri.DirectoryURI.init_from_string(res)
2768 d.addCallback(_after_mkdir)
2769 d.addErrback(self.explain_web_error)
2772 def test_POST_mkdir_no_parentdir_redirect(self):
2773 d = self.POST("/uri?t=mkdir&redirect_to_result=true")
2774 d.addBoth(self.shouldRedirect, None, statuscode='303')
2775 def _check_target(target):
2776 target = urllib.unquote(target)
2777 self.failUnless(target.startswith("uri/URI:DIR2:"), target)
2778 d.addCallback(_check_target)
2781 def test_POST_mkdir_no_parentdir_redirect2(self):
2782 d = self.POST("/uri", t="mkdir", redirect_to_result="true")
2783 d.addBoth(self.shouldRedirect, None, statuscode='303')
2784 def _check_target(target):
2785 target = urllib.unquote(target)
2786 self.failUnless(target.startswith("uri/URI:DIR2:"), target)
2787 d.addCallback(_check_target)
2788 d.addErrback(self.explain_web_error)
2791 def _make_readonly(self, u):
2792 ro_uri = uri.from_string(u).get_readonly()
2795 return ro_uri.to_string()
2797 def _create_initial_children(self):
2798 contents, n, filecap1 = self.makefile(12)
2799 md1 = {"metakey1": "metavalue1"}
2800 filecap2 = make_mutable_file_uri()
2801 node3 = self.s.create_node_from_uri(make_mutable_file_uri())
2802 filecap3 = node3.get_readonly_uri()
2803 node4 = self.s.create_node_from_uri(make_mutable_file_uri())
2804 dircap = DirectoryNode(node4, None, None).get_uri()
2805 mdmfcap = make_mutable_file_uri(mdmf=True)
2806 litdircap = "URI:DIR2-LIT:ge3dumj2mewdcotyfqydulbshj5x2lbm"
2807 emptydircap = "URI:DIR2-LIT:"
2808 newkids = {u"child-imm": ["filenode", {"rw_uri": filecap1,
2809 "ro_uri": self._make_readonly(filecap1),
2810 "metadata": md1, }],
2811 u"child-mutable": ["filenode", {"rw_uri": filecap2,
2812 "ro_uri": self._make_readonly(filecap2)}],
2813 u"child-mutable-ro": ["filenode", {"ro_uri": filecap3}],
2814 u"unknownchild-rw": ["unknown", {"rw_uri": unknown_rwcap,
2815 "ro_uri": unknown_rocap}],
2816 u"unknownchild-ro": ["unknown", {"ro_uri": unknown_rocap}],
2817 u"unknownchild-imm": ["unknown", {"ro_uri": unknown_immcap}],
2818 u"dirchild": ["dirnode", {"rw_uri": dircap,
2819 "ro_uri": self._make_readonly(dircap)}],
2820 u"dirchild-lit": ["dirnode", {"ro_uri": litdircap}],
2821 u"dirchild-empty": ["dirnode", {"ro_uri": emptydircap}],
2822 u"child-mutable-mdmf": ["filenode", {"rw_uri": mdmfcap,
2823 "ro_uri": self._make_readonly(mdmfcap)}],
2825 return newkids, {'filecap1': filecap1,
2826 'filecap2': filecap2,
2827 'filecap3': filecap3,
2828 'unknown_rwcap': unknown_rwcap,
2829 'unknown_rocap': unknown_rocap,
2830 'unknown_immcap': unknown_immcap,
2832 'litdircap': litdircap,
2833 'emptydircap': emptydircap,
2836 def _create_immutable_children(self):
2837 contents, n, filecap1 = self.makefile(12)
2838 md1 = {"metakey1": "metavalue1"}
2839 tnode = create_chk_filenode("immutable directory contents\n"*10)
2840 dnode = DirectoryNode(tnode, None, None)
2841 assert not dnode.is_mutable()
2842 immdircap = dnode.get_uri()
2843 litdircap = "URI:DIR2-LIT:ge3dumj2mewdcotyfqydulbshj5x2lbm"
2844 emptydircap = "URI:DIR2-LIT:"
2845 newkids = {u"child-imm": ["filenode", {"ro_uri": filecap1,
2846 "metadata": md1, }],
2847 u"unknownchild-imm": ["unknown", {"ro_uri": unknown_immcap}],
2848 u"dirchild-imm": ["dirnode", {"ro_uri": immdircap}],
2849 u"dirchild-lit": ["dirnode", {"ro_uri": litdircap}],
2850 u"dirchild-empty": ["dirnode", {"ro_uri": emptydircap}],
2852 return newkids, {'filecap1': filecap1,
2853 'unknown_immcap': unknown_immcap,
2854 'immdircap': immdircap,
2855 'litdircap': litdircap,
2856 'emptydircap': emptydircap}
2858 def test_POST_mkdir_no_parentdir_initial_children(self):
2859 (newkids, caps) = self._create_initial_children()
2860 d = self.POST2("/uri?t=mkdir-with-children", simplejson.dumps(newkids))
2861 def _after_mkdir(res):
2862 self.failUnless(res.startswith("URI:DIR"), res)
2863 n = self.s.create_node_from_uri(res)
2864 d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
2865 d2.addCallback(lambda ign:
2866 self.failUnlessROChildURIIs(n, u"child-imm",
2868 d2.addCallback(lambda ign:
2869 self.failUnlessRWChildURIIs(n, u"child-mutable",
2871 d2.addCallback(lambda ign:
2872 self.failUnlessROChildURIIs(n, u"child-mutable-ro",
2874 d2.addCallback(lambda ign:
2875 self.failUnlessRWChildURIIs(n, u"unknownchild-rw",
2876 caps['unknown_rwcap']))
2877 d2.addCallback(lambda ign:
2878 self.failUnlessROChildURIIs(n, u"unknownchild-ro",
2879 caps['unknown_rocap']))
2880 d2.addCallback(lambda ign:
2881 self.failUnlessROChildURIIs(n, u"unknownchild-imm",
2882 caps['unknown_immcap']))
2883 d2.addCallback(lambda ign:
2884 self.failUnlessRWChildURIIs(n, u"dirchild",
2887 d.addCallback(_after_mkdir)
2890 def test_POST_mkdir_no_parentdir_unexpected_children(self):
2891 # the regular /uri?t=mkdir operation is specified to ignore its body.
2892 # Only t=mkdir-with-children pays attention to it.
2893 (newkids, caps) = self._create_initial_children()
2894 d = self.shouldHTTPError("POST_mkdir_no_parentdir_unexpected_children",
2896 "t=mkdir does not accept children=, "
2897 "try t=mkdir-with-children instead",
2898 self.POST2, "/uri?t=mkdir", # without children
2899 simplejson.dumps(newkids))
2902 def test_POST_noparent_bad(self):
2903 d = self.shouldHTTPError("POST_noparent_bad",
2905 "/uri accepts only PUT, PUT?t=mkdir, "
2906 "POST?t=upload, and POST?t=mkdir",
2907 self.POST, "/uri?t=bogus")
2910 def test_POST_mkdir_no_parentdir_immutable(self):
2911 (newkids, caps) = self._create_immutable_children()
2912 d = self.POST2("/uri?t=mkdir-immutable", simplejson.dumps(newkids))
2913 def _after_mkdir(res):
2914 self.failUnless(res.startswith("URI:DIR"), res)
2915 n = self.s.create_node_from_uri(res)
2916 d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
2917 d2.addCallback(lambda ign:
2918 self.failUnlessROChildURIIs(n, u"child-imm",
2920 d2.addCallback(lambda ign:
2921 self.failUnlessROChildURIIs(n, u"unknownchild-imm",
2922 caps['unknown_immcap']))
2923 d2.addCallback(lambda ign:
2924 self.failUnlessROChildURIIs(n, u"dirchild-imm",
2926 d2.addCallback(lambda ign:
2927 self.failUnlessROChildURIIs(n, u"dirchild-lit",
2929 d2.addCallback(lambda ign:
2930 self.failUnlessROChildURIIs(n, u"dirchild-empty",
2931 caps['emptydircap']))
2933 d.addCallback(_after_mkdir)
2936 def test_POST_mkdir_no_parentdir_immutable_bad(self):
2937 (newkids, caps) = self._create_initial_children()
2938 d = self.shouldFail2(error.Error,
2939 "test_POST_mkdir_no_parentdir_immutable_bad",
2941 "needed to be immutable but was not",
2943 "/uri?t=mkdir-immutable",
2944 simplejson.dumps(newkids))
2947 def test_welcome_page_mkdir_button(self):
2948 # Fetch the welcome page.
2950 def _after_get_welcome_page(res):
2951 MKDIR_BUTTON_RE = re.compile(
2952 '<form action="([^"]*)" method="post".*?'
2953 '<input type="hidden" name="t" value="([^"]*)" />'
2954 '<input type="hidden" name="([^"]*)" value="([^"]*)" />'
2955 '<input type="submit" value="Create a directory" />',
2957 mo = MKDIR_BUTTON_RE.search(res)
2958 formaction = mo.group(1)
2960 formaname = mo.group(3)
2961 formavalue = mo.group(4)
2962 return (formaction, formt, formaname, formavalue)
2963 d.addCallback(_after_get_welcome_page)
2964 def _after_parse_form(res):
2965 (formaction, formt, formaname, formavalue) = res
2966 return self.POST("/%s?t=%s&%s=%s" % (formaction, formt, formaname, formavalue))
2967 d.addCallback(_after_parse_form)
2968 d.addBoth(self.shouldRedirect, None, statuscode='303')
2971 def test_POST_mkdir_replace(self): # return value?
2972 d = self.POST(self.public_url + "/foo", t="mkdir", name="sub")
2973 d.addCallback(lambda res: self._foo_node.get(u"sub"))
2974 d.addCallback(self.failUnlessNodeKeysAre, [])
2977 def test_POST_mkdir_no_replace_queryarg(self): # return value?
2978 d = self.POST(self.public_url + "/foo?replace=false", t="mkdir", name="sub")
2979 d.addBoth(self.shouldFail, error.Error,
2980 "POST_mkdir_no_replace_queryarg",
2982 "There was already a child by that name, and you asked me "
2983 "to not replace it")
2984 d.addCallback(lambda res: self._foo_node.get(u"sub"))
2985 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
2988 def test_POST_mkdir_no_replace_field(self): # return value?
2989 d = self.POST(self.public_url + "/foo", t="mkdir", name="sub",
2991 d.addBoth(self.shouldFail, error.Error, "POST_mkdir_no_replace_field",
2993 "There was already a child by that name, and you asked me "
2994 "to not replace it")
2995 d.addCallback(lambda res: self._foo_node.get(u"sub"))
2996 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
2999 def test_POST_mkdir_whendone_field(self):
3000 d = self.POST(self.public_url + "/foo",
3001 t="mkdir", name="newdir", when_done="/THERE")
3002 d.addBoth(self.shouldRedirect, "/THERE")
3003 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
3004 d.addCallback(self.failUnlessNodeKeysAre, [])
3007 def test_POST_mkdir_whendone_queryarg(self):
3008 d = self.POST(self.public_url + "/foo?when_done=/THERE",
3009 t="mkdir", name="newdir")
3010 d.addBoth(self.shouldRedirect, "/THERE")
3011 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
3012 d.addCallback(self.failUnlessNodeKeysAre, [])
3015 def test_POST_bad_t(self):
3016 d = self.shouldFail2(error.Error, "POST_bad_t",
3018 "POST to a directory with bad t=BOGUS",
3019 self.POST, self.public_url + "/foo", t="BOGUS")
3022 def test_POST_set_children(self, command_name="set_children"):
3023 contents9, n9, newuri9 = self.makefile(9)
3024 contents10, n10, newuri10 = self.makefile(10)
3025 contents11, n11, newuri11 = self.makefile(11)
3028 "atomic_added_1": [ "filenode", { "rw_uri": "%s",
3031 "ctime": 1002777696.7564139,
3032 "mtime": 1002777696.7564139
3035 "atomic_added_2": [ "filenode", { "rw_uri": "%s",
3038 "ctime": 1002777696.7564139,
3039 "mtime": 1002777696.7564139
3042 "atomic_added_3": [ "filenode", { "rw_uri": "%s",
3045 "ctime": 1002777696.7564139,
3046 "mtime": 1002777696.7564139
3049 }""" % (newuri9, newuri10, newuri11)
3051 url = self.webish_url + self.public_url + "/foo" + "?t=" + command_name
3053 d = client.getPage(url, method="POST", postdata=reqbody)
3055 self.failUnlessURIMatchesROChild(newuri9, self._foo_node, u"atomic_added_1")
3056 self.failUnlessURIMatchesROChild(newuri10, self._foo_node, u"atomic_added_2")
3057 self.failUnlessURIMatchesROChild(newuri11, self._foo_node, u"atomic_added_3")
3059 d.addCallback(_then)
3060 d.addErrback(self.dump_error)
3063 def test_POST_set_children_with_hyphen(self):
3064 return self.test_POST_set_children(command_name="set-children")
3066 def test_POST_link_uri(self):
3067 contents, n, newuri = self.makefile(8)
3068 d = self.POST(self.public_url + "/foo", t="uri", name="new.txt", uri=newuri)
3069 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"new.txt")
3070 d.addCallback(lambda res:
3071 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
3075 def test_POST_link_uri_replace(self):
3076 contents, n, newuri = self.makefile(8)
3077 d = self.POST(self.public_url + "/foo", t="uri", name="bar.txt", uri=newuri)
3078 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"bar.txt")
3079 d.addCallback(lambda res:
3080 self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
3084 def test_POST_link_uri_unknown_bad(self):
3085 d = self.POST(self.public_url + "/foo", t="uri", name="future.txt", uri=unknown_rwcap)
3086 d.addBoth(self.shouldFail, error.Error,
3087 "POST_link_uri_unknown_bad",
3089 "unknown cap in a write slot")
3092 def test_POST_link_uri_unknown_ro_good(self):
3093 d = self.POST(self.public_url + "/foo", t="uri", name="future-ro.txt", uri=unknown_rocap)
3094 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"future-ro.txt")
3097 def test_POST_link_uri_unknown_imm_good(self):
3098 d = self.POST(self.public_url + "/foo", t="uri", name="future-imm.txt", uri=unknown_immcap)
3099 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"future-imm.txt")
3102 def test_POST_link_uri_no_replace_queryarg(self):
3103 contents, n, newuri = self.makefile(8)
3104 d = self.POST(self.public_url + "/foo?replace=false", t="uri",
3105 name="bar.txt", uri=newuri)
3106 d.addBoth(self.shouldFail, error.Error,
3107 "POST_link_uri_no_replace_queryarg",
3109 "There was already a child by that name, and you asked me "
3110 "to not replace it")
3111 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
3112 d.addCallback(self.failUnlessIsBarDotTxt)
3115 def test_POST_link_uri_no_replace_field(self):
3116 contents, n, newuri = self.makefile(8)
3117 d = self.POST(self.public_url + "/foo", t="uri", replace="false",
3118 name="bar.txt", uri=newuri)
3119 d.addBoth(self.shouldFail, error.Error,
3120 "POST_link_uri_no_replace_field",
3122 "There was already a child by that name, and you asked me "
3123 "to not replace it")
3124 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
3125 d.addCallback(self.failUnlessIsBarDotTxt)
3128 def test_POST_delete(self, command_name='delete'):
3129 d = self._foo_node.list()
3130 def _check_before(children):
3131 self.failUnlessIn(u"bar.txt", children)
3132 d.addCallback(_check_before)
3133 d.addCallback(lambda res: self.POST(self.public_url + "/foo", t=command_name, name="bar.txt"))
3134 d.addCallback(lambda res: self._foo_node.list())
3135 def _check_after(children):
3136 self.failIfIn(u"bar.txt", children)
3137 d.addCallback(_check_after)
3140 def test_POST_unlink(self):
3141 return self.test_POST_delete(command_name='unlink')
3143 def test_POST_rename_file(self):
3144 d = self.POST(self.public_url + "/foo", t="rename",
3145 from_name="bar.txt", to_name='wibble.txt')
3146 d.addCallback(lambda res:
3147 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
3148 d.addCallback(lambda res:
3149 self.failUnlessNodeHasChild(self._foo_node, u"wibble.txt"))
3150 d.addCallback(lambda res: self.GET(self.public_url + "/foo/wibble.txt"))
3151 d.addCallback(self.failUnlessIsBarDotTxt)
3152 d.addCallback(lambda res: self.GET(self.public_url + "/foo/wibble.txt?t=json"))
3153 d.addCallback(self.failUnlessIsBarJSON)
3156 def test_POST_rename_file_redundant(self):
3157 d = self.POST(self.public_url + "/foo", t="rename",
3158 from_name="bar.txt", to_name='bar.txt')
3159 d.addCallback(lambda res:
3160 self.failUnlessNodeHasChild(self._foo_node, u"bar.txt"))
3161 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
3162 d.addCallback(self.failUnlessIsBarDotTxt)
3163 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt?t=json"))
3164 d.addCallback(self.failUnlessIsBarJSON)
3167 def test_POST_rename_file_replace(self):
3168 # rename a file and replace a directory with it
3169 d = self.POST(self.public_url + "/foo", t="rename",
3170 from_name="bar.txt", to_name='empty')
3171 d.addCallback(lambda res:
3172 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
3173 d.addCallback(lambda res:
3174 self.failUnlessNodeHasChild(self._foo_node, u"empty"))
3175 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty"))
3176 d.addCallback(self.failUnlessIsBarDotTxt)
3177 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
3178 d.addCallback(self.failUnlessIsBarJSON)
3181 def test_POST_rename_file_no_replace_queryarg(self):
3182 # rename a file and replace a directory with it
3183 d = self.POST(self.public_url + "/foo?replace=false", t="rename",
3184 from_name="bar.txt", to_name='empty')
3185 d.addBoth(self.shouldFail, error.Error,
3186 "POST_rename_file_no_replace_queryarg",
3188 "There was already a child by that name, and you asked me "
3189 "to not replace it")
3190 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
3191 d.addCallback(self.failUnlessIsEmptyJSON)
3194 def test_POST_rename_file_no_replace_field(self):
3195 # rename a file and replace a directory with it
3196 d = self.POST(self.public_url + "/foo", t="rename", replace="false",
3197 from_name="bar.txt", to_name='empty')
3198 d.addBoth(self.shouldFail, error.Error,
3199 "POST_rename_file_no_replace_field",
3201 "There was already a child by that name, and you asked me "
3202 "to not replace it")
3203 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
3204 d.addCallback(self.failUnlessIsEmptyJSON)
3207 def failUnlessIsEmptyJSON(self, res):
3208 data = simplejson.loads(res)
3209 self.failUnlessEqual(data[0], "dirnode", data)
3210 self.failUnlessReallyEqual(len(data[1]["children"]), 0)
3212 def test_POST_rename_file_slash_fail(self):
3213 d = self.POST(self.public_url + "/foo", t="rename",
3214 from_name="bar.txt", to_name='kirk/spock.txt')
3215 d.addBoth(self.shouldFail, error.Error,
3216 "test_POST_rename_file_slash_fail",
3218 "to_name= may not contain a slash",
3220 d.addCallback(lambda res:
3221 self.failUnlessNodeHasChild(self._foo_node, u"bar.txt"))
3224 def test_POST_rename_dir(self):
3225 d = self.POST(self.public_url, t="rename",
3226 from_name="foo", to_name='plunk')
3227 d.addCallback(lambda res:
3228 self.failIfNodeHasChild(self.public_root, u"foo"))
3229 d.addCallback(lambda res:
3230 self.failUnlessNodeHasChild(self.public_root, u"plunk"))
3231 d.addCallback(lambda res: self.GET(self.public_url + "/plunk?t=json"))
3232 d.addCallback(self.failUnlessIsFooJSON)
3235 def test_POST_move_file(self):
3236 d = self.POST(self.public_url + "/foo", t="move",
3237 from_name="bar.txt", to_dir="sub")
3238 d.addCallback(lambda res:
3239 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
3240 d.addCallback(lambda res:
3241 self.failUnlessNodeHasChild(self._sub_node, u"bar.txt"))
3242 d.addCallback(lambda res: self.GET(self.public_url + "/foo/sub/bar.txt"))
3243 d.addCallback(self.failUnlessIsBarDotTxt)
3244 d.addCallback(lambda res: self.GET(self.public_url + "/foo/sub/bar.txt?t=json"))
3245 d.addCallback(self.failUnlessIsBarJSON)
3248 def test_POST_move_file_new_name(self):
3249 d = self.POST(self.public_url + "/foo", t="move",
3250 from_name="bar.txt", to_name="wibble.txt", to_dir="sub")
3251 d.addCallback(lambda res:
3252 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
3253 d.addCallback(lambda res:
3254 self.failIfNodeHasChild(self._sub_node, u"bar.txt"))
3255 d.addCallback(lambda res:
3256 self.failUnlessNodeHasChild(self._sub_node, u"wibble.txt"))
3257 d.addCallback(lambda res: self.GET(self.public_url + "/foo/sub/wibble.txt"))
3258 d.addCallback(self.failUnlessIsBarDotTxt)
3259 d.addCallback(lambda res: self.GET(self.public_url + "/foo/sub/wibble.txt?t=json"))
3260 d.addCallback(self.failUnlessIsBarJSON)
3263 def test_POST_move_file_replace(self):
3264 d = self.POST(self.public_url + "/foo", t="move",
3265 from_name="bar.txt", to_name="baz.txt", to_dir="sub")
3266 d.addCallback(lambda res:
3267 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
3268 d.addCallback(lambda res: self.GET(self.public_url + "/foo/sub/baz.txt"))
3269 d.addCallback(self.failUnlessIsBarDotTxt)
3270 d.addCallback(lambda res: self.GET(self.public_url + "/foo/sub/baz.txt?t=json"))
3271 d.addCallback(self.failUnlessIsBarJSON)
3274 def test_POST_move_file_no_replace(self):
3275 d = self.shouldFail2(error.Error, "POST_move_file_no_replace",
3277 "There was already a child by that name, and you asked me to not replace it",
3278 self.POST, self.public_url + "/foo", t="move",
3279 replace="false", from_name="bar.txt",
3280 to_name="baz.txt", to_dir="sub")
3281 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
3282 d.addCallback(self.failUnlessIsBarDotTxt)
3283 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt?t=json"))
3284 d.addCallback(self.failUnlessIsBarJSON)
3285 d.addCallback(lambda res: self.GET(self.public_url + "/foo/sub/baz.txt"))
3286 d.addCallback(self.failUnlessIsSubBazDotTxt)
3289 def test_POST_move_file_slash_fail(self):
3290 d = self.shouldFail2(error.Error, "test_POST_rename_file_slash_fail",
3292 "to_name= may not contain a slash",
3293 self.POST, self.public_url + "/foo", t="move",
3294 from_name="bar.txt",
3295 to_name="slash/fail.txt", to_dir="sub")
3296 d.addCallback(lambda res:
3297 self.failUnlessNodeHasChild(self._foo_node, u"bar.txt"))
3298 d.addCallback(lambda res:
3299 self.failIfNodeHasChild(self._sub_node, u"slash/fail.txt"))
3300 d.addCallback(lambda ign:
3301 self.shouldFail2(error.Error,
3302 "test_POST_rename_file_slash_fail2",
3304 "from_name= may not contain a slash",
3305 self.POST, self.public_url + "/foo",
3307 from_name="nope/bar.txt",
3308 to_name="fail.txt", to_dir="sub"))
3311 def test_POST_move_file_no_target(self):
3312 d = self.shouldFail2(error.Error, "POST_move_file_no_target",
3314 "move requires from_name and to_dir",
3315 self.POST, self.public_url + "/foo", t="move",
3316 from_name="bar.txt", to_name="baz.txt")
3317 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
3318 d.addCallback(self.failUnlessIsBarDotTxt)
3319 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt?t=json"))
3320 d.addCallback(self.failUnlessIsBarJSON)
3321 d.addCallback(lambda res: self.GET(self.public_url + "/foo/baz.txt"))
3322 d.addCallback(self.failUnlessIsBazDotTxt)
3325 def test_POST_move_file_bad_target_type(self):
3326 d = self.shouldFail2(error.Error, "test_POST_move_file_bad_target_type",
3327 "400 Bad Request", "invalid target_type parameter",
3329 self.public_url + "/foo", t="move",
3330 target_type="*D", from_name="bar.txt",
3334 def test_POST_move_file_multi_level(self):
3335 d = self.POST(self.public_url + "/foo/sub/level2?t=mkdir", "")
3336 d.addCallback(lambda res: self.POST(self.public_url + "/foo", t="move",
3337 from_name="bar.txt", to_dir="sub/level2"))
3338 d.addCallback(lambda res: self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
3339 d.addCallback(lambda res: self.failIfNodeHasChild(self._sub_node, u"bar.txt"))
3340 d.addCallback(lambda res: self.GET(self.public_url + "/foo/sub/level2/bar.txt"))
3341 d.addCallback(self.failUnlessIsBarDotTxt)
3342 d.addCallback(lambda res: self.GET(self.public_url + "/foo/sub/level2/bar.txt?t=json"))
3343 d.addCallback(self.failUnlessIsBarJSON)
3346 def test_POST_move_file_to_uri(self):
3347 d = self.POST(self.public_url + "/foo", t="move", target_type="uri",
3348 from_name="bar.txt", to_dir=self._sub_uri)
3349 d.addCallback(lambda res:
3350 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
3351 d.addCallback(lambda res: self.GET(self.public_url + "/foo/sub/bar.txt"))
3352 d.addCallback(self.failUnlessIsBarDotTxt)
3353 d.addCallback(lambda res: self.GET(self.public_url + "/foo/sub/bar.txt?t=json"))
3354 d.addCallback(self.failUnlessIsBarJSON)
3357 def test_POST_move_file_to_nonexist_dir(self):
3358 d = self.shouldFail2(error.Error, "POST_move_file_to_nonexist_dir",
3359 "404 Not Found", "No such child: nopechucktesta",
3360 self.POST, self.public_url + "/foo", t="move",
3361 from_name="bar.txt", to_dir="nopechucktesta")
3364 def test_POST_move_file_into_file(self):
3365 d = self.shouldFail2(error.Error, "POST_move_file_into_file",
3366 "400 Bad Request", "to_dir is not a directory",
3367 self.POST, self.public_url + "/foo", t="move",
3368 from_name="bar.txt", to_dir="baz.txt")
3369 d.addCallback(lambda res: self.GET(self.public_url + "/foo/baz.txt"))
3370 d.addCallback(self.failUnlessIsBazDotTxt)
3371 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
3372 d.addCallback(self.failUnlessIsBarDotTxt)
3373 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt?t=json"))
3374 d.addCallback(self.failUnlessIsBarJSON)
3377 def test_POST_move_file_to_bad_uri(self):
3378 d = self.shouldFail2(error.Error, "POST_move_file_to_bad_uri",
3379 "400 Bad Request", "to_dir is not a directory",
3380 self.POST, self.public_url + "/foo", t="move",
3381 from_name="bar.txt", target_type="uri",
3382 to_dir="URI:DIR2:mn5jlyjnrjeuydyswlzyui72i:rmneifcj6k6sycjljjhj3f6majsq2zqffydnnul5hfa4j577arma")
3383 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
3384 d.addCallback(self.failUnlessIsBarDotTxt)
3385 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt?t=json"))
3386 d.addCallback(self.failUnlessIsBarJSON)
3389 def test_POST_move_dir(self):
3390 d = self.POST(self.public_url + "/foo", t="move",
3391 from_name="bar.txt", to_dir="empty")
3392 d.addCallback(lambda res: self.POST(self.public_url + "/foo",
3393 t="move", from_name="empty", to_dir="sub"))
3394 d.addCallback(lambda res:
3395 self.failIfNodeHasChild(self._foo_node, u"empty"))
3396 d.addCallback(lambda res:
3397 self.failUnlessNodeHasChild(self._sub_node, u"empty"))
3398 d.addCallback(lambda res:
3399 self._sub_node.get_child_at_path(u"empty"))
3400 d.addCallback(lambda node:
3401 self.failUnlessNodeHasChild(node, u"bar.txt"))
3402 d.addCallback(lambda res:
3403 self.GET(self.public_url + "/foo/sub/empty/bar.txt"))
3404 d.addCallback(self.failUnlessIsBarDotTxt)
3407 def shouldRedirect(self, res, target=None, statuscode=None, which=""):
3408 """ If target is not None then the redirection has to go to target. If
3409 statuscode is not None then the redirection has to be accomplished with
3410 that HTTP status code."""
3411 if not isinstance(res, failure.Failure):
3412 to_where = (target is None) and "somewhere" or ("to " + target)
3413 self.fail("%s: we were expecting to get redirected %s, not get an"
3414 " actual page: %s" % (which, to_where, res))
3415 res.trap(error.PageRedirect)
3416 if statuscode is not None:
3417 self.failUnlessReallyEqual(res.value.status, statuscode,
3418 "%s: not a redirect" % which)
3419 if target is not None:
3420 # the PageRedirect does not seem to capture the uri= query arg
3421 # properly, so we can't check for it.
3422 realtarget = self.webish_url + target
3423 self.failUnlessReallyEqual(res.value.location, realtarget,
3424 "%s: wrong target" % which)
3425 return res.value.location
3427 def test_GET_URI_form(self):
3428 base = "/uri?uri=%s" % self._bar_txt_uri
3429 # this is supposed to give us a redirect to /uri/$URI, plus arguments
3430 targetbase = "/uri/%s" % urllib.quote(self._bar_txt_uri)
3432 d.addBoth(self.shouldRedirect, targetbase)
3433 d.addCallback(lambda res: self.GET(base+"&filename=bar.txt"))
3434 d.addBoth(self.shouldRedirect, targetbase+"?filename=bar.txt")
3435 d.addCallback(lambda res: self.GET(base+"&t=json"))
3436 d.addBoth(self.shouldRedirect, targetbase+"?t=json")
3437 d.addCallback(self.log, "about to get file by uri")
3438 d.addCallback(lambda res: self.GET(base, followRedirect=True))
3439 d.addCallback(self.failUnlessIsBarDotTxt)
3440 d.addCallback(self.log, "got file by uri, about to get dir by uri")
3441 d.addCallback(lambda res: self.GET("/uri?uri=%s&t=json" % self._foo_uri,
3442 followRedirect=True))
3443 d.addCallback(self.failUnlessIsFooJSON)
3444 d.addCallback(self.log, "got dir by uri")
3448 def test_GET_URI_form_bad(self):
3449 d = self.shouldFail2(error.Error, "test_GET_URI_form_bad",
3450 "400 Bad Request", "GET /uri requires uri=",
3454 def test_GET_rename_form(self):
3455 d = self.GET(self.public_url + "/foo?t=rename-form&name=bar.txt",
3456 followRedirect=True)
3458 self.failUnlessIn('name="when_done" value="."', res)
3459 self.failUnless(re.search(r'name="from_name" value="bar\.txt"', res))
3460 self.failUnlessIn(FAVICON_MARKUP, res)
3461 d.addCallback(_check)
3464 def log(self, res, msg):
3465 #print "MSG: %s RES: %s" % (msg, res)
3469 def test_GET_URI_URL(self):
3470 base = "/uri/%s" % self._bar_txt_uri
3472 d.addCallback(self.failUnlessIsBarDotTxt)
3473 d.addCallback(lambda res: self.GET(base+"?filename=bar.txt"))
3474 d.addCallback(self.failUnlessIsBarDotTxt)
3475 d.addCallback(lambda res: self.GET(base+"?filename=bar.txt&save=true"))
3476 d.addCallback(self.failUnlessIsBarDotTxt)
3479 def test_GET_URI_URL_dir(self):
3480 base = "/uri/%s?t=json" % self._foo_uri
3482 d.addCallback(self.failUnlessIsFooJSON)
3485 def test_GET_URI_URL_missing(self):
3486 base = "/uri/%s" % self._bad_file_uri
3487 d = self.shouldHTTPError("test_GET_URI_URL_missing",
3488 http.GONE, None, "NotEnoughSharesError",
3490 # TODO: how can we exercise both sides of WebDownloadTarget.fail
3491 # here? we must arrange for a download to fail after target.open()
3492 # has been called, and then inspect the response to see that it is
3493 # shorter than we expected.
3496 def test_PUT_DIRURL_uri(self):
3497 d = self.s.create_dirnode()
3499 new_uri = dn.get_uri()
3500 # replace /foo with a new (empty) directory
3501 d = self.PUT(self.public_url + "/foo?t=uri", new_uri)
3502 d.addCallback(lambda res:
3503 self.failUnlessReallyEqual(res.strip(), new_uri))
3504 d.addCallback(lambda res:
3505 self.failUnlessRWChildURIIs(self.public_root,
3509 d.addCallback(_made_dir)
3512 def test_PUT_DIRURL_uri_noreplace(self):
3513 d = self.s.create_dirnode()
3515 new_uri = dn.get_uri()
3516 # replace /foo with a new (empty) directory, but ask that
3517 # replace=false, so it should fail
3518 d = self.shouldFail2(error.Error, "test_PUT_DIRURL_uri_noreplace",
3519 "409 Conflict", "There was already a child by that name, and you asked me to not replace it",
3521 self.public_url + "/foo?t=uri&replace=false",
3523 d.addCallback(lambda res:
3524 self.failUnlessRWChildURIIs(self.public_root,
3528 d.addCallback(_made_dir)
3531 def test_PUT_DIRURL_bad_t(self):
3532 d = self.shouldFail2(error.Error, "test_PUT_DIRURL_bad_t",
3533 "400 Bad Request", "PUT to a directory",
3534 self.PUT, self.public_url + "/foo?t=BOGUS", "")
3535 d.addCallback(lambda res:
3536 self.failUnlessRWChildURIIs(self.public_root,
3541 def test_PUT_NEWFILEURL_uri(self):
3542 contents, n, new_uri = self.makefile(8)
3543 d = self.PUT(self.public_url + "/foo/new.txt?t=uri", new_uri)
3544 d.addCallback(lambda res: self.failUnlessReallyEqual(res.strip(), new_uri))
3545 d.addCallback(lambda res:
3546 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
3550 def test_PUT_NEWFILEURL_mdmf(self):
3551 new_contents = self.NEWFILE_CONTENTS * 300000
3552 d = self.PUT(self.public_url + \
3553 "/foo/mdmf.txt?format=mdmf",
3555 d.addCallback(lambda ignored:
3556 self.GET(self.public_url + "/foo/mdmf.txt?t=json"))
3557 def _got_json(json):
3558 data = simplejson.loads(json)
3560 self.failUnlessIn("format", data)
3561 self.failUnlessEqual(data["format"], "MDMF")
3562 self.failUnless(data['rw_uri'].startswith("URI:MDMF"))
3563 self.failUnless(data['ro_uri'].startswith("URI:MDMF"))
3564 d.addCallback(_got_json)
3567 def test_PUT_NEWFILEURL_sdmf(self):
3568 new_contents = self.NEWFILE_CONTENTS * 300000
3569 d = self.PUT(self.public_url + \
3570 "/foo/sdmf.txt?format=sdmf",
3572 d.addCallback(lambda ignored:
3573 self.GET(self.public_url + "/foo/sdmf.txt?t=json"))
3574 def _got_json(json):
3575 data = simplejson.loads(json)
3577 self.failUnlessIn("format", data)
3578 self.failUnlessEqual(data["format"], "SDMF")
3579 d.addCallback(_got_json)
3582 def test_PUT_NEWFILEURL_bad_format(self):
3583 new_contents = self.NEWFILE_CONTENTS * 300000
3584 return self.shouldHTTPError("PUT_NEWFILEURL_bad_format",
3585 400, "Bad Request", "Unknown format: foo",
3586 self.PUT, self.public_url + \
3587 "/foo/foo.txt?format=foo",
3590 def test_PUT_NEWFILEURL_uri_replace(self):
3591 contents, n, new_uri = self.makefile(8)
3592 d = self.PUT(self.public_url + "/foo/bar.txt?t=uri", new_uri)
3593 d.addCallback(lambda res: self.failUnlessReallyEqual(res.strip(), new_uri))
3594 d.addCallback(lambda res:
3595 self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
3599 def test_PUT_NEWFILEURL_uri_no_replace(self):
3600 contents, n, new_uri = self.makefile(8)
3601 d = self.PUT(self.public_url + "/foo/bar.txt?t=uri&replace=false", new_uri)
3602 d.addBoth(self.shouldFail, error.Error,
3603 "PUT_NEWFILEURL_uri_no_replace",
3605 "There was already a child by that name, and you asked me "
3606 "to not replace it")
3609 def test_PUT_NEWFILEURL_uri_unknown_bad(self):
3610 d = self.PUT(self.public_url + "/foo/put-future.txt?t=uri", unknown_rwcap)
3611 d.addBoth(self.shouldFail, error.Error,
3612 "POST_put_uri_unknown_bad",
3614 "unknown cap in a write slot")
3617 def test_PUT_NEWFILEURL_uri_unknown_ro_good(self):
3618 d = self.PUT(self.public_url + "/foo/put-future-ro.txt?t=uri", unknown_rocap)
3619 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node,
3620 u"put-future-ro.txt")
3623 def test_PUT_NEWFILEURL_uri_unknown_imm_good(self):
3624 d = self.PUT(self.public_url + "/foo/put-future-imm.txt?t=uri", unknown_immcap)
3625 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node,
3626 u"put-future-imm.txt")
3629 def test_PUT_NEWFILE_URI(self):
3630 file_contents = "New file contents here\n"
3631 d = self.PUT("/uri", file_contents)
3633 assert isinstance(uri, str), uri
3634 self.failUnlessIn(uri, FakeCHKFileNode.all_contents)
3635 self.failUnlessReallyEqual(FakeCHKFileNode.all_contents[uri],
3637 return self.GET("/uri/%s" % uri)
3638 d.addCallback(_check)
3640 self.failUnlessReallyEqual(res, file_contents)
3641 d.addCallback(_check2)
3644 def test_PUT_NEWFILE_URI_not_mutable(self):
3645 file_contents = "New file contents here\n"
3646 d = self.PUT("/uri?mutable=false", file_contents)
3648 assert isinstance(uri, str), uri
3649 self.failUnlessIn(uri, FakeCHKFileNode.all_contents)
3650 self.failUnlessReallyEqual(FakeCHKFileNode.all_contents[uri],
3652 return self.GET("/uri/%s" % uri)
3653 d.addCallback(_check)
3655 self.failUnlessReallyEqual(res, file_contents)
3656 d.addCallback(_check2)
3659 def test_PUT_NEWFILE_URI_only_PUT(self):
3660 d = self.PUT("/uri?t=bogus", "")
3661 d.addBoth(self.shouldFail, error.Error,
3662 "PUT_NEWFILE_URI_only_PUT",
3664 "/uri accepts only PUT, PUT?t=mkdir, POST?t=upload, and POST?t=mkdir")
3667 def test_PUT_NEWFILE_URI_mutable(self):
3668 file_contents = "New file contents here\n"
3669 d = self.PUT("/uri?mutable=true", file_contents)
3670 def _check1(filecap):
3671 filecap = filecap.strip()
3672 self.failUnless(filecap.startswith("URI:SSK:"), filecap)
3673 self.filecap = filecap
3674 u = uri.WriteableSSKFileURI.init_from_string(filecap)
3675 self.failUnlessIn(u.get_storage_index(), FakeMutableFileNode.all_contents)
3676 n = self.s.create_node_from_uri(filecap)
3677 return n.download_best_version()
3678 d.addCallback(_check1)
3680 self.failUnlessReallyEqual(data, file_contents)
3681 return self.GET("/uri/%s" % urllib.quote(self.filecap))
3682 d.addCallback(_check2)
3684 self.failUnlessReallyEqual(res, file_contents)
3685 d.addCallback(_check3)
3688 def test_PUT_mkdir(self):
3689 d = self.PUT("/uri?t=mkdir", "")
3691 n = self.s.create_node_from_uri(uri.strip())
3692 d2 = self.failUnlessNodeKeysAre(n, [])
3693 d2.addCallback(lambda res:
3694 self.GET("/uri/%s?t=json" % uri))
3696 d.addCallback(_check)
3697 d.addCallback(self.failUnlessIsEmptyJSON)
3700 def test_PUT_mkdir_mdmf(self):
3701 d = self.PUT("/uri?t=mkdir&format=mdmf", "")
3703 u = uri.from_string(res)
3704 # Check that this is an MDMF writecap
3705 self.failUnlessIsInstance(u, uri.MDMFDirectoryURI)
3709 def test_PUT_mkdir_sdmf(self):
3710 d = self.PUT("/uri?t=mkdir&format=sdmf", "")
3712 u = uri.from_string(res)
3713 self.failUnlessIsInstance(u, uri.DirectoryURI)
3717 def test_PUT_mkdir_bad_format(self):
3718 return self.shouldHTTPError("PUT_mkdir_bad_format",
3719 400, "Bad Request", "Unknown format: foo",
3720 self.PUT, "/uri?t=mkdir&format=foo",
3723 def test_POST_check(self):
3724 d = self.POST(self.public_url + "/foo", t="check", name="bar.txt")
3726 # this returns a string form of the results, which are probably
3727 # None since we're using fake filenodes.
3728 # TODO: verify that the check actually happened, by changing
3729 # FakeCHKFileNode to count how many times .check() has been
3732 d.addCallback(_done)
3736 def test_PUT_update_at_offset(self):
3737 file_contents = "test file" * 100000 # about 900 KiB
3738 d = self.PUT("/uri?mutable=true", file_contents)
3740 self.filecap = filecap
3741 new_data = file_contents[:100]
3742 new = "replaced and so on"
3744 new_data += file_contents[len(new_data):]
3745 assert len(new_data) == len(file_contents)
3746 self.new_data = new_data
3747 d.addCallback(_then)
3748 d.addCallback(lambda ignored:
3749 self.PUT("/uri/%s?replace=True&offset=100" % self.filecap,
3750 "replaced and so on"))
3751 def _get_data(filecap):
3752 n = self.s.create_node_from_uri(filecap)
3753 return n.download_best_version()
3754 d.addCallback(_get_data)
3755 d.addCallback(lambda results:
3756 self.failUnlessEqual(results, self.new_data))
3757 # Now try appending things to the file
3758 d.addCallback(lambda ignored:
3759 self.PUT("/uri/%s?offset=%d" % (self.filecap, len(self.new_data)),
3761 d.addCallback(_get_data)
3762 d.addCallback(lambda results:
3763 self.failUnlessEqual(results, self.new_data + ("puppies" * 100)))
3764 # and try replacing the beginning of the file
3765 d.addCallback(lambda ignored:
3766 self.PUT("/uri/%s?offset=0" % self.filecap, "begin"))
3767 d.addCallback(_get_data)
3768 d.addCallback(lambda results:
3769 self.failUnlessEqual(results, "begin"+self.new_data[len("begin"):]+("puppies"*100)))
3772 def test_PUT_update_at_invalid_offset(self):
3773 file_contents = "test file" * 100000 # about 900 KiB
3774 d = self.PUT("/uri?mutable=true", file_contents)
3776 self.filecap = filecap
3777 d.addCallback(_then)
3778 # Negative offsets should cause an error.
3779 d.addCallback(lambda ignored:
3780 self.shouldHTTPError("PUT_update_at_invalid_offset",
3784 "/uri/%s?offset=-1" % self.filecap,
3788 def test_PUT_update_at_offset_immutable(self):
3789 file_contents = "Test file" * 100000
3790 d = self.PUT("/uri", file_contents)
3792 self.filecap = filecap
3793 d.addCallback(_then)
3794 d.addCallback(lambda ignored:
3795 self.shouldHTTPError("PUT_update_at_offset_immutable",
3799 "/uri/%s?offset=50" % self.filecap,
3804 def test_bad_method(self):
3805 url = self.webish_url + self.public_url + "/foo/bar.txt"
3806 d = self.shouldHTTPError("bad_method",
3807 501, "Not Implemented",
3808 "I don't know how to treat a BOGUS request.",
3809 client.getPage, url, method="BOGUS")
3812 def test_short_url(self):
3813 url = self.webish_url + "/uri"
3814 d = self.shouldHTTPError("short_url", 501, "Not Implemented",
3815 "I don't know how to treat a DELETE request.",
3816 client.getPage, url, method="DELETE")
3819 def test_ophandle_bad(self):
3820 url = self.webish_url + "/operations/bogus?t=status"
3821 d = self.shouldHTTPError("ophandle_bad", 404, "404 Not Found",
3822 "unknown/expired handle 'bogus'",
3823 client.getPage, url)
3826 def test_ophandle_cancel(self):
3827 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=128",
3828 followRedirect=True)
3829 d.addCallback(lambda ignored:
3830 self.GET("/operations/128?t=status&output=JSON"))
3832 data = simplejson.loads(res)
3833 self.failUnless("finished" in data, res)
3834 monitor = self.ws.root.child_operations.handles["128"][0]
3835 d = self.POST("/operations/128?t=cancel&output=JSON")
3837 data = simplejson.loads(res)
3838 self.failUnless("finished" in data, res)
3839 # t=cancel causes the handle to be forgotten
3840 self.failUnless(monitor.is_cancelled())
3841 d.addCallback(_check2)
3843 d.addCallback(_check1)
3844 d.addCallback(lambda ignored:
3845 self.shouldHTTPError("ophandle_cancel",
3846 404, "404 Not Found",
3847 "unknown/expired handle '128'",
3849 "/operations/128?t=status&output=JSON"))
3852 def test_ophandle_retainfor(self):
3853 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=129&retain-for=60",
3854 followRedirect=True)
3855 d.addCallback(lambda ignored:
3856 self.GET("/operations/129?t=status&output=JSON&retain-for=0"))
3858 data = simplejson.loads(res)
3859 self.failUnless("finished" in data, res)
3860 d.addCallback(_check1)
3861 # the retain-for=0 will cause the handle to be expired very soon
3862 d.addCallback(lambda ign:
3863 self.clock.advance(2.0))
3864 d.addCallback(lambda ignored:
3865 self.shouldHTTPError("ophandle_retainfor",
3866 404, "404 Not Found",
3867 "unknown/expired handle '129'",
3869 "/operations/129?t=status&output=JSON"))
3872 def test_ophandle_release_after_complete(self):
3873 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=130",
3874 followRedirect=True)
3875 d.addCallback(self.wait_for_operation, "130")
3876 d.addCallback(lambda ignored:
3877 self.GET("/operations/130?t=status&output=JSON&release-after-complete=true"))
3878 # the release-after-complete=true will cause the handle to be expired
3879 d.addCallback(lambda ignored:
3880 self.shouldHTTPError("ophandle_release_after_complete",
3881 404, "404 Not Found",
3882 "unknown/expired handle '130'",
3884 "/operations/130?t=status&output=JSON"))
3887 def test_uncollected_ophandle_expiration(self):
3888 # uncollected ophandles should expire after 4 days
3889 def _make_uncollected_ophandle(ophandle):
3890 d = self.POST(self.public_url +
3891 "/foo/?t=start-manifest&ophandle=%d" % ophandle,
3892 followRedirect=False)
3893 # When we start the operation, the webapi server will want
3894 # to redirect us to the page for the ophandle, so we get
3895 # confirmation that the operation has started. If the
3896 # manifest operation has finished by the time we get there,
3897 # following that redirect (by setting followRedirect=True
3898 # above) has the side effect of collecting the ophandle that
3899 # we've just created, which means that we can't use the
3900 # ophandle to test the uncollected timeout anymore. So,
3901 # instead, catch the 302 here and don't follow it.
3902 d.addBoth(self.should302, "uncollected_ophandle_creation")
3904 # Create an ophandle, don't collect it, then advance the clock by
3905 # 4 days - 1 second and make sure that the ophandle is still there.
3906 d = _make_uncollected_ophandle(131)
3907 d.addCallback(lambda ign:
3908 self.clock.advance((96*60*60) - 1)) # 96 hours = 4 days
3909 d.addCallback(lambda ign:
3910 self.GET("/operations/131?t=status&output=JSON"))
3912 data = simplejson.loads(res)
3913 self.failUnless("finished" in data, res)
3914 d.addCallback(_check1)
3915 # Create an ophandle, don't collect it, then try to collect it
3916 # after 4 days. It should be gone.
3917 d.addCallback(lambda ign:
3918 _make_uncollected_ophandle(132))
3919 d.addCallback(lambda ign:
3920 self.clock.advance(96*60*60))
3921 d.addCallback(lambda ign:
3922 self.shouldHTTPError("uncollected_ophandle_expired_after_100_hours",
3923 404, "404 Not Found",
3924 "unknown/expired handle '132'",
3926 "/operations/132?t=status&output=JSON"))
3929 def test_collected_ophandle_expiration(self):
3930 # collected ophandles should expire after 1 day
3931 def _make_collected_ophandle(ophandle):
3932 d = self.POST(self.public_url +
3933 "/foo/?t=start-manifest&ophandle=%d" % ophandle,
3934 followRedirect=True)
3935 # By following the initial redirect, we collect the ophandle
3936 # we've just created.
3938 # Create a collected ophandle, then collect it after 23 hours
3939 # and 59 seconds to make sure that it is still there.
3940 d = _make_collected_ophandle(133)
3941 d.addCallback(lambda ign:
3942 self.clock.advance((24*60*60) - 1))
3943 d.addCallback(lambda ign:
3944 self.GET("/operations/133?t=status&output=JSON"))
3946 data = simplejson.loads(res)
3947 self.failUnless("finished" in data, res)
3948 d.addCallback(_check1)
3949 # Create another uncollected ophandle, then try to collect it
3950 # after 24 hours to make sure that it is gone.
3951 d.addCallback(lambda ign:
3952 _make_collected_ophandle(134))
3953 d.addCallback(lambda ign:
3954 self.clock.advance(24*60*60))
3955 d.addCallback(lambda ign:
3956 self.shouldHTTPError("collected_ophandle_expired_after_1_day",
3957 404, "404 Not Found",
3958 "unknown/expired handle '134'",
3960 "/operations/134?t=status&output=JSON"))
3963 def test_incident(self):
3964 d = self.POST("/report_incident", details="eek")
3966 self.failIfIn("<html>", res)
3967 self.failUnlessIn("Thank you for your report!", res)
3968 d.addCallback(_done)
3971 def test_static(self):
3972 webdir = os.path.join(self.staticdir, "subdir")
3973 fileutil.make_dirs(webdir)
3974 f = open(os.path.join(webdir, "hello.txt"), "wb")
3978 d = self.GET("/static/subdir/hello.txt")
3980 self.failUnlessReallyEqual(res, "hello")
3981 d.addCallback(_check)
3985 class IntroducerWeb(unittest.TestCase):
3990 d = defer.succeed(None)
3992 d.addCallback(lambda ign: self.node.stopService())
3993 d.addCallback(flushEventualQueue)
3996 def test_welcome(self):
3997 basedir = "web.IntroducerWeb.test_welcome"
3999 fileutil.write(os.path.join(basedir, "tahoe.cfg"), "[node]\nweb.port = tcp:0\n")
4000 self.node = IntroducerNode(basedir)
4001 self.ws = self.node.getServiceNamed("webish")
4003 d = fireEventually(None)
4004 d.addCallback(lambda ign: self.node.startService())
4005 d.addCallback(lambda ign: self.node.when_tub_ready())
4007 d.addCallback(lambda ign: self.GET("/"))
4009 self.failUnlessIn('Welcome to the Tahoe-LAFS Introducer', res)
4010 self.failUnlessIn(FAVICON_MARKUP, res)
4011 d.addCallback(_check)
4014 def GET(self, urlpath, followRedirect=False, return_response=False,
4016 # if return_response=True, this fires with (data, statuscode,
4017 # respheaders) instead of just data.
4018 assert not isinstance(urlpath, unicode)
4019 url = self.ws.getURL().rstrip('/') + urlpath
4020 factory = HTTPClientGETFactory(url, method="GET",
4021 followRedirect=followRedirect, **kwargs)
4022 reactor.connectTCP("localhost", self.ws.getPortnum(), factory)
4023 d = factory.deferred
4024 def _got_data(data):
4025 return (data, factory.status, factory.response_headers)
4027 d.addCallback(_got_data)
4028 return factory.deferred
4031 class Util(ShouldFailMixin, testutil.ReallyEqualMixin, unittest.TestCase):
4032 def test_load_file(self):
4033 # This will raise an exception unless a well-formed XML file is found under that name.
4034 common.getxmlfile('directory.xhtml').load()
4036 def test_parse_replace_arg(self):
4037 self.failUnlessReallyEqual(common.parse_replace_arg("true"), True)
4038 self.failUnlessReallyEqual(common.parse_replace_arg("false"), False)
4039 self.failUnlessReallyEqual(common.parse_replace_arg("only-files"),
4041 self.shouldFail(AssertionError, "test_parse_replace_arg", "",
4042 common.parse_replace_arg, "only_fles")
4044 def test_abbreviate_time(self):
4045 self.failUnlessReallyEqual(common.abbreviate_time(None), "")
4046 self.failUnlessReallyEqual(common.abbreviate_time(1.234), "1.23s")
4047 self.failUnlessReallyEqual(common.abbreviate_time(0.123), "123ms")
4048 self.failUnlessReallyEqual(common.abbreviate_time(0.00123), "1.2ms")
4049 self.failUnlessReallyEqual(common.abbreviate_time(0.000123), "123us")
4050 self.failUnlessReallyEqual(common.abbreviate_time(-123000), "-123000000000us")
4052 def test_compute_rate(self):
4053 self.failUnlessReallyEqual(common.compute_rate(None, None), None)
4054 self.failUnlessReallyEqual(common.compute_rate(None, 1), None)
4055 self.failUnlessReallyEqual(common.compute_rate(250000, None), None)
4056 self.failUnlessReallyEqual(common.compute_rate(250000, 0), None)
4057 self.failUnlessReallyEqual(common.compute_rate(250000, 10), 25000.0)
4058 self.failUnlessReallyEqual(common.compute_rate(0, 10), 0.0)
4059 self.shouldFail(AssertionError, "test_compute_rate", "",
4060 common.compute_rate, -100, 10)
4061 self.shouldFail(AssertionError, "test_compute_rate", "",
4062 common.compute_rate, 100, -10)
4065 rate = common.compute_rate(10*1000*1000, 1)
4066 self.failUnlessReallyEqual(common.abbreviate_rate(rate), "10.00MBps")
4068 def test_abbreviate_rate(self):
4069 self.failUnlessReallyEqual(common.abbreviate_rate(None), "")
4070 self.failUnlessReallyEqual(common.abbreviate_rate(1234000), "1.23MBps")
4071 self.failUnlessReallyEqual(common.abbreviate_rate(12340), "12.3kBps")
4072 self.failUnlessReallyEqual(common.abbreviate_rate(123), "123Bps")
4074 def test_abbreviate_size(self):
4075 self.failUnlessReallyEqual(common.abbreviate_size(None), "")
4076 self.failUnlessReallyEqual(common.abbreviate_size(1.23*1000*1000*1000), "1.23GB")
4077 self.failUnlessReallyEqual(common.abbreviate_size(1.23*1000*1000), "1.23MB")
4078 self.failUnlessReallyEqual(common.abbreviate_size(1230), "1.2kB")
4079 self.failUnlessReallyEqual(common.abbreviate_size(123), "123B")
4081 def test_plural(self):
4083 return "%d second%s" % (s, status.plural(s))
4084 self.failUnlessReallyEqual(convert(0), "0 seconds")
4085 self.failUnlessReallyEqual(convert(1), "1 second")
4086 self.failUnlessReallyEqual(convert(2), "2 seconds")
4088 return "has share%s: %s" % (status.plural(s), ",".join(s))
4089 self.failUnlessReallyEqual(convert2([]), "has shares: ")
4090 self.failUnlessReallyEqual(convert2(["1"]), "has share: 1")
4091 self.failUnlessReallyEqual(convert2(["1","2"]), "has shares: 1,2")
4094 class Grid(GridTestMixin, WebErrorMixin, ShouldFailMixin, testutil.ReallyEqualMixin, unittest.TestCase):
4096 def CHECK(self, ign, which, args, clientnum=0):
4097 fileurl = self.fileurls[which]
4098 url = fileurl + "?" + args
4099 return self.GET(url, method="POST", clientnum=clientnum)
4101 def test_filecheck(self):
4102 self.basedir = "web/Grid/filecheck"
4104 c0 = self.g.clients[0]
4107 d = c0.upload(upload.Data(DATA, convergence=""))
4108 def _stash_uri(ur, which):
4109 self.uris[which] = ur.uri
4110 d.addCallback(_stash_uri, "good")
4111 d.addCallback(lambda ign:
4112 c0.upload(upload.Data(DATA+"1", convergence="")))
4113 d.addCallback(_stash_uri, "sick")
4114 d.addCallback(lambda ign:
4115 c0.upload(upload.Data(DATA+"2", convergence="")))
4116 d.addCallback(_stash_uri, "dead")
4117 def _stash_mutable_uri(n, which):
4118 self.uris[which] = n.get_uri()
4119 assert isinstance(self.uris[which], str)
4120 d.addCallback(lambda ign:
4121 c0.create_mutable_file(publish.MutableData(DATA+"3")))
4122 d.addCallback(_stash_mutable_uri, "corrupt")
4123 d.addCallback(lambda ign:
4124 c0.upload(upload.Data("literal", convergence="")))
4125 d.addCallback(_stash_uri, "small")
4126 d.addCallback(lambda ign: c0.create_immutable_dirnode({}))
4127 d.addCallback(_stash_mutable_uri, "smalldir")
4129 def _compute_fileurls(ignored):
4131 for which in self.uris:
4132 self.fileurls[which] = "uri/" + urllib.quote(self.uris[which])
4133 d.addCallback(_compute_fileurls)
4135 def _clobber_shares(ignored):
4136 good_shares = self.find_uri_shares(self.uris["good"])
4137 self.failUnlessReallyEqual(len(good_shares), 10)
4138 sick_shares = self.find_uri_shares(self.uris["sick"])
4139 os.unlink(sick_shares[0][2])
4140 dead_shares = self.find_uri_shares(self.uris["dead"])
4141 for i in range(1, 10):
4142 os.unlink(dead_shares[i][2])
4143 c_shares = self.find_uri_shares(self.uris["corrupt"])
4144 cso = CorruptShareOptions()
4145 cso.stdout = StringIO()
4146 cso.parseOptions([c_shares[0][2]])
4148 d.addCallback(_clobber_shares)
4150 d.addCallback(self.CHECK, "good", "t=check")
4151 def _got_html_good(res):
4152 self.failUnlessIn("Healthy", res)
4153 self.failIfIn("Not Healthy", res)
4154 self.failUnlessIn(FAVICON_MARKUP, res)
4155 d.addCallback(_got_html_good)
4156 d.addCallback(self.CHECK, "good", "t=check&return_to=somewhere")
4157 def _got_html_good_return_to(res):
4158 self.failUnlessIn("Healthy", res)
4159 self.failIfIn("Not Healthy", res)
4160 self.failUnlessIn('<a href="somewhere">Return to file', res)
4161 d.addCallback(_got_html_good_return_to)
4162 d.addCallback(self.CHECK, "good", "t=check&output=json")
4163 def _got_json_good(res):
4164 r = simplejson.loads(res)
4165 self.failUnlessEqual(r["summary"], "Healthy")
4166 self.failUnless(r["results"]["healthy"])
4167 self.failIf(r["results"]["needs-rebalancing"])
4168 self.failUnless(r["results"]["recoverable"])
4169 d.addCallback(_got_json_good)
4171 d.addCallback(self.CHECK, "small", "t=check")
4172 def _got_html_small(res):
4173 self.failUnlessIn("Literal files are always healthy", res)
4174 self.failIfIn("Not Healthy", res)
4175 d.addCallback(_got_html_small)
4176 d.addCallback(self.CHECK, "small", "t=check&return_to=somewhere")
4177 def _got_html_small_return_to(res):
4178 self.failUnlessIn("Literal files are always healthy", res)
4179 self.failIfIn("Not Healthy", res)
4180 self.failUnlessIn('<a href="somewhere">Return to file', res)
4181 d.addCallback(_got_html_small_return_to)
4182 d.addCallback(self.CHECK, "small", "t=check&output=json")
4183 def _got_json_small(res):
4184 r = simplejson.loads(res)
4185 self.failUnlessEqual(r["storage-index"], "")
4186 self.failUnless(r["results"]["healthy"])
4187 d.addCallback(_got_json_small)
4189 d.addCallback(self.CHECK, "smalldir", "t=check")
4190 def _got_html_smalldir(res):
4191 self.failUnlessIn("Literal files are always healthy", res)
4192 self.failIfIn("Not Healthy", res)
4193 d.addCallback(_got_html_smalldir)
4194 d.addCallback(self.CHECK, "smalldir", "t=check&output=json")
4195 def _got_json_smalldir(res):
4196 r = simplejson.loads(res)
4197 self.failUnlessEqual(r["storage-index"], "")
4198 self.failUnless(r["results"]["healthy"])
4199 d.addCallback(_got_json_smalldir)
4201 d.addCallback(self.CHECK, "sick", "t=check")
4202 def _got_html_sick(res):
4203 self.failUnlessIn("Not Healthy", res)
4204 d.addCallback(_got_html_sick)
4205 d.addCallback(self.CHECK, "sick", "t=check&output=json")
4206 def _got_json_sick(res):
4207 r = simplejson.loads(res)
4208 self.failUnlessEqual(r["summary"],
4209 "Not Healthy: 9 shares (enc 3-of-10)")
4210 self.failIf(r["results"]["healthy"])
4211 self.failIf(r["results"]["needs-rebalancing"])
4212 self.failUnless(r["results"]["recoverable"])
4213 d.addCallback(_got_json_sick)
4215 d.addCallback(self.CHECK, "dead", "t=check")
4216 def _got_html_dead(res):
4217 self.failUnlessIn("Not Healthy", res)
4218 d.addCallback(_got_html_dead)
4219 d.addCallback(self.CHECK, "dead", "t=check&output=json")
4220 def _got_json_dead(res):
4221 r = simplejson.loads(res)
4222 self.failUnlessEqual(r["summary"],
4223 "Not Healthy: 1 shares (enc 3-of-10)")
4224 self.failIf(r["results"]["healthy"])
4225 self.failIf(r["results"]["needs-rebalancing"])
4226 self.failIf(r["results"]["recoverable"])
4227 d.addCallback(_got_json_dead)
4229 d.addCallback(self.CHECK, "corrupt", "t=check&verify=true")
4230 def _got_html_corrupt(res):
4231 self.failUnlessIn("Not Healthy! : Unhealthy", res)
4232 d.addCallback(_got_html_corrupt)
4233 d.addCallback(self.CHECK, "corrupt", "t=check&verify=true&output=json")
4234 def _got_json_corrupt(res):
4235 r = simplejson.loads(res)
4236 self.failUnlessIn("Unhealthy: 9 shares (enc 3-of-10)", r["summary"])
4237 self.failIf(r["results"]["healthy"])
4238 self.failUnless(r["results"]["recoverable"])
4239 self.failUnlessReallyEqual(r["results"]["count-shares-good"], 9)
4240 self.failUnlessReallyEqual(r["results"]["count-corrupt-shares"], 1)
4241 d.addCallback(_got_json_corrupt)
4243 d.addErrback(self.explain_web_error)
4246 def test_repair_html(self):
4247 self.basedir = "web/Grid/repair_html"
4249 c0 = self.g.clients[0]
4252 d = c0.upload(upload.Data(DATA, convergence=""))
4253 def _stash_uri(ur, which):
4254 self.uris[which] = ur.uri
4255 d.addCallback(_stash_uri, "good")
4256 d.addCallback(lambda ign:
4257 c0.upload(upload.Data(DATA+"1", convergence="")))
4258 d.addCallback(_stash_uri, "sick")
4259 d.addCallback(lambda ign:
4260 c0.upload(upload.Data(DATA+"2", convergence="")))
4261 d.addCallback(_stash_uri, "dead")
4262 def _stash_mutable_uri(n, which):
4263 self.uris[which] = n.get_uri()
4264 assert isinstance(self.uris[which], str)
4265 d.addCallback(lambda ign:
4266 c0.create_mutable_file(publish.MutableData(DATA+"3")))
4267 d.addCallback(_stash_mutable_uri, "corrupt")
4269 def _compute_fileurls(ignored):
4271 for which in self.uris:
4272 self.fileurls[which] = "uri/" + urllib.quote(self.uris[which])
4273 d.addCallback(_compute_fileurls)
4275 def _clobber_shares(ignored):
4276 good_shares = self.find_uri_shares(self.uris["good"])
4277 self.failUnlessReallyEqual(len(good_shares), 10)
4278 sick_shares = self.find_uri_shares(self.uris["sick"])
4279 os.unlink(sick_shares[0][2])
4280 dead_shares = self.find_uri_shares(self.uris["dead"])
4281 for i in range(1, 10):
4282 os.unlink(dead_shares[i][2])
4283 c_shares = self.find_uri_shares(self.uris["corrupt"])
4284 cso = CorruptShareOptions()
4285 cso.stdout = StringIO()
4286 cso.parseOptions([c_shares[0][2]])
4288 d.addCallback(_clobber_shares)
4290 d.addCallback(self.CHECK, "good", "t=check&repair=true")
4291 def _got_html_good(res):
4292 self.failUnlessIn("Healthy", res)
4293 self.failIfIn("Not Healthy", res)
4294 self.failUnlessIn("No repair necessary", res)
4295 self.failUnlessIn(FAVICON_MARKUP, res)
4296 d.addCallback(_got_html_good)
4298 d.addCallback(self.CHECK, "sick", "t=check&repair=true")
4299 def _got_html_sick(res):
4300 self.failUnlessIn("Healthy : healthy", res)
4301 self.failIfIn("Not Healthy", res)
4302 self.failUnlessIn("Repair successful", res)
4303 d.addCallback(_got_html_sick)
4305 # repair of a dead file will fail, of course, but it isn't yet
4306 # clear how this should be reported. Right now it shows up as
4309 #d.addCallback(self.CHECK, "dead", "t=check&repair=true")
4310 #def _got_html_dead(res):
4312 # self.failUnlessIn("Healthy : healthy", res)
4313 # self.failIfIn("Not Healthy", res)
4314 # self.failUnlessIn("No repair necessary", res)
4315 #d.addCallback(_got_html_dead)
4317 d.addCallback(self.CHECK, "corrupt", "t=check&verify=true&repair=true")
4318 def _got_html_corrupt(res):
4319 self.failUnlessIn("Healthy : Healthy", res)
4320 self.failIfIn("Not Healthy", res)
4321 self.failUnlessIn("Repair successful", res)
4322 d.addCallback(_got_html_corrupt)
4324 d.addErrback(self.explain_web_error)
4327 def test_repair_json(self):
4328 self.basedir = "web/Grid/repair_json"
4330 c0 = self.g.clients[0]
4333 d = c0.upload(upload.Data(DATA+"1", convergence=""))
4334 def _stash_uri(ur, which):
4335 self.uris[which] = ur.uri
4336 d.addCallback(_stash_uri, "sick")
4338 def _compute_fileurls(ignored):
4340 for which in self.uris:
4341 self.fileurls[which] = "uri/" + urllib.quote(self.uris[which])
4342 d.addCallback(_compute_fileurls)
4344 def _clobber_shares(ignored):
4345 sick_shares = self.find_uri_shares(self.uris["sick"])
4346 os.unlink(sick_shares[0][2])
4347 d.addCallback(_clobber_shares)
4349 d.addCallback(self.CHECK, "sick", "t=check&repair=true&output=json")
4350 def _got_json_sick(res):
4351 r = simplejson.loads(res)
4352 self.failUnlessReallyEqual(r["repair-attempted"], True)
4353 self.failUnlessReallyEqual(r["repair-successful"], True)
4354 self.failUnlessEqual(r["pre-repair-results"]["summary"],
4355 "Not Healthy: 9 shares (enc 3-of-10)")
4356 self.failIf(r["pre-repair-results"]["results"]["healthy"])
4357 self.failUnlessEqual(r["post-repair-results"]["summary"], "healthy")
4358 self.failUnless(r["post-repair-results"]["results"]["healthy"])
4359 d.addCallback(_got_json_sick)
4361 d.addErrback(self.explain_web_error)
4364 def test_unknown(self, immutable=False):
4365 self.basedir = "web/Grid/unknown"
4367 self.basedir = "web/Grid/unknown-immutable"
4370 c0 = self.g.clients[0]
4374 # the future cap format may contain slashes, which must be tolerated
4375 expected_info_url = "uri/%s?t=info" % urllib.quote(unknown_rwcap,
4379 name = u"future-imm"
4380 future_node = UnknownNode(None, unknown_immcap, deep_immutable=True)
4381 d = c0.create_immutable_dirnode({name: (future_node, {})})
4384 future_node = UnknownNode(unknown_rwcap, unknown_rocap)
4385 d = c0.create_dirnode()
4387 def _stash_root_and_create_file(n):
4389 self.rooturl = "uri/" + urllib.quote(n.get_uri()) + "/"
4390 self.rourl = "uri/" + urllib.quote(n.get_readonly_uri()) + "/"
4392 return self.rootnode.set_node(name, future_node)
4393 d.addCallback(_stash_root_and_create_file)
4395 # make sure directory listing tolerates unknown nodes
4396 d.addCallback(lambda ign: self.GET(self.rooturl))
4397 def _check_directory_html(res, expected_type_suffix):
4398 pattern = re.compile(r'<td>\?%s</td>[ \t\n\r]*'
4399 '<td>%s</td>' % (expected_type_suffix, str(name)),
4401 self.failUnless(re.search(pattern, res), res)
4402 # find the More Info link for name, should be relative
4403 mo = re.search(r'<a href="([^"]+)">More Info</a>', res)
4404 info_url = mo.group(1)
4405 self.failUnlessReallyEqual(info_url, "%s?t=info" % (str(name),))
4407 d.addCallback(_check_directory_html, "-IMM")
4409 d.addCallback(_check_directory_html, "")
4411 d.addCallback(lambda ign: self.GET(self.rooturl+"?t=json"))
4412 def _check_directory_json(res, expect_rw_uri):
4413 data = simplejson.loads(res)
4414 self.failUnlessEqual(data[0], "dirnode")
4415 f = data[1]["children"][name]
4416 self.failUnlessEqual(f[0], "unknown")
4418 self.failUnlessReallyEqual(to_str(f[1]["rw_uri"]), unknown_rwcap, data)
4420 self.failIfIn("rw_uri", f[1])
4422 self.failUnlessReallyEqual(to_str(f[1]["ro_uri"]), unknown_immcap, data)
4424 self.failUnlessReallyEqual(to_str(f[1]["ro_uri"]), unknown_rocap, data)
4425 self.failUnlessIn("metadata", f[1])
4426 d.addCallback(_check_directory_json, expect_rw_uri=not immutable)
4428 def _check_info(res, expect_rw_uri, expect_ro_uri):
4429 self.failUnlessIn("Object Type: <span>unknown</span>", res)
4431 self.failUnlessIn(unknown_rwcap, res)
4434 self.failUnlessIn(unknown_immcap, res)
4436 self.failUnlessIn(unknown_rocap, res)
4438 self.failIfIn(unknown_rocap, res)
4439 self.failIfIn("Raw data as", res)
4440 self.failIfIn("Directory writecap", res)
4441 self.failIfIn("Checker Operations", res)
4442 self.failIfIn("Mutable File Operations", res)
4443 self.failIfIn("Directory Operations", res)
4445 # FIXME: these should have expect_rw_uri=not immutable; I don't know
4446 # why they fail. Possibly related to ticket #922.
4448 d.addCallback(lambda ign: self.GET(expected_info_url))
4449 d.addCallback(_check_info, expect_rw_uri=False, expect_ro_uri=False)
4450 d.addCallback(lambda ign: self.GET("%s%s?t=info" % (self.rooturl, str(name))))
4451 d.addCallback(_check_info, expect_rw_uri=False, expect_ro_uri=True)
4453 def _check_json(res, expect_rw_uri):
4454 data = simplejson.loads(res)
4455 self.failUnlessEqual(data[0], "unknown")
4457 self.failUnlessReallyEqual(to_str(data[1]["rw_uri"]), unknown_rwcap, data)
4459 self.failIfIn("rw_uri", data[1])
4462 self.failUnlessReallyEqual(to_str(data[1]["ro_uri"]), unknown_immcap, data)
4463 self.failUnlessReallyEqual(data[1]["mutable"], False)
4465 self.failUnlessReallyEqual(to_str(data[1]["ro_uri"]), unknown_rocap, data)
4466 self.failUnlessReallyEqual(data[1]["mutable"], True)
4468 self.failUnlessReallyEqual(to_str(data[1]["ro_uri"]), unknown_rocap, data)
4469 self.failIfIn("mutable", data[1])
4471 # TODO: check metadata contents
4472 self.failUnlessIn("metadata", data[1])
4474 d.addCallback(lambda ign: self.GET("%s%s?t=json" % (self.rooturl, str(name))))
4475 d.addCallback(_check_json, expect_rw_uri=not immutable)
4477 # and make sure that a read-only version of the directory can be
4478 # rendered too. This version will not have unknown_rwcap, whether
4479 # or not future_node was immutable.
4480 d.addCallback(lambda ign: self.GET(self.rourl))
4482 d.addCallback(_check_directory_html, "-IMM")
4484 d.addCallback(_check_directory_html, "-RO")
4486 d.addCallback(lambda ign: self.GET(self.rourl+"?t=json"))
4487 d.addCallback(_check_directory_json, expect_rw_uri=False)
4489 d.addCallback(lambda ign: self.GET("%s%s?t=json" % (self.rourl, str(name))))
4490 d.addCallback(_check_json, expect_rw_uri=False)
4492 # TODO: check that getting t=info from the Info link in the ro directory
4493 # works, and does not include the writecap URI.
4496 def test_immutable_unknown(self):
4497 return self.test_unknown(immutable=True)
4499 def test_mutant_dirnodes_are_omitted(self):
4500 self.basedir = "web/Grid/mutant_dirnodes_are_omitted"
4503 c = self.g.clients[0]
4508 lonely_uri = "URI:LIT:n5xgk" # LIT for "one"
4509 mut_write_uri = "URI:SSK:vfvcbdfbszyrsaxchgevhmmlii:euw4iw7bbnkrrwpzuburbhppuxhc3gwxv26f6imekhz7zyw2ojnq"
4510 mut_read_uri = "URI:SSK-RO:e3mdrzfwhoq42hy5ubcz6rp3o4:ybyibhnp3vvwuq2vaw2ckjmesgkklfs6ghxleztqidihjyofgw7q"
4512 # This method tests mainly dirnode, but we'd have to duplicate code in order to
4513 # test the dirnode and web layers separately.
4515 # 'lonely' is a valid LIT child, 'ro' is a mutant child with an SSK-RO readcap,
4516 # and 'write-in-ro' is a mutant child with an SSK writecap in the ro_uri field.
4517 # When the directory is read, the mutants should be silently disposed of, leaving
4518 # their lonely sibling.
4519 # We don't test the case of a retrieving a cap from the encrypted rw_uri field,
4520 # because immutable directories don't have a writecap and therefore that field
4521 # isn't (and can't be) decrypted.
4522 # TODO: The field still exists in the netstring. Technically we should check what
4523 # happens if something is put there (_unpack_contents should raise ValueError),
4524 # but that can wait.
4526 lonely_child = nm.create_from_cap(lonely_uri)
4527 mutant_ro_child = nm.create_from_cap(mut_read_uri)
4528 mutant_write_in_ro_child = nm.create_from_cap(mut_write_uri)
4530 def _by_hook_or_by_crook():
4532 for n in [mutant_ro_child, mutant_write_in_ro_child]:
4533 n.is_allowed_in_immutable_directory = _by_hook_or_by_crook
4535 mutant_write_in_ro_child.get_write_uri = lambda: None
4536 mutant_write_in_ro_child.get_readonly_uri = lambda: mut_write_uri
4538 kids = {u"lonely": (lonely_child, {}),
4539 u"ro": (mutant_ro_child, {}),
4540 u"write-in-ro": (mutant_write_in_ro_child, {}),
4542 d = c.create_immutable_dirnode(kids)
4545 self.failUnless(isinstance(dn, dirnode.DirectoryNode))
4546 self.failIf(dn.is_mutable())
4547 self.failUnless(dn.is_readonly())
4548 # This checks that if we somehow ended up calling dn._decrypt_rwcapdata, it would fail.
4549 self.failIf(hasattr(dn._node, 'get_writekey'))
4551 self.failUnlessIn("RO-IMM", rep)
4553 self.failUnlessIn("CHK", cap.to_string())
4556 self.rooturl = "uri/" + urllib.quote(dn.get_uri()) + "/"
4557 return download_to_data(dn._node)
4558 d.addCallback(_created)
4560 def _check_data(data):
4561 # Decode the netstring representation of the directory to check that all children
4562 # are present. This is a bit of an abstraction violation, but there's not really
4563 # any other way to do it given that the real DirectoryNode._unpack_contents would
4564 # strip the mutant children out (which is what we're trying to test, later).
4567 while position < len(data):
4568 entries, position = split_netstring(data, 1, position)
4570 (name_utf8, ro_uri, rwcapdata, metadata_s), subpos = split_netstring(entry, 4)
4571 name = name_utf8.decode("utf-8")
4572 self.failUnlessEqual(rwcapdata, "")
4573 self.failUnlessIn(name, kids)
4574 (expected_child, ign) = kids[name]
4575 self.failUnlessReallyEqual(ro_uri, expected_child.get_readonly_uri())
4578 self.failUnlessReallyEqual(numkids, 3)
4579 return self.rootnode.list()
4580 d.addCallback(_check_data)
4582 # Now when we use the real directory listing code, the mutants should be absent.
4583 def _check_kids(children):
4584 self.failUnlessReallyEqual(sorted(children.keys()), [u"lonely"])
4585 lonely_node, lonely_metadata = children[u"lonely"]
4587 self.failUnlessReallyEqual(lonely_node.get_write_uri(), None)
4588 self.failUnlessReallyEqual(lonely_node.get_readonly_uri(), lonely_uri)
4589 d.addCallback(_check_kids)
4591 d.addCallback(lambda ign: nm.create_from_cap(self.cap.to_string()))
4592 d.addCallback(lambda n: n.list())
4593 d.addCallback(_check_kids) # again with dirnode recreated from cap
4595 # Make sure the lonely child can be listed in HTML...
4596 d.addCallback(lambda ign: self.GET(self.rooturl))
4597 def _check_html(res):
4598 self.failIfIn("URI:SSK", res)
4599 get_lonely = "".join([r'<td>FILE</td>',
4601 r'<a href="[^"]+%s[^"]+">lonely</a>' % (urllib.quote(lonely_uri),),
4603 r'\s+<td align="right">%d</td>' % len("one"),
4605 self.failUnless(re.search(get_lonely, res), res)
4607 # find the More Info link for name, should be relative
4608 mo = re.search(r'<a href="([^"]+)">More Info</a>', res)
4609 info_url = mo.group(1)
4610 self.failUnless(info_url.endswith(urllib.quote(lonely_uri) + "?t=info"), info_url)
4611 d.addCallback(_check_html)
4614 d.addCallback(lambda ign: self.GET(self.rooturl+"?t=json"))
4615 def _check_json(res):
4616 data = simplejson.loads(res)
4617 self.failUnlessEqual(data[0], "dirnode")
4618 listed_children = data[1]["children"]
4619 self.failUnlessReallyEqual(sorted(listed_children.keys()), [u"lonely"])
4620 ll_type, ll_data = listed_children[u"lonely"]
4621 self.failUnlessEqual(ll_type, "filenode")
4622 self.failIfIn("rw_uri", ll_data)
4623 self.failUnlessReallyEqual(to_str(ll_data["ro_uri"]), lonely_uri)
4624 d.addCallback(_check_json)
4627 def test_deep_check(self):
4628 self.basedir = "web/Grid/deep_check"
4630 c0 = self.g.clients[0]
4634 d = c0.create_dirnode()
4635 def _stash_root_and_create_file(n):
4637 self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri()) + "/"
4638 return n.add_file(u"good", upload.Data(DATA, convergence=""))
4639 d.addCallback(_stash_root_and_create_file)
4640 def _stash_uri(fn, which):
4641 self.uris[which] = fn.get_uri()
4643 d.addCallback(_stash_uri, "good")
4644 d.addCallback(lambda ign:
4645 self.rootnode.add_file(u"small",
4646 upload.Data("literal",
4648 d.addCallback(_stash_uri, "small")
4649 d.addCallback(lambda ign:
4650 self.rootnode.add_file(u"sick",
4651 upload.Data(DATA+"1",
4653 d.addCallback(_stash_uri, "sick")
4655 # this tests that deep-check and stream-manifest will ignore
4656 # UnknownNode instances. Hopefully this will also cover deep-stats.
4657 future_node = UnknownNode(unknown_rwcap, unknown_rocap)
4658 d.addCallback(lambda ign: self.rootnode.set_node(u"future", future_node))
4660 def _clobber_shares(ignored):
4661 self.delete_shares_numbered(self.uris["sick"], [0,1])
4662 d.addCallback(_clobber_shares)
4670 d.addCallback(self.CHECK, "root", "t=stream-deep-check")
4673 units = [simplejson.loads(line)
4674 for line in res.splitlines()
4677 print "response is:", res
4678 print "undecodeable line was '%s'" % line
4680 self.failUnlessReallyEqual(len(units), 5+1)
4681 # should be parent-first
4683 self.failUnlessEqual(u0["path"], [])
4684 self.failUnlessEqual(u0["type"], "directory")
4685 self.failUnlessReallyEqual(to_str(u0["cap"]), self.rootnode.get_uri())
4686 u0cr = u0["check-results"]
4687 self.failUnlessReallyEqual(u0cr["results"]["count-shares-good"], 10)
4689 ugood = [u for u in units
4690 if u["type"] == "file" and u["path"] == [u"good"]][0]
4691 self.failUnlessReallyEqual(to_str(ugood["cap"]), self.uris["good"])
4692 ugoodcr = ugood["check-results"]
4693 self.failUnlessReallyEqual(ugoodcr["results"]["count-shares-good"], 10)
4696 self.failUnlessEqual(stats["type"], "stats")
4698 self.failUnlessReallyEqual(s["count-immutable-files"], 2)
4699 self.failUnlessReallyEqual(s["count-literal-files"], 1)
4700 self.failUnlessReallyEqual(s["count-directories"], 1)
4701 self.failUnlessReallyEqual(s["count-unknown"], 1)
4702 d.addCallback(_done)
4704 d.addCallback(self.CHECK, "root", "t=stream-manifest")
4705 def _check_manifest(res):
4706 self.failUnless(res.endswith("\n"))
4707 units = [simplejson.loads(t) for t in res[:-1].split("\n")]
4708 self.failUnlessReallyEqual(len(units), 5+1)
4709 self.failUnlessEqual(units[-1]["type"], "stats")
4711 self.failUnlessEqual(first["path"], [])
4712 self.failUnlessEqual(to_str(first["cap"]), self.rootnode.get_uri())
4713 self.failUnlessEqual(first["type"], "directory")
4714 stats = units[-1]["stats"]
4715 self.failUnlessReallyEqual(stats["count-immutable-files"], 2)
4716 self.failUnlessReallyEqual(stats["count-literal-files"], 1)
4717 self.failUnlessReallyEqual(stats["count-mutable-files"], 0)
4718 self.failUnlessReallyEqual(stats["count-immutable-files"], 2)
4719 self.failUnlessReallyEqual(stats["count-unknown"], 1)
4720 d.addCallback(_check_manifest)
4722 # now add root/subdir and root/subdir/grandchild, then make subdir
4723 # unrecoverable, then see what happens
4725 d.addCallback(lambda ign:
4726 self.rootnode.create_subdirectory(u"subdir"))
4727 d.addCallback(_stash_uri, "subdir")
4728 d.addCallback(lambda subdir_node:
4729 subdir_node.add_file(u"grandchild",
4730 upload.Data(DATA+"2",
4732 d.addCallback(_stash_uri, "grandchild")
4734 d.addCallback(lambda ign:
4735 self.delete_shares_numbered(self.uris["subdir"],
4743 # root/subdir [unrecoverable]
4744 # root/subdir/grandchild
4746 # how should a streaming-JSON API indicate fatal error?
4747 # answer: emit ERROR: instead of a JSON string
4749 d.addCallback(self.CHECK, "root", "t=stream-manifest")
4750 def _check_broken_manifest(res):
4751 lines = res.splitlines()
4753 for (i,line) in enumerate(lines)
4754 if line.startswith("ERROR:")]
4756 self.fail("no ERROR: in output: %s" % (res,))
4757 first_error = error_lines[0]
4758 error_line = lines[first_error]
4759 error_msg = lines[first_error+1:]
4760 error_msg_s = "\n".join(error_msg) + "\n"
4761 self.failUnlessIn("ERROR: UnrecoverableFileError(no recoverable versions)",
4763 self.failUnless(len(error_msg) > 2, error_msg_s) # some traceback
4764 units = [simplejson.loads(line) for line in lines[:first_error]]
4765 self.failUnlessReallyEqual(len(units), 6) # includes subdir
4766 last_unit = units[-1]
4767 self.failUnlessEqual(last_unit["path"], ["subdir"])
4768 d.addCallback(_check_broken_manifest)
4770 d.addCallback(self.CHECK, "root", "t=stream-deep-check")
4771 def _check_broken_deepcheck(res):
4772 lines = res.splitlines()
4774 for (i,line) in enumerate(lines)
4775 if line.startswith("ERROR:")]
4777 self.fail("no ERROR: in output: %s" % (res,))
4778 first_error = error_lines[0]
4779 error_line = lines[first_error]
4780 error_msg = lines[first_error+1:]
4781 error_msg_s = "\n".join(error_msg) + "\n"
4782 self.failUnlessIn("ERROR: UnrecoverableFileError(no recoverable versions)",
4784 self.failUnless(len(error_msg) > 2, error_msg_s) # some traceback
4785 units = [simplejson.loads(line) for line in lines[:first_error]]
4786 self.failUnlessReallyEqual(len(units), 6) # includes subdir
4787 last_unit = units[-1]
4788 self.failUnlessEqual(last_unit["path"], ["subdir"])
4789 r = last_unit["check-results"]["results"]
4790 self.failUnlessReallyEqual(r["count-recoverable-versions"], 0)
4791 self.failUnlessReallyEqual(r["count-shares-good"], 1)
4792 self.failUnlessReallyEqual(r["recoverable"], False)
4793 d.addCallback(_check_broken_deepcheck)
4795 d.addErrback(self.explain_web_error)
4798 def test_deep_check_and_repair(self):
4799 self.basedir = "web/Grid/deep_check_and_repair"
4801 c0 = self.g.clients[0]
4805 d = c0.create_dirnode()
4806 def _stash_root_and_create_file(n):
4808 self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri()) + "/"
4809 return n.add_file(u"good", upload.Data(DATA, convergence=""))
4810 d.addCallback(_stash_root_and_create_file)
4811 def _stash_uri(fn, which):
4812 self.uris[which] = fn.get_uri()
4813 d.addCallback(_stash_uri, "good")
4814 d.addCallback(lambda ign:
4815 self.rootnode.add_file(u"small",
4816 upload.Data("literal",
4818 d.addCallback(_stash_uri, "small")
4819 d.addCallback(lambda ign:
4820 self.rootnode.add_file(u"sick",
4821 upload.Data(DATA+"1",
4823 d.addCallback(_stash_uri, "sick")
4824 #d.addCallback(lambda ign:
4825 # self.rootnode.add_file(u"dead",
4826 # upload.Data(DATA+"2",
4828 #d.addCallback(_stash_uri, "dead")
4830 #d.addCallback(lambda ign: c0.create_mutable_file("mutable"))
4831 #d.addCallback(lambda fn: self.rootnode.set_node(u"corrupt", fn))
4832 #d.addCallback(_stash_uri, "corrupt")
4834 def _clobber_shares(ignored):
4835 good_shares = self.find_uri_shares(self.uris["good"])
4836 self.failUnlessReallyEqual(len(good_shares), 10)
4837 sick_shares = self.find_uri_shares(self.uris["sick"])
4838 os.unlink(sick_shares[0][2])
4839 #dead_shares = self.find_uri_shares(self.uris["dead"])
4840 #for i in range(1, 10):
4841 # os.unlink(dead_shares[i][2])
4843 #c_shares = self.find_uri_shares(self.uris["corrupt"])
4844 #cso = CorruptShareOptions()
4845 #cso.stdout = StringIO()
4846 #cso.parseOptions([c_shares[0][2]])
4848 d.addCallback(_clobber_shares)
4851 # root/good CHK, 10 shares
4853 # root/sick CHK, 9 shares
4855 d.addCallback(self.CHECK, "root", "t=stream-deep-check&repair=true")
4857 units = [simplejson.loads(line)
4858 for line in res.splitlines()
4860 self.failUnlessReallyEqual(len(units), 4+1)
4861 # should be parent-first
4863 self.failUnlessEqual(u0["path"], [])
4864 self.failUnlessEqual(u0["type"], "directory")
4865 self.failUnlessReallyEqual(to_str(u0["cap"]), self.rootnode.get_uri())
4866 u0crr = u0["check-and-repair-results"]
4867 self.failUnlessReallyEqual(u0crr["repair-attempted"], False)
4868 self.failUnlessReallyEqual(u0crr["pre-repair-results"]["results"]["count-shares-good"], 10)
4870 ugood = [u for u in units
4871 if u["type"] == "file" and u["path"] == [u"good"]][0]
4872 self.failUnlessEqual(to_str(ugood["cap"]), self.uris["good"])
4873 ugoodcrr = ugood["check-and-repair-results"]
4874 self.failUnlessReallyEqual(ugoodcrr["repair-attempted"], False)
4875 self.failUnlessReallyEqual(ugoodcrr["pre-repair-results"]["results"]["count-shares-good"], 10)
4877 usick = [u for u in units
4878 if u["type"] == "file" and u["path"] == [u"sick"]][0]
4879 self.failUnlessReallyEqual(to_str(usick["cap"]), self.uris["sick"])
4880 usickcrr = usick["check-and-repair-results"]
4881 self.failUnlessReallyEqual(usickcrr["repair-attempted"], True)
4882 self.failUnlessReallyEqual(usickcrr["repair-successful"], True)
4883 self.failUnlessReallyEqual(usickcrr["pre-repair-results"]["results"]["count-shares-good"], 9)
4884 self.failUnlessReallyEqual(usickcrr["post-repair-results"]["results"]["count-shares-good"], 10)
4887 self.failUnlessEqual(stats["type"], "stats")
4889 self.failUnlessReallyEqual(s["count-immutable-files"], 2)
4890 self.failUnlessReallyEqual(s["count-literal-files"], 1)
4891 self.failUnlessReallyEqual(s["count-directories"], 1)
4892 d.addCallback(_done)
4894 d.addErrback(self.explain_web_error)
4897 def _count_leases(self, ignored, which):
4898 u = self.uris[which]
4899 shares = self.find_uri_shares(u)
4901 for shnum, serverid, fn in shares:
4902 sf = get_share_file(fn)
4903 num_leases = len(list(sf.get_leases()))
4904 lease_counts.append( (fn, num_leases) )
4907 def _assert_leasecount(self, lease_counts, expected):
4908 for (fn, num_leases) in lease_counts:
4909 if num_leases != expected:
4910 self.fail("expected %d leases, have %d, on %s" %
4911 (expected, num_leases, fn))
4913 def test_add_lease(self):
4914 self.basedir = "web/Grid/add_lease"
4915 self.set_up_grid(num_clients=2)
4916 c0 = self.g.clients[0]
4919 d = c0.upload(upload.Data(DATA, convergence=""))
4920 def _stash_uri(ur, which):
4921 self.uris[which] = ur.uri
4922 d.addCallback(_stash_uri, "one")
4923 d.addCallback(lambda ign:
4924 c0.upload(upload.Data(DATA+"1", convergence="")))
4925 d.addCallback(_stash_uri, "two")
4926 def _stash_mutable_uri(n, which):
4927 self.uris[which] = n.get_uri()
4928 assert isinstance(self.uris[which], str)
4929 d.addCallback(lambda ign:
4930 c0.create_mutable_file(publish.MutableData(DATA+"2")))
4931 d.addCallback(_stash_mutable_uri, "mutable")
4933 def _compute_fileurls(ignored):
4935 for which in self.uris:
4936 self.fileurls[which] = "uri/" + urllib.quote(self.uris[which])
4937 d.addCallback(_compute_fileurls)
4939 d.addCallback(self._count_leases, "one")
4940 d.addCallback(self._assert_leasecount, 1)
4941 d.addCallback(self._count_leases, "two")
4942 d.addCallback(self._assert_leasecount, 1)
4943 d.addCallback(self._count_leases, "mutable")
4944 d.addCallback(self._assert_leasecount, 1)
4946 d.addCallback(self.CHECK, "one", "t=check") # no add-lease
4947 def _got_html_good(res):
4948 self.failUnlessIn("Healthy", res)
4949 self.failIfIn("Not Healthy", res)
4950 d.addCallback(_got_html_good)
4952 d.addCallback(self._count_leases, "one")
4953 d.addCallback(self._assert_leasecount, 1)
4954 d.addCallback(self._count_leases, "two")
4955 d.addCallback(self._assert_leasecount, 1)
4956 d.addCallback(self._count_leases, "mutable")
4957 d.addCallback(self._assert_leasecount, 1)
4959 # this CHECK uses the original client, which uses the same
4960 # lease-secrets, so it will just renew the original lease
4961 d.addCallback(self.CHECK, "one", "t=check&add-lease=true")
4962 d.addCallback(_got_html_good)
4964 d.addCallback(self._count_leases, "one")
4965 d.addCallback(self._assert_leasecount, 1)
4966 d.addCallback(self._count_leases, "two")
4967 d.addCallback(self._assert_leasecount, 1)
4968 d.addCallback(self._count_leases, "mutable")
4969 d.addCallback(self._assert_leasecount, 1)
4971 # this CHECK uses an alternate client, which adds a second lease
4972 d.addCallback(self.CHECK, "one", "t=check&add-lease=true", clientnum=1)
4973 d.addCallback(_got_html_good)
4975 d.addCallback(self._count_leases, "one")
4976 d.addCallback(self._assert_leasecount, 2)
4977 d.addCallback(self._count_leases, "two")
4978 d.addCallback(self._assert_leasecount, 1)
4979 d.addCallback(self._count_leases, "mutable")
4980 d.addCallback(self._assert_leasecount, 1)
4982 d.addCallback(self.CHECK, "mutable", "t=check&add-lease=true")
4983 d.addCallback(_got_html_good)
4985 d.addCallback(self._count_leases, "one")
4986 d.addCallback(self._assert_leasecount, 2)
4987 d.addCallback(self._count_leases, "two")
4988 d.addCallback(self._assert_leasecount, 1)
4989 d.addCallback(self._count_leases, "mutable")
4990 d.addCallback(self._assert_leasecount, 1)
4992 d.addCallback(self.CHECK, "mutable", "t=check&add-lease=true",
4994 d.addCallback(_got_html_good)
4996 d.addCallback(self._count_leases, "one")
4997 d.addCallback(self._assert_leasecount, 2)
4998 d.addCallback(self._count_leases, "two")
4999 d.addCallback(self._assert_leasecount, 1)
5000 d.addCallback(self._count_leases, "mutable")
5001 d.addCallback(self._assert_leasecount, 2)
5003 d.addErrback(self.explain_web_error)
5006 def test_deep_add_lease(self):
5007 self.basedir = "web/Grid/deep_add_lease"
5008 self.set_up_grid(num_clients=2)
5009 c0 = self.g.clients[0]
5013 d = c0.create_dirnode()
5014 def _stash_root_and_create_file(n):
5016 self.uris["root"] = n.get_uri()
5017 self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri()) + "/"
5018 return n.add_file(u"one", upload.Data(DATA, convergence=""))
5019 d.addCallback(_stash_root_and_create_file)
5020 def _stash_uri(fn, which):
5021 self.uris[which] = fn.get_uri()
5022 d.addCallback(_stash_uri, "one")
5023 d.addCallback(lambda ign:
5024 self.rootnode.add_file(u"small",
5025 upload.Data("literal",
5027 d.addCallback(_stash_uri, "small")
5029 d.addCallback(lambda ign:
5030 c0.create_mutable_file(publish.MutableData("mutable")))
5031 d.addCallback(lambda fn: self.rootnode.set_node(u"mutable", fn))
5032 d.addCallback(_stash_uri, "mutable")
5034 d.addCallback(self.CHECK, "root", "t=stream-deep-check") # no add-lease
5036 units = [simplejson.loads(line)
5037 for line in res.splitlines()
5039 # root, one, small, mutable, stats
5040 self.failUnlessReallyEqual(len(units), 4+1)
5041 d.addCallback(_done)
5043 d.addCallback(self._count_leases, "root")
5044 d.addCallback(self._assert_leasecount, 1)
5045 d.addCallback(self._count_leases, "one")
5046 d.addCallback(self._assert_leasecount, 1)
5047 d.addCallback(self._count_leases, "mutable")
5048 d.addCallback(self._assert_leasecount, 1)
5050 d.addCallback(self.CHECK, "root", "t=stream-deep-check&add-lease=true")
5051 d.addCallback(_done)
5053 d.addCallback(self._count_leases, "root")
5054 d.addCallback(self._assert_leasecount, 1)
5055 d.addCallback(self._count_leases, "one")
5056 d.addCallback(self._assert_leasecount, 1)
5057 d.addCallback(self._count_leases, "mutable")
5058 d.addCallback(self._assert_leasecount, 1)
5060 d.addCallback(self.CHECK, "root", "t=stream-deep-check&add-lease=true",
5062 d.addCallback(_done)
5064 d.addCallback(self._count_leases, "root")
5065 d.addCallback(self._assert_leasecount, 2)
5066 d.addCallback(self._count_leases, "one")
5067 d.addCallback(self._assert_leasecount, 2)
5068 d.addCallback(self._count_leases, "mutable")
5069 d.addCallback(self._assert_leasecount, 2)
5071 d.addErrback(self.explain_web_error)
5075 def test_exceptions(self):
5076 self.basedir = "web/Grid/exceptions"
5077 self.set_up_grid(num_clients=1, num_servers=2)
5078 c0 = self.g.clients[0]
5079 c0.DEFAULT_ENCODING_PARAMETERS['happy'] = 2
5082 d = c0.create_dirnode()
5084 self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri()) + "/"
5085 self.fileurls["imaginary"] = self.fileurls["root"] + "imaginary"
5087 d.addCallback(_stash_root)
5088 d.addCallback(lambda ign: c0.upload(upload.Data(DATA, convergence="")))
5090 self.fileurls["1share"] = "uri/" + urllib.quote(ur.uri)
5091 self.delete_shares_numbered(ur.uri, range(1,10))
5093 u = uri.from_string(ur.uri)
5094 u.key = testutil.flip_bit(u.key, 0)
5095 baduri = u.to_string()
5096 self.fileurls["0shares"] = "uri/" + urllib.quote(baduri)
5097 d.addCallback(_stash_bad)
5098 d.addCallback(lambda ign: c0.create_dirnode())
5099 def _mangle_dirnode_1share(n):
5101 url = self.fileurls["dir-1share"] = "uri/" + urllib.quote(u) + "/"
5102 self.fileurls["dir-1share-json"] = url + "?t=json"
5103 self.delete_shares_numbered(u, range(1,10))
5104 d.addCallback(_mangle_dirnode_1share)
5105 d.addCallback(lambda ign: c0.create_dirnode())
5106 def _mangle_dirnode_0share(n):
5108 url = self.fileurls["dir-0share"] = "uri/" + urllib.quote(u) + "/"
5109 self.fileurls["dir-0share-json"] = url + "?t=json"
5110 self.delete_shares_numbered(u, range(0,10))
5111 d.addCallback(_mangle_dirnode_0share)
5113 # NotEnoughSharesError should be reported sensibly, with a
5114 # text/plain explanation of the problem, and perhaps some
5115 # information on which shares *could* be found.
5117 d.addCallback(lambda ignored:
5118 self.shouldHTTPError("GET unrecoverable",
5119 410, "Gone", "NoSharesError",
5120 self.GET, self.fileurls["0shares"]))
5121 def _check_zero_shares(body):
5122 self.failIfIn("<html>", body)
5123 body = " ".join(body.strip().split())
5124 exp = ("NoSharesError: no shares could be found. "
5125 "Zero shares usually indicates a corrupt URI, or that "
5126 "no servers were connected, but it might also indicate "
5127 "severe corruption. You should perform a filecheck on "
5128 "this object to learn more. The full error message is: "
5129 "no shares (need 3). Last failure: None")
5130 self.failUnlessReallyEqual(exp, body)
5131 d.addCallback(_check_zero_shares)
5134 d.addCallback(lambda ignored:
5135 self.shouldHTTPError("GET 1share",
5136 410, "Gone", "NotEnoughSharesError",
5137 self.GET, self.fileurls["1share"]))
5138 def _check_one_share(body):
5139 self.failIfIn("<html>", body)
5140 body = " ".join(body.strip().split())
5141 msgbase = ("NotEnoughSharesError: This indicates that some "
5142 "servers were unavailable, or that shares have been "
5143 "lost to server departure, hard drive failure, or disk "
5144 "corruption. You should perform a filecheck on "
5145 "this object to learn more. The full error message is:"
5147 msg1 = msgbase + (" ran out of shares:"
5150 " overdue= unused= need 3. Last failure: None")
5151 msg2 = msgbase + (" ran out of shares:"
5153 " pending=Share(sh0-on-xgru5)"
5154 " overdue= unused= need 3. Last failure: None")
5155 self.failUnless(body == msg1 or body == msg2, body)
5156 d.addCallback(_check_one_share)
5158 d.addCallback(lambda ignored:
5159 self.shouldHTTPError("GET imaginary",
5160 404, "Not Found", None,
5161 self.GET, self.fileurls["imaginary"]))
5162 def _missing_child(body):
5163 self.failUnlessIn("No such child: imaginary", body)
5164 d.addCallback(_missing_child)
5166 d.addCallback(lambda ignored: self.GET(self.fileurls["dir-0share"]))
5167 def _check_0shares_dir_html(body):
5168 self.failUnlessIn("<html>", body)
5169 # we should see the regular page, but without the child table or
5171 body = " ".join(body.strip().split())
5172 self.failUnlessIn('href="?t=info">More info on this directory',
5174 exp = ("UnrecoverableFileError: the directory (or mutable file) "
5175 "could not be retrieved, because there were insufficient "
5176 "good shares. This might indicate that no servers were "
5177 "connected, insufficient servers were connected, the URI "
5178 "was corrupt, or that shares have been lost due to server "
5179 "departure, hard drive failure, or disk corruption. You "
5180 "should perform a filecheck on this object to learn more.")
5181 self.failUnlessIn(exp, body)
5182 self.failUnlessIn("No upload forms: directory is unreadable", body)
5183 d.addCallback(_check_0shares_dir_html)
5185 d.addCallback(lambda ignored: self.GET(self.fileurls["dir-1share"]))
5186 def _check_1shares_dir_html(body):
5187 # at some point, we'll split UnrecoverableFileError into 0-shares
5188 # and some-shares like we did for immutable files (since there
5189 # are different sorts of advice to offer in each case). For now,
5190 # they present the same way.
5191 self.failUnlessIn("<html>", body)
5192 body = " ".join(body.strip().split())
5193 self.failUnlessIn('href="?t=info">More info on this directory',
5195 exp = ("UnrecoverableFileError: the directory (or mutable file) "
5196 "could not be retrieved, because there were insufficient "
5197 "good shares. This might indicate that no servers were "
5198 "connected, insufficient servers were connected, the URI "
5199 "was corrupt, or that shares have been lost due to server "
5200 "departure, hard drive failure, or disk corruption. You "
5201 "should perform a filecheck on this object to learn more.")
5202 self.failUnlessIn(exp, body)
5203 self.failUnlessIn("No upload forms: directory is unreadable", body)
5204 d.addCallback(_check_1shares_dir_html)
5206 d.addCallback(lambda ignored:
5207 self.shouldHTTPError("GET dir-0share-json",
5208 410, "Gone", "UnrecoverableFileError",
5210 self.fileurls["dir-0share-json"]))
5211 def _check_unrecoverable_file(body):
5212 self.failIfIn("<html>", body)
5213 body = " ".join(body.strip().split())
5214 exp = ("UnrecoverableFileError: the directory (or mutable file) "
5215 "could not be retrieved, because there were insufficient "
5216 "good shares. This might indicate that no servers were "
5217 "connected, insufficient servers were connected, the URI "
5218 "was corrupt, or that shares have been lost due to server "
5219 "departure, hard drive failure, or disk corruption. You "
5220 "should perform a filecheck on this object to learn more.")
5221 self.failUnlessReallyEqual(exp, body)
5222 d.addCallback(_check_unrecoverable_file)
5224 d.addCallback(lambda ignored:
5225 self.shouldHTTPError("GET dir-1share-json",
5226 410, "Gone", "UnrecoverableFileError",
5228 self.fileurls["dir-1share-json"]))
5229 d.addCallback(_check_unrecoverable_file)
5231 d.addCallback(lambda ignored:
5232 self.shouldHTTPError("GET imaginary",
5233 404, "Not Found", None,
5234 self.GET, self.fileurls["imaginary"]))
5236 # attach a webapi child that throws a random error, to test how it
5238 w = c0.getServiceNamed("webish")
5239 w.root.putChild("ERRORBOOM", ErrorBoom())
5241 # "Accept: */*" : should get a text/html stack trace
5242 # "Accept: text/plain" : should get a text/plain stack trace
5243 # "Accept: text/plain, application/octet-stream" : text/plain (CLI)
5244 # no Accept header: should get a text/html stack trace
5246 d.addCallback(lambda ignored:
5247 self.shouldHTTPError("GET errorboom_html",
5248 500, "Internal Server Error", None,
5249 self.GET, "ERRORBOOM",
5250 headers={"accept": ["*/*"]}))
5251 def _internal_error_html1(body):
5252 self.failUnlessIn("<html>", "expected HTML, not '%s'" % body)
5253 d.addCallback(_internal_error_html1)
5255 d.addCallback(lambda ignored:
5256 self.shouldHTTPError("GET errorboom_text",
5257 500, "Internal Server Error", None,
5258 self.GET, "ERRORBOOM",
5259 headers={"accept": ["text/plain"]}))
5260 def _internal_error_text2(body):
5261 self.failIfIn("<html>", body)
5262 self.failUnless(body.startswith("Traceback "), body)
5263 d.addCallback(_internal_error_text2)
5265 CLI_accepts = "text/plain, application/octet-stream"
5266 d.addCallback(lambda ignored:
5267 self.shouldHTTPError("GET errorboom_text",
5268 500, "Internal Server Error", None,
5269 self.GET, "ERRORBOOM",
5270 headers={"accept": [CLI_accepts]}))
5271 def _internal_error_text3(body):
5272 self.failIfIn("<html>", body)
5273 self.failUnless(body.startswith("Traceback "), body)
5274 d.addCallback(_internal_error_text3)
5276 d.addCallback(lambda ignored:
5277 self.shouldHTTPError("GET errorboom_text",
5278 500, "Internal Server Error", None,
5279 self.GET, "ERRORBOOM"))
5280 def _internal_error_html4(body):
5281 self.failUnlessIn("<html>", body)
5282 d.addCallback(_internal_error_html4)
5284 def _flush_errors(res):
5285 # Trial: please ignore the CompletelyUnhandledError in the logs
5286 self.flushLoggedErrors(CompletelyUnhandledError)
5288 d.addBoth(_flush_errors)
5292 def test_blacklist(self):
5293 # download from a blacklisted URI, get an error
5294 self.basedir = "web/Grid/blacklist"
5296 c0 = self.g.clients[0]
5297 c0_basedir = c0.basedir
5298 fn = os.path.join(c0_basedir, "access.blacklist")
5300 DATA = "off-limits " * 50
5302 d = c0.upload(upload.Data(DATA, convergence=""))
5303 def _stash_uri_and_create_dir(ur):
5305 self.url = "uri/"+self.uri
5306 u = uri.from_string_filenode(self.uri)
5307 self.si = u.get_storage_index()
5308 childnode = c0.create_node_from_uri(self.uri, None)
5309 return c0.create_dirnode({u"blacklisted.txt": (childnode,{}) })
5310 d.addCallback(_stash_uri_and_create_dir)
5311 def _stash_dir(node):
5312 self.dir_node = node
5313 self.dir_uri = node.get_uri()
5314 self.dir_url = "uri/"+self.dir_uri
5315 d.addCallback(_stash_dir)
5316 d.addCallback(lambda ign: self.GET(self.dir_url, followRedirect=True))
5317 def _check_dir_html(body):
5318 self.failUnlessIn("<html>", body)
5319 self.failUnlessIn("blacklisted.txt</a>", body)
5320 d.addCallback(_check_dir_html)
5321 d.addCallback(lambda ign: self.GET(self.url))
5322 d.addCallback(lambda body: self.failUnlessEqual(DATA, body))
5324 def _blacklist(ign):
5326 f.write(" # this is a comment\n")
5328 f.write("\n") # also exercise blank lines
5329 f.write("%s %s\n" % (base32.b2a(self.si), "off-limits to you"))
5331 # clients should be checking the blacklist each time, so we don't
5332 # need to restart the client
5333 d.addCallback(_blacklist)
5334 d.addCallback(lambda ign: self.shouldHTTPError("get_from_blacklisted_uri",
5336 "Access Prohibited: off-limits",
5337 self.GET, self.url))
5339 # We should still be able to list the parent directory, in HTML...
5340 d.addCallback(lambda ign: self.GET(self.dir_url, followRedirect=True))
5341 def _check_dir_html2(body):
5342 self.failUnlessIn("<html>", body)
5343 self.failUnlessIn("blacklisted.txt</strike>", body)
5344 d.addCallback(_check_dir_html2)
5346 # ... and in JSON (used by CLI).
5347 d.addCallback(lambda ign: self.GET(self.dir_url+"?t=json", followRedirect=True))
5348 def _check_dir_json(res):
5349 data = simplejson.loads(res)
5350 self.failUnless(isinstance(data, list), data)
5351 self.failUnlessEqual(data[0], "dirnode")
5352 self.failUnless(isinstance(data[1], dict), data)
5353 self.failUnlessIn("children", data[1])
5354 self.failUnlessIn("blacklisted.txt", data[1]["children"])
5355 childdata = data[1]["children"]["blacklisted.txt"]
5356 self.failUnless(isinstance(childdata, list), data)
5357 self.failUnlessEqual(childdata[0], "filenode")
5358 self.failUnless(isinstance(childdata[1], dict), data)
5359 d.addCallback(_check_dir_json)
5361 def _unblacklist(ign):
5362 open(fn, "w").close()
5363 # the Blacklist object watches mtime to tell when the file has
5364 # changed, but on windows this test will run faster than the
5365 # filesystem's mtime resolution. So we edit Blacklist.last_mtime
5366 # to force a reload.
5367 self.g.clients[0].blacklist.last_mtime -= 2.0
5368 d.addCallback(_unblacklist)
5370 # now a read should work
5371 d.addCallback(lambda ign: self.GET(self.url))
5372 d.addCallback(lambda body: self.failUnlessEqual(DATA, body))
5374 # read again to exercise the blacklist-is-unchanged logic
5375 d.addCallback(lambda ign: self.GET(self.url))
5376 d.addCallback(lambda body: self.failUnlessEqual(DATA, body))
5378 # now add a blacklisted directory, and make sure files under it are
5381 childnode = c0.create_node_from_uri(self.uri, None)
5382 return c0.create_dirnode({u"child": (childnode,{}) })
5383 d.addCallback(_add_dir)
5384 def _get_dircap(dn):
5385 self.dir_si_b32 = base32.b2a(dn.get_storage_index())
5386 self.dir_url_base = "uri/"+dn.get_write_uri()
5387 self.dir_url_json1 = "uri/"+dn.get_write_uri()+"?t=json"
5388 self.dir_url_json2 = "uri/"+dn.get_write_uri()+"/?t=json"
5389 self.dir_url_json_ro = "uri/"+dn.get_readonly_uri()+"/?t=json"
5390 self.child_url = "uri/"+dn.get_readonly_uri()+"/child"
5391 d.addCallback(_get_dircap)
5392 d.addCallback(lambda ign: self.GET(self.dir_url_base, followRedirect=True))
5393 d.addCallback(lambda body: self.failUnlessIn("<html>", body))
5394 d.addCallback(lambda ign: self.GET(self.dir_url_json1))
5395 d.addCallback(lambda res: simplejson.loads(res)) # just check it decodes
5396 d.addCallback(lambda ign: self.GET(self.dir_url_json2))
5397 d.addCallback(lambda res: simplejson.loads(res)) # just check it decodes
5398 d.addCallback(lambda ign: self.GET(self.dir_url_json_ro))
5399 d.addCallback(lambda res: simplejson.loads(res)) # just check it decodes
5400 d.addCallback(lambda ign: self.GET(self.child_url))
5401 d.addCallback(lambda body: self.failUnlessEqual(DATA, body))
5403 def _block_dir(ign):
5405 f.write("%s %s\n" % (self.dir_si_b32, "dir-off-limits to you"))
5407 self.g.clients[0].blacklist.last_mtime -= 2.0
5408 d.addCallback(_block_dir)
5409 d.addCallback(lambda ign: self.shouldHTTPError("get_from_blacklisted_dir base",
5411 "Access Prohibited: dir-off-limits",
5412 self.GET, self.dir_url_base))
5413 d.addCallback(lambda ign: self.shouldHTTPError("get_from_blacklisted_dir json1",
5415 "Access Prohibited: dir-off-limits",
5416 self.GET, self.dir_url_json1))
5417 d.addCallback(lambda ign: self.shouldHTTPError("get_from_blacklisted_dir json2",
5419 "Access Prohibited: dir-off-limits",
5420 self.GET, self.dir_url_json2))
5421 d.addCallback(lambda ign: self.shouldHTTPError("get_from_blacklisted_dir json_ro",
5423 "Access Prohibited: dir-off-limits",
5424 self.GET, self.dir_url_json_ro))
5425 d.addCallback(lambda ign: self.shouldHTTPError("get_from_blacklisted_dir child",
5427 "Access Prohibited: dir-off-limits",
5428 self.GET, self.child_url))
5432 class CompletelyUnhandledError(Exception):
5434 class ErrorBoom(rend.Page):
5435 def beforeRender(self, ctx):
5436 raise CompletelyUnhandledError("whoops")