1 import os.path, re, urllib, time
3 from StringIO import StringIO
4 from twisted.application import service
5 from twisted.trial import unittest
6 from twisted.internet import defer, reactor
7 from twisted.internet.task import Clock
8 from twisted.web import client, error, http
9 from twisted.python import failure, log
10 from nevow import rend
11 from allmydata import interfaces, uri, webish, dirnode
12 from allmydata.storage.shares import get_share_file
13 from allmydata.storage_client import StorageFarmBroker
14 from allmydata.immutable import upload
15 from allmydata.immutable.downloader.status import DownloadStatus
16 from allmydata.dirnode import DirectoryNode
17 from allmydata.nodemaker import NodeMaker
18 from allmydata.unknown import UnknownNode
19 from allmydata.web import status, common
20 from allmydata.scripts.debug import CorruptShareOptions, corrupt_share
21 from allmydata.util import fileutil, base32, hashutil
22 from allmydata.util.consumer import download_to_data
23 from allmydata.util.netstring import split_netstring
24 from allmydata.util.encodingutil import to_str
25 from allmydata.test.common import FakeCHKFileNode, FakeMutableFileNode, \
26 create_chk_filenode, WebErrorMixin, ShouldFailMixin, \
27 make_mutable_file_uri, create_mutable_filenode
28 from allmydata.interfaces import IMutableFileNode, SDMF_VERSION, MDMF_VERSION
29 from allmydata.mutable import servermap, publish, retrieve
30 import allmydata.test.common_util as testutil
31 from allmydata.test.no_network import GridTestMixin
32 from allmydata.test.common_web import HTTPClientGETFactory, \
34 from allmydata.client import Client, SecretHolder
36 # create a fake uploader/downloader, and a couple of fake dirnodes, then
37 # create a webserver that works against them
39 timeout = 480 # Most of these take longer than 240 seconds on Francois's arm box.
41 unknown_rwcap = u"lafs://from_the_future_rw_\u263A".encode('utf-8')
42 unknown_rocap = u"ro.lafs://readonly_from_the_future_ro_\u263A".encode('utf-8')
43 unknown_immcap = u"imm.lafs://immutable_from_the_future_imm_\u263A".encode('utf-8')
45 class FakeStatsProvider:
47 stats = {'stats': {}, 'counters': {}}
50 class FakeNodeMaker(NodeMaker):
55 'max_segment_size':128*1024 # 1024=KiB
57 def _create_lit(self, cap):
58 return FakeCHKFileNode(cap)
59 def _create_immutable(self, cap):
60 return FakeCHKFileNode(cap)
61 def _create_mutable(self, cap):
62 return FakeMutableFileNode(None,
64 self.encoding_params, None).init_from_cap(cap)
65 def create_mutable_file(self, contents="", keysize=None,
66 version=SDMF_VERSION):
67 n = FakeMutableFileNode(None, None, self.encoding_params, None)
68 return n.create(contents, version=version)
70 class FakeUploader(service.Service):
72 def upload(self, uploadable, history=None):
73 d = uploadable.get_size()
74 d.addCallback(lambda size: uploadable.read(size))
77 n = create_chk_filenode(data)
78 results = upload.UploadResults()
79 results.uri = n.get_uri()
81 d.addCallback(_got_data)
83 def get_helper_info(self):
87 def __init__(self, binaryserverid):
88 self.binaryserverid = binaryserverid
89 def get_name(self): return "short"
90 def get_longname(self): return "long"
91 def get_serverid(self): return self.binaryserverid
94 ds = DownloadStatus("storage_index", 1234)
97 serverA = FakeIServer(hashutil.tagged_hash("foo", "serverid_a")[:20])
98 serverB = FakeIServer(hashutil.tagged_hash("foo", "serverid_b")[:20])
99 storage_index = hashutil.storage_index_hash("SI")
100 e0 = ds.add_segment_request(0, now)
102 e0.deliver(now+1, 0, 100, 0.5) # when, start,len, decodetime
103 e1 = ds.add_segment_request(1, now+2)
105 # two outstanding requests
106 e2 = ds.add_segment_request(2, now+4)
107 e3 = ds.add_segment_request(3, now+5)
108 del e2,e3 # hush pyflakes
110 # simulate a segment which gets delivered faster than a system clock tick (ticket #1166)
111 e = ds.add_segment_request(4, now)
113 e.deliver(now, 0, 140, 0.5)
115 e = ds.add_dyhb_request(serverA, now)
116 e.finished([1,2], now+1)
117 e = ds.add_dyhb_request(serverB, now+2) # left unfinished
119 e = ds.add_read_event(0, 120, now)
120 e.update(60, 0.5, 0.1) # bytes, decrypttime, pausetime
122 e = ds.add_read_event(120, 30, now+2) # left unfinished
124 e = ds.add_block_request(serverA, 1, 100, 20, now)
125 e.finished(20, now+1)
126 e = ds.add_block_request(serverB, 1, 120, 30, now+1) # left unfinished
128 # make sure that add_read_event() can come first too
129 ds1 = DownloadStatus(storage_index, 1234)
130 e = ds1.add_read_event(0, 120, now)
131 e.update(60, 0.5, 0.1) # bytes, decrypttime, pausetime
137 _all_upload_status = [upload.UploadStatus()]
138 _all_download_status = [build_one_ds()]
139 _all_mapupdate_statuses = [servermap.UpdateStatus()]
140 _all_publish_statuses = [publish.PublishStatus()]
141 _all_retrieve_statuses = [retrieve.RetrieveStatus()]
143 def list_all_upload_statuses(self):
144 return self._all_upload_status
145 def list_all_download_statuses(self):
146 return self._all_download_status
147 def list_all_mapupdate_statuses(self):
148 return self._all_mapupdate_statuses
149 def list_all_publish_statuses(self):
150 return self._all_publish_statuses
151 def list_all_retrieve_statuses(self):
152 return self._all_retrieve_statuses
153 def list_all_helper_statuses(self):
156 class FakeClient(Client):
158 # don't upcall to Client.__init__, since we only want to initialize a
160 service.MultiService.__init__(self)
161 self.nodeid = "fake_nodeid"
162 self.nickname = "fake_nickname"
163 self.introducer_furl = "None"
164 self.stats_provider = FakeStatsProvider()
165 self._secret_holder = SecretHolder("lease secret", "convergence secret")
167 self.convergence = "some random string"
168 self.storage_broker = StorageFarmBroker(None, permute_peers=True)
169 self.introducer_client = None
170 self.history = FakeHistory()
171 self.uploader = FakeUploader()
172 self.uploader.setServiceParent(self)
173 self.nodemaker = FakeNodeMaker(None, self._secret_holder, None,
176 self.mutable_file_default = SDMF_VERSION
178 def startService(self):
179 return service.MultiService.startService(self)
180 def stopService(self):
181 return service.MultiService.stopService(self)
183 MUTABLE_SIZELIMIT = FakeMutableFileNode.MUTABLE_SIZELIMIT
185 class WebMixin(object):
187 self.s = FakeClient()
188 self.s.startService()
189 self.staticdir = self.mktemp()
191 self.ws = webish.WebishServer(self.s, "0", staticdir=self.staticdir,
193 self.ws.setServiceParent(self.s)
194 self.webish_port = self.ws.getPortnum()
195 self.webish_url = self.ws.getURL()
196 assert self.webish_url.endswith("/")
197 self.webish_url = self.webish_url[:-1] # these tests add their own /
199 l = [ self.s.create_dirnode() for x in range(6) ]
200 d = defer.DeferredList(l)
202 self.public_root = res[0][1]
203 assert interfaces.IDirectoryNode.providedBy(self.public_root), res
204 self.public_url = "/uri/" + self.public_root.get_uri()
205 self.private_root = res[1][1]
209 self._foo_uri = foo.get_uri()
210 self._foo_readonly_uri = foo.get_readonly_uri()
211 self._foo_verifycap = foo.get_verify_cap().to_string()
212 # NOTE: we ignore the deferred on all set_uri() calls, because we
213 # know the fake nodes do these synchronously
214 self.public_root.set_uri(u"foo", foo.get_uri(),
215 foo.get_readonly_uri())
217 self.BAR_CONTENTS, n, self._bar_txt_uri = self.makefile(0)
218 foo.set_uri(u"bar.txt", self._bar_txt_uri, self._bar_txt_uri)
219 self._bar_txt_verifycap = n.get_verify_cap().to_string()
222 # XXX: Do we ever use this?
223 self.BAZ_CONTENTS, n, self._baz_txt_uri, self._baz_txt_readonly_uri = self.makefile_mutable(0)
225 foo.set_uri(u"baz.txt", self._baz_txt_uri, self._baz_txt_readonly_uri)
228 self.QUUX_CONTENTS, n, self._quux_txt_uri, self._quux_txt_readonly_uri = self.makefile_mutable(0, mdmf=True)
229 assert self._quux_txt_uri.startswith("URI:MDMF")
230 foo.set_uri(u"quux.txt", self._quux_txt_uri, self._quux_txt_readonly_uri)
232 foo.set_uri(u"empty", res[3][1].get_uri(),
233 res[3][1].get_readonly_uri())
234 sub_uri = res[4][1].get_uri()
235 self._sub_uri = sub_uri
236 foo.set_uri(u"sub", sub_uri, sub_uri)
237 sub = self.s.create_node_from_uri(sub_uri)
239 _ign, n, blocking_uri = self.makefile(1)
240 foo.set_uri(u"blockingfile", blocking_uri, blocking_uri)
242 unicode_filename = u"n\u00fc.txt" # n u-umlaut . t x t
243 # ok, unicode calls it LATIN SMALL LETTER U WITH DIAERESIS but I
244 # still think of it as an umlaut
245 foo.set_uri(unicode_filename, self._bar_txt_uri, self._bar_txt_uri)
247 _ign, n, baz_file = self.makefile(2)
248 self._baz_file_uri = baz_file
249 sub.set_uri(u"baz.txt", baz_file, baz_file)
251 _ign, n, self._bad_file_uri = self.makefile(3)
252 # this uri should not be downloadable
253 del FakeCHKFileNode.all_contents[self._bad_file_uri]
256 self.public_root.set_uri(u"reedownlee", rodir.get_readonly_uri(),
257 rodir.get_readonly_uri())
258 rodir.set_uri(u"nor", baz_file, baz_file)
264 # public/foo/quux.txt
265 # public/foo/blockingfile
268 # public/foo/sub/baz.txt
270 # public/reedownlee/nor
271 self.NEWFILE_CONTENTS = "newfile contents\n"
273 return foo.get_metadata_for(u"bar.txt")
275 def _got_metadata(metadata):
276 self._bar_txt_metadata = metadata
277 d.addCallback(_got_metadata)
280 def makefile(self, number):
281 contents = "contents of file %s\n" % number
282 n = create_chk_filenode(contents)
283 return contents, n, n.get_uri()
285 def makefile_mutable(self, number, mdmf=False):
286 contents = "contents of mutable file %s\n" % number
287 n = create_mutable_filenode(contents, mdmf)
288 return contents, n, n.get_uri(), n.get_readonly_uri()
291 return self.s.stopService()
293 def failUnlessIsBarDotTxt(self, res):
294 self.failUnlessReallyEqual(res, self.BAR_CONTENTS, res)
296 def failUnlessIsQuuxDotTxt(self, res):
297 self.failUnlessReallyEqual(res, self.QUUX_CONTENTS, res)
299 def failUnlessIsBazDotTxt(self, res):
300 self.failUnlessReallyEqual(res, self.BAZ_CONTENTS, res)
302 def failUnlessIsBarJSON(self, res):
303 data = simplejson.loads(res)
304 self.failUnless(isinstance(data, list))
305 self.failUnlessEqual(data[0], "filenode")
306 self.failUnless(isinstance(data[1], dict))
307 self.failIf(data[1]["mutable"])
308 self.failIf("rw_uri" in data[1]) # immutable
309 self.failUnlessReallyEqual(to_str(data[1]["ro_uri"]), self._bar_txt_uri)
310 self.failUnlessReallyEqual(to_str(data[1]["verify_uri"]), self._bar_txt_verifycap)
311 self.failUnlessReallyEqual(data[1]["size"], len(self.BAR_CONTENTS))
313 def failUnlessIsQuuxJSON(self, res, readonly=False):
314 data = simplejson.loads(res)
315 self.failUnless(isinstance(data, list))
316 self.failUnlessEqual(data[0], "filenode")
317 self.failUnless(isinstance(data[1], dict))
319 return self.failUnlessIsQuuxDotTxtMetadata(metadata, readonly)
321 def failUnlessIsQuuxDotTxtMetadata(self, metadata, readonly):
322 self.failUnless(metadata['mutable'])
324 self.failIf("rw_uri" in metadata)
326 self.failUnless("rw_uri" in metadata)
327 self.failUnlessEqual(metadata['rw_uri'], self._quux_txt_uri)
328 self.failUnless("ro_uri" in metadata)
329 self.failUnlessEqual(metadata['ro_uri'], self._quux_txt_readonly_uri)
330 self.failUnlessReallyEqual(metadata['size'], len(self.QUUX_CONTENTS))
332 def failUnlessIsFooJSON(self, res):
333 data = simplejson.loads(res)
334 self.failUnless(isinstance(data, list))
335 self.failUnlessEqual(data[0], "dirnode", res)
336 self.failUnless(isinstance(data[1], dict))
337 self.failUnless(data[1]["mutable"])
338 self.failUnless("rw_uri" in data[1]) # mutable
339 self.failUnlessReallyEqual(to_str(data[1]["rw_uri"]), self._foo_uri)
340 self.failUnlessReallyEqual(to_str(data[1]["ro_uri"]), self._foo_readonly_uri)
341 self.failUnlessReallyEqual(to_str(data[1]["verify_uri"]), self._foo_verifycap)
343 kidnames = sorted([unicode(n) for n in data[1]["children"]])
344 self.failUnlessEqual(kidnames,
345 [u"bar.txt", u"baz.txt", u"blockingfile",
346 u"empty", u"n\u00fc.txt", u"quux.txt", u"sub"])
347 kids = dict( [(unicode(name),value)
349 in data[1]["children"].iteritems()] )
350 self.failUnlessEqual(kids[u"sub"][0], "dirnode")
351 self.failUnlessIn("metadata", kids[u"sub"][1])
352 self.failUnlessIn("tahoe", kids[u"sub"][1]["metadata"])
353 tahoe_md = kids[u"sub"][1]["metadata"]["tahoe"]
354 self.failUnlessIn("linkcrtime", tahoe_md)
355 self.failUnlessIn("linkmotime", tahoe_md)
356 self.failUnlessEqual(kids[u"bar.txt"][0], "filenode")
357 self.failUnlessReallyEqual(kids[u"bar.txt"][1]["size"], len(self.BAR_CONTENTS))
358 self.failUnlessReallyEqual(to_str(kids[u"bar.txt"][1]["ro_uri"]), self._bar_txt_uri)
359 self.failUnlessReallyEqual(to_str(kids[u"bar.txt"][1]["verify_uri"]),
360 self._bar_txt_verifycap)
361 self.failUnlessIn("metadata", kids[u"bar.txt"][1])
362 self.failUnlessIn("tahoe", kids[u"bar.txt"][1]["metadata"])
363 self.failUnlessReallyEqual(kids[u"bar.txt"][1]["metadata"]["tahoe"]["linkcrtime"],
364 self._bar_txt_metadata["tahoe"]["linkcrtime"])
365 self.failUnlessReallyEqual(to_str(kids[u"n\u00fc.txt"][1]["ro_uri"]),
367 self.failUnlessIn("quux.txt", kids)
368 self.failUnlessReallyEqual(to_str(kids[u"quux.txt"][1]["rw_uri"]),
370 self.failUnlessReallyEqual(to_str(kids[u"quux.txt"][1]["ro_uri"]),
371 self._quux_txt_readonly_uri)
373 def GET(self, urlpath, followRedirect=False, return_response=False,
375 # if return_response=True, this fires with (data, statuscode,
376 # respheaders) instead of just data.
377 assert not isinstance(urlpath, unicode)
378 url = self.webish_url + urlpath
379 factory = HTTPClientGETFactory(url, method="GET",
380 followRedirect=followRedirect, **kwargs)
381 reactor.connectTCP("localhost", self.webish_port, factory)
384 return (data, factory.status, factory.response_headers)
386 d.addCallback(_got_data)
387 return factory.deferred
389 def HEAD(self, urlpath, return_response=False, **kwargs):
390 # this requires some surgery, because twisted.web.client doesn't want
391 # to give us back the response headers.
392 factory = HTTPClientHEADFactory(urlpath, method="HEAD", **kwargs)
393 reactor.connectTCP("localhost", self.webish_port, factory)
396 return (data, factory.status, factory.response_headers)
398 d.addCallback(_got_data)
399 return factory.deferred
401 def PUT(self, urlpath, data, **kwargs):
402 url = self.webish_url + urlpath
403 return client.getPage(url, method="PUT", postdata=data, **kwargs)
405 def DELETE(self, urlpath):
406 url = self.webish_url + urlpath
407 return client.getPage(url, method="DELETE")
409 def POST(self, urlpath, followRedirect=False, **fields):
410 sepbase = "boogabooga"
414 form.append('Content-Disposition: form-data; name="_charset"')
418 for name, value in fields.iteritems():
419 if isinstance(value, tuple):
420 filename, value = value
421 form.append('Content-Disposition: form-data; name="%s"; '
422 'filename="%s"' % (name, filename.encode("utf-8")))
424 form.append('Content-Disposition: form-data; name="%s"' % name)
426 if isinstance(value, unicode):
427 value = value.encode("utf-8")
430 assert isinstance(value, str)
437 body = "\r\n".join(form) + "\r\n"
438 headers["content-type"] = "multipart/form-data; boundary=%s" % sepbase
439 return self.POST2(urlpath, body, headers, followRedirect)
441 def POST2(self, urlpath, body="", headers={}, followRedirect=False):
442 url = self.webish_url + urlpath
443 return client.getPage(url, method="POST", postdata=body,
444 headers=headers, followRedirect=followRedirect)
446 def shouldFail(self, res, expected_failure, which,
447 substring=None, response_substring=None):
448 if isinstance(res, failure.Failure):
449 res.trap(expected_failure)
451 self.failUnless(substring in str(res),
452 "substring '%s' not in '%s'"
453 % (substring, str(res)))
454 if response_substring:
455 self.failUnless(response_substring in res.value.response,
456 "response substring '%s' not in '%s'"
457 % (response_substring, res.value.response))
459 self.fail("%s was supposed to raise %s, not get '%s'" %
460 (which, expected_failure, res))
462 def shouldFail2(self, expected_failure, which, substring,
464 callable, *args, **kwargs):
465 assert substring is None or isinstance(substring, str)
466 assert response_substring is None or isinstance(response_substring, str)
467 d = defer.maybeDeferred(callable, *args, **kwargs)
469 if isinstance(res, failure.Failure):
470 res.trap(expected_failure)
472 self.failUnless(substring in str(res),
473 "%s: substring '%s' not in '%s'"
474 % (which, substring, str(res)))
475 if response_substring:
476 self.failUnless(response_substring in res.value.response,
477 "%s: response substring '%s' not in '%s'"
479 response_substring, res.value.response))
481 self.fail("%s was supposed to raise %s, not get '%s'" %
482 (which, expected_failure, res))
486 def should404(self, res, which):
487 if isinstance(res, failure.Failure):
488 res.trap(error.Error)
489 self.failUnlessReallyEqual(res.value.status, "404")
491 self.fail("%s was supposed to Error(404), not get '%s'" %
494 def should302(self, res, which):
495 if isinstance(res, failure.Failure):
496 res.trap(error.Error)
497 self.failUnlessReallyEqual(res.value.status, "302")
499 self.fail("%s was supposed to Error(302), not get '%s'" %
503 class Web(WebMixin, WebErrorMixin, testutil.StallMixin, testutil.ReallyEqualMixin, unittest.TestCase):
504 def test_create(self):
507 def test_welcome(self):
510 self.failUnless('Welcome To Tahoe-LAFS' in res, res)
512 self.s.basedir = 'web/test_welcome'
513 fileutil.make_dirs("web/test_welcome")
514 fileutil.make_dirs("web/test_welcome/private")
516 d.addCallback(_check)
519 def test_provisioning(self):
520 d = self.GET("/provisioning/")
522 self.failUnless('Provisioning Tool' in res)
523 fields = {'filled': True,
524 "num_users": int(50e3),
525 "files_per_user": 1000,
526 "space_per_user": int(1e9),
527 "sharing_ratio": 1.0,
528 "encoding_parameters": "3-of-10-5",
530 "ownership_mode": "A",
531 "download_rate": 100,
536 return self.POST("/provisioning/", **fields)
538 d.addCallback(_check)
540 self.failUnless('Provisioning Tool' in res)
541 self.failUnless("Share space consumed: 167.01TB" in res)
543 fields = {'filled': True,
544 "num_users": int(50e6),
545 "files_per_user": 1000,
546 "space_per_user": int(5e9),
547 "sharing_ratio": 1.0,
548 "encoding_parameters": "25-of-100-50",
549 "num_servers": 30000,
550 "ownership_mode": "E",
551 "drive_failure_model": "U",
553 "download_rate": 1000,
558 return self.POST("/provisioning/", **fields)
559 d.addCallback(_check2)
561 self.failUnless("Share space consumed: huge!" in res)
562 fields = {'filled': True}
563 return self.POST("/provisioning/", **fields)
564 d.addCallback(_check3)
566 self.failUnless("Share space consumed:" in res)
567 d.addCallback(_check4)
570 def test_reliability_tool(self):
572 from allmydata import reliability
573 _hush_pyflakes = reliability
576 raise unittest.SkipTest("reliability tool requires NumPy")
578 d = self.GET("/reliability/")
580 self.failUnless('Reliability Tool' in res)
581 fields = {'drive_lifetime': "8Y",
586 "check_period": "1M",
587 "report_period": "3M",
590 return self.POST("/reliability/", **fields)
592 d.addCallback(_check)
594 self.failUnless('Reliability Tool' in res)
595 r = r'Probability of loss \(no maintenance\):\s+<span>0.033591'
596 self.failUnless(re.search(r, res), res)
597 d.addCallback(_check2)
600 def test_status(self):
601 h = self.s.get_history()
602 dl_num = h.list_all_download_statuses()[0].get_counter()
603 ul_num = h.list_all_upload_statuses()[0].get_counter()
604 mu_num = h.list_all_mapupdate_statuses()[0].get_counter()
605 pub_num = h.list_all_publish_statuses()[0].get_counter()
606 ret_num = h.list_all_retrieve_statuses()[0].get_counter()
607 d = self.GET("/status", followRedirect=True)
609 self.failUnless('Upload and Download Status' in res, res)
610 self.failUnless('"down-%d"' % dl_num in res, res)
611 self.failUnless('"up-%d"' % ul_num in res, res)
612 self.failUnless('"mapupdate-%d"' % mu_num in res, res)
613 self.failUnless('"publish-%d"' % pub_num in res, res)
614 self.failUnless('"retrieve-%d"' % ret_num in res, res)
615 d.addCallback(_check)
616 d.addCallback(lambda res: self.GET("/status/?t=json"))
617 def _check_json(res):
618 data = simplejson.loads(res)
619 self.failUnless(isinstance(data, dict))
620 #active = data["active"]
621 # TODO: test more. We need a way to fake an active operation
623 d.addCallback(_check_json)
625 d.addCallback(lambda res: self.GET("/status/down-%d" % dl_num))
627 self.failUnless("File Download Status" in res, res)
628 d.addCallback(_check_dl)
629 d.addCallback(lambda res: self.GET("/status/down-%d/event_json" % dl_num))
630 def _check_dl_json(res):
631 data = simplejson.loads(res)
632 self.failUnless(isinstance(data, dict))
633 self.failUnless("read" in data)
634 self.failUnlessEqual(data["read"][0]["length"], 120)
635 self.failUnlessEqual(data["segment"][0]["segment_length"], 100)
636 self.failUnlessEqual(data["segment"][2]["segment_number"], 2)
637 self.failUnlessEqual(data["segment"][2]["finish_time"], None)
638 phwr_id = base32.b2a(hashutil.tagged_hash("foo", "serverid_a")[:20])
639 cmpu_id = base32.b2a(hashutil.tagged_hash("foo", "serverid_b")[:20])
640 # serverids[] keys are strings, since that's what JSON does, but
641 # we'd really like them to be ints
642 self.failUnlessEqual(data["serverids"]["0"], "phwr")
643 self.failUnless(data["serverids"].has_key("1"), data["serverids"])
644 self.failUnlessEqual(data["serverids"]["1"], "cmpu", data["serverids"])
645 self.failUnlessEqual(data["server_info"][phwr_id]["short"], "phwr")
646 self.failUnlessEqual(data["server_info"][cmpu_id]["short"], "cmpu")
647 self.failUnless("dyhb" in data)
648 d.addCallback(_check_dl_json)
649 d.addCallback(lambda res: self.GET("/status/up-%d" % ul_num))
651 self.failUnless("File Upload Status" in res, res)
652 d.addCallback(_check_ul)
653 d.addCallback(lambda res: self.GET("/status/mapupdate-%d" % mu_num))
654 def _check_mapupdate(res):
655 self.failUnless("Mutable File Servermap Update Status" in res, res)
656 d.addCallback(_check_mapupdate)
657 d.addCallback(lambda res: self.GET("/status/publish-%d" % pub_num))
658 def _check_publish(res):
659 self.failUnless("Mutable File Publish Status" in res, res)
660 d.addCallback(_check_publish)
661 d.addCallback(lambda res: self.GET("/status/retrieve-%d" % ret_num))
662 def _check_retrieve(res):
663 self.failUnless("Mutable File Retrieve Status" in res, res)
664 d.addCallback(_check_retrieve)
668 def test_status_numbers(self):
669 drrm = status.DownloadResultsRendererMixin()
670 self.failUnlessReallyEqual(drrm.render_time(None, None), "")
671 self.failUnlessReallyEqual(drrm.render_time(None, 2.5), "2.50s")
672 self.failUnlessReallyEqual(drrm.render_time(None, 0.25), "250ms")
673 self.failUnlessReallyEqual(drrm.render_time(None, 0.0021), "2.1ms")
674 self.failUnlessReallyEqual(drrm.render_time(None, 0.000123), "123us")
675 self.failUnlessReallyEqual(drrm.render_rate(None, None), "")
676 self.failUnlessReallyEqual(drrm.render_rate(None, 2500000), "2.50MBps")
677 self.failUnlessReallyEqual(drrm.render_rate(None, 30100), "30.1kBps")
678 self.failUnlessReallyEqual(drrm.render_rate(None, 123), "123Bps")
680 urrm = status.UploadResultsRendererMixin()
681 self.failUnlessReallyEqual(urrm.render_time(None, None), "")
682 self.failUnlessReallyEqual(urrm.render_time(None, 2.5), "2.50s")
683 self.failUnlessReallyEqual(urrm.render_time(None, 0.25), "250ms")
684 self.failUnlessReallyEqual(urrm.render_time(None, 0.0021), "2.1ms")
685 self.failUnlessReallyEqual(urrm.render_time(None, 0.000123), "123us")
686 self.failUnlessReallyEqual(urrm.render_rate(None, None), "")
687 self.failUnlessReallyEqual(urrm.render_rate(None, 2500000), "2.50MBps")
688 self.failUnlessReallyEqual(urrm.render_rate(None, 30100), "30.1kBps")
689 self.failUnlessReallyEqual(urrm.render_rate(None, 123), "123Bps")
691 def test_GET_FILEURL(self):
692 d = self.GET(self.public_url + "/foo/bar.txt")
693 d.addCallback(self.failUnlessIsBarDotTxt)
696 def test_GET_FILEURL_range(self):
697 headers = {"range": "bytes=1-10"}
698 d = self.GET(self.public_url + "/foo/bar.txt", headers=headers,
699 return_response=True)
700 def _got((res, status, headers)):
701 self.failUnlessReallyEqual(int(status), 206)
702 self.failUnless(headers.has_key("content-range"))
703 self.failUnlessReallyEqual(headers["content-range"][0],
704 "bytes 1-10/%d" % len(self.BAR_CONTENTS))
705 self.failUnlessReallyEqual(res, self.BAR_CONTENTS[1:11])
709 def test_GET_FILEURL_partial_range(self):
710 headers = {"range": "bytes=5-"}
711 length = len(self.BAR_CONTENTS)
712 d = self.GET(self.public_url + "/foo/bar.txt", headers=headers,
713 return_response=True)
714 def _got((res, status, headers)):
715 self.failUnlessReallyEqual(int(status), 206)
716 self.failUnless(headers.has_key("content-range"))
717 self.failUnlessReallyEqual(headers["content-range"][0],
718 "bytes 5-%d/%d" % (length-1, length))
719 self.failUnlessReallyEqual(res, self.BAR_CONTENTS[5:])
723 def test_GET_FILEURL_partial_end_range(self):
724 headers = {"range": "bytes=-5"}
725 length = len(self.BAR_CONTENTS)
726 d = self.GET(self.public_url + "/foo/bar.txt", headers=headers,
727 return_response=True)
728 def _got((res, status, headers)):
729 self.failUnlessReallyEqual(int(status), 206)
730 self.failUnless(headers.has_key("content-range"))
731 self.failUnlessReallyEqual(headers["content-range"][0],
732 "bytes %d-%d/%d" % (length-5, length-1, length))
733 self.failUnlessReallyEqual(res, self.BAR_CONTENTS[-5:])
737 def test_GET_FILEURL_partial_range_overrun(self):
738 headers = {"range": "bytes=100-200"}
739 d = self.shouldFail2(error.Error, "test_GET_FILEURL_range_overrun",
740 "416 Requested Range not satisfiable",
741 "First beyond end of file",
742 self.GET, self.public_url + "/foo/bar.txt",
746 def test_HEAD_FILEURL_range(self):
747 headers = {"range": "bytes=1-10"}
748 d = self.HEAD(self.public_url + "/foo/bar.txt", headers=headers,
749 return_response=True)
750 def _got((res, status, headers)):
751 self.failUnlessReallyEqual(res, "")
752 self.failUnlessReallyEqual(int(status), 206)
753 self.failUnless(headers.has_key("content-range"))
754 self.failUnlessReallyEqual(headers["content-range"][0],
755 "bytes 1-10/%d" % len(self.BAR_CONTENTS))
759 def test_HEAD_FILEURL_partial_range(self):
760 headers = {"range": "bytes=5-"}
761 length = len(self.BAR_CONTENTS)
762 d = self.HEAD(self.public_url + "/foo/bar.txt", headers=headers,
763 return_response=True)
764 def _got((res, status, headers)):
765 self.failUnlessReallyEqual(int(status), 206)
766 self.failUnless(headers.has_key("content-range"))
767 self.failUnlessReallyEqual(headers["content-range"][0],
768 "bytes 5-%d/%d" % (length-1, length))
772 def test_HEAD_FILEURL_partial_end_range(self):
773 headers = {"range": "bytes=-5"}
774 length = len(self.BAR_CONTENTS)
775 d = self.HEAD(self.public_url + "/foo/bar.txt", headers=headers,
776 return_response=True)
777 def _got((res, status, headers)):
778 self.failUnlessReallyEqual(int(status), 206)
779 self.failUnless(headers.has_key("content-range"))
780 self.failUnlessReallyEqual(headers["content-range"][0],
781 "bytes %d-%d/%d" % (length-5, length-1, length))
785 def test_HEAD_FILEURL_partial_range_overrun(self):
786 headers = {"range": "bytes=100-200"}
787 d = self.shouldFail2(error.Error, "test_HEAD_FILEURL_range_overrun",
788 "416 Requested Range not satisfiable",
790 self.HEAD, self.public_url + "/foo/bar.txt",
794 def test_GET_FILEURL_range_bad(self):
795 headers = {"range": "BOGUS=fizbop-quarnak"}
796 d = self.GET(self.public_url + "/foo/bar.txt", headers=headers,
797 return_response=True)
798 def _got((res, status, headers)):
799 self.failUnlessReallyEqual(int(status), 200)
800 self.failUnless(not headers.has_key("content-range"))
801 self.failUnlessReallyEqual(res, self.BAR_CONTENTS)
805 def test_HEAD_FILEURL(self):
806 d = self.HEAD(self.public_url + "/foo/bar.txt", return_response=True)
807 def _got((res, status, headers)):
808 self.failUnlessReallyEqual(res, "")
809 self.failUnlessReallyEqual(headers["content-length"][0],
810 str(len(self.BAR_CONTENTS)))
811 self.failUnlessReallyEqual(headers["content-type"], ["text/plain"])
815 def test_GET_FILEURL_named(self):
816 base = "/file/%s" % urllib.quote(self._bar_txt_uri)
817 base2 = "/named/%s" % urllib.quote(self._bar_txt_uri)
818 d = self.GET(base + "/@@name=/blah.txt")
819 d.addCallback(self.failUnlessIsBarDotTxt)
820 d.addCallback(lambda res: self.GET(base + "/blah.txt"))
821 d.addCallback(self.failUnlessIsBarDotTxt)
822 d.addCallback(lambda res: self.GET(base + "/ignore/lots/blah.txt"))
823 d.addCallback(self.failUnlessIsBarDotTxt)
824 d.addCallback(lambda res: self.GET(base2 + "/@@name=/blah.txt"))
825 d.addCallback(self.failUnlessIsBarDotTxt)
826 save_url = base + "?save=true&filename=blah.txt"
827 d.addCallback(lambda res: self.GET(save_url))
828 d.addCallback(self.failUnlessIsBarDotTxt) # TODO: check headers
829 u_filename = u"n\u00e9wer.txt" # n e-acute w e r . t x t
830 u_fn_e = urllib.quote(u_filename.encode("utf-8"))
831 u_url = base + "?save=true&filename=" + u_fn_e
832 d.addCallback(lambda res: self.GET(u_url))
833 d.addCallback(self.failUnlessIsBarDotTxt) # TODO: check headers
836 def test_PUT_FILEURL_named_bad(self):
837 base = "/file/%s" % urllib.quote(self._bar_txt_uri)
838 d = self.shouldFail2(error.Error, "test_PUT_FILEURL_named_bad",
840 "/file can only be used with GET or HEAD",
841 self.PUT, base + "/@@name=/blah.txt", "")
845 def test_GET_DIRURL_named_bad(self):
846 base = "/file/%s" % urllib.quote(self._foo_uri)
847 d = self.shouldFail2(error.Error, "test_PUT_DIRURL_named_bad",
850 self.GET, base + "/@@name=/blah.txt")
853 def test_GET_slash_file_bad(self):
854 d = self.shouldFail2(error.Error, "test_GET_slash_file_bad",
856 "/file must be followed by a file-cap and a name",
860 def test_GET_unhandled_URI_named(self):
861 contents, n, newuri = self.makefile(12)
862 verifier_cap = n.get_verify_cap().to_string()
863 base = "/file/%s" % urllib.quote(verifier_cap)
864 # client.create_node_from_uri() can't handle verify-caps
865 d = self.shouldFail2(error.Error, "GET_unhandled_URI_named",
866 "400 Bad Request", "is not a file-cap",
870 def test_GET_unhandled_URI(self):
871 contents, n, newuri = self.makefile(12)
872 verifier_cap = n.get_verify_cap().to_string()
873 base = "/uri/%s" % urllib.quote(verifier_cap)
874 # client.create_node_from_uri() can't handle verify-caps
875 d = self.shouldFail2(error.Error, "test_GET_unhandled_URI",
877 "GET unknown URI type: can only do t=info",
881 def test_GET_FILE_URI(self):
882 base = "/uri/%s" % urllib.quote(self._bar_txt_uri)
884 d.addCallback(self.failUnlessIsBarDotTxt)
887 def test_GET_FILE_URI_mdmf(self):
888 base = "/uri/%s" % urllib.quote(self._quux_txt_uri)
890 d.addCallback(self.failUnlessIsQuuxDotTxt)
893 def test_GET_FILE_URI_mdmf_extensions(self):
894 base = "/uri/%s" % urllib.quote("%s:3:131073" % self._quux_txt_uri)
896 d.addCallback(self.failUnlessIsQuuxDotTxt)
899 def test_GET_FILE_URI_mdmf_bare_cap(self):
900 cap_elements = self._quux_txt_uri.split(":")
901 # 6 == expected cap length with two extensions.
902 self.failUnlessEqual(len(cap_elements), 6)
904 # Now lop off the extension parameters and stitch everything
906 quux_uri = ":".join(cap_elements[:len(cap_elements) - 2])
908 # Now GET that. We should get back quux.
909 base = "/uri/%s" % urllib.quote(quux_uri)
911 d.addCallback(self.failUnlessIsQuuxDotTxt)
914 def test_GET_FILE_URI_mdmf_readonly(self):
915 base = "/uri/%s" % urllib.quote(self._quux_txt_readonly_uri)
917 d.addCallback(self.failUnlessIsQuuxDotTxt)
920 def test_GET_FILE_URI_badchild(self):
921 base = "/uri/%s/boguschild" % urllib.quote(self._bar_txt_uri)
922 errmsg = "Files have no children, certainly not named 'boguschild'"
923 d = self.shouldFail2(error.Error, "test_GET_FILE_URI_badchild",
924 "400 Bad Request", errmsg,
928 def test_PUT_FILE_URI_badchild(self):
929 base = "/uri/%s/boguschild" % urllib.quote(self._bar_txt_uri)
930 errmsg = "Cannot create directory 'boguschild', because its parent is a file, not a directory"
931 d = self.shouldFail2(error.Error, "test_GET_FILE_URI_badchild",
932 "400 Bad Request", errmsg,
936 def test_PUT_FILE_URI_mdmf(self):
937 base = "/uri/%s" % urllib.quote(self._quux_txt_uri)
938 self._quux_new_contents = "new_contents"
940 d.addCallback(lambda res:
941 self.failUnlessIsQuuxDotTxt(res))
942 d.addCallback(lambda ignored:
943 self.PUT(base, self._quux_new_contents))
944 d.addCallback(lambda ignored:
946 d.addCallback(lambda res:
947 self.failUnlessReallyEqual(res, self._quux_new_contents))
950 def test_PUT_FILE_URI_mdmf_extensions(self):
951 base = "/uri/%s" % urllib.quote("%s:3:131073" % self._quux_txt_uri)
952 self._quux_new_contents = "new_contents"
954 d.addCallback(lambda res: self.failUnlessIsQuuxDotTxt(res))
955 d.addCallback(lambda ignored: self.PUT(base, self._quux_new_contents))
956 d.addCallback(lambda ignored: self.GET(base))
957 d.addCallback(lambda res: self.failUnlessEqual(self._quux_new_contents,
961 def test_PUT_FILE_URI_mdmf_bare_cap(self):
962 elements = self._quux_txt_uri.split(":")
963 self.failUnlessEqual(len(elements), 6)
965 quux_uri = ":".join(elements[:len(elements) - 2])
966 base = "/uri/%s" % urllib.quote(quux_uri)
967 self._quux_new_contents = "new_contents" * 50000
970 d.addCallback(self.failUnlessIsQuuxDotTxt)
971 d.addCallback(lambda ignored: self.PUT(base, self._quux_new_contents))
972 d.addCallback(lambda ignored: self.GET(base))
973 d.addCallback(lambda res:
974 self.failUnlessEqual(res, self._quux_new_contents))
977 def test_PUT_FILE_URI_mdmf_readonly(self):
978 # We're not allowed to PUT things to a readonly cap.
979 base = "/uri/%s" % self._quux_txt_readonly_uri
981 d.addCallback(lambda res:
982 self.failUnlessIsQuuxDotTxt(res))
983 # What should we get here? We get a 500 error now; that's not right.
984 d.addCallback(lambda ignored:
985 self.shouldFail2(error.Error, "test_PUT_FILE_URI_mdmf_readonly",
986 "400 Bad Request", "read-only cap",
987 self.PUT, base, "new data"))
990 def test_PUT_FILE_URI_sdmf_readonly(self):
991 # We're not allowed to put things to a readonly cap.
992 base = "/uri/%s" % self._baz_txt_readonly_uri
994 d.addCallback(lambda res:
995 self.failUnlessIsBazDotTxt(res))
996 d.addCallback(lambda ignored:
997 self.shouldFail2(error.Error, "test_PUT_FILE_URI_sdmf_readonly",
998 "400 Bad Request", "read-only cap",
999 self.PUT, base, "new_data"))
1002 # TODO: version of this with a Unicode filename
1003 def test_GET_FILEURL_save(self):
1004 d = self.GET(self.public_url + "/foo/bar.txt?filename=bar.txt&save=true",
1005 return_response=True)
1006 def _got((res, statuscode, headers)):
1007 content_disposition = headers["content-disposition"][0]
1008 self.failUnless(content_disposition == 'attachment; filename="bar.txt"', content_disposition)
1009 self.failUnlessIsBarDotTxt(res)
1013 def test_GET_FILEURL_missing(self):
1014 d = self.GET(self.public_url + "/foo/missing")
1015 d.addBoth(self.should404, "test_GET_FILEURL_missing")
1018 def test_GET_FILEURL_info_mdmf(self):
1019 d = self.GET("/uri/%s?t=info" % self._quux_txt_uri)
1021 self.failUnlessIn("mutable file (mdmf)", res)
1022 self.failUnlessIn(self._quux_txt_uri, res)
1023 self.failUnlessIn(self._quux_txt_readonly_uri, res)
1027 def test_GET_FILEURL_info_mdmf_readonly(self):
1028 d = self.GET("/uri/%s?t=info" % self._quux_txt_readonly_uri)
1030 self.failUnlessIn("mutable file (mdmf)", res)
1031 self.failIfIn(self._quux_txt_uri, res)
1032 self.failUnlessIn(self._quux_txt_readonly_uri, res)
1036 def test_GET_FILEURL_info_sdmf(self):
1037 d = self.GET("/uri/%s?t=info" % self._baz_txt_uri)
1039 self.failUnlessIn("mutable file (sdmf)", res)
1040 self.failUnlessIn(self._baz_txt_uri, res)
1044 def test_GET_FILEURL_info_mdmf_extensions(self):
1045 d = self.GET("/uri/%s:3:131073?t=info" % self._quux_txt_uri)
1047 self.failUnlessIn("mutable file (mdmf)", res)
1048 self.failUnlessIn(self._quux_txt_uri, res)
1049 self.failUnlessIn(self._quux_txt_readonly_uri, res)
1053 def test_GET_FILEURL_info_mdmf_bare_cap(self):
1054 elements = self._quux_txt_uri.split(":")
1055 self.failUnlessEqual(len(elements), 6)
1057 quux_uri = ":".join(elements[:len(elements) - 2])
1058 base = "/uri/%s?t=info" % urllib.quote(quux_uri)
1061 self.failUnlessIn("mutable file (mdmf)", res)
1062 self.failUnlessIn(quux_uri, res)
1066 def test_PUT_overwrite_only_files(self):
1067 # create a directory, put a file in that directory.
1068 contents, n, filecap = self.makefile(8)
1069 d = self.PUT(self.public_url + "/foo/dir?t=mkdir", "")
1070 d.addCallback(lambda res:
1071 self.PUT(self.public_url + "/foo/dir/file1.txt",
1072 self.NEWFILE_CONTENTS))
1073 # try to overwrite the file with replace=only-files
1074 # (this should work)
1075 d.addCallback(lambda res:
1076 self.PUT(self.public_url + "/foo/dir/file1.txt?t=uri&replace=only-files",
1078 d.addCallback(lambda res:
1079 self.shouldFail2(error.Error, "PUT_bad_t", "409 Conflict",
1080 "There was already a child by that name, and you asked me "
1081 "to not replace it",
1082 self.PUT, self.public_url + "/foo/dir?t=uri&replace=only-files",
1086 def test_PUT_NEWFILEURL(self):
1087 d = self.PUT(self.public_url + "/foo/new.txt", self.NEWFILE_CONTENTS)
1088 # TODO: we lose the response code, so we can't check this
1089 #self.failUnlessReallyEqual(responsecode, 201)
1090 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"new.txt")
1091 d.addCallback(lambda res:
1092 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
1093 self.NEWFILE_CONTENTS))
1096 def test_PUT_NEWFILEURL_not_mutable(self):
1097 d = self.PUT(self.public_url + "/foo/new.txt?mutable=false",
1098 self.NEWFILE_CONTENTS)
1099 # TODO: we lose the response code, so we can't check this
1100 #self.failUnlessReallyEqual(responsecode, 201)
1101 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"new.txt")
1102 d.addCallback(lambda res:
1103 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
1104 self.NEWFILE_CONTENTS))
1107 def test_PUT_NEWFILEURL_unlinked_mdmf(self):
1108 # this should get us a few segments of an MDMF mutable file,
1109 # which we can then test for.
1110 contents = self.NEWFILE_CONTENTS * 300000
1111 d = self.PUT("/uri?mutable=true&mutable-type=mdmf",
1113 def _got_filecap(filecap):
1114 self.failUnless(filecap.startswith("URI:MDMF"))
1116 d.addCallback(_got_filecap)
1117 d.addCallback(lambda filecap: self.GET("/uri/%s?t=json" % filecap))
1118 d.addCallback(lambda json: self.failUnlessIn("mdmf", json))
1121 def test_PUT_NEWFILEURL_unlinked_sdmf(self):
1122 contents = self.NEWFILE_CONTENTS * 300000
1123 d = self.PUT("/uri?mutable=true&mutable-type=sdmf",
1125 d.addCallback(lambda filecap: self.GET("/uri/%s?t=json" % filecap))
1126 d.addCallback(lambda json: self.failUnlessIn("sdmf", json))
1129 def test_PUT_NEWFILEURL_unlinked_bad_mutable_type(self):
1130 contents = self.NEWFILE_CONTENTS * 300000
1131 return self.shouldHTTPError("test bad mutable type",
1132 400, "Bad Request", "Unknown type: foo",
1133 self.PUT, "/uri?mutable=true&mutable-type=foo",
1136 def test_PUT_NEWFILEURL_range_bad(self):
1137 headers = {"content-range": "bytes 1-10/%d" % len(self.NEWFILE_CONTENTS)}
1138 target = self.public_url + "/foo/new.txt"
1139 d = self.shouldFail2(error.Error, "test_PUT_NEWFILEURL_range_bad",
1140 "501 Not Implemented",
1141 "Content-Range in PUT not yet supported",
1142 # (and certainly not for immutable files)
1143 self.PUT, target, self.NEWFILE_CONTENTS[1:11],
1145 d.addCallback(lambda res:
1146 self.failIfNodeHasChild(self._foo_node, u"new.txt"))
1149 def test_PUT_NEWFILEURL_mutable(self):
1150 d = self.PUT(self.public_url + "/foo/new.txt?mutable=true",
1151 self.NEWFILE_CONTENTS)
1152 # TODO: we lose the response code, so we can't check this
1153 #self.failUnlessReallyEqual(responsecode, 201)
1154 def _check_uri(res):
1155 u = uri.from_string_mutable_filenode(res)
1156 self.failUnless(u.is_mutable())
1157 self.failIf(u.is_readonly())
1159 d.addCallback(_check_uri)
1160 d.addCallback(self.failUnlessURIMatchesRWChild, self._foo_node, u"new.txt")
1161 d.addCallback(lambda res:
1162 self.failUnlessMutableChildContentsAre(self._foo_node,
1164 self.NEWFILE_CONTENTS))
1167 def test_PUT_NEWFILEURL_mutable_toobig(self):
1168 # It is okay to upload large mutable files, so we should be able
1170 d = self.PUT(self.public_url + "/foo/new.txt?mutable=true",
1171 "b" * (self.s.MUTABLE_SIZELIMIT + 1))
1174 def test_PUT_NEWFILEURL_replace(self):
1175 d = self.PUT(self.public_url + "/foo/bar.txt", self.NEWFILE_CONTENTS)
1176 # TODO: we lose the response code, so we can't check this
1177 #self.failUnlessReallyEqual(responsecode, 200)
1178 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"bar.txt")
1179 d.addCallback(lambda res:
1180 self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
1181 self.NEWFILE_CONTENTS))
1184 def test_PUT_NEWFILEURL_bad_t(self):
1185 d = self.shouldFail2(error.Error, "PUT_bad_t", "400 Bad Request",
1186 "PUT to a file: bad t=bogus",
1187 self.PUT, self.public_url + "/foo/bar.txt?t=bogus",
1191 def test_PUT_NEWFILEURL_no_replace(self):
1192 d = self.PUT(self.public_url + "/foo/bar.txt?replace=false",
1193 self.NEWFILE_CONTENTS)
1194 d.addBoth(self.shouldFail, error.Error, "PUT_NEWFILEURL_no_replace",
1196 "There was already a child by that name, and you asked me "
1197 "to not replace it")
1200 def test_PUT_NEWFILEURL_mkdirs(self):
1201 d = self.PUT(self.public_url + "/foo/newdir/new.txt", self.NEWFILE_CONTENTS)
1203 d.addCallback(self.failUnlessURIMatchesROChild, fn, u"newdir/new.txt")
1204 d.addCallback(lambda res: self.failIfNodeHasChild(fn, u"new.txt"))
1205 d.addCallback(lambda res: self.failUnlessNodeHasChild(fn, u"newdir"))
1206 d.addCallback(lambda res:
1207 self.failUnlessChildContentsAre(fn, u"newdir/new.txt",
1208 self.NEWFILE_CONTENTS))
1211 def test_PUT_NEWFILEURL_blocked(self):
1212 d = self.PUT(self.public_url + "/foo/blockingfile/new.txt",
1213 self.NEWFILE_CONTENTS)
1214 d.addBoth(self.shouldFail, error.Error, "PUT_NEWFILEURL_blocked",
1216 "Unable to create directory 'blockingfile': a file was in the way")
1219 def test_PUT_NEWFILEURL_emptyname(self):
1220 # an empty pathname component (i.e. a double-slash) is disallowed
1221 d = self.shouldFail2(error.Error, "test_PUT_NEWFILEURL_emptyname",
1223 "The webapi does not allow empty pathname components",
1224 self.PUT, self.public_url + "/foo//new.txt", "")
1227 def test_DELETE_FILEURL(self):
1228 d = self.DELETE(self.public_url + "/foo/bar.txt")
1229 d.addCallback(lambda res:
1230 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
1233 def test_DELETE_FILEURL_missing(self):
1234 d = self.DELETE(self.public_url + "/foo/missing")
1235 d.addBoth(self.should404, "test_DELETE_FILEURL_missing")
1238 def test_DELETE_FILEURL_missing2(self):
1239 d = self.DELETE(self.public_url + "/missing/missing")
1240 d.addBoth(self.should404, "test_DELETE_FILEURL_missing2")
1243 def failUnlessHasBarDotTxtMetadata(self, res):
1244 data = simplejson.loads(res)
1245 self.failUnless(isinstance(data, list))
1246 self.failUnlessIn("metadata", data[1])
1247 self.failUnlessIn("tahoe", data[1]["metadata"])
1248 self.failUnlessIn("linkcrtime", data[1]["metadata"]["tahoe"])
1249 self.failUnlessIn("linkmotime", data[1]["metadata"]["tahoe"])
1250 self.failUnlessReallyEqual(data[1]["metadata"]["tahoe"]["linkcrtime"],
1251 self._bar_txt_metadata["tahoe"]["linkcrtime"])
1253 def test_GET_FILEURL_json(self):
1254 # twisted.web.http.parse_qs ignores any query args without an '=', so
1255 # I can't do "GET /path?json", I have to do "GET /path/t=json"
1256 # instead. This may make it tricky to emulate the S3 interface
1258 d = self.GET(self.public_url + "/foo/bar.txt?t=json")
1260 self.failUnlessIsBarJSON(data)
1261 self.failUnlessHasBarDotTxtMetadata(data)
1263 d.addCallback(_check1)
1266 def test_GET_FILEURL_json_mutable_type(self):
1267 # The JSON should include mutable-type, which says whether the
1268 # file is SDMF or MDMF
1269 d = self.PUT("/uri?mutable=true&mutable-type=mdmf",
1270 self.NEWFILE_CONTENTS * 300000)
1271 d.addCallback(lambda filecap: self.GET("/uri/%s?t=json" % filecap))
1272 def _got_json(json, version):
1273 data = simplejson.loads(json)
1274 assert "filenode" == data[0]
1276 assert isinstance(data, dict)
1278 self.failUnlessIn("mutable-type", data)
1279 self.failUnlessEqual(data['mutable-type'], version)
1281 d.addCallback(_got_json, "mdmf")
1282 # Now make an SDMF file and check that it is reported correctly.
1283 d.addCallback(lambda ignored:
1284 self.PUT("/uri?mutable=true&mutable-type=sdmf",
1285 self.NEWFILE_CONTENTS * 300000))
1286 d.addCallback(lambda filecap: self.GET("/uri/%s?t=json" % filecap))
1287 d.addCallback(_got_json, "sdmf")
1290 def test_GET_FILEURL_json_mdmf_extensions(self):
1291 # A GET invoked against a URL that includes an MDMF cap with
1292 # extensions should fetch the same JSON information as a GET
1293 # invoked against a bare cap.
1294 self._quux_txt_uri = "%s:3:131073" % self._quux_txt_uri
1295 self._quux_txt_readonly_uri = "%s:3:131073" % self._quux_txt_readonly_uri
1296 d = self.GET("/uri/%s?t=json" % urllib.quote(self._quux_txt_uri))
1297 d.addCallback(self.failUnlessIsQuuxJSON)
1300 def test_GET_FILEURL_json_mdmf_bare_cap(self):
1301 elements = self._quux_txt_uri.split(":")
1302 self.failUnlessEqual(len(elements), 6)
1304 quux_uri = ":".join(elements[:len(elements) - 2])
1305 # so failUnlessIsQuuxJSON will work.
1306 self._quux_txt_uri = quux_uri
1308 # we need to alter the readonly URI in the same way, again so
1309 # failUnlessIsQuuxJSON will work
1310 elements = self._quux_txt_readonly_uri.split(":")
1311 self.failUnlessEqual(len(elements), 6)
1312 quux_ro_uri = ":".join(elements[:len(elements) - 2])
1313 self._quux_txt_readonly_uri = quux_ro_uri
1315 base = "/uri/%s?t=json" % urllib.quote(quux_uri)
1317 d.addCallback(self.failUnlessIsQuuxJSON)
1320 def test_GET_FILEURL_json_mdmf_bare_readonly_cap(self):
1321 elements = self._quux_txt_readonly_uri.split(":")
1322 self.failUnlessEqual(len(elements), 6)
1324 quux_readonly_uri = ":".join(elements[:len(elements) - 2])
1325 # so failUnlessIsQuuxJSON will work
1326 self._quux_txt_readonly_uri = quux_readonly_uri
1327 base = "/uri/%s?t=json" % quux_readonly_uri
1329 # XXX: We may need to make a method that knows how to check for
1330 # readonly JSON, or else alter that one so that it knows how to
1332 d.addCallback(self.failUnlessIsQuuxJSON, readonly=True)
1335 def test_GET_FILEURL_json_mdmf(self):
1336 d = self.GET("/uri/%s?t=json" % urllib.quote(self._quux_txt_uri))
1337 d.addCallback(self.failUnlessIsQuuxJSON)
1340 def test_GET_FILEURL_json_missing(self):
1341 d = self.GET(self.public_url + "/foo/missing?json")
1342 d.addBoth(self.should404, "test_GET_FILEURL_json_missing")
1345 def test_GET_FILEURL_uri(self):
1346 d = self.GET(self.public_url + "/foo/bar.txt?t=uri")
1348 self.failUnlessReallyEqual(res, self._bar_txt_uri)
1349 d.addCallback(_check)
1350 d.addCallback(lambda res:
1351 self.GET(self.public_url + "/foo/bar.txt?t=readonly-uri"))
1353 # for now, for files, uris and readonly-uris are the same
1354 self.failUnlessReallyEqual(res, self._bar_txt_uri)
1355 d.addCallback(_check2)
1358 def test_GET_FILEURL_badtype(self):
1359 d = self.shouldHTTPError("GET t=bogus", 400, "Bad Request",
1362 self.public_url + "/foo/bar.txt?t=bogus")
1365 def test_CSS_FILE(self):
1366 d = self.GET("/tahoe_css", followRedirect=True)
1368 CSS_STYLE=re.compile('toolbar\s{.+text-align:\scenter.+toolbar-item.+display:\sinline',re.DOTALL)
1369 self.failUnless(CSS_STYLE.search(res), res)
1370 d.addCallback(_check)
1373 def test_GET_FILEURL_uri_missing(self):
1374 d = self.GET(self.public_url + "/foo/missing?t=uri")
1375 d.addBoth(self.should404, "test_GET_FILEURL_uri_missing")
1378 def test_GET_DIRECTORY_html(self):
1379 d = self.GET(self.public_url + "/foo", followRedirect=True)
1381 self.failUnlessIn('<div class="toolbar-item"><a href="../../..">Return to Welcome page</a></div>',res)
1382 # These are radio buttons that allow a user to toggle
1383 # whether a particular mutable file is SDMF or MDMF.
1384 self.failUnlessIn("mutable-type-mdmf", res)
1385 self.failUnlessIn("mutable-type-sdmf", res)
1386 # Similarly, these toggle whether a particular directory
1387 # should be MDMF or SDMF.
1388 self.failUnlessIn("mutable-directory-mdmf", res)
1389 self.failUnlessIn("mutable-directory-sdmf", res)
1390 self.failUnlessIn("quux", res)
1391 d.addCallback(_check)
1394 def test_GET_root_html(self):
1395 # make sure that we have the option to upload an unlinked
1396 # mutable file in SDMF and MDMF formats.
1398 def _got_html(html):
1399 # These are radio buttons that allow the user to toggle
1400 # whether a particular mutable file is MDMF or SDMF.
1401 self.failUnlessIn("mutable-type-mdmf", html)
1402 self.failUnlessIn("mutable-type-sdmf", html)
1403 # We should also have the ability to create a mutable directory.
1404 self.failUnlessIn("mkdir", html)
1405 # ...and we should have the ability to say whether that's an
1406 # MDMF or SDMF directory
1407 self.failUnlessIn("mutable-directory-mdmf", html)
1408 self.failUnlessIn("mutable-directory-sdmf", html)
1409 d.addCallback(_got_html)
1412 def test_mutable_type_defaults(self):
1413 # The checked="checked" attribute of the inputs corresponding to
1414 # the mutable-type parameter should change as expected with the
1415 # value configured in tahoe.cfg.
1417 # By default, the value configured with the client is
1418 # SDMF_VERSION, so that should be checked.
1419 assert self.s.mutable_file_default == SDMF_VERSION
1422 def _got_html(html, value):
1423 i = 'input checked="checked" type="radio" id="mutable-type-%s"'
1424 self.failUnlessIn(i % value, html)
1425 d.addCallback(_got_html, "sdmf")
1426 d.addCallback(lambda ignored:
1427 self.GET(self.public_url + "/foo", followRedirect=True))
1428 d.addCallback(_got_html, "sdmf")
1429 # Now switch the configuration value to MDMF. The MDMF radio
1430 # buttons should now be checked on these pages.
1431 def _swap_values(ignored):
1432 self.s.mutable_file_default = MDMF_VERSION
1433 d.addCallback(_swap_values)
1434 d.addCallback(lambda ignored: self.GET("/"))
1435 d.addCallback(_got_html, "mdmf")
1436 d.addCallback(lambda ignored:
1437 self.GET(self.public_url + "/foo", followRedirect=True))
1438 d.addCallback(_got_html, "mdmf")
1441 def test_GET_DIRURL(self):
1442 # the addSlash means we get a redirect here
1443 # from /uri/$URI/foo/ , we need ../../../ to get back to the root
1445 d = self.GET(self.public_url + "/foo", followRedirect=True)
1447 self.failUnless(('<a href="%s">Return to Welcome page' % ROOT)
1449 # the FILE reference points to a URI, but it should end in bar.txt
1450 bar_url = ("%s/file/%s/@@named=/bar.txt" %
1451 (ROOT, urllib.quote(self._bar_txt_uri)))
1452 get_bar = "".join([r'<td>FILE</td>',
1454 r'<a href="%s">bar.txt</a>' % bar_url,
1456 r'\s+<td align="right">%d</td>' % len(self.BAR_CONTENTS),
1458 self.failUnless(re.search(get_bar, res), res)
1459 for label in ['unlink', 'rename']:
1460 for line in res.split("\n"):
1461 # find the line that contains the relevant button for bar.txt
1462 if ("form action" in line and
1463 ('value="%s"' % (label,)) in line and
1464 'value="bar.txt"' in line):
1465 # the form target should use a relative URL
1466 foo_url = urllib.quote("%s/uri/%s/" % (ROOT, self._foo_uri))
1467 self.failUnlessIn('action="%s"' % foo_url, line)
1468 # and the when_done= should too
1469 #done_url = urllib.quote(???)
1470 #self.failUnlessIn('name="when_done" value="%s"' % done_url, line)
1472 # 'unlink' needs to use POST because it directly has a side effect
1473 if label == 'unlink':
1474 self.failUnlessIn('method="post"', line)
1477 self.fail("unable to find '%s bar.txt' line" % (label,), res)
1479 # the DIR reference just points to a URI
1480 sub_url = ("%s/uri/%s/" % (ROOT, urllib.quote(self._sub_uri)))
1481 get_sub = ((r'<td>DIR</td>')
1482 +r'\s+<td><a href="%s">sub</a></td>' % sub_url)
1483 self.failUnless(re.search(get_sub, res), res)
1484 d.addCallback(_check)
1486 # look at a readonly directory
1487 d.addCallback(lambda res:
1488 self.GET(self.public_url + "/reedownlee", followRedirect=True))
1490 self.failUnless("(read-only)" in res, res)
1491 self.failIf("Upload a file" in res, res)
1492 d.addCallback(_check2)
1494 # and at a directory that contains a readonly directory
1495 d.addCallback(lambda res:
1496 self.GET(self.public_url, followRedirect=True))
1498 self.failUnless(re.search('<td>DIR-RO</td>'
1499 r'\s+<td><a href="[\.\/]+/uri/URI%3ADIR2-RO%3A[^"]+">reedownlee</a></td>', res), res)
1500 d.addCallback(_check3)
1502 # and an empty directory
1503 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty/"))
1505 self.failUnless("directory is empty" in res, res)
1506 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)
1507 self.failUnless(MKDIR_BUTTON_RE.search(res), res)
1508 d.addCallback(_check4)
1510 # and at a literal directory
1511 tiny_litdir_uri = "URI:DIR2-LIT:gqytunj2onug64tufqzdcosvkjetutcjkq5gw4tvm5vwszdgnz5hgyzufqydulbshj5x2lbm" # contains one child which is itself also LIT
1512 d.addCallback(lambda res:
1513 self.GET("/uri/" + tiny_litdir_uri + "/", followRedirect=True))
1515 self.failUnless('(immutable)' in res, res)
1516 self.failUnless(re.search('<td>FILE</td>'
1517 r'\s+<td><a href="[\.\/]+/file/URI%3ALIT%3Akrugkidfnzsc4/@@named=/short">short</a></td>', res), res)
1518 d.addCallback(_check5)
1521 def test_GET_DIRURL_badtype(self):
1522 d = self.shouldHTTPError("test_GET_DIRURL_badtype",
1526 self.public_url + "/foo?t=bogus")
1529 def test_GET_DIRURL_json(self):
1530 d = self.GET(self.public_url + "/foo?t=json")
1531 d.addCallback(self.failUnlessIsFooJSON)
1534 def test_GET_DIRURL_json_mutable_type(self):
1535 d = self.PUT(self.public_url + \
1536 "/foo/sdmf.txt?mutable=true&mutable-type=sdmf",
1537 self.NEWFILE_CONTENTS * 300000)
1538 d.addCallback(lambda ignored:
1539 self.PUT(self.public_url + \
1540 "/foo/mdmf.txt?mutable=true&mutable-type=mdmf",
1541 self.NEWFILE_CONTENTS * 300000))
1542 # Now we have an MDMF and SDMF file in the directory. If we GET
1543 # its JSON, we should see their encodings.
1544 d.addCallback(lambda ignored:
1545 self.GET(self.public_url + "/foo?t=json"))
1546 def _got_json(json):
1547 data = simplejson.loads(json)
1548 assert data[0] == "dirnode"
1551 kids = data['children']
1553 mdmf_data = kids['mdmf.txt'][1]
1554 self.failUnlessIn("mutable-type", mdmf_data)
1555 self.failUnlessEqual(mdmf_data['mutable-type'], "mdmf")
1557 sdmf_data = kids['sdmf.txt'][1]
1558 self.failUnlessIn("mutable-type", sdmf_data)
1559 self.failUnlessEqual(sdmf_data['mutable-type'], "sdmf")
1560 d.addCallback(_got_json)
1564 def test_POST_DIRURL_manifest_no_ophandle(self):
1565 d = self.shouldFail2(error.Error,
1566 "test_POST_DIRURL_manifest_no_ophandle",
1568 "slow operation requires ophandle=",
1569 self.POST, self.public_url, t="start-manifest")
1572 def test_POST_DIRURL_manifest(self):
1573 d = defer.succeed(None)
1574 def getman(ignored, output):
1575 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=125",
1576 followRedirect=True)
1577 d.addCallback(self.wait_for_operation, "125")
1578 d.addCallback(self.get_operation_results, "125", output)
1580 d.addCallback(getman, None)
1581 def _got_html(manifest):
1582 self.failUnless("Manifest of SI=" in manifest)
1583 self.failUnless("<td>sub</td>" in manifest)
1584 self.failUnless(self._sub_uri in manifest)
1585 self.failUnless("<td>sub/baz.txt</td>" in manifest)
1586 d.addCallback(_got_html)
1588 # both t=status and unadorned GET should be identical
1589 d.addCallback(lambda res: self.GET("/operations/125"))
1590 d.addCallback(_got_html)
1592 d.addCallback(getman, "html")
1593 d.addCallback(_got_html)
1594 d.addCallback(getman, "text")
1595 def _got_text(manifest):
1596 self.failUnless("\nsub " + self._sub_uri + "\n" in manifest)
1597 self.failUnless("\nsub/baz.txt URI:CHK:" in manifest)
1598 d.addCallback(_got_text)
1599 d.addCallback(getman, "JSON")
1601 data = res["manifest"]
1603 for (path_list, cap) in data:
1604 got[tuple(path_list)] = cap
1605 self.failUnlessReallyEqual(to_str(got[(u"sub",)]), self._sub_uri)
1606 self.failUnless((u"sub",u"baz.txt") in got)
1607 self.failUnless("finished" in res)
1608 self.failUnless("origin" in res)
1609 self.failUnless("storage-index" in res)
1610 self.failUnless("verifycaps" in res)
1611 self.failUnless("stats" in res)
1612 d.addCallback(_got_json)
1615 def test_POST_DIRURL_deepsize_no_ophandle(self):
1616 d = self.shouldFail2(error.Error,
1617 "test_POST_DIRURL_deepsize_no_ophandle",
1619 "slow operation requires ophandle=",
1620 self.POST, self.public_url, t="start-deep-size")
1623 def test_POST_DIRURL_deepsize(self):
1624 d = self.POST(self.public_url + "/foo/?t=start-deep-size&ophandle=126",
1625 followRedirect=True)
1626 d.addCallback(self.wait_for_operation, "126")
1627 d.addCallback(self.get_operation_results, "126", "json")
1628 def _got_json(data):
1629 self.failUnlessReallyEqual(data["finished"], True)
1631 self.failUnless(size > 1000)
1632 d.addCallback(_got_json)
1633 d.addCallback(self.get_operation_results, "126", "text")
1635 mo = re.search(r'^size: (\d+)$', res, re.M)
1636 self.failUnless(mo, res)
1637 size = int(mo.group(1))
1638 # with directories, the size varies.
1639 self.failUnless(size > 1000)
1640 d.addCallback(_got_text)
1643 def test_POST_DIRURL_deepstats_no_ophandle(self):
1644 d = self.shouldFail2(error.Error,
1645 "test_POST_DIRURL_deepstats_no_ophandle",
1647 "slow operation requires ophandle=",
1648 self.POST, self.public_url, t="start-deep-stats")
1651 def test_POST_DIRURL_deepstats(self):
1652 d = self.POST(self.public_url + "/foo/?t=start-deep-stats&ophandle=127",
1653 followRedirect=True)
1654 d.addCallback(self.wait_for_operation, "127")
1655 d.addCallback(self.get_operation_results, "127", "json")
1656 def _got_json(stats):
1657 expected = {"count-immutable-files": 3,
1658 "count-mutable-files": 2,
1659 "count-literal-files": 0,
1661 "count-directories": 3,
1662 "size-immutable-files": 57,
1663 "size-literal-files": 0,
1664 #"size-directories": 1912, # varies
1665 #"largest-directory": 1590,
1666 "largest-directory-children": 7,
1667 "largest-immutable-file": 19,
1669 for k,v in expected.iteritems():
1670 self.failUnlessReallyEqual(stats[k], v,
1671 "stats[%s] was %s, not %s" %
1673 self.failUnlessReallyEqual(stats["size-files-histogram"],
1675 d.addCallback(_got_json)
1678 def test_POST_DIRURL_stream_manifest(self):
1679 d = self.POST(self.public_url + "/foo/?t=stream-manifest")
1681 self.failUnless(res.endswith("\n"))
1682 units = [simplejson.loads(t) for t in res[:-1].split("\n")]
1683 self.failUnlessReallyEqual(len(units), 9)
1684 self.failUnlessEqual(units[-1]["type"], "stats")
1686 self.failUnlessEqual(first["path"], [])
1687 self.failUnlessReallyEqual(to_str(first["cap"]), self._foo_uri)
1688 self.failUnlessEqual(first["type"], "directory")
1689 baz = [u for u in units[:-1] if to_str(u["cap"]) == self._baz_file_uri][0]
1690 self.failUnlessEqual(baz["path"], ["sub", "baz.txt"])
1691 self.failIfEqual(baz["storage-index"], None)
1692 self.failIfEqual(baz["verifycap"], None)
1693 self.failIfEqual(baz["repaircap"], None)
1694 # XXX: Add quux and baz to this test.
1696 d.addCallback(_check)
1699 def test_GET_DIRURL_uri(self):
1700 d = self.GET(self.public_url + "/foo?t=uri")
1702 self.failUnlessReallyEqual(to_str(res), self._foo_uri)
1703 d.addCallback(_check)
1706 def test_GET_DIRURL_readonly_uri(self):
1707 d = self.GET(self.public_url + "/foo?t=readonly-uri")
1709 self.failUnlessReallyEqual(to_str(res), self._foo_readonly_uri)
1710 d.addCallback(_check)
1713 def test_PUT_NEWDIRURL(self):
1714 d = self.PUT(self.public_url + "/foo/newdir?t=mkdir", "")
1715 d.addCallback(lambda res:
1716 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1717 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1718 d.addCallback(self.failUnlessNodeKeysAre, [])
1721 def test_PUT_NEWDIRURL_mdmf(self):
1722 d = self.PUT(self.public_url + "/foo/newdir?t=mkdir&mutable-type=mdmf", "")
1723 d.addCallback(lambda res:
1724 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1725 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1726 d.addCallback(lambda node:
1727 self.failUnlessEqual(node._node.get_version(), MDMF_VERSION))
1730 def test_PUT_NEWDIRURL_sdmf(self):
1731 d = self.PUT(self.public_url + "/foo/newdir?t=mkdir&mutable-type=sdmf",
1733 d.addCallback(lambda res:
1734 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1735 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1736 d.addCallback(lambda node:
1737 self.failUnlessEqual(node._node.get_version(), SDMF_VERSION))
1740 def test_PUT_NEWDIRURL_bad_mutable_type(self):
1741 return self.shouldHTTPError("test bad mutable type",
1742 400, "Bad Request", "Unknown type: foo",
1743 self.PUT, self.public_url + \
1744 "/foo/newdir=?t=mkdir&mutable-type=foo", "")
1746 def test_POST_NEWDIRURL(self):
1747 d = self.POST2(self.public_url + "/foo/newdir?t=mkdir", "")
1748 d.addCallback(lambda res:
1749 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1750 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1751 d.addCallback(self.failUnlessNodeKeysAre, [])
1754 def test_POST_NEWDIRURL_mdmf(self):
1755 d = self.POST2(self.public_url + "/foo/newdir?t=mkdir&mutable-type=mdmf", "")
1756 d.addCallback(lambda res:
1757 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1758 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1759 d.addCallback(lambda node:
1760 self.failUnlessEqual(node._node.get_version(), MDMF_VERSION))
1763 def test_POST_NEWDIRURL_sdmf(self):
1764 d = self.POST2(self.public_url + "/foo/newdir?t=mkdir&mutable-type=sdmf", "")
1765 d.addCallback(lambda res:
1766 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1767 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1768 d.addCallback(lambda node:
1769 self.failUnlessEqual(node._node.get_version(), SDMF_VERSION))
1772 def test_POST_NEWDIRURL_bad_mutable_type(self):
1773 return self.shouldHTTPError("test bad mutable type",
1774 400, "Bad Request", "Unknown type: foo",
1775 self.POST2, self.public_url + \
1776 "/foo/newdir?t=mkdir&mutable-type=foo", "")
1778 def test_POST_NEWDIRURL_emptyname(self):
1779 # an empty pathname component (i.e. a double-slash) is disallowed
1780 d = self.shouldFail2(error.Error, "test_POST_NEWDIRURL_emptyname",
1782 "The webapi does not allow empty pathname components, i.e. a double slash",
1783 self.POST, self.public_url + "//?t=mkdir")
1786 def _do_POST_NEWDIRURL_initial_children_test(self, version=None):
1787 (newkids, caps) = self._create_initial_children()
1788 query = "/foo/newdir?t=mkdir-with-children"
1789 if version == MDMF_VERSION:
1790 query += "&mutable-type=mdmf"
1791 elif version == SDMF_VERSION:
1792 query += "&mutable-type=sdmf"
1794 version = SDMF_VERSION # for later
1795 d = self.POST2(self.public_url + query,
1796 simplejson.dumps(newkids))
1798 n = self.s.create_node_from_uri(uri.strip())
1799 d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
1800 self.failUnlessEqual(n._node.get_version(), version)
1801 d2.addCallback(lambda ign:
1802 self.failUnlessROChildURIIs(n, u"child-imm",
1804 d2.addCallback(lambda ign:
1805 self.failUnlessRWChildURIIs(n, u"child-mutable",
1807 d2.addCallback(lambda ign:
1808 self.failUnlessROChildURIIs(n, u"child-mutable-ro",
1810 d2.addCallback(lambda ign:
1811 self.failUnlessROChildURIIs(n, u"unknownchild-ro",
1812 caps['unknown_rocap']))
1813 d2.addCallback(lambda ign:
1814 self.failUnlessRWChildURIIs(n, u"unknownchild-rw",
1815 caps['unknown_rwcap']))
1816 d2.addCallback(lambda ign:
1817 self.failUnlessROChildURIIs(n, u"unknownchild-imm",
1818 caps['unknown_immcap']))
1819 d2.addCallback(lambda ign:
1820 self.failUnlessRWChildURIIs(n, u"dirchild",
1822 d2.addCallback(lambda ign:
1823 self.failUnlessROChildURIIs(n, u"dirchild-lit",
1825 d2.addCallback(lambda ign:
1826 self.failUnlessROChildURIIs(n, u"dirchild-empty",
1827 caps['emptydircap']))
1829 d.addCallback(_check)
1830 d.addCallback(lambda res:
1831 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1832 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1833 d.addCallback(self.failUnlessNodeKeysAre, newkids.keys())
1834 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1835 d.addCallback(self.failUnlessROChildURIIs, u"child-imm", caps['filecap1'])
1838 def test_POST_NEWDIRURL_initial_children(self):
1839 return self._do_POST_NEWDIRURL_initial_children_test()
1841 def test_POST_NEWDIRURL_initial_children_mdmf(self):
1842 return self._do_POST_NEWDIRURL_initial_children_test(MDMF_VERSION)
1844 def test_POST_NEWDIRURL_initial_children_sdmf(self):
1845 return self._do_POST_NEWDIRURL_initial_children_test(SDMF_VERSION)
1847 def test_POST_NEWDIRURL_initial_children_bad_mutable_type(self):
1848 (newkids, caps) = self._create_initial_children()
1849 return self.shouldHTTPError("test bad mutable type",
1850 400, "Bad Request", "Unknown type: foo",
1851 self.POST2, self.public_url + \
1852 "/foo/newdir?t=mkdir-with-children&mutable-type=foo",
1853 simplejson.dumps(newkids))
1855 def test_POST_NEWDIRURL_immutable(self):
1856 (newkids, caps) = self._create_immutable_children()
1857 d = self.POST2(self.public_url + "/foo/newdir?t=mkdir-immutable",
1858 simplejson.dumps(newkids))
1860 n = self.s.create_node_from_uri(uri.strip())
1861 d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
1862 d2.addCallback(lambda ign:
1863 self.failUnlessROChildURIIs(n, u"child-imm",
1865 d2.addCallback(lambda ign:
1866 self.failUnlessROChildURIIs(n, u"unknownchild-imm",
1867 caps['unknown_immcap']))
1868 d2.addCallback(lambda ign:
1869 self.failUnlessROChildURIIs(n, u"dirchild-imm",
1871 d2.addCallback(lambda ign:
1872 self.failUnlessROChildURIIs(n, u"dirchild-lit",
1874 d2.addCallback(lambda ign:
1875 self.failUnlessROChildURIIs(n, u"dirchild-empty",
1876 caps['emptydircap']))
1878 d.addCallback(_check)
1879 d.addCallback(lambda res:
1880 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1881 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1882 d.addCallback(self.failUnlessNodeKeysAre, newkids.keys())
1883 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1884 d.addCallback(self.failUnlessROChildURIIs, u"child-imm", caps['filecap1'])
1885 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1886 d.addCallback(self.failUnlessROChildURIIs, u"unknownchild-imm", caps['unknown_immcap'])
1887 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1888 d.addCallback(self.failUnlessROChildURIIs, u"dirchild-imm", caps['immdircap'])
1889 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1890 d.addCallback(self.failUnlessROChildURIIs, u"dirchild-lit", caps['litdircap'])
1891 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1892 d.addCallback(self.failUnlessROChildURIIs, u"dirchild-empty", caps['emptydircap'])
1893 d.addErrback(self.explain_web_error)
1896 def test_POST_NEWDIRURL_immutable_bad(self):
1897 (newkids, caps) = self._create_initial_children()
1898 d = self.shouldFail2(error.Error, "test_POST_NEWDIRURL_immutable_bad",
1900 "needed to be immutable but was not",
1902 self.public_url + "/foo/newdir?t=mkdir-immutable",
1903 simplejson.dumps(newkids))
1906 def test_PUT_NEWDIRURL_exists(self):
1907 d = self.PUT(self.public_url + "/foo/sub?t=mkdir", "")
1908 d.addCallback(lambda res:
1909 self.failUnlessNodeHasChild(self._foo_node, u"sub"))
1910 d.addCallback(lambda res: self._foo_node.get(u"sub"))
1911 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
1914 def test_PUT_NEWDIRURL_blocked(self):
1915 d = self.shouldFail2(error.Error, "PUT_NEWDIRURL_blocked",
1916 "409 Conflict", "Unable to create directory 'bar.txt': a file was in the way",
1918 self.public_url + "/foo/bar.txt/sub?t=mkdir", "")
1919 d.addCallback(lambda res:
1920 self.failUnlessNodeHasChild(self._foo_node, u"sub"))
1921 d.addCallback(lambda res: self._foo_node.get(u"sub"))
1922 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
1925 def test_PUT_NEWDIRURL_mkdir_p(self):
1926 d = defer.succeed(None)
1927 d.addCallback(lambda res: self.POST(self.public_url + "/foo", t='mkdir', name='mkp'))
1928 d.addCallback(lambda res: self.failUnlessNodeHasChild(self._foo_node, u"mkp"))
1929 d.addCallback(lambda res: self._foo_node.get(u"mkp"))
1930 def mkdir_p(mkpnode):
1931 url = '/uri/%s?t=mkdir-p&path=/sub1/sub2' % urllib.quote(mkpnode.get_uri())
1933 def made_subsub(ssuri):
1934 d = self._foo_node.get_child_at_path(u"mkp/sub1/sub2")
1935 d.addCallback(lambda ssnode: self.failUnlessReallyEqual(ssnode.get_uri(), ssuri))
1937 d.addCallback(lambda uri2: self.failUnlessReallyEqual(uri2, ssuri))
1939 d.addCallback(made_subsub)
1941 d.addCallback(mkdir_p)
1944 def test_PUT_NEWDIRURL_mkdirs(self):
1945 d = self.PUT(self.public_url + "/foo/subdir/newdir?t=mkdir", "")
1946 d.addCallback(lambda res:
1947 self.failIfNodeHasChild(self._foo_node, u"newdir"))
1948 d.addCallback(lambda res:
1949 self.failUnlessNodeHasChild(self._foo_node, u"subdir"))
1950 d.addCallback(lambda res:
1951 self._foo_node.get_child_at_path(u"subdir/newdir"))
1952 d.addCallback(self.failUnlessNodeKeysAre, [])
1955 def test_PUT_NEWDIRURL_mkdirs_mdmf(self):
1956 d = self.PUT(self.public_url + "/foo/subdir/newdir?t=mkdir&mutable-type=mdmf", "")
1957 d.addCallback(lambda ignored:
1958 self.failUnlessNodeHasChild(self._foo_node, u"subdir"))
1959 d.addCallback(lambda ignored:
1960 self.failIfNodeHasChild(self._foo_node, u"newdir"))
1961 d.addCallback(lambda ignored:
1962 self._foo_node.get_child_at_path(u"subdir"))
1963 def _got_subdir(subdir):
1964 # XXX: What we want?
1965 #self.failUnlessEqual(subdir._node.get_version(), MDMF_VERSION)
1966 self.failUnlessNodeHasChild(subdir, u"newdir")
1967 return subdir.get_child_at_path(u"newdir")
1968 d.addCallback(_got_subdir)
1969 d.addCallback(lambda newdir:
1970 self.failUnlessEqual(newdir._node.get_version(), MDMF_VERSION))
1973 def test_PUT_NEWDIRURL_mkdirs_sdmf(self):
1974 d = self.PUT(self.public_url + "/foo/subdir/newdir?t=mkdir&mutable-type=sdmf", "")
1975 d.addCallback(lambda ignored:
1976 self.failUnlessNodeHasChild(self._foo_node, u"subdir"))
1977 d.addCallback(lambda ignored:
1978 self.failIfNodeHasChild(self._foo_node, u"newdir"))
1979 d.addCallback(lambda ignored:
1980 self._foo_node.get_child_at_path(u"subdir"))
1981 def _got_subdir(subdir):
1982 # XXX: What we want?
1983 #self.failUnlessEqual(subdir._node.get_version(), MDMF_VERSION)
1984 self.failUnlessNodeHasChild(subdir, u"newdir")
1985 return subdir.get_child_at_path(u"newdir")
1986 d.addCallback(_got_subdir)
1987 d.addCallback(lambda newdir:
1988 self.failUnlessEqual(newdir._node.get_version(), SDMF_VERSION))
1991 def test_PUT_NEWDIRURL_mkdirs_bad_mutable_type(self):
1992 return self.shouldHTTPError("test bad mutable type",
1993 400, "Bad Request", "Unknown type: foo",
1994 self.PUT, self.public_url + \
1995 "/foo/subdir/newdir?t=mkdir&mutable-type=foo",
1998 def test_DELETE_DIRURL(self):
1999 d = self.DELETE(self.public_url + "/foo")
2000 d.addCallback(lambda res:
2001 self.failIfNodeHasChild(self.public_root, u"foo"))
2004 def test_DELETE_DIRURL_missing(self):
2005 d = self.DELETE(self.public_url + "/foo/missing")
2006 d.addBoth(self.should404, "test_DELETE_DIRURL_missing")
2007 d.addCallback(lambda res:
2008 self.failUnlessNodeHasChild(self.public_root, u"foo"))
2011 def test_DELETE_DIRURL_missing2(self):
2012 d = self.DELETE(self.public_url + "/missing")
2013 d.addBoth(self.should404, "test_DELETE_DIRURL_missing2")
2016 def dump_root(self):
2018 w = webish.DirnodeWalkerMixin()
2019 def visitor(childpath, childnode, metadata):
2021 d = w.walk(self.public_root, visitor)
2024 def failUnlessNodeKeysAre(self, node, expected_keys):
2025 for k in expected_keys:
2026 assert isinstance(k, unicode)
2028 def _check(children):
2029 self.failUnlessReallyEqual(sorted(children.keys()), sorted(expected_keys))
2030 d.addCallback(_check)
2032 def failUnlessNodeHasChild(self, node, name):
2033 assert isinstance(name, unicode)
2035 def _check(children):
2036 self.failUnless(name in children)
2037 d.addCallback(_check)
2039 def failIfNodeHasChild(self, node, name):
2040 assert isinstance(name, unicode)
2042 def _check(children):
2043 self.failIf(name in children)
2044 d.addCallback(_check)
2047 def failUnlessChildContentsAre(self, node, name, expected_contents):
2048 assert isinstance(name, unicode)
2049 d = node.get_child_at_path(name)
2050 d.addCallback(lambda node: download_to_data(node))
2051 def _check(contents):
2052 self.failUnlessReallyEqual(contents, expected_contents)
2053 d.addCallback(_check)
2056 def failUnlessMutableChildContentsAre(self, node, name, expected_contents):
2057 assert isinstance(name, unicode)
2058 d = node.get_child_at_path(name)
2059 d.addCallback(lambda node: node.download_best_version())
2060 def _check(contents):
2061 self.failUnlessReallyEqual(contents, expected_contents)
2062 d.addCallback(_check)
2065 def failUnlessRWChildURIIs(self, node, name, expected_uri):
2066 assert isinstance(name, unicode)
2067 d = node.get_child_at_path(name)
2069 self.failUnless(child.is_unknown() or not child.is_readonly())
2070 self.failUnlessReallyEqual(child.get_uri(), expected_uri.strip())
2071 self.failUnlessReallyEqual(child.get_write_uri(), expected_uri.strip())
2072 expected_ro_uri = self._make_readonly(expected_uri)
2074 self.failUnlessReallyEqual(child.get_readonly_uri(), expected_ro_uri.strip())
2075 d.addCallback(_check)
2078 def failUnlessROChildURIIs(self, node, name, expected_uri):
2079 assert isinstance(name, unicode)
2080 d = node.get_child_at_path(name)
2082 self.failUnless(child.is_unknown() or child.is_readonly())
2083 self.failUnlessReallyEqual(child.get_write_uri(), None)
2084 self.failUnlessReallyEqual(child.get_uri(), expected_uri.strip())
2085 self.failUnlessReallyEqual(child.get_readonly_uri(), expected_uri.strip())
2086 d.addCallback(_check)
2089 def failUnlessURIMatchesRWChild(self, got_uri, node, name):
2090 assert isinstance(name, unicode)
2091 d = node.get_child_at_path(name)
2093 self.failUnless(child.is_unknown() or not child.is_readonly())
2094 self.failUnlessReallyEqual(child.get_uri(), got_uri.strip())
2095 self.failUnlessReallyEqual(child.get_write_uri(), got_uri.strip())
2096 expected_ro_uri = self._make_readonly(got_uri)
2098 self.failUnlessReallyEqual(child.get_readonly_uri(), expected_ro_uri.strip())
2099 d.addCallback(_check)
2102 def failUnlessURIMatchesROChild(self, got_uri, node, name):
2103 assert isinstance(name, unicode)
2104 d = node.get_child_at_path(name)
2106 self.failUnless(child.is_unknown() or child.is_readonly())
2107 self.failUnlessReallyEqual(child.get_write_uri(), None)
2108 self.failUnlessReallyEqual(got_uri.strip(), child.get_uri())
2109 self.failUnlessReallyEqual(got_uri.strip(), child.get_readonly_uri())
2110 d.addCallback(_check)
2113 def failUnlessCHKURIHasContents(self, got_uri, contents):
2114 self.failUnless(FakeCHKFileNode.all_contents[got_uri] == contents)
2116 def test_POST_upload(self):
2117 d = self.POST(self.public_url + "/foo", t="upload",
2118 file=("new.txt", self.NEWFILE_CONTENTS))
2120 d.addCallback(self.failUnlessURIMatchesROChild, fn, u"new.txt")
2121 d.addCallback(lambda res:
2122 self.failUnlessChildContentsAre(fn, u"new.txt",
2123 self.NEWFILE_CONTENTS))
2126 def test_POST_upload_unicode(self):
2127 filename = u"n\u00e9wer.txt" # n e-acute w e r . t x t
2128 d = self.POST(self.public_url + "/foo", t="upload",
2129 file=(filename, self.NEWFILE_CONTENTS))
2131 d.addCallback(self.failUnlessURIMatchesROChild, fn, filename)
2132 d.addCallback(lambda res:
2133 self.failUnlessChildContentsAre(fn, filename,
2134 self.NEWFILE_CONTENTS))
2135 target_url = self.public_url + "/foo/" + filename.encode("utf-8")
2136 d.addCallback(lambda res: self.GET(target_url))
2137 d.addCallback(lambda contents: self.failUnlessReallyEqual(contents,
2138 self.NEWFILE_CONTENTS,
2142 def test_POST_upload_unicode_named(self):
2143 filename = u"n\u00e9wer.txt" # n e-acute w e r . t x t
2144 d = self.POST(self.public_url + "/foo", t="upload",
2146 file=("overridden", self.NEWFILE_CONTENTS))
2148 d.addCallback(self.failUnlessURIMatchesROChild, fn, filename)
2149 d.addCallback(lambda res:
2150 self.failUnlessChildContentsAre(fn, filename,
2151 self.NEWFILE_CONTENTS))
2152 target_url = self.public_url + "/foo/" + filename.encode("utf-8")
2153 d.addCallback(lambda res: self.GET(target_url))
2154 d.addCallback(lambda contents: self.failUnlessReallyEqual(contents,
2155 self.NEWFILE_CONTENTS,
2159 def test_POST_upload_no_link(self):
2160 d = self.POST("/uri", t="upload",
2161 file=("new.txt", self.NEWFILE_CONTENTS))
2162 def _check_upload_results(page):
2163 # this should be a page which describes the results of the upload
2164 # that just finished.
2165 self.failUnless("Upload Results:" in page)
2166 self.failUnless("URI:" in page)
2167 uri_re = re.compile("URI: <tt><span>(.*)</span>")
2168 mo = uri_re.search(page)
2169 self.failUnless(mo, page)
2170 new_uri = mo.group(1)
2172 d.addCallback(_check_upload_results)
2173 d.addCallback(self.failUnlessCHKURIHasContents, self.NEWFILE_CONTENTS)
2176 def test_POST_upload_no_link_whendone(self):
2177 d = self.POST("/uri", t="upload", when_done="/",
2178 file=("new.txt", self.NEWFILE_CONTENTS))
2179 d.addBoth(self.shouldRedirect, "/")
2182 def shouldRedirect2(self, which, checker, callable, *args, **kwargs):
2183 d = defer.maybeDeferred(callable, *args, **kwargs)
2185 if isinstance(res, failure.Failure):
2186 res.trap(error.PageRedirect)
2187 statuscode = res.value.status
2188 target = res.value.location
2189 return checker(statuscode, target)
2190 self.fail("%s: callable was supposed to redirect, not return '%s'"
2195 def test_POST_upload_no_link_whendone_results(self):
2196 def check(statuscode, target):
2197 self.failUnlessReallyEqual(statuscode, str(http.FOUND))
2198 self.failUnless(target.startswith(self.webish_url), target)
2199 return client.getPage(target, method="GET")
2200 d = self.shouldRedirect2("test_POST_upload_no_link_whendone_results",
2202 self.POST, "/uri", t="upload",
2203 when_done="/uri/%(uri)s",
2204 file=("new.txt", self.NEWFILE_CONTENTS))
2205 d.addCallback(lambda res:
2206 self.failUnlessReallyEqual(res, self.NEWFILE_CONTENTS))
2209 def test_POST_upload_no_link_mutable(self):
2210 d = self.POST("/uri", t="upload", mutable="true",
2211 file=("new.txt", self.NEWFILE_CONTENTS))
2212 def _check(filecap):
2213 filecap = filecap.strip()
2214 self.failUnless(filecap.startswith("URI:SSK:"), filecap)
2215 self.filecap = filecap
2216 u = uri.WriteableSSKFileURI.init_from_string(filecap)
2217 self.failUnless(u.get_storage_index() in FakeMutableFileNode.all_contents)
2218 n = self.s.create_node_from_uri(filecap)
2219 return n.download_best_version()
2220 d.addCallback(_check)
2222 self.failUnlessReallyEqual(data, self.NEWFILE_CONTENTS)
2223 return self.GET("/uri/%s" % urllib.quote(self.filecap))
2224 d.addCallback(_check2)
2226 self.failUnlessReallyEqual(data, self.NEWFILE_CONTENTS)
2227 return self.GET("/file/%s" % urllib.quote(self.filecap))
2228 d.addCallback(_check3)
2230 self.failUnlessReallyEqual(data, self.NEWFILE_CONTENTS)
2231 d.addCallback(_check4)
2234 def test_POST_upload_no_link_mutable_toobig(self):
2235 # The SDMF size limit is no longer in place, so we should be
2236 # able to upload mutable files that are as large as we want them
2238 d = self.POST("/uri", t="upload", mutable="true",
2239 file=("new.txt", "b" * (self.s.MUTABLE_SIZELIMIT + 1)))
2243 def test_POST_upload_mutable_type_unlinked(self):
2244 d = self.POST("/uri?t=upload&mutable=true&mutable-type=sdmf",
2245 file=("sdmf.txt", self.NEWFILE_CONTENTS * 300000))
2246 d.addCallback(lambda filecap: self.GET("/uri/%s?t=json" % filecap))
2247 def _got_json(json, version):
2248 data = simplejson.loads(json)
2251 self.failUnlessIn("mutable-type", data)
2252 self.failUnlessEqual(data['mutable-type'], version)
2253 d.addCallback(_got_json, "sdmf")
2254 d.addCallback(lambda ignored:
2255 self.POST("/uri?t=upload&mutable=true&mutable-type=mdmf",
2256 file=('mdmf.txt', self.NEWFILE_CONTENTS * 300000)))
2257 def _got_filecap(filecap):
2258 self.failUnless(filecap.startswith("URI:MDMF"))
2260 d.addCallback(_got_filecap)
2261 d.addCallback(lambda filecap: self.GET("/uri/%s?t=json" % filecap))
2262 d.addCallback(_got_json, "mdmf")
2265 def test_POST_upload_mutable_type_unlinked_bad_mutable_type(self):
2266 return self.shouldHTTPError("test bad mutable type",
2267 400, "Bad Request", "Unknown type: foo",
2269 "/uri?5=upload&mutable=true&mutable-type=foo",
2270 file=("foo.txt", self.NEWFILE_CONTENTS * 300000))
2272 def test_POST_upload_mutable_type(self):
2273 d = self.POST(self.public_url + \
2274 "/foo?t=upload&mutable=true&mutable-type=sdmf",
2275 file=("sdmf.txt", self.NEWFILE_CONTENTS * 300000))
2277 def _got_cap(filecap, filename):
2278 filenameu = unicode(filename)
2279 self.failUnlessURIMatchesRWChild(filecap, fn, filenameu)
2280 return self.GET(self.public_url + "/foo/%s?t=json" % filename)
2281 def _got_mdmf_cap(filecap):
2282 self.failUnless(filecap.startswith("URI:MDMF"))
2284 d.addCallback(_got_cap, "sdmf.txt")
2285 def _got_json(json, version):
2286 data = simplejson.loads(json)
2289 self.failUnlessIn("mutable-type", data)
2290 self.failUnlessEqual(data['mutable-type'], version)
2291 d.addCallback(_got_json, "sdmf")
2292 d.addCallback(lambda ignored:
2293 self.POST(self.public_url + \
2294 "/foo?t=upload&mutable=true&mutable-type=mdmf",
2295 file=("mdmf.txt", self.NEWFILE_CONTENTS * 300000)))
2296 d.addCallback(_got_mdmf_cap)
2297 d.addCallback(_got_cap, "mdmf.txt")
2298 d.addCallback(_got_json, "mdmf")
2301 def test_POST_upload_bad_mutable_type(self):
2302 return self.shouldHTTPError("test bad mutable type",
2303 400, "Bad Request", "Unknown type: foo",
2304 self.POST, self.public_url + \
2305 "/foo?t=upload&mutable=true&mutable-type=foo",
2306 file=("foo.txt", self.NEWFILE_CONTENTS * 300000))
2308 def test_POST_upload_mutable(self):
2309 # this creates a mutable file
2310 d = self.POST(self.public_url + "/foo", t="upload", mutable="true",
2311 file=("new.txt", self.NEWFILE_CONTENTS))
2313 d.addCallback(self.failUnlessURIMatchesRWChild, fn, u"new.txt")
2314 d.addCallback(lambda res:
2315 self.failUnlessMutableChildContentsAre(fn, u"new.txt",
2316 self.NEWFILE_CONTENTS))
2317 d.addCallback(lambda res: self._foo_node.get(u"new.txt"))
2319 self.failUnless(IMutableFileNode.providedBy(newnode))
2320 self.failUnless(newnode.is_mutable())
2321 self.failIf(newnode.is_readonly())
2322 self._mutable_node = newnode
2323 self._mutable_uri = newnode.get_uri()
2326 # now upload it again and make sure that the URI doesn't change
2327 NEWER_CONTENTS = self.NEWFILE_CONTENTS + "newer\n"
2328 d.addCallback(lambda res:
2329 self.POST(self.public_url + "/foo", t="upload",
2331 file=("new.txt", NEWER_CONTENTS)))
2332 d.addCallback(self.failUnlessURIMatchesRWChild, fn, u"new.txt")
2333 d.addCallback(lambda res:
2334 self.failUnlessMutableChildContentsAre(fn, u"new.txt",
2336 d.addCallback(lambda res: self._foo_node.get(u"new.txt"))
2338 self.failUnless(IMutableFileNode.providedBy(newnode))
2339 self.failUnless(newnode.is_mutable())
2340 self.failIf(newnode.is_readonly())
2341 self.failUnlessReallyEqual(self._mutable_uri, newnode.get_uri())
2342 d.addCallback(_got2)
2344 # upload a second time, using PUT instead of POST
2345 NEW2_CONTENTS = NEWER_CONTENTS + "overwrite with PUT\n"
2346 d.addCallback(lambda res:
2347 self.PUT(self.public_url + "/foo/new.txt", NEW2_CONTENTS))
2348 d.addCallback(self.failUnlessURIMatchesRWChild, fn, u"new.txt")
2349 d.addCallback(lambda res:
2350 self.failUnlessMutableChildContentsAre(fn, u"new.txt",
2353 # finally list the directory, since mutable files are displayed
2354 # slightly differently
2356 d.addCallback(lambda res:
2357 self.GET(self.public_url + "/foo/",
2358 followRedirect=True))
2359 def _check_page(res):
2360 # TODO: assert more about the contents
2361 self.failUnless("SSK" in res)
2363 d.addCallback(_check_page)
2365 d.addCallback(lambda res: self._foo_node.get(u"new.txt"))
2367 self.failUnless(IMutableFileNode.providedBy(newnode))
2368 self.failUnless(newnode.is_mutable())
2369 self.failIf(newnode.is_readonly())
2370 self.failUnlessReallyEqual(self._mutable_uri, newnode.get_uri())
2371 d.addCallback(_got3)
2373 # look at the JSON form of the enclosing directory
2374 d.addCallback(lambda res:
2375 self.GET(self.public_url + "/foo/?t=json",
2376 followRedirect=True))
2377 def _check_page_json(res):
2378 parsed = simplejson.loads(res)
2379 self.failUnlessEqual(parsed[0], "dirnode")
2380 children = dict( [(unicode(name),value)
2382 in parsed[1]["children"].iteritems()] )
2383 self.failUnless(u"new.txt" in children)
2384 new_json = children[u"new.txt"]
2385 self.failUnlessEqual(new_json[0], "filenode")
2386 self.failUnless(new_json[1]["mutable"])
2387 self.failUnlessReallyEqual(to_str(new_json[1]["rw_uri"]), self._mutable_uri)
2388 ro_uri = self._mutable_node.get_readonly().to_string()
2389 self.failUnlessReallyEqual(to_str(new_json[1]["ro_uri"]), ro_uri)
2390 d.addCallback(_check_page_json)
2392 # and the JSON form of the file
2393 d.addCallback(lambda res:
2394 self.GET(self.public_url + "/foo/new.txt?t=json"))
2395 def _check_file_json(res):
2396 parsed = simplejson.loads(res)
2397 self.failUnlessEqual(parsed[0], "filenode")
2398 self.failUnless(parsed[1]["mutable"])
2399 self.failUnlessReallyEqual(to_str(parsed[1]["rw_uri"]), self._mutable_uri)
2400 ro_uri = self._mutable_node.get_readonly().to_string()
2401 self.failUnlessReallyEqual(to_str(parsed[1]["ro_uri"]), ro_uri)
2402 d.addCallback(_check_file_json)
2404 # and look at t=uri and t=readonly-uri
2405 d.addCallback(lambda res:
2406 self.GET(self.public_url + "/foo/new.txt?t=uri"))
2407 d.addCallback(lambda res: self.failUnlessReallyEqual(res, self._mutable_uri))
2408 d.addCallback(lambda res:
2409 self.GET(self.public_url + "/foo/new.txt?t=readonly-uri"))
2410 def _check_ro_uri(res):
2411 ro_uri = self._mutable_node.get_readonly().to_string()
2412 self.failUnlessReallyEqual(res, ro_uri)
2413 d.addCallback(_check_ro_uri)
2415 # make sure we can get to it from /uri/URI
2416 d.addCallback(lambda res:
2417 self.GET("/uri/%s" % urllib.quote(self._mutable_uri)))
2418 d.addCallback(lambda res:
2419 self.failUnlessReallyEqual(res, NEW2_CONTENTS))
2421 # and that HEAD computes the size correctly
2422 d.addCallback(lambda res:
2423 self.HEAD(self.public_url + "/foo/new.txt",
2424 return_response=True))
2425 def _got_headers((res, status, headers)):
2426 self.failUnlessReallyEqual(res, "")
2427 self.failUnlessReallyEqual(headers["content-length"][0],
2428 str(len(NEW2_CONTENTS)))
2429 self.failUnlessReallyEqual(headers["content-type"], ["text/plain"])
2430 d.addCallback(_got_headers)
2432 # make sure that outdated size limits aren't enforced anymore.
2433 d.addCallback(lambda ignored:
2434 self.POST(self.public_url + "/foo", t="upload",
2437 "b" * (self.s.MUTABLE_SIZELIMIT+1))))
2438 d.addErrback(self.dump_error)
2441 def test_POST_upload_mutable_toobig(self):
2442 # SDMF had a size limti that was removed a while ago. MDMF has
2443 # never had a size limit. Test to make sure that we do not
2444 # encounter errors when trying to upload large mutable files,
2445 # since there should be no coded prohibitions regarding large
2447 d = self.POST(self.public_url + "/foo",
2448 t="upload", mutable="true",
2449 file=("new.txt", "b" * (self.s.MUTABLE_SIZELIMIT + 1)))
2452 def dump_error(self, f):
2453 # if the web server returns an error code (like 400 Bad Request),
2454 # web.client.getPage puts the HTTP response body into the .response
2455 # attribute of the exception object that it gives back. It does not
2456 # appear in the Failure's repr(), so the ERROR that trial displays
2457 # will be rather terse and unhelpful. addErrback this method to the
2458 # end of your chain to get more information out of these errors.
2459 if f.check(error.Error):
2460 print "web.error.Error:"
2462 print f.value.response
2465 def test_POST_upload_replace(self):
2466 d = self.POST(self.public_url + "/foo", t="upload",
2467 file=("bar.txt", self.NEWFILE_CONTENTS))
2469 d.addCallback(self.failUnlessURIMatchesROChild, fn, u"bar.txt")
2470 d.addCallback(lambda res:
2471 self.failUnlessChildContentsAre(fn, u"bar.txt",
2472 self.NEWFILE_CONTENTS))
2475 def test_POST_upload_no_replace_ok(self):
2476 d = self.POST(self.public_url + "/foo?replace=false", t="upload",
2477 file=("new.txt", self.NEWFILE_CONTENTS))
2478 d.addCallback(lambda res: self.GET(self.public_url + "/foo/new.txt"))
2479 d.addCallback(lambda res: self.failUnlessReallyEqual(res,
2480 self.NEWFILE_CONTENTS))
2483 def test_POST_upload_no_replace_queryarg(self):
2484 d = self.POST(self.public_url + "/foo?replace=false", t="upload",
2485 file=("bar.txt", self.NEWFILE_CONTENTS))
2486 d.addBoth(self.shouldFail, error.Error,
2487 "POST_upload_no_replace_queryarg",
2489 "There was already a child by that name, and you asked me "
2490 "to not replace it")
2491 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
2492 d.addCallback(self.failUnlessIsBarDotTxt)
2495 def test_POST_upload_no_replace_field(self):
2496 d = self.POST(self.public_url + "/foo", t="upload", replace="false",
2497 file=("bar.txt", self.NEWFILE_CONTENTS))
2498 d.addBoth(self.shouldFail, error.Error, "POST_upload_no_replace_field",
2500 "There was already a child by that name, and you asked me "
2501 "to not replace it")
2502 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
2503 d.addCallback(self.failUnlessIsBarDotTxt)
2506 def test_POST_upload_whendone(self):
2507 d = self.POST(self.public_url + "/foo", t="upload", when_done="/THERE",
2508 file=("new.txt", self.NEWFILE_CONTENTS))
2509 d.addBoth(self.shouldRedirect, "/THERE")
2511 d.addCallback(lambda res:
2512 self.failUnlessChildContentsAre(fn, u"new.txt",
2513 self.NEWFILE_CONTENTS))
2516 def test_POST_upload_named(self):
2518 d = self.POST(self.public_url + "/foo", t="upload",
2519 name="new.txt", file=self.NEWFILE_CONTENTS)
2520 d.addCallback(self.failUnlessURIMatchesROChild, fn, u"new.txt")
2521 d.addCallback(lambda res:
2522 self.failUnlessChildContentsAre(fn, u"new.txt",
2523 self.NEWFILE_CONTENTS))
2526 def test_POST_upload_named_badfilename(self):
2527 d = self.POST(self.public_url + "/foo", t="upload",
2528 name="slashes/are/bad.txt", file=self.NEWFILE_CONTENTS)
2529 d.addBoth(self.shouldFail, error.Error,
2530 "test_POST_upload_named_badfilename",
2532 "name= may not contain a slash",
2534 # make sure that nothing was added
2535 d.addCallback(lambda res:
2536 self.failUnlessNodeKeysAre(self._foo_node,
2537 [u"bar.txt", u"baz.txt", u"blockingfile",
2538 u"empty", u"n\u00fc.txt", u"quux.txt",
2542 def test_POST_FILEURL_check(self):
2543 bar_url = self.public_url + "/foo/bar.txt"
2544 d = self.POST(bar_url, t="check")
2546 self.failUnless("Healthy :" in res)
2547 d.addCallback(_check)
2548 redir_url = "http://allmydata.org/TARGET"
2549 def _check2(statuscode, target):
2550 self.failUnlessReallyEqual(statuscode, str(http.FOUND))
2551 self.failUnlessReallyEqual(target, redir_url)
2552 d.addCallback(lambda res:
2553 self.shouldRedirect2("test_POST_FILEURL_check",
2557 when_done=redir_url))
2558 d.addCallback(lambda res:
2559 self.POST(bar_url, t="check", return_to=redir_url))
2561 self.failUnless("Healthy :" in res)
2562 self.failUnless("Return to file" in res)
2563 self.failUnless(redir_url in res)
2564 d.addCallback(_check3)
2566 d.addCallback(lambda res:
2567 self.POST(bar_url, t="check", output="JSON"))
2568 def _check_json(res):
2569 data = simplejson.loads(res)
2570 self.failUnless("storage-index" in data)
2571 self.failUnless(data["results"]["healthy"])
2572 d.addCallback(_check_json)
2576 def test_POST_FILEURL_check_and_repair(self):
2577 bar_url = self.public_url + "/foo/bar.txt"
2578 d = self.POST(bar_url, t="check", repair="true")
2580 self.failUnless("Healthy :" in res)
2581 d.addCallback(_check)
2582 redir_url = "http://allmydata.org/TARGET"
2583 def _check2(statuscode, target):
2584 self.failUnlessReallyEqual(statuscode, str(http.FOUND))
2585 self.failUnlessReallyEqual(target, redir_url)
2586 d.addCallback(lambda res:
2587 self.shouldRedirect2("test_POST_FILEURL_check_and_repair",
2590 t="check", repair="true",
2591 when_done=redir_url))
2592 d.addCallback(lambda res:
2593 self.POST(bar_url, t="check", return_to=redir_url))
2595 self.failUnless("Healthy :" in res)
2596 self.failUnless("Return to file" in res)
2597 self.failUnless(redir_url in res)
2598 d.addCallback(_check3)
2601 def test_POST_DIRURL_check(self):
2602 foo_url = self.public_url + "/foo/"
2603 d = self.POST(foo_url, t="check")
2605 self.failUnless("Healthy :" in res, res)
2606 d.addCallback(_check)
2607 redir_url = "http://allmydata.org/TARGET"
2608 def _check2(statuscode, target):
2609 self.failUnlessReallyEqual(statuscode, str(http.FOUND))
2610 self.failUnlessReallyEqual(target, redir_url)
2611 d.addCallback(lambda res:
2612 self.shouldRedirect2("test_POST_DIRURL_check",
2616 when_done=redir_url))
2617 d.addCallback(lambda res:
2618 self.POST(foo_url, t="check", return_to=redir_url))
2620 self.failUnless("Healthy :" in res, res)
2621 self.failUnless("Return to file/directory" in res)
2622 self.failUnless(redir_url in res)
2623 d.addCallback(_check3)
2625 d.addCallback(lambda res:
2626 self.POST(foo_url, t="check", output="JSON"))
2627 def _check_json(res):
2628 data = simplejson.loads(res)
2629 self.failUnless("storage-index" in data)
2630 self.failUnless(data["results"]["healthy"])
2631 d.addCallback(_check_json)
2635 def test_POST_DIRURL_check_and_repair(self):
2636 foo_url = self.public_url + "/foo/"
2637 d = self.POST(foo_url, t="check", repair="true")
2639 self.failUnless("Healthy :" in res, res)
2640 d.addCallback(_check)
2641 redir_url = "http://allmydata.org/TARGET"
2642 def _check2(statuscode, target):
2643 self.failUnlessReallyEqual(statuscode, str(http.FOUND))
2644 self.failUnlessReallyEqual(target, redir_url)
2645 d.addCallback(lambda res:
2646 self.shouldRedirect2("test_POST_DIRURL_check_and_repair",
2649 t="check", repair="true",
2650 when_done=redir_url))
2651 d.addCallback(lambda res:
2652 self.POST(foo_url, t="check", return_to=redir_url))
2654 self.failUnless("Healthy :" in res)
2655 self.failUnless("Return to file/directory" in res)
2656 self.failUnless(redir_url in res)
2657 d.addCallback(_check3)
2660 def test_POST_FILEURL_mdmf_check(self):
2661 quux_url = "/uri/%s" % urllib.quote(self._quux_txt_uri)
2662 d = self.POST(quux_url, t="check")
2664 self.failUnlessIn("Healthy", res)
2665 d.addCallback(_check)
2666 quux_extension_url = "/uri/%s" % urllib.quote("%s:3:131073" % self._quux_txt_uri)
2667 d.addCallback(lambda ignored:
2668 self.POST(quux_extension_url, t="check"))
2669 d.addCallback(_check)
2672 def test_POST_FILEURL_mdmf_check_and_repair(self):
2673 quux_url = "/uri/%s" % urllib.quote(self._quux_txt_uri)
2674 d = self.POST(quux_url, t="check", repair="true")
2676 self.failUnlessIn("Healthy", res)
2677 d.addCallback(_check)
2678 quux_extension_url = "/uri/%s" %\
2679 urllib.quote("%s:3:131073" % self._quux_txt_uri)
2680 d.addCallback(lambda ignored:
2681 self.POST(quux_extension_url, t="check", repair="true"))
2682 d.addCallback(_check)
2685 def wait_for_operation(self, ignored, ophandle):
2686 url = "/operations/" + ophandle
2687 url += "?t=status&output=JSON"
2690 data = simplejson.loads(res)
2691 if not data["finished"]:
2692 d = self.stall(delay=1.0)
2693 d.addCallback(self.wait_for_operation, ophandle)
2699 def get_operation_results(self, ignored, ophandle, output=None):
2700 url = "/operations/" + ophandle
2703 url += "&output=" + output
2706 if output and output.lower() == "json":
2707 return simplejson.loads(res)
2712 def test_POST_DIRURL_deepcheck_no_ophandle(self):
2713 d = self.shouldFail2(error.Error,
2714 "test_POST_DIRURL_deepcheck_no_ophandle",
2716 "slow operation requires ophandle=",
2717 self.POST, self.public_url, t="start-deep-check")
2720 def test_POST_DIRURL_deepcheck(self):
2721 def _check_redirect(statuscode, target):
2722 self.failUnlessReallyEqual(statuscode, str(http.FOUND))
2723 self.failUnless(target.endswith("/operations/123"))
2724 d = self.shouldRedirect2("test_POST_DIRURL_deepcheck", _check_redirect,
2725 self.POST, self.public_url,
2726 t="start-deep-check", ophandle="123")
2727 d.addCallback(self.wait_for_operation, "123")
2728 def _check_json(data):
2729 self.failUnlessReallyEqual(data["finished"], True)
2730 self.failUnlessReallyEqual(data["count-objects-checked"], 10)
2731 self.failUnlessReallyEqual(data["count-objects-healthy"], 10)
2732 d.addCallback(_check_json)
2733 d.addCallback(self.get_operation_results, "123", "html")
2734 def _check_html(res):
2735 self.failUnless("Objects Checked: <span>10</span>" in res)
2736 self.failUnless("Objects Healthy: <span>10</span>" in res)
2737 d.addCallback(_check_html)
2739 d.addCallback(lambda res:
2740 self.GET("/operations/123/"))
2741 d.addCallback(_check_html) # should be the same as without the slash
2743 d.addCallback(lambda res:
2744 self.shouldFail2(error.Error, "one", "404 Not Found",
2745 "No detailed results for SI bogus",
2746 self.GET, "/operations/123/bogus"))
2748 foo_si = self._foo_node.get_storage_index()
2749 foo_si_s = base32.b2a(foo_si)
2750 d.addCallback(lambda res:
2751 self.GET("/operations/123/%s?output=JSON" % foo_si_s))
2752 def _check_foo_json(res):
2753 data = simplejson.loads(res)
2754 self.failUnlessEqual(data["storage-index"], foo_si_s)
2755 self.failUnless(data["results"]["healthy"])
2756 d.addCallback(_check_foo_json)
2759 def test_POST_DIRURL_deepcheck_and_repair(self):
2760 d = self.POST(self.public_url, t="start-deep-check", repair="true",
2761 ophandle="124", output="json", followRedirect=True)
2762 d.addCallback(self.wait_for_operation, "124")
2763 def _check_json(data):
2764 self.failUnlessReallyEqual(data["finished"], True)
2765 self.failUnlessReallyEqual(data["count-objects-checked"], 10)
2766 self.failUnlessReallyEqual(data["count-objects-healthy-pre-repair"], 10)
2767 self.failUnlessReallyEqual(data["count-objects-unhealthy-pre-repair"], 0)
2768 self.failUnlessReallyEqual(data["count-corrupt-shares-pre-repair"], 0)
2769 self.failUnlessReallyEqual(data["count-repairs-attempted"], 0)
2770 self.failUnlessReallyEqual(data["count-repairs-successful"], 0)
2771 self.failUnlessReallyEqual(data["count-repairs-unsuccessful"], 0)
2772 self.failUnlessReallyEqual(data["count-objects-healthy-post-repair"], 10)
2773 self.failUnlessReallyEqual(data["count-objects-unhealthy-post-repair"], 0)
2774 self.failUnlessReallyEqual(data["count-corrupt-shares-post-repair"], 0)
2775 d.addCallback(_check_json)
2776 d.addCallback(self.get_operation_results, "124", "html")
2777 def _check_html(res):
2778 self.failUnless("Objects Checked: <span>10</span>" in res)
2780 self.failUnless("Objects Healthy (before repair): <span>10</span>" in res)
2781 self.failUnless("Objects Unhealthy (before repair): <span>0</span>" in res)
2782 self.failUnless("Corrupt Shares (before repair): <span>0</span>" in res)
2784 self.failUnless("Repairs Attempted: <span>0</span>" in res)
2785 self.failUnless("Repairs Successful: <span>0</span>" in res)
2786 self.failUnless("Repairs Unsuccessful: <span>0</span>" in res)
2788 self.failUnless("Objects Healthy (after repair): <span>10</span>" in res)
2789 self.failUnless("Objects Unhealthy (after repair): <span>0</span>" in res)
2790 self.failUnless("Corrupt Shares (after repair): <span>0</span>" in res)
2791 d.addCallback(_check_html)
2794 def test_POST_FILEURL_bad_t(self):
2795 d = self.shouldFail2(error.Error, "POST_bad_t", "400 Bad Request",
2796 "POST to file: bad t=bogus",
2797 self.POST, self.public_url + "/foo/bar.txt",
2801 def test_POST_mkdir(self): # return value?
2802 d = self.POST(self.public_url + "/foo", t="mkdir", name="newdir")
2803 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2804 d.addCallback(self.failUnlessNodeKeysAre, [])
2807 def test_POST_mkdir_mdmf(self):
2808 d = self.POST(self.public_url + "/foo?t=mkdir&name=newdir&mutable-type=mdmf")
2809 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2810 d.addCallback(lambda node:
2811 self.failUnlessEqual(node._node.get_version(), MDMF_VERSION))
2814 def test_POST_mkdir_sdmf(self):
2815 d = self.POST(self.public_url + "/foo?t=mkdir&name=newdir&mutable-type=sdmf")
2816 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2817 d.addCallback(lambda node:
2818 self.failUnlessEqual(node._node.get_version(), SDMF_VERSION))
2821 def test_POST_mkdir_bad_mutable_type(self):
2822 return self.shouldHTTPError("test bad mutable type",
2823 400, "Bad Request", "Unknown type: foo",
2824 self.POST, self.public_url + \
2825 "/foo?t=mkdir&name=newdir&mutable-type=foo")
2827 def test_POST_mkdir_initial_children(self):
2828 (newkids, caps) = self._create_initial_children()
2829 d = self.POST2(self.public_url +
2830 "/foo?t=mkdir-with-children&name=newdir",
2831 simplejson.dumps(newkids))
2832 d.addCallback(lambda res:
2833 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
2834 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2835 d.addCallback(self.failUnlessNodeKeysAre, newkids.keys())
2836 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2837 d.addCallback(self.failUnlessROChildURIIs, u"child-imm", caps['filecap1'])
2840 def test_POST_mkdir_initial_children_mdmf(self):
2841 (newkids, caps) = self._create_initial_children()
2842 d = self.POST2(self.public_url +
2843 "/foo?t=mkdir-with-children&name=newdir&mutable-type=mdmf",
2844 simplejson.dumps(newkids))
2845 d.addCallback(lambda res:
2846 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
2847 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2848 d.addCallback(lambda node:
2849 self.failUnlessEqual(node._node.get_version(), MDMF_VERSION))
2850 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2851 d.addCallback(self.failUnlessROChildURIIs, u"child-imm",
2856 def test_POST_mkdir_initial_children_sdmf(self):
2857 (newkids, caps) = self._create_initial_children()
2858 d = self.POST2(self.public_url +
2859 "/foo?t=mkdir-with-children&name=newdir&mutable-type=sdmf",
2860 simplejson.dumps(newkids))
2861 d.addCallback(lambda res:
2862 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
2863 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2864 d.addCallback(lambda node:
2865 self.failUnlessEqual(node._node.get_version(), SDMF_VERSION))
2866 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2867 d.addCallback(self.failUnlessROChildURIIs, u"child-imm",
2871 def test_POST_mkdir_initial_children_bad_mutable_type(self):
2872 (newkids, caps) = self._create_initial_children()
2873 return self.shouldHTTPError("test bad mutable type",
2874 400, "Bad Request", "Unknown type: foo",
2875 self.POST, self.public_url + \
2876 "/foo?t=mkdir-with-children&name=newdir&mutable-type=foo",
2877 simplejson.dumps(newkids))
2879 def test_POST_mkdir_immutable(self):
2880 (newkids, caps) = self._create_immutable_children()
2881 d = self.POST2(self.public_url +
2882 "/foo?t=mkdir-immutable&name=newdir",
2883 simplejson.dumps(newkids))
2884 d.addCallback(lambda res:
2885 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
2886 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2887 d.addCallback(self.failUnlessNodeKeysAre, newkids.keys())
2888 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2889 d.addCallback(self.failUnlessROChildURIIs, u"child-imm", caps['filecap1'])
2890 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2891 d.addCallback(self.failUnlessROChildURIIs, u"unknownchild-imm", caps['unknown_immcap'])
2892 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2893 d.addCallback(self.failUnlessROChildURIIs, u"dirchild-imm", caps['immdircap'])
2894 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2895 d.addCallback(self.failUnlessROChildURIIs, u"dirchild-lit", caps['litdircap'])
2896 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2897 d.addCallback(self.failUnlessROChildURIIs, u"dirchild-empty", caps['emptydircap'])
2900 def test_POST_mkdir_immutable_bad(self):
2901 (newkids, caps) = self._create_initial_children()
2902 d = self.shouldFail2(error.Error, "test_POST_mkdir_immutable_bad",
2904 "needed to be immutable but was not",
2907 "/foo?t=mkdir-immutable&name=newdir",
2908 simplejson.dumps(newkids))
2911 def test_POST_mkdir_2(self):
2912 d = self.POST(self.public_url + "/foo/newdir?t=mkdir", "")
2913 d.addCallback(lambda res:
2914 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
2915 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2916 d.addCallback(self.failUnlessNodeKeysAre, [])
2919 def test_POST_mkdirs_2(self):
2920 d = self.POST(self.public_url + "/foo/bardir/newdir?t=mkdir", "")
2921 d.addCallback(lambda res:
2922 self.failUnlessNodeHasChild(self._foo_node, u"bardir"))
2923 d.addCallback(lambda res: self._foo_node.get(u"bardir"))
2924 d.addCallback(lambda bardirnode: bardirnode.get(u"newdir"))
2925 d.addCallback(self.failUnlessNodeKeysAre, [])
2928 def test_POST_mkdir_no_parentdir_noredirect(self):
2929 d = self.POST("/uri?t=mkdir")
2930 def _after_mkdir(res):
2931 uri.DirectoryURI.init_from_string(res)
2932 d.addCallback(_after_mkdir)
2935 def test_POST_mkdir_no_parentdir_noredirect_mdmf(self):
2936 d = self.POST("/uri?t=mkdir&mutable-type=mdmf")
2937 def _after_mkdir(res):
2938 u = uri.from_string(res)
2939 # Check that this is an MDMF writecap
2940 self.failUnlessIsInstance(u, uri.MDMFDirectoryURI)
2941 d.addCallback(_after_mkdir)
2944 def test_POST_mkdir_no_parentdir_noredirect_sdmf(self):
2945 d = self.POST("/uri?t=mkdir&mutable-type=sdmf")
2946 def _after_mkdir(res):
2947 u = uri.from_string(res)
2948 self.failUnlessIsInstance(u, uri.DirectoryURI)
2949 d.addCallback(_after_mkdir)
2952 def test_POST_mkdir_no_parentdir_noredirect_bad_mutable_type(self):
2953 return self.shouldHTTPError("test bad mutable type",
2954 400, "Bad Request", "Unknown type: foo",
2955 self.POST, self.public_url + \
2956 "/uri?t=mkdir&mutable-type=foo")
2958 def test_POST_mkdir_no_parentdir_noredirect2(self):
2959 # make sure form-based arguments (as on the welcome page) still work
2960 d = self.POST("/uri", t="mkdir")
2961 def _after_mkdir(res):
2962 uri.DirectoryURI.init_from_string(res)
2963 d.addCallback(_after_mkdir)
2964 d.addErrback(self.explain_web_error)
2967 def test_POST_mkdir_no_parentdir_redirect(self):
2968 d = self.POST("/uri?t=mkdir&redirect_to_result=true")
2969 d.addBoth(self.shouldRedirect, None, statuscode='303')
2970 def _check_target(target):
2971 target = urllib.unquote(target)
2972 self.failUnless(target.startswith("uri/URI:DIR2:"), target)
2973 d.addCallback(_check_target)
2976 def test_POST_mkdir_no_parentdir_redirect2(self):
2977 d = self.POST("/uri", t="mkdir", redirect_to_result="true")
2978 d.addBoth(self.shouldRedirect, None, statuscode='303')
2979 def _check_target(target):
2980 target = urllib.unquote(target)
2981 self.failUnless(target.startswith("uri/URI:DIR2:"), target)
2982 d.addCallback(_check_target)
2983 d.addErrback(self.explain_web_error)
2986 def _make_readonly(self, u):
2987 ro_uri = uri.from_string(u).get_readonly()
2990 return ro_uri.to_string()
2992 def _create_initial_children(self):
2993 contents, n, filecap1 = self.makefile(12)
2994 md1 = {"metakey1": "metavalue1"}
2995 filecap2 = make_mutable_file_uri()
2996 node3 = self.s.create_node_from_uri(make_mutable_file_uri())
2997 filecap3 = node3.get_readonly_uri()
2998 node4 = self.s.create_node_from_uri(make_mutable_file_uri())
2999 dircap = DirectoryNode(node4, None, None).get_uri()
3000 mdmfcap = make_mutable_file_uri(mdmf=True)
3001 litdircap = "URI:DIR2-LIT:ge3dumj2mewdcotyfqydulbshj5x2lbm"
3002 emptydircap = "URI:DIR2-LIT:"
3003 newkids = {u"child-imm": ["filenode", {"rw_uri": filecap1,
3004 "ro_uri": self._make_readonly(filecap1),
3005 "metadata": md1, }],
3006 u"child-mutable": ["filenode", {"rw_uri": filecap2,
3007 "ro_uri": self._make_readonly(filecap2)}],
3008 u"child-mutable-ro": ["filenode", {"ro_uri": filecap3}],
3009 u"unknownchild-rw": ["unknown", {"rw_uri": unknown_rwcap,
3010 "ro_uri": unknown_rocap}],
3011 u"unknownchild-ro": ["unknown", {"ro_uri": unknown_rocap}],
3012 u"unknownchild-imm": ["unknown", {"ro_uri": unknown_immcap}],
3013 u"dirchild": ["dirnode", {"rw_uri": dircap,
3014 "ro_uri": self._make_readonly(dircap)}],
3015 u"dirchild-lit": ["dirnode", {"ro_uri": litdircap}],
3016 u"dirchild-empty": ["dirnode", {"ro_uri": emptydircap}],
3017 u"child-mutable-mdmf": ["filenode", {"rw_uri": mdmfcap,
3018 "ro_uri": self._make_readonly(mdmfcap)}],
3020 return newkids, {'filecap1': filecap1,
3021 'filecap2': filecap2,
3022 'filecap3': filecap3,
3023 'unknown_rwcap': unknown_rwcap,
3024 'unknown_rocap': unknown_rocap,
3025 'unknown_immcap': unknown_immcap,
3027 'litdircap': litdircap,
3028 'emptydircap': emptydircap,
3031 def _create_immutable_children(self):
3032 contents, n, filecap1 = self.makefile(12)
3033 md1 = {"metakey1": "metavalue1"}
3034 tnode = create_chk_filenode("immutable directory contents\n"*10)
3035 dnode = DirectoryNode(tnode, None, None)
3036 assert not dnode.is_mutable()
3037 immdircap = dnode.get_uri()
3038 litdircap = "URI:DIR2-LIT:ge3dumj2mewdcotyfqydulbshj5x2lbm"
3039 emptydircap = "URI:DIR2-LIT:"
3040 newkids = {u"child-imm": ["filenode", {"ro_uri": filecap1,
3041 "metadata": md1, }],
3042 u"unknownchild-imm": ["unknown", {"ro_uri": unknown_immcap}],
3043 u"dirchild-imm": ["dirnode", {"ro_uri": immdircap}],
3044 u"dirchild-lit": ["dirnode", {"ro_uri": litdircap}],
3045 u"dirchild-empty": ["dirnode", {"ro_uri": emptydircap}],
3047 return newkids, {'filecap1': filecap1,
3048 'unknown_immcap': unknown_immcap,
3049 'immdircap': immdircap,
3050 'litdircap': litdircap,
3051 'emptydircap': emptydircap}
3053 def test_POST_mkdir_no_parentdir_initial_children(self):
3054 (newkids, caps) = self._create_initial_children()
3055 d = self.POST2("/uri?t=mkdir-with-children", simplejson.dumps(newkids))
3056 def _after_mkdir(res):
3057 self.failUnless(res.startswith("URI:DIR"), res)
3058 n = self.s.create_node_from_uri(res)
3059 d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
3060 d2.addCallback(lambda ign:
3061 self.failUnlessROChildURIIs(n, u"child-imm",
3063 d2.addCallback(lambda ign:
3064 self.failUnlessRWChildURIIs(n, u"child-mutable",
3066 d2.addCallback(lambda ign:
3067 self.failUnlessROChildURIIs(n, u"child-mutable-ro",
3069 d2.addCallback(lambda ign:
3070 self.failUnlessRWChildURIIs(n, u"unknownchild-rw",
3071 caps['unknown_rwcap']))
3072 d2.addCallback(lambda ign:
3073 self.failUnlessROChildURIIs(n, u"unknownchild-ro",
3074 caps['unknown_rocap']))
3075 d2.addCallback(lambda ign:
3076 self.failUnlessROChildURIIs(n, u"unknownchild-imm",
3077 caps['unknown_immcap']))
3078 d2.addCallback(lambda ign:
3079 self.failUnlessRWChildURIIs(n, u"dirchild",
3082 d.addCallback(_after_mkdir)
3085 def test_POST_mkdir_no_parentdir_unexpected_children(self):
3086 # the regular /uri?t=mkdir operation is specified to ignore its body.
3087 # Only t=mkdir-with-children pays attention to it.
3088 (newkids, caps) = self._create_initial_children()
3089 d = self.shouldHTTPError("POST t=mkdir unexpected children",
3091 "t=mkdir does not accept children=, "
3092 "try t=mkdir-with-children instead",
3093 self.POST2, "/uri?t=mkdir", # without children
3094 simplejson.dumps(newkids))
3097 def test_POST_noparent_bad(self):
3098 d = self.shouldHTTPError("POST /uri?t=bogus", 400, "Bad Request",
3099 "/uri accepts only PUT, PUT?t=mkdir, "
3100 "POST?t=upload, and POST?t=mkdir",
3101 self.POST, "/uri?t=bogus")
3104 def test_POST_mkdir_no_parentdir_immutable(self):
3105 (newkids, caps) = self._create_immutable_children()
3106 d = self.POST2("/uri?t=mkdir-immutable", simplejson.dumps(newkids))
3107 def _after_mkdir(res):
3108 self.failUnless(res.startswith("URI:DIR"), res)
3109 n = self.s.create_node_from_uri(res)
3110 d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
3111 d2.addCallback(lambda ign:
3112 self.failUnlessROChildURIIs(n, u"child-imm",
3114 d2.addCallback(lambda ign:
3115 self.failUnlessROChildURIIs(n, u"unknownchild-imm",
3116 caps['unknown_immcap']))
3117 d2.addCallback(lambda ign:
3118 self.failUnlessROChildURIIs(n, u"dirchild-imm",
3120 d2.addCallback(lambda ign:
3121 self.failUnlessROChildURIIs(n, u"dirchild-lit",
3123 d2.addCallback(lambda ign:
3124 self.failUnlessROChildURIIs(n, u"dirchild-empty",
3125 caps['emptydircap']))
3127 d.addCallback(_after_mkdir)
3130 def test_POST_mkdir_no_parentdir_immutable_bad(self):
3131 (newkids, caps) = self._create_initial_children()
3132 d = self.shouldFail2(error.Error,
3133 "test_POST_mkdir_no_parentdir_immutable_bad",
3135 "needed to be immutable but was not",
3137 "/uri?t=mkdir-immutable",
3138 simplejson.dumps(newkids))
3141 def test_welcome_page_mkdir_button(self):
3142 # Fetch the welcome page.
3144 def _after_get_welcome_page(res):
3145 MKDIR_BUTTON_RE = re.compile(
3146 '<form action="([^"]*)" method="post".*?'
3147 '<input type="hidden" name="t" value="([^"]*)" />'
3148 '<input type="hidden" name="([^"]*)" value="([^"]*)" />'
3149 '<input type="submit" value="Create a directory" />',
3151 mo = MKDIR_BUTTON_RE.search(res)
3152 formaction = mo.group(1)
3154 formaname = mo.group(3)
3155 formavalue = mo.group(4)
3156 return (formaction, formt, formaname, formavalue)
3157 d.addCallback(_after_get_welcome_page)
3158 def _after_parse_form(res):
3159 (formaction, formt, formaname, formavalue) = res
3160 return self.POST("/%s?t=%s&%s=%s" % (formaction, formt, formaname, formavalue))
3161 d.addCallback(_after_parse_form)
3162 d.addBoth(self.shouldRedirect, None, statuscode='303')
3165 def test_POST_mkdir_replace(self): # return value?
3166 d = self.POST(self.public_url + "/foo", t="mkdir", name="sub")
3167 d.addCallback(lambda res: self._foo_node.get(u"sub"))
3168 d.addCallback(self.failUnlessNodeKeysAre, [])
3171 def test_POST_mkdir_no_replace_queryarg(self): # return value?
3172 d = self.POST(self.public_url + "/foo?replace=false", t="mkdir", name="sub")
3173 d.addBoth(self.shouldFail, error.Error,
3174 "POST_mkdir_no_replace_queryarg",
3176 "There was already a child by that name, and you asked me "
3177 "to not replace it")
3178 d.addCallback(lambda res: self._foo_node.get(u"sub"))
3179 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
3182 def test_POST_mkdir_no_replace_field(self): # return value?
3183 d = self.POST(self.public_url + "/foo", t="mkdir", name="sub",
3185 d.addBoth(self.shouldFail, error.Error, "POST_mkdir_no_replace_field",
3187 "There was already a child by that name, and you asked me "
3188 "to not replace it")
3189 d.addCallback(lambda res: self._foo_node.get(u"sub"))
3190 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
3193 def test_POST_mkdir_whendone_field(self):
3194 d = self.POST(self.public_url + "/foo",
3195 t="mkdir", name="newdir", when_done="/THERE")
3196 d.addBoth(self.shouldRedirect, "/THERE")
3197 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
3198 d.addCallback(self.failUnlessNodeKeysAre, [])
3201 def test_POST_mkdir_whendone_queryarg(self):
3202 d = self.POST(self.public_url + "/foo?when_done=/THERE",
3203 t="mkdir", name="newdir")
3204 d.addBoth(self.shouldRedirect, "/THERE")
3205 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
3206 d.addCallback(self.failUnlessNodeKeysAre, [])
3209 def test_POST_bad_t(self):
3210 d = self.shouldFail2(error.Error, "POST_bad_t", "400 Bad Request",
3211 "POST to a directory with bad t=BOGUS",
3212 self.POST, self.public_url + "/foo", t="BOGUS")
3215 def test_POST_set_children(self, command_name="set_children"):
3216 contents9, n9, newuri9 = self.makefile(9)
3217 contents10, n10, newuri10 = self.makefile(10)
3218 contents11, n11, newuri11 = self.makefile(11)
3221 "atomic_added_1": [ "filenode", { "rw_uri": "%s",
3224 "ctime": 1002777696.7564139,
3225 "mtime": 1002777696.7564139
3228 "atomic_added_2": [ "filenode", { "rw_uri": "%s",
3231 "ctime": 1002777696.7564139,
3232 "mtime": 1002777696.7564139
3235 "atomic_added_3": [ "filenode", { "rw_uri": "%s",
3238 "ctime": 1002777696.7564139,
3239 "mtime": 1002777696.7564139
3242 }""" % (newuri9, newuri10, newuri11)
3244 url = self.webish_url + self.public_url + "/foo" + "?t=" + command_name
3246 d = client.getPage(url, method="POST", postdata=reqbody)
3248 self.failUnlessURIMatchesROChild(newuri9, self._foo_node, u"atomic_added_1")
3249 self.failUnlessURIMatchesROChild(newuri10, self._foo_node, u"atomic_added_2")
3250 self.failUnlessURIMatchesROChild(newuri11, self._foo_node, u"atomic_added_3")
3252 d.addCallback(_then)
3253 d.addErrback(self.dump_error)
3256 def test_POST_set_children_with_hyphen(self):
3257 return self.test_POST_set_children(command_name="set-children")
3259 def test_POST_link_uri(self):
3260 contents, n, newuri = self.makefile(8)
3261 d = self.POST(self.public_url + "/foo", t="uri", name="new.txt", uri=newuri)
3262 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"new.txt")
3263 d.addCallback(lambda res:
3264 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
3268 def test_POST_link_uri_replace(self):
3269 contents, n, newuri = self.makefile(8)
3270 d = self.POST(self.public_url + "/foo", t="uri", name="bar.txt", uri=newuri)
3271 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"bar.txt")
3272 d.addCallback(lambda res:
3273 self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
3277 def test_POST_link_uri_unknown_bad(self):
3278 d = self.POST(self.public_url + "/foo", t="uri", name="future.txt", uri=unknown_rwcap)
3279 d.addBoth(self.shouldFail, error.Error,
3280 "POST_link_uri_unknown_bad",
3282 "unknown cap in a write slot")
3285 def test_POST_link_uri_unknown_ro_good(self):
3286 d = self.POST(self.public_url + "/foo", t="uri", name="future-ro.txt", uri=unknown_rocap)
3287 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"future-ro.txt")
3290 def test_POST_link_uri_unknown_imm_good(self):
3291 d = self.POST(self.public_url + "/foo", t="uri", name="future-imm.txt", uri=unknown_immcap)
3292 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"future-imm.txt")
3295 def test_POST_link_uri_no_replace_queryarg(self):
3296 contents, n, newuri = self.makefile(8)
3297 d = self.POST(self.public_url + "/foo?replace=false", t="uri",
3298 name="bar.txt", uri=newuri)
3299 d.addBoth(self.shouldFail, error.Error,
3300 "POST_link_uri_no_replace_queryarg",
3302 "There was already a child by that name, and you asked me "
3303 "to not replace it")
3304 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
3305 d.addCallback(self.failUnlessIsBarDotTxt)
3308 def test_POST_link_uri_no_replace_field(self):
3309 contents, n, newuri = self.makefile(8)
3310 d = self.POST(self.public_url + "/foo", t="uri", replace="false",
3311 name="bar.txt", uri=newuri)
3312 d.addBoth(self.shouldFail, error.Error,
3313 "POST_link_uri_no_replace_field",
3315 "There was already a child by that name, and you asked me "
3316 "to not replace it")
3317 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
3318 d.addCallback(self.failUnlessIsBarDotTxt)
3321 def test_POST_delete(self, command_name='delete'):
3322 d = self._foo_node.list()
3323 def _check_before(children):
3324 self.failUnless(u"bar.txt" in children)
3325 d.addCallback(_check_before)
3326 d.addCallback(lambda res: self.POST(self.public_url + "/foo", t=command_name, name="bar.txt"))
3327 d.addCallback(lambda res: self._foo_node.list())
3328 def _check_after(children):
3329 self.failIf(u"bar.txt" in children)
3330 d.addCallback(_check_after)
3333 def test_POST_unlink(self):
3334 return self.test_POST_delete(command_name='unlink')
3336 def test_POST_rename_file(self):
3337 d = self.POST(self.public_url + "/foo", t="rename",
3338 from_name="bar.txt", to_name='wibble.txt')
3339 d.addCallback(lambda res:
3340 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
3341 d.addCallback(lambda res:
3342 self.failUnlessNodeHasChild(self._foo_node, u"wibble.txt"))
3343 d.addCallback(lambda res: self.GET(self.public_url + "/foo/wibble.txt"))
3344 d.addCallback(self.failUnlessIsBarDotTxt)
3345 d.addCallback(lambda res: self.GET(self.public_url + "/foo/wibble.txt?t=json"))
3346 d.addCallback(self.failUnlessIsBarJSON)
3349 def test_POST_rename_file_redundant(self):
3350 d = self.POST(self.public_url + "/foo", t="rename",
3351 from_name="bar.txt", to_name='bar.txt')
3352 d.addCallback(lambda res:
3353 self.failUnlessNodeHasChild(self._foo_node, u"bar.txt"))
3354 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
3355 d.addCallback(self.failUnlessIsBarDotTxt)
3356 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt?t=json"))
3357 d.addCallback(self.failUnlessIsBarJSON)
3360 def test_POST_rename_file_replace(self):
3361 # rename a file and replace a directory with it
3362 d = self.POST(self.public_url + "/foo", t="rename",
3363 from_name="bar.txt", to_name='empty')
3364 d.addCallback(lambda res:
3365 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
3366 d.addCallback(lambda res:
3367 self.failUnlessNodeHasChild(self._foo_node, u"empty"))
3368 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty"))
3369 d.addCallback(self.failUnlessIsBarDotTxt)
3370 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
3371 d.addCallback(self.failUnlessIsBarJSON)
3374 def test_POST_rename_file_no_replace_queryarg(self):
3375 # rename a file and replace a directory with it
3376 d = self.POST(self.public_url + "/foo?replace=false", t="rename",
3377 from_name="bar.txt", to_name='empty')
3378 d.addBoth(self.shouldFail, error.Error,
3379 "POST_rename_file_no_replace_queryarg",
3381 "There was already a child by that name, and you asked me "
3382 "to not replace it")
3383 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
3384 d.addCallback(self.failUnlessIsEmptyJSON)
3387 def test_POST_rename_file_no_replace_field(self):
3388 # rename a file and replace a directory with it
3389 d = self.POST(self.public_url + "/foo", t="rename", replace="false",
3390 from_name="bar.txt", to_name='empty')
3391 d.addBoth(self.shouldFail, error.Error,
3392 "POST_rename_file_no_replace_field",
3394 "There was already a child by that name, and you asked me "
3395 "to not replace it")
3396 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
3397 d.addCallback(self.failUnlessIsEmptyJSON)
3400 def failUnlessIsEmptyJSON(self, res):
3401 data = simplejson.loads(res)
3402 self.failUnlessEqual(data[0], "dirnode", data)
3403 self.failUnlessReallyEqual(len(data[1]["children"]), 0)
3405 def test_POST_rename_file_slash_fail(self):
3406 d = self.POST(self.public_url + "/foo", t="rename",
3407 from_name="bar.txt", to_name='kirk/spock.txt')
3408 d.addBoth(self.shouldFail, error.Error,
3409 "test_POST_rename_file_slash_fail",
3411 "to_name= may not contain a slash",
3413 d.addCallback(lambda res:
3414 self.failUnlessNodeHasChild(self._foo_node, u"bar.txt"))
3417 def test_POST_rename_dir(self):
3418 d = self.POST(self.public_url, t="rename",
3419 from_name="foo", to_name='plunk')
3420 d.addCallback(lambda res:
3421 self.failIfNodeHasChild(self.public_root, u"foo"))
3422 d.addCallback(lambda res:
3423 self.failUnlessNodeHasChild(self.public_root, u"plunk"))
3424 d.addCallback(lambda res: self.GET(self.public_url + "/plunk?t=json"))
3425 d.addCallback(self.failUnlessIsFooJSON)
3428 def shouldRedirect(self, res, target=None, statuscode=None, which=""):
3429 """ If target is not None then the redirection has to go to target. If
3430 statuscode is not None then the redirection has to be accomplished with
3431 that HTTP status code."""
3432 if not isinstance(res, failure.Failure):
3433 to_where = (target is None) and "somewhere" or ("to " + target)
3434 self.fail("%s: we were expecting to get redirected %s, not get an"
3435 " actual page: %s" % (which, to_where, res))
3436 res.trap(error.PageRedirect)
3437 if statuscode is not None:
3438 self.failUnlessReallyEqual(res.value.status, statuscode,
3439 "%s: not a redirect" % which)
3440 if target is not None:
3441 # the PageRedirect does not seem to capture the uri= query arg
3442 # properly, so we can't check for it.
3443 realtarget = self.webish_url + target
3444 self.failUnlessReallyEqual(res.value.location, realtarget,
3445 "%s: wrong target" % which)
3446 return res.value.location
3448 def test_GET_URI_form(self):
3449 base = "/uri?uri=%s" % self._bar_txt_uri
3450 # this is supposed to give us a redirect to /uri/$URI, plus arguments
3451 targetbase = "/uri/%s" % urllib.quote(self._bar_txt_uri)
3453 d.addBoth(self.shouldRedirect, targetbase)
3454 d.addCallback(lambda res: self.GET(base+"&filename=bar.txt"))
3455 d.addBoth(self.shouldRedirect, targetbase+"?filename=bar.txt")
3456 d.addCallback(lambda res: self.GET(base+"&t=json"))
3457 d.addBoth(self.shouldRedirect, targetbase+"?t=json")
3458 d.addCallback(self.log, "about to get file by uri")
3459 d.addCallback(lambda res: self.GET(base, followRedirect=True))
3460 d.addCallback(self.failUnlessIsBarDotTxt)
3461 d.addCallback(self.log, "got file by uri, about to get dir by uri")
3462 d.addCallback(lambda res: self.GET("/uri?uri=%s&t=json" % self._foo_uri,
3463 followRedirect=True))
3464 d.addCallback(self.failUnlessIsFooJSON)
3465 d.addCallback(self.log, "got dir by uri")
3469 def test_GET_URI_form_bad(self):
3470 d = self.shouldFail2(error.Error, "test_GET_URI_form_bad",
3471 "400 Bad Request", "GET /uri requires uri=",
3475 def test_GET_rename_form(self):
3476 d = self.GET(self.public_url + "/foo?t=rename-form&name=bar.txt",
3477 followRedirect=True)
3479 self.failUnless('name="when_done" value="."' in res, res)
3480 self.failUnless(re.search(r'name="from_name" value="bar\.txt"', res))
3481 d.addCallback(_check)
3484 def log(self, res, msg):
3485 #print "MSG: %s RES: %s" % (msg, res)
3489 def test_GET_URI_URL(self):
3490 base = "/uri/%s" % self._bar_txt_uri
3492 d.addCallback(self.failUnlessIsBarDotTxt)
3493 d.addCallback(lambda res: self.GET(base+"?filename=bar.txt"))
3494 d.addCallback(self.failUnlessIsBarDotTxt)
3495 d.addCallback(lambda res: self.GET(base+"?filename=bar.txt&save=true"))
3496 d.addCallback(self.failUnlessIsBarDotTxt)
3499 def test_GET_URI_URL_dir(self):
3500 base = "/uri/%s?t=json" % self._foo_uri
3502 d.addCallback(self.failUnlessIsFooJSON)
3505 def test_GET_URI_URL_missing(self):
3506 base = "/uri/%s" % self._bad_file_uri
3507 d = self.shouldHTTPError("test_GET_URI_URL_missing",
3508 http.GONE, None, "NotEnoughSharesError",
3510 # TODO: how can we exercise both sides of WebDownloadTarget.fail
3511 # here? we must arrange for a download to fail after target.open()
3512 # has been called, and then inspect the response to see that it is
3513 # shorter than we expected.
3516 def test_PUT_DIRURL_uri(self):
3517 d = self.s.create_dirnode()
3519 new_uri = dn.get_uri()
3520 # replace /foo with a new (empty) directory
3521 d = self.PUT(self.public_url + "/foo?t=uri", new_uri)
3522 d.addCallback(lambda res:
3523 self.failUnlessReallyEqual(res.strip(), new_uri))
3524 d.addCallback(lambda res:
3525 self.failUnlessRWChildURIIs(self.public_root,
3529 d.addCallback(_made_dir)
3532 def test_PUT_DIRURL_uri_noreplace(self):
3533 d = self.s.create_dirnode()
3535 new_uri = dn.get_uri()
3536 # replace /foo with a new (empty) directory, but ask that
3537 # replace=false, so it should fail
3538 d = self.shouldFail2(error.Error, "test_PUT_DIRURL_uri_noreplace",
3539 "409 Conflict", "There was already a child by that name, and you asked me to not replace it",
3541 self.public_url + "/foo?t=uri&replace=false",
3543 d.addCallback(lambda res:
3544 self.failUnlessRWChildURIIs(self.public_root,
3548 d.addCallback(_made_dir)
3551 def test_PUT_DIRURL_bad_t(self):
3552 d = self.shouldFail2(error.Error, "test_PUT_DIRURL_bad_t",
3553 "400 Bad Request", "PUT to a directory",
3554 self.PUT, self.public_url + "/foo?t=BOGUS", "")
3555 d.addCallback(lambda res:
3556 self.failUnlessRWChildURIIs(self.public_root,
3561 def test_PUT_NEWFILEURL_uri(self):
3562 contents, n, new_uri = self.makefile(8)
3563 d = self.PUT(self.public_url + "/foo/new.txt?t=uri", new_uri)
3564 d.addCallback(lambda res: self.failUnlessReallyEqual(res.strip(), new_uri))
3565 d.addCallback(lambda res:
3566 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
3570 def test_PUT_NEWFILEURL_mdmf(self):
3571 new_contents = self.NEWFILE_CONTENTS * 300000
3572 d = self.PUT(self.public_url + \
3573 "/foo/mdmf.txt?mutable=true&mutable-type=mdmf",
3575 d.addCallback(lambda ignored:
3576 self.GET(self.public_url + "/foo/mdmf.txt?t=json"))
3577 def _got_json(json):
3578 data = simplejson.loads(json)
3580 self.failUnlessIn("mutable-type", data)
3581 self.failUnlessEqual(data['mutable-type'], "mdmf")
3582 self.failUnless(data['rw_uri'].startswith("URI:MDMF"))
3583 self.failUnless(data['ro_uri'].startswith("URI:MDMF"))
3584 d.addCallback(_got_json)
3587 def test_PUT_NEWFILEURL_sdmf(self):
3588 new_contents = self.NEWFILE_CONTENTS * 300000
3589 d = self.PUT(self.public_url + \
3590 "/foo/sdmf.txt?mutable=true&mutable-type=sdmf",
3592 d.addCallback(lambda ignored:
3593 self.GET(self.public_url + "/foo/sdmf.txt?t=json"))
3594 def _got_json(json):
3595 data = simplejson.loads(json)
3597 self.failUnlessIn("mutable-type", data)
3598 self.failUnlessEqual(data['mutable-type'], "sdmf")
3599 d.addCallback(_got_json)
3602 def test_PUT_NEWFILEURL_bad_mutable_type(self):
3603 new_contents = self.NEWFILE_CONTENTS * 300000
3604 return self.shouldHTTPError("test bad mutable type",
3605 400, "Bad Request", "Unknown type: foo",
3606 self.PUT, self.public_url + \
3607 "/foo/foo.txt?mutable=true&mutable-type=foo",
3610 def test_PUT_NEWFILEURL_uri_replace(self):
3611 contents, n, new_uri = self.makefile(8)
3612 d = self.PUT(self.public_url + "/foo/bar.txt?t=uri", new_uri)
3613 d.addCallback(lambda res: self.failUnlessReallyEqual(res.strip(), new_uri))
3614 d.addCallback(lambda res:
3615 self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
3619 def test_PUT_NEWFILEURL_uri_no_replace(self):
3620 contents, n, new_uri = self.makefile(8)
3621 d = self.PUT(self.public_url + "/foo/bar.txt?t=uri&replace=false", new_uri)
3622 d.addBoth(self.shouldFail, error.Error, "PUT_NEWFILEURL_uri_no_replace",
3624 "There was already a child by that name, and you asked me "
3625 "to not replace it")
3628 def test_PUT_NEWFILEURL_uri_unknown_bad(self):
3629 d = self.PUT(self.public_url + "/foo/put-future.txt?t=uri", unknown_rwcap)
3630 d.addBoth(self.shouldFail, error.Error,
3631 "POST_put_uri_unknown_bad",
3633 "unknown cap in a write slot")
3636 def test_PUT_NEWFILEURL_uri_unknown_ro_good(self):
3637 d = self.PUT(self.public_url + "/foo/put-future-ro.txt?t=uri", unknown_rocap)
3638 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node,
3639 u"put-future-ro.txt")
3642 def test_PUT_NEWFILEURL_uri_unknown_imm_good(self):
3643 d = self.PUT(self.public_url + "/foo/put-future-imm.txt?t=uri", unknown_immcap)
3644 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node,
3645 u"put-future-imm.txt")
3648 def test_PUT_NEWFILE_URI(self):
3649 file_contents = "New file contents here\n"
3650 d = self.PUT("/uri", file_contents)
3652 assert isinstance(uri, str), uri
3653 self.failUnless(uri in FakeCHKFileNode.all_contents)
3654 self.failUnlessReallyEqual(FakeCHKFileNode.all_contents[uri],
3656 return self.GET("/uri/%s" % uri)
3657 d.addCallback(_check)
3659 self.failUnlessReallyEqual(res, file_contents)
3660 d.addCallback(_check2)
3663 def test_PUT_NEWFILE_URI_not_mutable(self):
3664 file_contents = "New file contents here\n"
3665 d = self.PUT("/uri?mutable=false", file_contents)
3667 assert isinstance(uri, str), uri
3668 self.failUnless(uri in FakeCHKFileNode.all_contents)
3669 self.failUnlessReallyEqual(FakeCHKFileNode.all_contents[uri],
3671 return self.GET("/uri/%s" % uri)
3672 d.addCallback(_check)
3674 self.failUnlessReallyEqual(res, file_contents)
3675 d.addCallback(_check2)
3678 def test_PUT_NEWFILE_URI_only_PUT(self):
3679 d = self.PUT("/uri?t=bogus", "")
3680 d.addBoth(self.shouldFail, error.Error,
3681 "PUT_NEWFILE_URI_only_PUT",
3683 "/uri accepts only PUT, PUT?t=mkdir, POST?t=upload, and POST?t=mkdir")
3686 def test_PUT_NEWFILE_URI_mutable(self):
3687 file_contents = "New file contents here\n"
3688 d = self.PUT("/uri?mutable=true", file_contents)
3689 def _check1(filecap):
3690 filecap = filecap.strip()
3691 self.failUnless(filecap.startswith("URI:SSK:"), filecap)
3692 self.filecap = filecap
3693 u = uri.WriteableSSKFileURI.init_from_string(filecap)
3694 self.failUnless(u.get_storage_index() in FakeMutableFileNode.all_contents)
3695 n = self.s.create_node_from_uri(filecap)
3696 return n.download_best_version()
3697 d.addCallback(_check1)
3699 self.failUnlessReallyEqual(data, file_contents)
3700 return self.GET("/uri/%s" % urllib.quote(self.filecap))
3701 d.addCallback(_check2)
3703 self.failUnlessReallyEqual(res, file_contents)
3704 d.addCallback(_check3)
3707 def test_PUT_mkdir(self):
3708 d = self.PUT("/uri?t=mkdir", "")
3710 n = self.s.create_node_from_uri(uri.strip())
3711 d2 = self.failUnlessNodeKeysAre(n, [])
3712 d2.addCallback(lambda res:
3713 self.GET("/uri/%s?t=json" % uri))
3715 d.addCallback(_check)
3716 d.addCallback(self.failUnlessIsEmptyJSON)
3719 def test_PUT_mkdir_mdmf(self):
3720 d = self.PUT("/uri?t=mkdir&mutable-type=mdmf", "")
3722 u = uri.from_string(res)
3723 # Check that this is an MDMF writecap
3724 self.failUnlessIsInstance(u, uri.MDMFDirectoryURI)
3728 def test_PUT_mkdir_sdmf(self):
3729 d = self.PUT("/uri?t=mkdir&mutable-type=sdmf", "")
3731 u = uri.from_string(res)
3732 self.failUnlessIsInstance(u, uri.DirectoryURI)
3736 def test_PUT_mkdir_bad_mutable_type(self):
3737 return self.shouldHTTPError("bad mutable type",
3738 400, "Bad Request", "Unknown type: foo",
3739 self.PUT, "/uri?t=mkdir&mutable-type=foo",
3742 def test_POST_check(self):
3743 d = self.POST(self.public_url + "/foo", t="check", name="bar.txt")
3745 # this returns a string form of the results, which are probably
3746 # None since we're using fake filenodes.
3747 # TODO: verify that the check actually happened, by changing
3748 # FakeCHKFileNode to count how many times .check() has been
3751 d.addCallback(_done)
3755 def test_PUT_update_at_offset(self):
3756 file_contents = "test file" * 100000 # about 900 KiB
3757 d = self.PUT("/uri?mutable=true", file_contents)
3759 self.filecap = filecap
3760 new_data = file_contents[:100]
3761 new = "replaced and so on"
3763 new_data += file_contents[len(new_data):]
3764 assert len(new_data) == len(file_contents)
3765 self.new_data = new_data
3766 d.addCallback(_then)
3767 d.addCallback(lambda ignored:
3768 self.PUT("/uri/%s?replace=True&offset=100" % self.filecap,
3769 "replaced and so on"))
3770 def _get_data(filecap):
3771 n = self.s.create_node_from_uri(filecap)
3772 return n.download_best_version()
3773 d.addCallback(_get_data)
3774 d.addCallback(lambda results:
3775 self.failUnlessEqual(results, self.new_data))
3776 # Now try appending things to the file
3777 d.addCallback(lambda ignored:
3778 self.PUT("/uri/%s?offset=%d" % (self.filecap, len(self.new_data)),
3780 d.addCallback(_get_data)
3781 d.addCallback(lambda results:
3782 self.failUnlessEqual(results, self.new_data + ("puppies" * 100)))
3783 # and try replacing the beginning of the file
3784 d.addCallback(lambda ignored:
3785 self.PUT("/uri/%s?offset=0" % self.filecap, "begin"))
3786 d.addCallback(_get_data)
3787 d.addCallback(lambda results:
3788 self.failUnlessEqual(results, "begin"+self.new_data[len("begin"):]+("puppies"*100)))
3791 def test_PUT_update_at_invalid_offset(self):
3792 file_contents = "test file" * 100000 # about 900 KiB
3793 d = self.PUT("/uri?mutable=true", file_contents)
3795 self.filecap = filecap
3796 d.addCallback(_then)
3797 # Negative offsets should cause an error.
3798 d.addCallback(lambda ignored:
3799 self.shouldHTTPError("test mutable invalid offset negative",
3803 "/uri/%s?offset=-1" % self.filecap,
3807 def test_PUT_update_at_offset_immutable(self):
3808 file_contents = "Test file" * 100000
3809 d = self.PUT("/uri", file_contents)
3811 self.filecap = filecap
3812 d.addCallback(_then)
3813 d.addCallback(lambda ignored:
3814 self.shouldHTTPError("test immutable update",
3818 "/uri/%s?offset=50" % self.filecap,
3823 def test_bad_method(self):
3824 url = self.webish_url + self.public_url + "/foo/bar.txt"
3825 d = self.shouldHTTPError("test_bad_method",
3826 501, "Not Implemented",
3827 "I don't know how to treat a BOGUS request.",
3828 client.getPage, url, method="BOGUS")
3831 def test_short_url(self):
3832 url = self.webish_url + "/uri"
3833 d = self.shouldHTTPError("test_short_url", 501, "Not Implemented",
3834 "I don't know how to treat a DELETE request.",
3835 client.getPage, url, method="DELETE")
3838 def test_ophandle_bad(self):
3839 url = self.webish_url + "/operations/bogus?t=status"
3840 d = self.shouldHTTPError("test_ophandle_bad", 404, "404 Not Found",
3841 "unknown/expired handle 'bogus'",
3842 client.getPage, url)
3845 def test_ophandle_cancel(self):
3846 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=128",
3847 followRedirect=True)
3848 d.addCallback(lambda ignored:
3849 self.GET("/operations/128?t=status&output=JSON"))
3851 data = simplejson.loads(res)
3852 self.failUnless("finished" in data, res)
3853 monitor = self.ws.root.child_operations.handles["128"][0]
3854 d = self.POST("/operations/128?t=cancel&output=JSON")
3856 data = simplejson.loads(res)
3857 self.failUnless("finished" in data, res)
3858 # t=cancel causes the handle to be forgotten
3859 self.failUnless(monitor.is_cancelled())
3860 d.addCallback(_check2)
3862 d.addCallback(_check1)
3863 d.addCallback(lambda ignored:
3864 self.shouldHTTPError("test_ophandle_cancel",
3865 404, "404 Not Found",
3866 "unknown/expired handle '128'",
3868 "/operations/128?t=status&output=JSON"))
3871 def test_ophandle_retainfor(self):
3872 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=129&retain-for=60",
3873 followRedirect=True)
3874 d.addCallback(lambda ignored:
3875 self.GET("/operations/129?t=status&output=JSON&retain-for=0"))
3877 data = simplejson.loads(res)
3878 self.failUnless("finished" in data, res)
3879 d.addCallback(_check1)
3880 # the retain-for=0 will cause the handle to be expired very soon
3881 d.addCallback(lambda ign:
3882 self.clock.advance(2.0))
3883 d.addCallback(lambda ignored:
3884 self.shouldHTTPError("test_ophandle_retainfor",
3885 404, "404 Not Found",
3886 "unknown/expired handle '129'",
3888 "/operations/129?t=status&output=JSON"))
3891 def test_ophandle_release_after_complete(self):
3892 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=130",
3893 followRedirect=True)
3894 d.addCallback(self.wait_for_operation, "130")
3895 d.addCallback(lambda ignored:
3896 self.GET("/operations/130?t=status&output=JSON&release-after-complete=true"))
3897 # the release-after-complete=true will cause the handle to be expired
3898 d.addCallback(lambda ignored:
3899 self.shouldHTTPError("test_ophandle_release_after_complete",
3900 404, "404 Not Found",
3901 "unknown/expired handle '130'",
3903 "/operations/130?t=status&output=JSON"))
3906 def test_uncollected_ophandle_expiration(self):
3907 # uncollected ophandles should expire after 4 days
3908 def _make_uncollected_ophandle(ophandle):
3909 d = self.POST(self.public_url +
3910 "/foo/?t=start-manifest&ophandle=%d" % ophandle,
3911 followRedirect=False)
3912 # When we start the operation, the webapi server will want
3913 # to redirect us to the page for the ophandle, so we get
3914 # confirmation that the operation has started. If the
3915 # manifest operation has finished by the time we get there,
3916 # following that redirect (by setting followRedirect=True
3917 # above) has the side effect of collecting the ophandle that
3918 # we've just created, which means that we can't use the
3919 # ophandle to test the uncollected timeout anymore. So,
3920 # instead, catch the 302 here and don't follow it.
3921 d.addBoth(self.should302, "uncollected_ophandle_creation")
3923 # Create an ophandle, don't collect it, then advance the clock by
3924 # 4 days - 1 second and make sure that the ophandle is still there.
3925 d = _make_uncollected_ophandle(131)
3926 d.addCallback(lambda ign:
3927 self.clock.advance((96*60*60) - 1)) # 96 hours = 4 days
3928 d.addCallback(lambda ign:
3929 self.GET("/operations/131?t=status&output=JSON"))
3931 data = simplejson.loads(res)
3932 self.failUnless("finished" in data, res)
3933 d.addCallback(_check1)
3934 # Create an ophandle, don't collect it, then try to collect it
3935 # after 4 days. It should be gone.
3936 d.addCallback(lambda ign:
3937 _make_uncollected_ophandle(132))
3938 d.addCallback(lambda ign:
3939 self.clock.advance(96*60*60))
3940 d.addCallback(lambda ign:
3941 self.shouldHTTPError("test_uncollected_ophandle_expired_after_100_hours",
3942 404, "404 Not Found",
3943 "unknown/expired handle '132'",
3945 "/operations/132?t=status&output=JSON"))
3948 def test_collected_ophandle_expiration(self):
3949 # collected ophandles should expire after 1 day
3950 def _make_collected_ophandle(ophandle):
3951 d = self.POST(self.public_url +
3952 "/foo/?t=start-manifest&ophandle=%d" % ophandle,
3953 followRedirect=True)
3954 # By following the initial redirect, we collect the ophandle
3955 # we've just created.
3957 # Create a collected ophandle, then collect it after 23 hours
3958 # and 59 seconds to make sure that it is still there.
3959 d = _make_collected_ophandle(133)
3960 d.addCallback(lambda ign:
3961 self.clock.advance((24*60*60) - 1))
3962 d.addCallback(lambda ign:
3963 self.GET("/operations/133?t=status&output=JSON"))
3965 data = simplejson.loads(res)
3966 self.failUnless("finished" in data, res)
3967 d.addCallback(_check1)
3968 # Create another uncollected ophandle, then try to collect it
3969 # after 24 hours to make sure that it is gone.
3970 d.addCallback(lambda ign:
3971 _make_collected_ophandle(134))
3972 d.addCallback(lambda ign:
3973 self.clock.advance(24*60*60))
3974 d.addCallback(lambda ign:
3975 self.shouldHTTPError("test_collected_ophandle_expired_after_1000_minutes",
3976 404, "404 Not Found",
3977 "unknown/expired handle '134'",
3979 "/operations/134?t=status&output=JSON"))
3982 def test_incident(self):
3983 d = self.POST("/report_incident", details="eek")
3985 self.failUnless("Thank you for your report!" in res, res)
3986 d.addCallback(_done)
3989 def test_static(self):
3990 webdir = os.path.join(self.staticdir, "subdir")
3991 fileutil.make_dirs(webdir)
3992 f = open(os.path.join(webdir, "hello.txt"), "wb")
3996 d = self.GET("/static/subdir/hello.txt")
3998 self.failUnlessReallyEqual(res, "hello")
3999 d.addCallback(_check)
4003 class Util(ShouldFailMixin, testutil.ReallyEqualMixin, unittest.TestCase):
4004 def test_load_file(self):
4005 # This will raise an exception unless a well-formed XML file is found under that name.
4006 common.getxmlfile('directory.xhtml').load()
4008 def test_parse_replace_arg(self):
4009 self.failUnlessReallyEqual(common.parse_replace_arg("true"), True)
4010 self.failUnlessReallyEqual(common.parse_replace_arg("false"), False)
4011 self.failUnlessReallyEqual(common.parse_replace_arg("only-files"),
4013 self.shouldFail(AssertionError, "test_parse_replace_arg", "",
4014 common.parse_replace_arg, "only_fles")
4016 def test_abbreviate_time(self):
4017 self.failUnlessReallyEqual(common.abbreviate_time(None), "")
4018 self.failUnlessReallyEqual(common.abbreviate_time(1.234), "1.23s")
4019 self.failUnlessReallyEqual(common.abbreviate_time(0.123), "123ms")
4020 self.failUnlessReallyEqual(common.abbreviate_time(0.00123), "1.2ms")
4021 self.failUnlessReallyEqual(common.abbreviate_time(0.000123), "123us")
4022 self.failUnlessReallyEqual(common.abbreviate_time(-123000), "-123000000000us")
4024 def test_compute_rate(self):
4025 self.failUnlessReallyEqual(common.compute_rate(None, None), None)
4026 self.failUnlessReallyEqual(common.compute_rate(None, 1), None)
4027 self.failUnlessReallyEqual(common.compute_rate(250000, None), None)
4028 self.failUnlessReallyEqual(common.compute_rate(250000, 0), None)
4029 self.failUnlessReallyEqual(common.compute_rate(250000, 10), 25000.0)
4030 self.failUnlessReallyEqual(common.compute_rate(0, 10), 0.0)
4031 self.shouldFail(AssertionError, "test_compute_rate", "",
4032 common.compute_rate, -100, 10)
4033 self.shouldFail(AssertionError, "test_compute_rate", "",
4034 common.compute_rate, 100, -10)
4037 rate = common.compute_rate(10*1000*1000, 1)
4038 self.failUnlessReallyEqual(common.abbreviate_rate(rate), "10.00MBps")
4040 def test_abbreviate_rate(self):
4041 self.failUnlessReallyEqual(common.abbreviate_rate(None), "")
4042 self.failUnlessReallyEqual(common.abbreviate_rate(1234000), "1.23MBps")
4043 self.failUnlessReallyEqual(common.abbreviate_rate(12340), "12.3kBps")
4044 self.failUnlessReallyEqual(common.abbreviate_rate(123), "123Bps")
4046 def test_abbreviate_size(self):
4047 self.failUnlessReallyEqual(common.abbreviate_size(None), "")
4048 self.failUnlessReallyEqual(common.abbreviate_size(1.23*1000*1000*1000), "1.23GB")
4049 self.failUnlessReallyEqual(common.abbreviate_size(1.23*1000*1000), "1.23MB")
4050 self.failUnlessReallyEqual(common.abbreviate_size(1230), "1.2kB")
4051 self.failUnlessReallyEqual(common.abbreviate_size(123), "123B")
4053 def test_plural(self):
4055 return "%d second%s" % (s, status.plural(s))
4056 self.failUnlessReallyEqual(convert(0), "0 seconds")
4057 self.failUnlessReallyEqual(convert(1), "1 second")
4058 self.failUnlessReallyEqual(convert(2), "2 seconds")
4060 return "has share%s: %s" % (status.plural(s), ",".join(s))
4061 self.failUnlessReallyEqual(convert2([]), "has shares: ")
4062 self.failUnlessReallyEqual(convert2(["1"]), "has share: 1")
4063 self.failUnlessReallyEqual(convert2(["1","2"]), "has shares: 1,2")
4066 class Grid(GridTestMixin, WebErrorMixin, ShouldFailMixin, testutil.ReallyEqualMixin, unittest.TestCase):
4068 def CHECK(self, ign, which, args, clientnum=0):
4069 fileurl = self.fileurls[which]
4070 url = fileurl + "?" + args
4071 return self.GET(url, method="POST", clientnum=clientnum)
4073 def test_filecheck(self):
4074 self.basedir = "web/Grid/filecheck"
4076 c0 = self.g.clients[0]
4079 d = c0.upload(upload.Data(DATA, convergence=""))
4080 def _stash_uri(ur, which):
4081 self.uris[which] = ur.uri
4082 d.addCallback(_stash_uri, "good")
4083 d.addCallback(lambda ign:
4084 c0.upload(upload.Data(DATA+"1", convergence="")))
4085 d.addCallback(_stash_uri, "sick")
4086 d.addCallback(lambda ign:
4087 c0.upload(upload.Data(DATA+"2", convergence="")))
4088 d.addCallback(_stash_uri, "dead")
4089 def _stash_mutable_uri(n, which):
4090 self.uris[which] = n.get_uri()
4091 assert isinstance(self.uris[which], str)
4092 d.addCallback(lambda ign:
4093 c0.create_mutable_file(publish.MutableData(DATA+"3")))
4094 d.addCallback(_stash_mutable_uri, "corrupt")
4095 d.addCallback(lambda ign:
4096 c0.upload(upload.Data("literal", convergence="")))
4097 d.addCallback(_stash_uri, "small")
4098 d.addCallback(lambda ign: c0.create_immutable_dirnode({}))
4099 d.addCallback(_stash_mutable_uri, "smalldir")
4101 def _compute_fileurls(ignored):
4103 for which in self.uris:
4104 self.fileurls[which] = "uri/" + urllib.quote(self.uris[which])
4105 d.addCallback(_compute_fileurls)
4107 def _clobber_shares(ignored):
4108 good_shares = self.find_uri_shares(self.uris["good"])
4109 self.failUnlessReallyEqual(len(good_shares), 10)
4110 sick_shares = self.find_uri_shares(self.uris["sick"])
4111 os.unlink(sick_shares[0][2])
4112 dead_shares = self.find_uri_shares(self.uris["dead"])
4113 for i in range(1, 10):
4114 os.unlink(dead_shares[i][2])
4115 c_shares = self.find_uri_shares(self.uris["corrupt"])
4116 cso = CorruptShareOptions()
4117 cso.stdout = StringIO()
4118 cso.parseOptions([c_shares[0][2]])
4120 d.addCallback(_clobber_shares)
4122 d.addCallback(self.CHECK, "good", "t=check")
4123 def _got_html_good(res):
4124 self.failUnless("Healthy" in res, res)
4125 self.failIf("Not Healthy" in res, res)
4126 d.addCallback(_got_html_good)
4127 d.addCallback(self.CHECK, "good", "t=check&return_to=somewhere")
4128 def _got_html_good_return_to(res):
4129 self.failUnless("Healthy" in res, res)
4130 self.failIf("Not Healthy" in res, res)
4131 self.failUnless('<a href="somewhere">Return to file'
4133 d.addCallback(_got_html_good_return_to)
4134 d.addCallback(self.CHECK, "good", "t=check&output=json")
4135 def _got_json_good(res):
4136 r = simplejson.loads(res)
4137 self.failUnlessEqual(r["summary"], "Healthy")
4138 self.failUnless(r["results"]["healthy"])
4139 self.failIf(r["results"]["needs-rebalancing"])
4140 self.failUnless(r["results"]["recoverable"])
4141 d.addCallback(_got_json_good)
4143 d.addCallback(self.CHECK, "small", "t=check")
4144 def _got_html_small(res):
4145 self.failUnless("Literal files are always healthy" in res, res)
4146 self.failIf("Not Healthy" in res, res)
4147 d.addCallback(_got_html_small)
4148 d.addCallback(self.CHECK, "small", "t=check&return_to=somewhere")
4149 def _got_html_small_return_to(res):
4150 self.failUnless("Literal files are always healthy" in res, res)
4151 self.failIf("Not Healthy" in res, res)
4152 self.failUnless('<a href="somewhere">Return to file'
4154 d.addCallback(_got_html_small_return_to)
4155 d.addCallback(self.CHECK, "small", "t=check&output=json")
4156 def _got_json_small(res):
4157 r = simplejson.loads(res)
4158 self.failUnlessEqual(r["storage-index"], "")
4159 self.failUnless(r["results"]["healthy"])
4160 d.addCallback(_got_json_small)
4162 d.addCallback(self.CHECK, "smalldir", "t=check")
4163 def _got_html_smalldir(res):
4164 self.failUnless("Literal files are always healthy" in res, res)
4165 self.failIf("Not Healthy" in res, res)
4166 d.addCallback(_got_html_smalldir)
4167 d.addCallback(self.CHECK, "smalldir", "t=check&output=json")
4168 def _got_json_smalldir(res):
4169 r = simplejson.loads(res)
4170 self.failUnlessEqual(r["storage-index"], "")
4171 self.failUnless(r["results"]["healthy"])
4172 d.addCallback(_got_json_smalldir)
4174 d.addCallback(self.CHECK, "sick", "t=check")
4175 def _got_html_sick(res):
4176 self.failUnless("Not Healthy" in res, res)
4177 d.addCallback(_got_html_sick)
4178 d.addCallback(self.CHECK, "sick", "t=check&output=json")
4179 def _got_json_sick(res):
4180 r = simplejson.loads(res)
4181 self.failUnlessEqual(r["summary"],
4182 "Not Healthy: 9 shares (enc 3-of-10)")
4183 self.failIf(r["results"]["healthy"])
4184 self.failIf(r["results"]["needs-rebalancing"])
4185 self.failUnless(r["results"]["recoverable"])
4186 d.addCallback(_got_json_sick)
4188 d.addCallback(self.CHECK, "dead", "t=check")
4189 def _got_html_dead(res):
4190 self.failUnless("Not Healthy" in res, res)
4191 d.addCallback(_got_html_dead)
4192 d.addCallback(self.CHECK, "dead", "t=check&output=json")
4193 def _got_json_dead(res):
4194 r = simplejson.loads(res)
4195 self.failUnlessEqual(r["summary"],
4196 "Not Healthy: 1 shares (enc 3-of-10)")
4197 self.failIf(r["results"]["healthy"])
4198 self.failIf(r["results"]["needs-rebalancing"])
4199 self.failIf(r["results"]["recoverable"])
4200 d.addCallback(_got_json_dead)
4202 d.addCallback(self.CHECK, "corrupt", "t=check&verify=true")
4203 def _got_html_corrupt(res):
4204 self.failUnless("Not Healthy! : Unhealthy" in res, res)
4205 d.addCallback(_got_html_corrupt)
4206 d.addCallback(self.CHECK, "corrupt", "t=check&verify=true&output=json")
4207 def _got_json_corrupt(res):
4208 r = simplejson.loads(res)
4209 self.failUnless("Unhealthy: 9 shares (enc 3-of-10)" in r["summary"],
4211 self.failIf(r["results"]["healthy"])
4212 self.failUnless(r["results"]["recoverable"])
4213 self.failUnlessReallyEqual(r["results"]["count-shares-good"], 9)
4214 self.failUnlessReallyEqual(r["results"]["count-corrupt-shares"], 1)
4215 d.addCallback(_got_json_corrupt)
4217 d.addErrback(self.explain_web_error)
4220 def test_repair_html(self):
4221 self.basedir = "web/Grid/repair_html"
4223 c0 = self.g.clients[0]
4226 d = c0.upload(upload.Data(DATA, convergence=""))
4227 def _stash_uri(ur, which):
4228 self.uris[which] = ur.uri
4229 d.addCallback(_stash_uri, "good")
4230 d.addCallback(lambda ign:
4231 c0.upload(upload.Data(DATA+"1", convergence="")))
4232 d.addCallback(_stash_uri, "sick")
4233 d.addCallback(lambda ign:
4234 c0.upload(upload.Data(DATA+"2", convergence="")))
4235 d.addCallback(_stash_uri, "dead")
4236 def _stash_mutable_uri(n, which):
4237 self.uris[which] = n.get_uri()
4238 assert isinstance(self.uris[which], str)
4239 d.addCallback(lambda ign:
4240 c0.create_mutable_file(publish.MutableData(DATA+"3")))
4241 d.addCallback(_stash_mutable_uri, "corrupt")
4243 def _compute_fileurls(ignored):
4245 for which in self.uris:
4246 self.fileurls[which] = "uri/" + urllib.quote(self.uris[which])
4247 d.addCallback(_compute_fileurls)
4249 def _clobber_shares(ignored):
4250 good_shares = self.find_uri_shares(self.uris["good"])
4251 self.failUnlessReallyEqual(len(good_shares), 10)
4252 sick_shares = self.find_uri_shares(self.uris["sick"])
4253 os.unlink(sick_shares[0][2])
4254 dead_shares = self.find_uri_shares(self.uris["dead"])
4255 for i in range(1, 10):
4256 os.unlink(dead_shares[i][2])
4257 c_shares = self.find_uri_shares(self.uris["corrupt"])
4258 cso = CorruptShareOptions()
4259 cso.stdout = StringIO()
4260 cso.parseOptions([c_shares[0][2]])
4262 d.addCallback(_clobber_shares)
4264 d.addCallback(self.CHECK, "good", "t=check&repair=true")
4265 def _got_html_good(res):
4266 self.failUnless("Healthy" in res, res)
4267 self.failIf("Not Healthy" in res, res)
4268 self.failUnless("No repair necessary" in res, res)
4269 d.addCallback(_got_html_good)
4271 d.addCallback(self.CHECK, "sick", "t=check&repair=true")
4272 def _got_html_sick(res):
4273 self.failUnless("Healthy : healthy" in res, res)
4274 self.failIf("Not Healthy" in res, res)
4275 self.failUnless("Repair successful" in res, res)
4276 d.addCallback(_got_html_sick)
4278 # repair of a dead file will fail, of course, but it isn't yet
4279 # clear how this should be reported. Right now it shows up as
4282 #d.addCallback(self.CHECK, "dead", "t=check&repair=true")
4283 #def _got_html_dead(res):
4285 # self.failUnless("Healthy : healthy" in res, res)
4286 # self.failIf("Not Healthy" in res, res)
4287 # self.failUnless("No repair necessary" in res, res)
4288 #d.addCallback(_got_html_dead)
4290 d.addCallback(self.CHECK, "corrupt", "t=check&verify=true&repair=true")
4291 def _got_html_corrupt(res):
4292 self.failUnless("Healthy : Healthy" in res, res)
4293 self.failIf("Not Healthy" in res, res)
4294 self.failUnless("Repair successful" in res, res)
4295 d.addCallback(_got_html_corrupt)
4297 d.addErrback(self.explain_web_error)
4300 def test_repair_json(self):
4301 self.basedir = "web/Grid/repair_json"
4303 c0 = self.g.clients[0]
4306 d = c0.upload(upload.Data(DATA+"1", convergence=""))
4307 def _stash_uri(ur, which):
4308 self.uris[which] = ur.uri
4309 d.addCallback(_stash_uri, "sick")
4311 def _compute_fileurls(ignored):
4313 for which in self.uris:
4314 self.fileurls[which] = "uri/" + urllib.quote(self.uris[which])
4315 d.addCallback(_compute_fileurls)
4317 def _clobber_shares(ignored):
4318 sick_shares = self.find_uri_shares(self.uris["sick"])
4319 os.unlink(sick_shares[0][2])
4320 d.addCallback(_clobber_shares)
4322 d.addCallback(self.CHECK, "sick", "t=check&repair=true&output=json")
4323 def _got_json_sick(res):
4324 r = simplejson.loads(res)
4325 self.failUnlessReallyEqual(r["repair-attempted"], True)
4326 self.failUnlessReallyEqual(r["repair-successful"], True)
4327 self.failUnlessEqual(r["pre-repair-results"]["summary"],
4328 "Not Healthy: 9 shares (enc 3-of-10)")
4329 self.failIf(r["pre-repair-results"]["results"]["healthy"])
4330 self.failUnlessEqual(r["post-repair-results"]["summary"], "healthy")
4331 self.failUnless(r["post-repair-results"]["results"]["healthy"])
4332 d.addCallback(_got_json_sick)
4334 d.addErrback(self.explain_web_error)
4337 def test_unknown(self, immutable=False):
4338 self.basedir = "web/Grid/unknown"
4340 self.basedir = "web/Grid/unknown-immutable"
4343 c0 = self.g.clients[0]
4347 # the future cap format may contain slashes, which must be tolerated
4348 expected_info_url = "uri/%s?t=info" % urllib.quote(unknown_rwcap,
4352 name = u"future-imm"
4353 future_node = UnknownNode(None, unknown_immcap, deep_immutable=True)
4354 d = c0.create_immutable_dirnode({name: (future_node, {})})
4357 future_node = UnknownNode(unknown_rwcap, unknown_rocap)
4358 d = c0.create_dirnode()
4360 def _stash_root_and_create_file(n):
4362 self.rooturl = "uri/" + urllib.quote(n.get_uri()) + "/"
4363 self.rourl = "uri/" + urllib.quote(n.get_readonly_uri()) + "/"
4365 return self.rootnode.set_node(name, future_node)
4366 d.addCallback(_stash_root_and_create_file)
4368 # make sure directory listing tolerates unknown nodes
4369 d.addCallback(lambda ign: self.GET(self.rooturl))
4370 def _check_directory_html(res, expected_type_suffix):
4371 pattern = re.compile(r'<td>\?%s</td>[ \t\n\r]*'
4372 '<td>%s</td>' % (expected_type_suffix, str(name)),
4374 self.failUnless(re.search(pattern, res), res)
4375 # find the More Info link for name, should be relative
4376 mo = re.search(r'<a href="([^"]+)">More Info</a>', res)
4377 info_url = mo.group(1)
4378 self.failUnlessReallyEqual(info_url, "%s?t=info" % (str(name),))
4380 d.addCallback(_check_directory_html, "-IMM")
4382 d.addCallback(_check_directory_html, "")
4384 d.addCallback(lambda ign: self.GET(self.rooturl+"?t=json"))
4385 def _check_directory_json(res, expect_rw_uri):
4386 data = simplejson.loads(res)
4387 self.failUnlessEqual(data[0], "dirnode")
4388 f = data[1]["children"][name]
4389 self.failUnlessEqual(f[0], "unknown")
4391 self.failUnlessReallyEqual(to_str(f[1]["rw_uri"]), unknown_rwcap, data)
4393 self.failIfIn("rw_uri", f[1])
4395 self.failUnlessReallyEqual(to_str(f[1]["ro_uri"]), unknown_immcap, data)
4397 self.failUnlessReallyEqual(to_str(f[1]["ro_uri"]), unknown_rocap, data)
4398 self.failUnless("metadata" in f[1])
4399 d.addCallback(_check_directory_json, expect_rw_uri=not immutable)
4401 def _check_info(res, expect_rw_uri, expect_ro_uri):
4402 self.failUnlessIn("Object Type: <span>unknown</span>", res)
4404 self.failUnlessIn(unknown_rwcap, res)
4407 self.failUnlessIn(unknown_immcap, res)
4409 self.failUnlessIn(unknown_rocap, res)
4411 self.failIfIn(unknown_rocap, res)
4412 self.failIfIn("Raw data as", res)
4413 self.failIfIn("Directory writecap", res)
4414 self.failIfIn("Checker Operations", res)
4415 self.failIfIn("Mutable File Operations", res)
4416 self.failIfIn("Directory Operations", res)
4418 # FIXME: these should have expect_rw_uri=not immutable; I don't know
4419 # why they fail. Possibly related to ticket #922.
4421 d.addCallback(lambda ign: self.GET(expected_info_url))
4422 d.addCallback(_check_info, expect_rw_uri=False, expect_ro_uri=False)
4423 d.addCallback(lambda ign: self.GET("%s%s?t=info" % (self.rooturl, str(name))))
4424 d.addCallback(_check_info, expect_rw_uri=False, expect_ro_uri=True)
4426 def _check_json(res, expect_rw_uri):
4427 data = simplejson.loads(res)
4428 self.failUnlessEqual(data[0], "unknown")
4430 self.failUnlessReallyEqual(to_str(data[1]["rw_uri"]), unknown_rwcap, data)
4432 self.failIfIn("rw_uri", data[1])
4435 self.failUnlessReallyEqual(to_str(data[1]["ro_uri"]), unknown_immcap, data)
4436 self.failUnlessReallyEqual(data[1]["mutable"], False)
4438 self.failUnlessReallyEqual(to_str(data[1]["ro_uri"]), unknown_rocap, data)
4439 self.failUnlessReallyEqual(data[1]["mutable"], True)
4441 self.failUnlessReallyEqual(to_str(data[1]["ro_uri"]), unknown_rocap, data)
4442 self.failIf("mutable" in data[1], data[1])
4444 # TODO: check metadata contents
4445 self.failUnless("metadata" in data[1])
4447 d.addCallback(lambda ign: self.GET("%s%s?t=json" % (self.rooturl, str(name))))
4448 d.addCallback(_check_json, expect_rw_uri=not immutable)
4450 # and make sure that a read-only version of the directory can be
4451 # rendered too. This version will not have unknown_rwcap, whether
4452 # or not future_node was immutable.
4453 d.addCallback(lambda ign: self.GET(self.rourl))
4455 d.addCallback(_check_directory_html, "-IMM")
4457 d.addCallback(_check_directory_html, "-RO")
4459 d.addCallback(lambda ign: self.GET(self.rourl+"?t=json"))
4460 d.addCallback(_check_directory_json, expect_rw_uri=False)
4462 d.addCallback(lambda ign: self.GET("%s%s?t=json" % (self.rourl, str(name))))
4463 d.addCallback(_check_json, expect_rw_uri=False)
4465 # TODO: check that getting t=info from the Info link in the ro directory
4466 # works, and does not include the writecap URI.
4469 def test_immutable_unknown(self):
4470 return self.test_unknown(immutable=True)
4472 def test_mutant_dirnodes_are_omitted(self):
4473 self.basedir = "web/Grid/mutant_dirnodes_are_omitted"
4476 c = self.g.clients[0]
4481 lonely_uri = "URI:LIT:n5xgk" # LIT for "one"
4482 mut_write_uri = "URI:SSK:vfvcbdfbszyrsaxchgevhmmlii:euw4iw7bbnkrrwpzuburbhppuxhc3gwxv26f6imekhz7zyw2ojnq"
4483 mut_read_uri = "URI:SSK-RO:e3mdrzfwhoq42hy5ubcz6rp3o4:ybyibhnp3vvwuq2vaw2ckjmesgkklfs6ghxleztqidihjyofgw7q"
4485 # This method tests mainly dirnode, but we'd have to duplicate code in order to
4486 # test the dirnode and web layers separately.
4488 # 'lonely' is a valid LIT child, 'ro' is a mutant child with an SSK-RO readcap,
4489 # and 'write-in-ro' is a mutant child with an SSK writecap in the ro_uri field.
4490 # When the directory is read, the mutants should be silently disposed of, leaving
4491 # their lonely sibling.
4492 # We don't test the case of a retrieving a cap from the encrypted rw_uri field,
4493 # because immutable directories don't have a writecap and therefore that field
4494 # isn't (and can't be) decrypted.
4495 # TODO: The field still exists in the netstring. Technically we should check what
4496 # happens if something is put there (_unpack_contents should raise ValueError),
4497 # but that can wait.
4499 lonely_child = nm.create_from_cap(lonely_uri)
4500 mutant_ro_child = nm.create_from_cap(mut_read_uri)
4501 mutant_write_in_ro_child = nm.create_from_cap(mut_write_uri)
4503 def _by_hook_or_by_crook():
4505 for n in [mutant_ro_child, mutant_write_in_ro_child]:
4506 n.is_allowed_in_immutable_directory = _by_hook_or_by_crook
4508 mutant_write_in_ro_child.get_write_uri = lambda: None
4509 mutant_write_in_ro_child.get_readonly_uri = lambda: mut_write_uri
4511 kids = {u"lonely": (lonely_child, {}),
4512 u"ro": (mutant_ro_child, {}),
4513 u"write-in-ro": (mutant_write_in_ro_child, {}),
4515 d = c.create_immutable_dirnode(kids)
4518 self.failUnless(isinstance(dn, dirnode.DirectoryNode))
4519 self.failIf(dn.is_mutable())
4520 self.failUnless(dn.is_readonly())
4521 # This checks that if we somehow ended up calling dn._decrypt_rwcapdata, it would fail.
4522 self.failIf(hasattr(dn._node, 'get_writekey'))
4524 self.failUnless("RO-IMM" in rep)
4526 self.failUnlessIn("CHK", cap.to_string())
4529 self.rooturl = "uri/" + urllib.quote(dn.get_uri()) + "/"
4530 return download_to_data(dn._node)
4531 d.addCallback(_created)
4533 def _check_data(data):
4534 # Decode the netstring representation of the directory to check that all children
4535 # are present. This is a bit of an abstraction violation, but there's not really
4536 # any other way to do it given that the real DirectoryNode._unpack_contents would
4537 # strip the mutant children out (which is what we're trying to test, later).
4540 while position < len(data):
4541 entries, position = split_netstring(data, 1, position)
4543 (name_utf8, ro_uri, rwcapdata, metadata_s), subpos = split_netstring(entry, 4)
4544 name = name_utf8.decode("utf-8")
4545 self.failUnless(rwcapdata == "")
4546 self.failUnless(name in kids)
4547 (expected_child, ign) = kids[name]
4548 self.failUnlessReallyEqual(ro_uri, expected_child.get_readonly_uri())
4551 self.failUnlessReallyEqual(numkids, 3)
4552 return self.rootnode.list()
4553 d.addCallback(_check_data)
4555 # Now when we use the real directory listing code, the mutants should be absent.
4556 def _check_kids(children):
4557 self.failUnlessReallyEqual(sorted(children.keys()), [u"lonely"])
4558 lonely_node, lonely_metadata = children[u"lonely"]
4560 self.failUnlessReallyEqual(lonely_node.get_write_uri(), None)
4561 self.failUnlessReallyEqual(lonely_node.get_readonly_uri(), lonely_uri)
4562 d.addCallback(_check_kids)
4564 d.addCallback(lambda ign: nm.create_from_cap(self.cap.to_string()))
4565 d.addCallback(lambda n: n.list())
4566 d.addCallback(_check_kids) # again with dirnode recreated from cap
4568 # Make sure the lonely child can be listed in HTML...
4569 d.addCallback(lambda ign: self.GET(self.rooturl))
4570 def _check_html(res):
4571 self.failIfIn("URI:SSK", res)
4572 get_lonely = "".join([r'<td>FILE</td>',
4574 r'<a href="[^"]+%s[^"]+">lonely</a>' % (urllib.quote(lonely_uri),),
4576 r'\s+<td align="right">%d</td>' % len("one"),
4578 self.failUnless(re.search(get_lonely, res), res)
4580 # find the More Info link for name, should be relative
4581 mo = re.search(r'<a href="([^"]+)">More Info</a>', res)
4582 info_url = mo.group(1)
4583 self.failUnless(info_url.endswith(urllib.quote(lonely_uri) + "?t=info"), info_url)
4584 d.addCallback(_check_html)
4587 d.addCallback(lambda ign: self.GET(self.rooturl+"?t=json"))
4588 def _check_json(res):
4589 data = simplejson.loads(res)
4590 self.failUnlessEqual(data[0], "dirnode")
4591 listed_children = data[1]["children"]
4592 self.failUnlessReallyEqual(sorted(listed_children.keys()), [u"lonely"])
4593 ll_type, ll_data = listed_children[u"lonely"]
4594 self.failUnlessEqual(ll_type, "filenode")
4595 self.failIf("rw_uri" in ll_data)
4596 self.failUnlessReallyEqual(to_str(ll_data["ro_uri"]), lonely_uri)
4597 d.addCallback(_check_json)
4600 def test_deep_check(self):
4601 self.basedir = "web/Grid/deep_check"
4603 c0 = self.g.clients[0]
4607 d = c0.create_dirnode()
4608 def _stash_root_and_create_file(n):
4610 self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri()) + "/"
4611 return n.add_file(u"good", upload.Data(DATA, convergence=""))
4612 d.addCallback(_stash_root_and_create_file)
4613 def _stash_uri(fn, which):
4614 self.uris[which] = fn.get_uri()
4616 d.addCallback(_stash_uri, "good")
4617 d.addCallback(lambda ign:
4618 self.rootnode.add_file(u"small",
4619 upload.Data("literal",
4621 d.addCallback(_stash_uri, "small")
4622 d.addCallback(lambda ign:
4623 self.rootnode.add_file(u"sick",
4624 upload.Data(DATA+"1",
4626 d.addCallback(_stash_uri, "sick")
4628 # this tests that deep-check and stream-manifest will ignore
4629 # UnknownNode instances. Hopefully this will also cover deep-stats.
4630 future_node = UnknownNode(unknown_rwcap, unknown_rocap)
4631 d.addCallback(lambda ign: self.rootnode.set_node(u"future", future_node))
4633 def _clobber_shares(ignored):
4634 self.delete_shares_numbered(self.uris["sick"], [0,1])
4635 d.addCallback(_clobber_shares)
4643 d.addCallback(self.CHECK, "root", "t=stream-deep-check")
4646 units = [simplejson.loads(line)
4647 for line in res.splitlines()
4650 print "response is:", res
4651 print "undecodeable line was '%s'" % line
4653 self.failUnlessReallyEqual(len(units), 5+1)
4654 # should be parent-first
4656 self.failUnlessEqual(u0["path"], [])
4657 self.failUnlessEqual(u0["type"], "directory")
4658 self.failUnlessReallyEqual(to_str(u0["cap"]), self.rootnode.get_uri())
4659 u0cr = u0["check-results"]
4660 self.failUnlessReallyEqual(u0cr["results"]["count-shares-good"], 10)
4662 ugood = [u for u in units
4663 if u["type"] == "file" and u["path"] == [u"good"]][0]
4664 self.failUnlessReallyEqual(to_str(ugood["cap"]), self.uris["good"])
4665 ugoodcr = ugood["check-results"]
4666 self.failUnlessReallyEqual(ugoodcr["results"]["count-shares-good"], 10)
4669 self.failUnlessEqual(stats["type"], "stats")
4671 self.failUnlessReallyEqual(s["count-immutable-files"], 2)
4672 self.failUnlessReallyEqual(s["count-literal-files"], 1)
4673 self.failUnlessReallyEqual(s["count-directories"], 1)
4674 self.failUnlessReallyEqual(s["count-unknown"], 1)
4675 d.addCallback(_done)
4677 d.addCallback(self.CHECK, "root", "t=stream-manifest")
4678 def _check_manifest(res):
4679 self.failUnless(res.endswith("\n"))
4680 units = [simplejson.loads(t) for t in res[:-1].split("\n")]
4681 self.failUnlessReallyEqual(len(units), 5+1)
4682 self.failUnlessEqual(units[-1]["type"], "stats")
4684 self.failUnlessEqual(first["path"], [])
4685 self.failUnlessEqual(to_str(first["cap"]), self.rootnode.get_uri())
4686 self.failUnlessEqual(first["type"], "directory")
4687 stats = units[-1]["stats"]
4688 self.failUnlessReallyEqual(stats["count-immutable-files"], 2)
4689 self.failUnlessReallyEqual(stats["count-literal-files"], 1)
4690 self.failUnlessReallyEqual(stats["count-mutable-files"], 0)
4691 self.failUnlessReallyEqual(stats["count-immutable-files"], 2)
4692 self.failUnlessReallyEqual(stats["count-unknown"], 1)
4693 d.addCallback(_check_manifest)
4695 # now add root/subdir and root/subdir/grandchild, then make subdir
4696 # unrecoverable, then see what happens
4698 d.addCallback(lambda ign:
4699 self.rootnode.create_subdirectory(u"subdir"))
4700 d.addCallback(_stash_uri, "subdir")
4701 d.addCallback(lambda subdir_node:
4702 subdir_node.add_file(u"grandchild",
4703 upload.Data(DATA+"2",
4705 d.addCallback(_stash_uri, "grandchild")
4707 d.addCallback(lambda ign:
4708 self.delete_shares_numbered(self.uris["subdir"],
4716 # root/subdir [unrecoverable]
4717 # root/subdir/grandchild
4719 # how should a streaming-JSON API indicate fatal error?
4720 # answer: emit ERROR: instead of a JSON string
4722 d.addCallback(self.CHECK, "root", "t=stream-manifest")
4723 def _check_broken_manifest(res):
4724 lines = res.splitlines()
4726 for (i,line) in enumerate(lines)
4727 if line.startswith("ERROR:")]
4729 self.fail("no ERROR: in output: %s" % (res,))
4730 first_error = error_lines[0]
4731 error_line = lines[first_error]
4732 error_msg = lines[first_error+1:]
4733 error_msg_s = "\n".join(error_msg) + "\n"
4734 self.failUnlessIn("ERROR: UnrecoverableFileError(no recoverable versions)",
4736 self.failUnless(len(error_msg) > 2, error_msg_s) # some traceback
4737 units = [simplejson.loads(line) for line in lines[:first_error]]
4738 self.failUnlessReallyEqual(len(units), 6) # includes subdir
4739 last_unit = units[-1]
4740 self.failUnlessEqual(last_unit["path"], ["subdir"])
4741 d.addCallback(_check_broken_manifest)
4743 d.addCallback(self.CHECK, "root", "t=stream-deep-check")
4744 def _check_broken_deepcheck(res):
4745 lines = res.splitlines()
4747 for (i,line) in enumerate(lines)
4748 if line.startswith("ERROR:")]
4750 self.fail("no ERROR: in output: %s" % (res,))
4751 first_error = error_lines[0]
4752 error_line = lines[first_error]
4753 error_msg = lines[first_error+1:]
4754 error_msg_s = "\n".join(error_msg) + "\n"
4755 self.failUnlessIn("ERROR: UnrecoverableFileError(no recoverable versions)",
4757 self.failUnless(len(error_msg) > 2, error_msg_s) # some traceback
4758 units = [simplejson.loads(line) for line in lines[:first_error]]
4759 self.failUnlessReallyEqual(len(units), 6) # includes subdir
4760 last_unit = units[-1]
4761 self.failUnlessEqual(last_unit["path"], ["subdir"])
4762 r = last_unit["check-results"]["results"]
4763 self.failUnlessReallyEqual(r["count-recoverable-versions"], 0)
4764 self.failUnlessReallyEqual(r["count-shares-good"], 1)
4765 self.failUnlessReallyEqual(r["recoverable"], False)
4766 d.addCallback(_check_broken_deepcheck)
4768 d.addErrback(self.explain_web_error)
4771 def test_deep_check_and_repair(self):
4772 self.basedir = "web/Grid/deep_check_and_repair"
4774 c0 = self.g.clients[0]
4778 d = c0.create_dirnode()
4779 def _stash_root_and_create_file(n):
4781 self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri()) + "/"
4782 return n.add_file(u"good", upload.Data(DATA, convergence=""))
4783 d.addCallback(_stash_root_and_create_file)
4784 def _stash_uri(fn, which):
4785 self.uris[which] = fn.get_uri()
4786 d.addCallback(_stash_uri, "good")
4787 d.addCallback(lambda ign:
4788 self.rootnode.add_file(u"small",
4789 upload.Data("literal",
4791 d.addCallback(_stash_uri, "small")
4792 d.addCallback(lambda ign:
4793 self.rootnode.add_file(u"sick",
4794 upload.Data(DATA+"1",
4796 d.addCallback(_stash_uri, "sick")
4797 #d.addCallback(lambda ign:
4798 # self.rootnode.add_file(u"dead",
4799 # upload.Data(DATA+"2",
4801 #d.addCallback(_stash_uri, "dead")
4803 #d.addCallback(lambda ign: c0.create_mutable_file("mutable"))
4804 #d.addCallback(lambda fn: self.rootnode.set_node(u"corrupt", fn))
4805 #d.addCallback(_stash_uri, "corrupt")
4807 def _clobber_shares(ignored):
4808 good_shares = self.find_uri_shares(self.uris["good"])
4809 self.failUnlessReallyEqual(len(good_shares), 10)
4810 sick_shares = self.find_uri_shares(self.uris["sick"])
4811 os.unlink(sick_shares[0][2])
4812 #dead_shares = self.find_uri_shares(self.uris["dead"])
4813 #for i in range(1, 10):
4814 # os.unlink(dead_shares[i][2])
4816 #c_shares = self.find_uri_shares(self.uris["corrupt"])
4817 #cso = CorruptShareOptions()
4818 #cso.stdout = StringIO()
4819 #cso.parseOptions([c_shares[0][2]])
4821 d.addCallback(_clobber_shares)
4824 # root/good CHK, 10 shares
4826 # root/sick CHK, 9 shares
4828 d.addCallback(self.CHECK, "root", "t=stream-deep-check&repair=true")
4830 units = [simplejson.loads(line)
4831 for line in res.splitlines()
4833 self.failUnlessReallyEqual(len(units), 4+1)
4834 # should be parent-first
4836 self.failUnlessEqual(u0["path"], [])
4837 self.failUnlessEqual(u0["type"], "directory")
4838 self.failUnlessReallyEqual(to_str(u0["cap"]), self.rootnode.get_uri())
4839 u0crr = u0["check-and-repair-results"]
4840 self.failUnlessReallyEqual(u0crr["repair-attempted"], False)
4841 self.failUnlessReallyEqual(u0crr["pre-repair-results"]["results"]["count-shares-good"], 10)
4843 ugood = [u for u in units
4844 if u["type"] == "file" and u["path"] == [u"good"]][0]
4845 self.failUnlessEqual(to_str(ugood["cap"]), self.uris["good"])
4846 ugoodcrr = ugood["check-and-repair-results"]
4847 self.failUnlessReallyEqual(ugoodcrr["repair-attempted"], False)
4848 self.failUnlessReallyEqual(ugoodcrr["pre-repair-results"]["results"]["count-shares-good"], 10)
4850 usick = [u for u in units
4851 if u["type"] == "file" and u["path"] == [u"sick"]][0]
4852 self.failUnlessReallyEqual(to_str(usick["cap"]), self.uris["sick"])
4853 usickcrr = usick["check-and-repair-results"]
4854 self.failUnlessReallyEqual(usickcrr["repair-attempted"], True)
4855 self.failUnlessReallyEqual(usickcrr["repair-successful"], True)
4856 self.failUnlessReallyEqual(usickcrr["pre-repair-results"]["results"]["count-shares-good"], 9)
4857 self.failUnlessReallyEqual(usickcrr["post-repair-results"]["results"]["count-shares-good"], 10)
4860 self.failUnlessEqual(stats["type"], "stats")
4862 self.failUnlessReallyEqual(s["count-immutable-files"], 2)
4863 self.failUnlessReallyEqual(s["count-literal-files"], 1)
4864 self.failUnlessReallyEqual(s["count-directories"], 1)
4865 d.addCallback(_done)
4867 d.addErrback(self.explain_web_error)
4870 def _count_leases(self, ignored, which):
4871 u = self.uris[which]
4872 shares = self.find_uri_shares(u)
4874 for shnum, serverid, fn in shares:
4875 sf = get_share_file(fn)
4876 num_leases = len(list(sf.get_leases()))
4877 lease_counts.append( (fn, num_leases) )
4880 def _assert_leasecount(self, lease_counts, expected):
4881 for (fn, num_leases) in lease_counts:
4882 if num_leases != expected:
4883 self.fail("expected %d leases, have %d, on %s" %
4884 (expected, num_leases, fn))
4886 def test_add_lease(self):
4887 self.basedir = "web/Grid/add_lease"
4888 self.set_up_grid(num_clients=2)
4889 c0 = self.g.clients[0]
4892 d = c0.upload(upload.Data(DATA, convergence=""))
4893 def _stash_uri(ur, which):
4894 self.uris[which] = ur.uri
4895 d.addCallback(_stash_uri, "one")
4896 d.addCallback(lambda ign:
4897 c0.upload(upload.Data(DATA+"1", convergence="")))
4898 d.addCallback(_stash_uri, "two")
4899 def _stash_mutable_uri(n, which):
4900 self.uris[which] = n.get_uri()
4901 assert isinstance(self.uris[which], str)
4902 d.addCallback(lambda ign:
4903 c0.create_mutable_file(publish.MutableData(DATA+"2")))
4904 d.addCallback(_stash_mutable_uri, "mutable")
4906 def _compute_fileurls(ignored):
4908 for which in self.uris:
4909 self.fileurls[which] = "uri/" + urllib.quote(self.uris[which])
4910 d.addCallback(_compute_fileurls)
4912 d.addCallback(self._count_leases, "one")
4913 d.addCallback(self._assert_leasecount, 1)
4914 d.addCallback(self._count_leases, "two")
4915 d.addCallback(self._assert_leasecount, 1)
4916 d.addCallback(self._count_leases, "mutable")
4917 d.addCallback(self._assert_leasecount, 1)
4919 d.addCallback(self.CHECK, "one", "t=check") # no add-lease
4920 def _got_html_good(res):
4921 self.failUnless("Healthy" in res, res)
4922 self.failIf("Not Healthy" in res, res)
4923 d.addCallback(_got_html_good)
4925 d.addCallback(self._count_leases, "one")
4926 d.addCallback(self._assert_leasecount, 1)
4927 d.addCallback(self._count_leases, "two")
4928 d.addCallback(self._assert_leasecount, 1)
4929 d.addCallback(self._count_leases, "mutable")
4930 d.addCallback(self._assert_leasecount, 1)
4932 # this CHECK uses the original client, which uses the same
4933 # lease-secrets, so it will just renew the original lease
4934 d.addCallback(self.CHECK, "one", "t=check&add-lease=true")
4935 d.addCallback(_got_html_good)
4937 d.addCallback(self._count_leases, "one")
4938 d.addCallback(self._assert_leasecount, 1)
4939 d.addCallback(self._count_leases, "two")
4940 d.addCallback(self._assert_leasecount, 1)
4941 d.addCallback(self._count_leases, "mutable")
4942 d.addCallback(self._assert_leasecount, 1)
4944 # this CHECK uses an alternate client, which adds a second lease
4945 d.addCallback(self.CHECK, "one", "t=check&add-lease=true", clientnum=1)
4946 d.addCallback(_got_html_good)
4948 d.addCallback(self._count_leases, "one")
4949 d.addCallback(self._assert_leasecount, 2)
4950 d.addCallback(self._count_leases, "two")
4951 d.addCallback(self._assert_leasecount, 1)
4952 d.addCallback(self._count_leases, "mutable")
4953 d.addCallback(self._assert_leasecount, 1)
4955 d.addCallback(self.CHECK, "mutable", "t=check&add-lease=true")
4956 d.addCallback(_got_html_good)
4958 d.addCallback(self._count_leases, "one")
4959 d.addCallback(self._assert_leasecount, 2)
4960 d.addCallback(self._count_leases, "two")
4961 d.addCallback(self._assert_leasecount, 1)
4962 d.addCallback(self._count_leases, "mutable")
4963 d.addCallback(self._assert_leasecount, 1)
4965 d.addCallback(self.CHECK, "mutable", "t=check&add-lease=true",
4967 d.addCallback(_got_html_good)
4969 d.addCallback(self._count_leases, "one")
4970 d.addCallback(self._assert_leasecount, 2)
4971 d.addCallback(self._count_leases, "two")
4972 d.addCallback(self._assert_leasecount, 1)
4973 d.addCallback(self._count_leases, "mutable")
4974 d.addCallback(self._assert_leasecount, 2)
4976 d.addErrback(self.explain_web_error)
4979 def test_deep_add_lease(self):
4980 self.basedir = "web/Grid/deep_add_lease"
4981 self.set_up_grid(num_clients=2)
4982 c0 = self.g.clients[0]
4986 d = c0.create_dirnode()
4987 def _stash_root_and_create_file(n):
4989 self.uris["root"] = n.get_uri()
4990 self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri()) + "/"
4991 return n.add_file(u"one", upload.Data(DATA, convergence=""))
4992 d.addCallback(_stash_root_and_create_file)
4993 def _stash_uri(fn, which):
4994 self.uris[which] = fn.get_uri()
4995 d.addCallback(_stash_uri, "one")
4996 d.addCallback(lambda ign:
4997 self.rootnode.add_file(u"small",
4998 upload.Data("literal",
5000 d.addCallback(_stash_uri, "small")
5002 d.addCallback(lambda ign:
5003 c0.create_mutable_file(publish.MutableData("mutable")))
5004 d.addCallback(lambda fn: self.rootnode.set_node(u"mutable", fn))
5005 d.addCallback(_stash_uri, "mutable")
5007 d.addCallback(self.CHECK, "root", "t=stream-deep-check") # no add-lease
5009 units = [simplejson.loads(line)
5010 for line in res.splitlines()
5012 # root, one, small, mutable, stats
5013 self.failUnlessReallyEqual(len(units), 4+1)
5014 d.addCallback(_done)
5016 d.addCallback(self._count_leases, "root")
5017 d.addCallback(self._assert_leasecount, 1)
5018 d.addCallback(self._count_leases, "one")
5019 d.addCallback(self._assert_leasecount, 1)
5020 d.addCallback(self._count_leases, "mutable")
5021 d.addCallback(self._assert_leasecount, 1)
5023 d.addCallback(self.CHECK, "root", "t=stream-deep-check&add-lease=true")
5024 d.addCallback(_done)
5026 d.addCallback(self._count_leases, "root")
5027 d.addCallback(self._assert_leasecount, 1)
5028 d.addCallback(self._count_leases, "one")
5029 d.addCallback(self._assert_leasecount, 1)
5030 d.addCallback(self._count_leases, "mutable")
5031 d.addCallback(self._assert_leasecount, 1)
5033 d.addCallback(self.CHECK, "root", "t=stream-deep-check&add-lease=true",
5035 d.addCallback(_done)
5037 d.addCallback(self._count_leases, "root")
5038 d.addCallback(self._assert_leasecount, 2)
5039 d.addCallback(self._count_leases, "one")
5040 d.addCallback(self._assert_leasecount, 2)
5041 d.addCallback(self._count_leases, "mutable")
5042 d.addCallback(self._assert_leasecount, 2)
5044 d.addErrback(self.explain_web_error)
5048 def test_exceptions(self):
5049 self.basedir = "web/Grid/exceptions"
5050 self.set_up_grid(num_clients=1, num_servers=2)
5051 c0 = self.g.clients[0]
5052 c0.DEFAULT_ENCODING_PARAMETERS['happy'] = 2
5055 d = c0.create_dirnode()
5057 self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri()) + "/"
5058 self.fileurls["imaginary"] = self.fileurls["root"] + "imaginary"
5060 d.addCallback(_stash_root)
5061 d.addCallback(lambda ign: c0.upload(upload.Data(DATA, convergence="")))
5063 self.fileurls["1share"] = "uri/" + urllib.quote(ur.uri)
5064 self.delete_shares_numbered(ur.uri, range(1,10))
5066 u = uri.from_string(ur.uri)
5067 u.key = testutil.flip_bit(u.key, 0)
5068 baduri = u.to_string()
5069 self.fileurls["0shares"] = "uri/" + urllib.quote(baduri)
5070 d.addCallback(_stash_bad)
5071 d.addCallback(lambda ign: c0.create_dirnode())
5072 def _mangle_dirnode_1share(n):
5074 url = self.fileurls["dir-1share"] = "uri/" + urllib.quote(u) + "/"
5075 self.fileurls["dir-1share-json"] = url + "?t=json"
5076 self.delete_shares_numbered(u, range(1,10))
5077 d.addCallback(_mangle_dirnode_1share)
5078 d.addCallback(lambda ign: c0.create_dirnode())
5079 def _mangle_dirnode_0share(n):
5081 url = self.fileurls["dir-0share"] = "uri/" + urllib.quote(u) + "/"
5082 self.fileurls["dir-0share-json"] = url + "?t=json"
5083 self.delete_shares_numbered(u, range(0,10))
5084 d.addCallback(_mangle_dirnode_0share)
5086 # NotEnoughSharesError should be reported sensibly, with a
5087 # text/plain explanation of the problem, and perhaps some
5088 # information on which shares *could* be found.
5090 d.addCallback(lambda ignored:
5091 self.shouldHTTPError("GET unrecoverable",
5092 410, "Gone", "NoSharesError",
5093 self.GET, self.fileurls["0shares"]))
5094 def _check_zero_shares(body):
5095 self.failIf("<html>" in body, body)
5096 body = " ".join(body.strip().split())
5097 exp = ("NoSharesError: no shares could be found. "
5098 "Zero shares usually indicates a corrupt URI, or that "
5099 "no servers were connected, but it might also indicate "
5100 "severe corruption. You should perform a filecheck on "
5101 "this object to learn more. The full error message is: "
5102 "no shares (need 3). Last failure: None")
5103 self.failUnlessReallyEqual(exp, body)
5104 d.addCallback(_check_zero_shares)
5107 d.addCallback(lambda ignored:
5108 self.shouldHTTPError("GET 1share",
5109 410, "Gone", "NotEnoughSharesError",
5110 self.GET, self.fileurls["1share"]))
5111 def _check_one_share(body):
5112 self.failIf("<html>" in body, body)
5113 body = " ".join(body.strip().split())
5114 msgbase = ("NotEnoughSharesError: This indicates that some "
5115 "servers were unavailable, or that shares have been "
5116 "lost to server departure, hard drive failure, or disk "
5117 "corruption. You should perform a filecheck on "
5118 "this object to learn more. The full error message is:"
5120 msg1 = msgbase + (" ran out of shares:"
5123 " overdue= unused= need 3. Last failure: None")
5124 msg2 = msgbase + (" ran out of shares:"
5126 " pending=Share(sh0-on-xgru5)"
5127 " overdue= unused= need 3. Last failure: None")
5128 self.failUnless(body == msg1 or body == msg2, body)
5129 d.addCallback(_check_one_share)
5131 d.addCallback(lambda ignored:
5132 self.shouldHTTPError("GET imaginary",
5133 404, "Not Found", None,
5134 self.GET, self.fileurls["imaginary"]))
5135 def _missing_child(body):
5136 self.failUnless("No such child: imaginary" in body, body)
5137 d.addCallback(_missing_child)
5139 d.addCallback(lambda ignored: self.GET(self.fileurls["dir-0share"]))
5140 def _check_0shares_dir_html(body):
5141 self.failUnless("<html>" in body, body)
5142 # we should see the regular page, but without the child table or
5144 body = " ".join(body.strip().split())
5145 self.failUnlessIn('href="?t=info">More info on this directory',
5147 exp = ("UnrecoverableFileError: the directory (or mutable file) "
5148 "could not be retrieved, because there were insufficient "
5149 "good shares. This might indicate that no servers were "
5150 "connected, insufficient servers were connected, the URI "
5151 "was corrupt, or that shares have been lost due to server "
5152 "departure, hard drive failure, or disk corruption. You "
5153 "should perform a filecheck on this object to learn more.")
5154 self.failUnlessIn(exp, body)
5155 self.failUnlessIn("No upload forms: directory is unreadable", body)
5156 d.addCallback(_check_0shares_dir_html)
5158 d.addCallback(lambda ignored: self.GET(self.fileurls["dir-1share"]))
5159 def _check_1shares_dir_html(body):
5160 # at some point, we'll split UnrecoverableFileError into 0-shares
5161 # and some-shares like we did for immutable files (since there
5162 # are different sorts of advice to offer in each case). For now,
5163 # they present the same way.
5164 self.failUnless("<html>" in body, body)
5165 body = " ".join(body.strip().split())
5166 self.failUnlessIn('href="?t=info">More info on this directory',
5168 exp = ("UnrecoverableFileError: the directory (or mutable file) "
5169 "could not be retrieved, because there were insufficient "
5170 "good shares. This might indicate that no servers were "
5171 "connected, insufficient servers were connected, the URI "
5172 "was corrupt, or that shares have been lost due to server "
5173 "departure, hard drive failure, or disk corruption. You "
5174 "should perform a filecheck on this object to learn more.")
5175 self.failUnlessIn(exp, body)
5176 self.failUnlessIn("No upload forms: directory is unreadable", body)
5177 d.addCallback(_check_1shares_dir_html)
5179 d.addCallback(lambda ignored:
5180 self.shouldHTTPError("GET dir-0share-json",
5181 410, "Gone", "UnrecoverableFileError",
5183 self.fileurls["dir-0share-json"]))
5184 def _check_unrecoverable_file(body):
5185 self.failIf("<html>" in body, body)
5186 body = " ".join(body.strip().split())
5187 exp = ("UnrecoverableFileError: the directory (or mutable file) "
5188 "could not be retrieved, because there were insufficient "
5189 "good shares. This might indicate that no servers were "
5190 "connected, insufficient servers were connected, the URI "
5191 "was corrupt, or that shares have been lost due to server "
5192 "departure, hard drive failure, or disk corruption. You "
5193 "should perform a filecheck on this object to learn more.")
5194 self.failUnlessReallyEqual(exp, body)
5195 d.addCallback(_check_unrecoverable_file)
5197 d.addCallback(lambda ignored:
5198 self.shouldHTTPError("GET dir-1share-json",
5199 410, "Gone", "UnrecoverableFileError",
5201 self.fileurls["dir-1share-json"]))
5202 d.addCallback(_check_unrecoverable_file)
5204 d.addCallback(lambda ignored:
5205 self.shouldHTTPError("GET imaginary",
5206 404, "Not Found", None,
5207 self.GET, self.fileurls["imaginary"]))
5209 # attach a webapi child that throws a random error, to test how it
5211 w = c0.getServiceNamed("webish")
5212 w.root.putChild("ERRORBOOM", ErrorBoom())
5214 # "Accept: */*" : should get a text/html stack trace
5215 # "Accept: text/plain" : should get a text/plain stack trace
5216 # "Accept: text/plain, application/octet-stream" : text/plain (CLI)
5217 # no Accept header: should get a text/html stack trace
5219 d.addCallback(lambda ignored:
5220 self.shouldHTTPError("GET errorboom_html",
5221 500, "Internal Server Error", None,
5222 self.GET, "ERRORBOOM",
5223 headers={"accept": ["*/*"]}))
5224 def _internal_error_html1(body):
5225 self.failUnless("<html>" in body, "expected HTML, not '%s'" % body)
5226 d.addCallback(_internal_error_html1)
5228 d.addCallback(lambda ignored:
5229 self.shouldHTTPError("GET errorboom_text",
5230 500, "Internal Server Error", None,
5231 self.GET, "ERRORBOOM",
5232 headers={"accept": ["text/plain"]}))
5233 def _internal_error_text2(body):
5234 self.failIf("<html>" in body, body)
5235 self.failUnless(body.startswith("Traceback "), body)
5236 d.addCallback(_internal_error_text2)
5238 CLI_accepts = "text/plain, application/octet-stream"
5239 d.addCallback(lambda ignored:
5240 self.shouldHTTPError("GET errorboom_text",
5241 500, "Internal Server Error", None,
5242 self.GET, "ERRORBOOM",
5243 headers={"accept": [CLI_accepts]}))
5244 def _internal_error_text3(body):
5245 self.failIf("<html>" in body, body)
5246 self.failUnless(body.startswith("Traceback "), body)
5247 d.addCallback(_internal_error_text3)
5249 d.addCallback(lambda ignored:
5250 self.shouldHTTPError("GET errorboom_text",
5251 500, "Internal Server Error", None,
5252 self.GET, "ERRORBOOM"))
5253 def _internal_error_html4(body):
5254 self.failUnless("<html>" in body, "expected HTML, not '%s'" % body)
5255 d.addCallback(_internal_error_html4)
5257 def _flush_errors(res):
5258 # Trial: please ignore the CompletelyUnhandledError in the logs
5259 self.flushLoggedErrors(CompletelyUnhandledError)
5261 d.addBoth(_flush_errors)
5265 class CompletelyUnhandledError(Exception):
5267 class ErrorBoom(rend.Page):
5268 def beforeRender(self, ctx):
5269 raise CompletelyUnhandledError("whoops")