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):
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.blacklist = None
174 self.nodemaker = FakeNodeMaker(None, self._secret_holder, None,
177 self.mutable_file_default = SDMF_VERSION
179 def startService(self):
180 return service.MultiService.startService(self)
181 def stopService(self):
182 return service.MultiService.stopService(self)
184 MUTABLE_SIZELIMIT = FakeMutableFileNode.MUTABLE_SIZELIMIT
186 class WebMixin(object):
188 self.s = FakeClient()
189 self.s.startService()
190 self.staticdir = self.mktemp()
192 self.ws = webish.WebishServer(self.s, "0", staticdir=self.staticdir,
194 self.ws.setServiceParent(self.s)
195 self.webish_port = self.ws.getPortnum()
196 self.webish_url = self.ws.getURL()
197 assert self.webish_url.endswith("/")
198 self.webish_url = self.webish_url[:-1] # these tests add their own /
200 l = [ self.s.create_dirnode() for x in range(6) ]
201 d = defer.DeferredList(l)
203 self.public_root = res[0][1]
204 assert interfaces.IDirectoryNode.providedBy(self.public_root), res
205 self.public_url = "/uri/" + self.public_root.get_uri()
206 self.private_root = res[1][1]
210 self._foo_uri = foo.get_uri()
211 self._foo_readonly_uri = foo.get_readonly_uri()
212 self._foo_verifycap = foo.get_verify_cap().to_string()
213 # NOTE: we ignore the deferred on all set_uri() calls, because we
214 # know the fake nodes do these synchronously
215 self.public_root.set_uri(u"foo", foo.get_uri(),
216 foo.get_readonly_uri())
218 self.BAR_CONTENTS, n, self._bar_txt_uri = self.makefile(0)
219 foo.set_uri(u"bar.txt", self._bar_txt_uri, self._bar_txt_uri)
220 self._bar_txt_verifycap = n.get_verify_cap().to_string()
223 # XXX: Do we ever use this?
224 self.BAZ_CONTENTS, n, self._baz_txt_uri, self._baz_txt_readonly_uri = self.makefile_mutable(0)
226 foo.set_uri(u"baz.txt", self._baz_txt_uri, self._baz_txt_readonly_uri)
229 self.QUUX_CONTENTS, n, self._quux_txt_uri, self._quux_txt_readonly_uri = self.makefile_mutable(0, mdmf=True)
230 assert self._quux_txt_uri.startswith("URI:MDMF")
231 foo.set_uri(u"quux.txt", self._quux_txt_uri, self._quux_txt_readonly_uri)
233 foo.set_uri(u"empty", res[3][1].get_uri(),
234 res[3][1].get_readonly_uri())
235 sub_uri = res[4][1].get_uri()
236 self._sub_uri = sub_uri
237 foo.set_uri(u"sub", sub_uri, sub_uri)
238 sub = self.s.create_node_from_uri(sub_uri)
240 _ign, n, blocking_uri = self.makefile(1)
241 foo.set_uri(u"blockingfile", blocking_uri, blocking_uri)
243 unicode_filename = u"n\u00fc.txt" # n u-umlaut . t x t
244 # ok, unicode calls it LATIN SMALL LETTER U WITH DIAERESIS but I
245 # still think of it as an umlaut
246 foo.set_uri(unicode_filename, self._bar_txt_uri, self._bar_txt_uri)
248 _ign, n, baz_file = self.makefile(2)
249 self._baz_file_uri = baz_file
250 sub.set_uri(u"baz.txt", baz_file, baz_file)
252 _ign, n, self._bad_file_uri = self.makefile(3)
253 # this uri should not be downloadable
254 del FakeCHKFileNode.all_contents[self._bad_file_uri]
257 self.public_root.set_uri(u"reedownlee", rodir.get_readonly_uri(),
258 rodir.get_readonly_uri())
259 rodir.set_uri(u"nor", baz_file, baz_file)
265 # public/foo/quux.txt
266 # public/foo/blockingfile
269 # public/foo/sub/baz.txt
271 # public/reedownlee/nor
272 self.NEWFILE_CONTENTS = "newfile contents\n"
274 return foo.get_metadata_for(u"bar.txt")
276 def _got_metadata(metadata):
277 self._bar_txt_metadata = metadata
278 d.addCallback(_got_metadata)
281 def makefile(self, number):
282 contents = "contents of file %s\n" % number
283 n = create_chk_filenode(contents)
284 return contents, n, n.get_uri()
286 def makefile_mutable(self, number, mdmf=False):
287 contents = "contents of mutable file %s\n" % number
288 n = create_mutable_filenode(contents, mdmf)
289 return contents, n, n.get_uri(), n.get_readonly_uri()
292 return self.s.stopService()
294 def failUnlessIsBarDotTxt(self, res):
295 self.failUnlessReallyEqual(res, self.BAR_CONTENTS, res)
297 def failUnlessIsQuuxDotTxt(self, res):
298 self.failUnlessReallyEqual(res, self.QUUX_CONTENTS, res)
300 def failUnlessIsBazDotTxt(self, res):
301 self.failUnlessReallyEqual(res, self.BAZ_CONTENTS, res)
303 def failUnlessIsBarJSON(self, res):
304 data = simplejson.loads(res)
305 self.failUnless(isinstance(data, list))
306 self.failUnlessEqual(data[0], "filenode")
307 self.failUnless(isinstance(data[1], dict))
308 self.failIf(data[1]["mutable"])
309 self.failIf("rw_uri" in data[1]) # immutable
310 self.failUnlessReallyEqual(to_str(data[1]["ro_uri"]), self._bar_txt_uri)
311 self.failUnlessReallyEqual(to_str(data[1]["verify_uri"]), self._bar_txt_verifycap)
312 self.failUnlessReallyEqual(data[1]["size"], len(self.BAR_CONTENTS))
314 def failUnlessIsQuuxJSON(self, res, readonly=False):
315 data = simplejson.loads(res)
316 self.failUnless(isinstance(data, list))
317 self.failUnlessEqual(data[0], "filenode")
318 self.failUnless(isinstance(data[1], dict))
320 return self.failUnlessIsQuuxDotTxtMetadata(metadata, readonly)
322 def failUnlessIsQuuxDotTxtMetadata(self, metadata, readonly):
323 self.failUnless(metadata['mutable'])
325 self.failIf("rw_uri" in metadata)
327 self.failUnless("rw_uri" in metadata)
328 self.failUnlessEqual(metadata['rw_uri'], self._quux_txt_uri)
329 self.failUnless("ro_uri" in metadata)
330 self.failUnlessEqual(metadata['ro_uri'], self._quux_txt_readonly_uri)
331 self.failUnlessReallyEqual(metadata['size'], len(self.QUUX_CONTENTS))
333 def failUnlessIsFooJSON(self, res):
334 data = simplejson.loads(res)
335 self.failUnless(isinstance(data, list))
336 self.failUnlessEqual(data[0], "dirnode", res)
337 self.failUnless(isinstance(data[1], dict))
338 self.failUnless(data[1]["mutable"])
339 self.failUnless("rw_uri" in data[1]) # mutable
340 self.failUnlessReallyEqual(to_str(data[1]["rw_uri"]), self._foo_uri)
341 self.failUnlessReallyEqual(to_str(data[1]["ro_uri"]), self._foo_readonly_uri)
342 self.failUnlessReallyEqual(to_str(data[1]["verify_uri"]), self._foo_verifycap)
344 kidnames = sorted([unicode(n) for n in data[1]["children"]])
345 self.failUnlessEqual(kidnames,
346 [u"bar.txt", u"baz.txt", u"blockingfile",
347 u"empty", u"n\u00fc.txt", u"quux.txt", u"sub"])
348 kids = dict( [(unicode(name),value)
350 in data[1]["children"].iteritems()] )
351 self.failUnlessEqual(kids[u"sub"][0], "dirnode")
352 self.failUnlessIn("metadata", kids[u"sub"][1])
353 self.failUnlessIn("tahoe", kids[u"sub"][1]["metadata"])
354 tahoe_md = kids[u"sub"][1]["metadata"]["tahoe"]
355 self.failUnlessIn("linkcrtime", tahoe_md)
356 self.failUnlessIn("linkmotime", tahoe_md)
357 self.failUnlessEqual(kids[u"bar.txt"][0], "filenode")
358 self.failUnlessReallyEqual(kids[u"bar.txt"][1]["size"], len(self.BAR_CONTENTS))
359 self.failUnlessReallyEqual(to_str(kids[u"bar.txt"][1]["ro_uri"]), self._bar_txt_uri)
360 self.failUnlessReallyEqual(to_str(kids[u"bar.txt"][1]["verify_uri"]),
361 self._bar_txt_verifycap)
362 self.failUnlessIn("metadata", kids[u"bar.txt"][1])
363 self.failUnlessIn("tahoe", kids[u"bar.txt"][1]["metadata"])
364 self.failUnlessReallyEqual(kids[u"bar.txt"][1]["metadata"]["tahoe"]["linkcrtime"],
365 self._bar_txt_metadata["tahoe"]["linkcrtime"])
366 self.failUnlessReallyEqual(to_str(kids[u"n\u00fc.txt"][1]["ro_uri"]),
368 self.failUnlessIn("quux.txt", kids)
369 self.failUnlessReallyEqual(to_str(kids[u"quux.txt"][1]["rw_uri"]),
371 self.failUnlessReallyEqual(to_str(kids[u"quux.txt"][1]["ro_uri"]),
372 self._quux_txt_readonly_uri)
374 def GET(self, urlpath, followRedirect=False, return_response=False,
376 # if return_response=True, this fires with (data, statuscode,
377 # respheaders) instead of just data.
378 assert not isinstance(urlpath, unicode)
379 url = self.webish_url + urlpath
380 factory = HTTPClientGETFactory(url, method="GET",
381 followRedirect=followRedirect, **kwargs)
382 reactor.connectTCP("localhost", self.webish_port, factory)
385 return (data, factory.status, factory.response_headers)
387 d.addCallback(_got_data)
388 return factory.deferred
390 def HEAD(self, urlpath, return_response=False, **kwargs):
391 # this requires some surgery, because twisted.web.client doesn't want
392 # to give us back the response headers.
393 factory = HTTPClientHEADFactory(urlpath, method="HEAD", **kwargs)
394 reactor.connectTCP("localhost", self.webish_port, factory)
397 return (data, factory.status, factory.response_headers)
399 d.addCallback(_got_data)
400 return factory.deferred
402 def PUT(self, urlpath, data, **kwargs):
403 url = self.webish_url + urlpath
404 return client.getPage(url, method="PUT", postdata=data, **kwargs)
406 def DELETE(self, urlpath):
407 url = self.webish_url + urlpath
408 return client.getPage(url, method="DELETE")
410 def POST(self, urlpath, followRedirect=False, **fields):
411 sepbase = "boogabooga"
415 form.append('Content-Disposition: form-data; name="_charset"')
419 for name, value in fields.iteritems():
420 if isinstance(value, tuple):
421 filename, value = value
422 form.append('Content-Disposition: form-data; name="%s"; '
423 'filename="%s"' % (name, filename.encode("utf-8")))
425 form.append('Content-Disposition: form-data; name="%s"' % name)
427 if isinstance(value, unicode):
428 value = value.encode("utf-8")
431 assert isinstance(value, str)
438 body = "\r\n".join(form) + "\r\n"
439 headers["content-type"] = "multipart/form-data; boundary=%s" % sepbase
440 return self.POST2(urlpath, body, headers, followRedirect)
442 def POST2(self, urlpath, body="", headers={}, followRedirect=False):
443 url = self.webish_url + urlpath
444 return client.getPage(url, method="POST", postdata=body,
445 headers=headers, followRedirect=followRedirect)
447 def shouldFail(self, res, expected_failure, which,
448 substring=None, response_substring=None):
449 if isinstance(res, failure.Failure):
450 res.trap(expected_failure)
452 self.failUnless(substring in str(res),
453 "substring '%s' not in '%s'"
454 % (substring, str(res)))
455 if response_substring:
456 self.failUnless(response_substring in res.value.response,
457 "response substring '%s' not in '%s'"
458 % (response_substring, res.value.response))
460 self.fail("%s was supposed to raise %s, not get '%s'" %
461 (which, expected_failure, res))
463 def shouldFail2(self, expected_failure, which, substring,
465 callable, *args, **kwargs):
466 assert substring is None or isinstance(substring, str)
467 assert response_substring is None or isinstance(response_substring, str)
468 d = defer.maybeDeferred(callable, *args, **kwargs)
470 if isinstance(res, failure.Failure):
471 res.trap(expected_failure)
473 self.failUnless(substring in str(res),
474 "%s: substring '%s' not in '%s'"
475 % (which, substring, str(res)))
476 if response_substring:
477 self.failUnless(response_substring in res.value.response,
478 "%s: response substring '%s' not in '%s'"
480 response_substring, res.value.response))
482 self.fail("%s was supposed to raise %s, not get '%s'" %
483 (which, expected_failure, res))
487 def should404(self, res, which):
488 if isinstance(res, failure.Failure):
489 res.trap(error.Error)
490 self.failUnlessReallyEqual(res.value.status, "404")
492 self.fail("%s was supposed to Error(404), not get '%s'" %
495 def should302(self, res, which):
496 if isinstance(res, failure.Failure):
497 res.trap(error.Error)
498 self.failUnlessReallyEqual(res.value.status, "302")
500 self.fail("%s was supposed to Error(302), not get '%s'" %
504 class Web(WebMixin, WebErrorMixin, testutil.StallMixin, testutil.ReallyEqualMixin, unittest.TestCase):
505 def test_create(self):
508 def test_welcome(self):
511 self.failUnless('Welcome To Tahoe-LAFS' in res, res)
513 self.s.basedir = 'web/test_welcome'
514 fileutil.make_dirs("web/test_welcome")
515 fileutil.make_dirs("web/test_welcome/private")
517 d.addCallback(_check)
520 def test_provisioning(self):
521 d = self.GET("/provisioning/")
523 self.failUnless('Provisioning Tool' in res)
524 fields = {'filled': True,
525 "num_users": int(50e3),
526 "files_per_user": 1000,
527 "space_per_user": int(1e9),
528 "sharing_ratio": 1.0,
529 "encoding_parameters": "3-of-10-5",
531 "ownership_mode": "A",
532 "download_rate": 100,
537 return self.POST("/provisioning/", **fields)
539 d.addCallback(_check)
541 self.failUnless('Provisioning Tool' in res)
542 self.failUnless("Share space consumed: 167.01TB" in res)
544 fields = {'filled': True,
545 "num_users": int(50e6),
546 "files_per_user": 1000,
547 "space_per_user": int(5e9),
548 "sharing_ratio": 1.0,
549 "encoding_parameters": "25-of-100-50",
550 "num_servers": 30000,
551 "ownership_mode": "E",
552 "drive_failure_model": "U",
554 "download_rate": 1000,
559 return self.POST("/provisioning/", **fields)
560 d.addCallback(_check2)
562 self.failUnless("Share space consumed: huge!" in res)
563 fields = {'filled': True}
564 return self.POST("/provisioning/", **fields)
565 d.addCallback(_check3)
567 self.failUnless("Share space consumed:" in res)
568 d.addCallback(_check4)
571 def test_reliability_tool(self):
573 from allmydata import reliability
574 _hush_pyflakes = reliability
577 raise unittest.SkipTest("reliability tool requires NumPy")
579 d = self.GET("/reliability/")
581 self.failUnless('Reliability Tool' in res)
582 fields = {'drive_lifetime': "8Y",
587 "check_period": "1M",
588 "report_period": "3M",
591 return self.POST("/reliability/", **fields)
593 d.addCallback(_check)
595 self.failUnless('Reliability Tool' in res)
596 r = r'Probability of loss \(no maintenance\):\s+<span>0.033591'
597 self.failUnless(re.search(r, res), res)
598 d.addCallback(_check2)
601 def test_status(self):
602 h = self.s.get_history()
603 dl_num = h.list_all_download_statuses()[0].get_counter()
604 ul_num = h.list_all_upload_statuses()[0].get_counter()
605 mu_num = h.list_all_mapupdate_statuses()[0].get_counter()
606 pub_num = h.list_all_publish_statuses()[0].get_counter()
607 ret_num = h.list_all_retrieve_statuses()[0].get_counter()
608 d = self.GET("/status", followRedirect=True)
610 self.failUnless('Upload and Download Status' in res, res)
611 self.failUnless('"down-%d"' % dl_num in res, res)
612 self.failUnless('"up-%d"' % ul_num in res, res)
613 self.failUnless('"mapupdate-%d"' % mu_num in res, res)
614 self.failUnless('"publish-%d"' % pub_num in res, res)
615 self.failUnless('"retrieve-%d"' % ret_num in res, res)
616 d.addCallback(_check)
617 d.addCallback(lambda res: self.GET("/status/?t=json"))
618 def _check_json(res):
619 data = simplejson.loads(res)
620 self.failUnless(isinstance(data, dict))
621 #active = data["active"]
622 # TODO: test more. We need a way to fake an active operation
624 d.addCallback(_check_json)
626 d.addCallback(lambda res: self.GET("/status/down-%d" % dl_num))
628 self.failUnless("File Download Status" in res, res)
629 d.addCallback(_check_dl)
630 d.addCallback(lambda res: self.GET("/status/down-%d/event_json" % dl_num))
631 def _check_dl_json(res):
632 data = simplejson.loads(res)
633 self.failUnless(isinstance(data, dict))
634 self.failUnless("read" in data)
635 self.failUnlessEqual(data["read"][0]["length"], 120)
636 self.failUnlessEqual(data["segment"][0]["segment_length"], 100)
637 self.failUnlessEqual(data["segment"][2]["segment_number"], 2)
638 self.failUnlessEqual(data["segment"][2]["finish_time"], None)
639 phwr_id = base32.b2a(hashutil.tagged_hash("foo", "serverid_a")[:20])
640 cmpu_id = base32.b2a(hashutil.tagged_hash("foo", "serverid_b")[:20])
641 # serverids[] keys are strings, since that's what JSON does, but
642 # we'd really like them to be ints
643 self.failUnlessEqual(data["serverids"]["0"], "phwr")
644 self.failUnless(data["serverids"].has_key("1"), data["serverids"])
645 self.failUnlessEqual(data["serverids"]["1"], "cmpu", data["serverids"])
646 self.failUnlessEqual(data["server_info"][phwr_id]["short"], "phwr")
647 self.failUnlessEqual(data["server_info"][cmpu_id]["short"], "cmpu")
648 self.failUnless("dyhb" in data)
649 d.addCallback(_check_dl_json)
650 d.addCallback(lambda res: self.GET("/status/up-%d" % ul_num))
652 self.failUnless("File Upload Status" in res, res)
653 d.addCallback(_check_ul)
654 d.addCallback(lambda res: self.GET("/status/mapupdate-%d" % mu_num))
655 def _check_mapupdate(res):
656 self.failUnless("Mutable File Servermap Update Status" in res, res)
657 d.addCallback(_check_mapupdate)
658 d.addCallback(lambda res: self.GET("/status/publish-%d" % pub_num))
659 def _check_publish(res):
660 self.failUnless("Mutable File Publish Status" in res, res)
661 d.addCallback(_check_publish)
662 d.addCallback(lambda res: self.GET("/status/retrieve-%d" % ret_num))
663 def _check_retrieve(res):
664 self.failUnless("Mutable File Retrieve Status" in res, res)
665 d.addCallback(_check_retrieve)
669 def test_status_numbers(self):
670 drrm = status.DownloadResultsRendererMixin()
671 self.failUnlessReallyEqual(drrm.render_time(None, None), "")
672 self.failUnlessReallyEqual(drrm.render_time(None, 2.5), "2.50s")
673 self.failUnlessReallyEqual(drrm.render_time(None, 0.25), "250ms")
674 self.failUnlessReallyEqual(drrm.render_time(None, 0.0021), "2.1ms")
675 self.failUnlessReallyEqual(drrm.render_time(None, 0.000123), "123us")
676 self.failUnlessReallyEqual(drrm.render_rate(None, None), "")
677 self.failUnlessReallyEqual(drrm.render_rate(None, 2500000), "2.50MBps")
678 self.failUnlessReallyEqual(drrm.render_rate(None, 30100), "30.1kBps")
679 self.failUnlessReallyEqual(drrm.render_rate(None, 123), "123Bps")
681 urrm = status.UploadResultsRendererMixin()
682 self.failUnlessReallyEqual(urrm.render_time(None, None), "")
683 self.failUnlessReallyEqual(urrm.render_time(None, 2.5), "2.50s")
684 self.failUnlessReallyEqual(urrm.render_time(None, 0.25), "250ms")
685 self.failUnlessReallyEqual(urrm.render_time(None, 0.0021), "2.1ms")
686 self.failUnlessReallyEqual(urrm.render_time(None, 0.000123), "123us")
687 self.failUnlessReallyEqual(urrm.render_rate(None, None), "")
688 self.failUnlessReallyEqual(urrm.render_rate(None, 2500000), "2.50MBps")
689 self.failUnlessReallyEqual(urrm.render_rate(None, 30100), "30.1kBps")
690 self.failUnlessReallyEqual(urrm.render_rate(None, 123), "123Bps")
692 def test_GET_FILEURL(self):
693 d = self.GET(self.public_url + "/foo/bar.txt")
694 d.addCallback(self.failUnlessIsBarDotTxt)
697 def test_GET_FILEURL_range(self):
698 headers = {"range": "bytes=1-10"}
699 d = self.GET(self.public_url + "/foo/bar.txt", headers=headers,
700 return_response=True)
701 def _got((res, status, headers)):
702 self.failUnlessReallyEqual(int(status), 206)
703 self.failUnless(headers.has_key("content-range"))
704 self.failUnlessReallyEqual(headers["content-range"][0],
705 "bytes 1-10/%d" % len(self.BAR_CONTENTS))
706 self.failUnlessReallyEqual(res, self.BAR_CONTENTS[1:11])
710 def test_GET_FILEURL_partial_range(self):
711 headers = {"range": "bytes=5-"}
712 length = len(self.BAR_CONTENTS)
713 d = self.GET(self.public_url + "/foo/bar.txt", headers=headers,
714 return_response=True)
715 def _got((res, status, headers)):
716 self.failUnlessReallyEqual(int(status), 206)
717 self.failUnless(headers.has_key("content-range"))
718 self.failUnlessReallyEqual(headers["content-range"][0],
719 "bytes 5-%d/%d" % (length-1, length))
720 self.failUnlessReallyEqual(res, self.BAR_CONTENTS[5:])
724 def test_GET_FILEURL_partial_end_range(self):
725 headers = {"range": "bytes=-5"}
726 length = len(self.BAR_CONTENTS)
727 d = self.GET(self.public_url + "/foo/bar.txt", headers=headers,
728 return_response=True)
729 def _got((res, status, headers)):
730 self.failUnlessReallyEqual(int(status), 206)
731 self.failUnless(headers.has_key("content-range"))
732 self.failUnlessReallyEqual(headers["content-range"][0],
733 "bytes %d-%d/%d" % (length-5, length-1, length))
734 self.failUnlessReallyEqual(res, self.BAR_CONTENTS[-5:])
738 def test_GET_FILEURL_partial_range_overrun(self):
739 headers = {"range": "bytes=100-200"}
740 d = self.shouldFail2(error.Error, "test_GET_FILEURL_range_overrun",
741 "416 Requested Range not satisfiable",
742 "First beyond end of file",
743 self.GET, self.public_url + "/foo/bar.txt",
747 def test_HEAD_FILEURL_range(self):
748 headers = {"range": "bytes=1-10"}
749 d = self.HEAD(self.public_url + "/foo/bar.txt", headers=headers,
750 return_response=True)
751 def _got((res, status, headers)):
752 self.failUnlessReallyEqual(res, "")
753 self.failUnlessReallyEqual(int(status), 206)
754 self.failUnless(headers.has_key("content-range"))
755 self.failUnlessReallyEqual(headers["content-range"][0],
756 "bytes 1-10/%d" % len(self.BAR_CONTENTS))
760 def test_HEAD_FILEURL_partial_range(self):
761 headers = {"range": "bytes=5-"}
762 length = len(self.BAR_CONTENTS)
763 d = self.HEAD(self.public_url + "/foo/bar.txt", headers=headers,
764 return_response=True)
765 def _got((res, status, headers)):
766 self.failUnlessReallyEqual(int(status), 206)
767 self.failUnless(headers.has_key("content-range"))
768 self.failUnlessReallyEqual(headers["content-range"][0],
769 "bytes 5-%d/%d" % (length-1, length))
773 def test_HEAD_FILEURL_partial_end_range(self):
774 headers = {"range": "bytes=-5"}
775 length = len(self.BAR_CONTENTS)
776 d = self.HEAD(self.public_url + "/foo/bar.txt", headers=headers,
777 return_response=True)
778 def _got((res, status, headers)):
779 self.failUnlessReallyEqual(int(status), 206)
780 self.failUnless(headers.has_key("content-range"))
781 self.failUnlessReallyEqual(headers["content-range"][0],
782 "bytes %d-%d/%d" % (length-5, length-1, length))
786 def test_HEAD_FILEURL_partial_range_overrun(self):
787 headers = {"range": "bytes=100-200"}
788 d = self.shouldFail2(error.Error, "test_HEAD_FILEURL_range_overrun",
789 "416 Requested Range not satisfiable",
791 self.HEAD, self.public_url + "/foo/bar.txt",
795 def test_GET_FILEURL_range_bad(self):
796 headers = {"range": "BOGUS=fizbop-quarnak"}
797 d = self.GET(self.public_url + "/foo/bar.txt", headers=headers,
798 return_response=True)
799 def _got((res, status, headers)):
800 self.failUnlessReallyEqual(int(status), 200)
801 self.failUnless(not headers.has_key("content-range"))
802 self.failUnlessReallyEqual(res, self.BAR_CONTENTS)
806 def test_HEAD_FILEURL(self):
807 d = self.HEAD(self.public_url + "/foo/bar.txt", return_response=True)
808 def _got((res, status, headers)):
809 self.failUnlessReallyEqual(res, "")
810 self.failUnlessReallyEqual(headers["content-length"][0],
811 str(len(self.BAR_CONTENTS)))
812 self.failUnlessReallyEqual(headers["content-type"], ["text/plain"])
816 def test_GET_FILEURL_named(self):
817 base = "/file/%s" % urllib.quote(self._bar_txt_uri)
818 base2 = "/named/%s" % urllib.quote(self._bar_txt_uri)
819 d = self.GET(base + "/@@name=/blah.txt")
820 d.addCallback(self.failUnlessIsBarDotTxt)
821 d.addCallback(lambda res: self.GET(base + "/blah.txt"))
822 d.addCallback(self.failUnlessIsBarDotTxt)
823 d.addCallback(lambda res: self.GET(base + "/ignore/lots/blah.txt"))
824 d.addCallback(self.failUnlessIsBarDotTxt)
825 d.addCallback(lambda res: self.GET(base2 + "/@@name=/blah.txt"))
826 d.addCallback(self.failUnlessIsBarDotTxt)
827 save_url = base + "?save=true&filename=blah.txt"
828 d.addCallback(lambda res: self.GET(save_url))
829 d.addCallback(self.failUnlessIsBarDotTxt) # TODO: check headers
830 u_filename = u"n\u00e9wer.txt" # n e-acute w e r . t x t
831 u_fn_e = urllib.quote(u_filename.encode("utf-8"))
832 u_url = base + "?save=true&filename=" + u_fn_e
833 d.addCallback(lambda res: self.GET(u_url))
834 d.addCallback(self.failUnlessIsBarDotTxt) # TODO: check headers
837 def test_PUT_FILEURL_named_bad(self):
838 base = "/file/%s" % urllib.quote(self._bar_txt_uri)
839 d = self.shouldFail2(error.Error, "test_PUT_FILEURL_named_bad",
841 "/file can only be used with GET or HEAD",
842 self.PUT, base + "/@@name=/blah.txt", "")
846 def test_GET_DIRURL_named_bad(self):
847 base = "/file/%s" % urllib.quote(self._foo_uri)
848 d = self.shouldFail2(error.Error, "test_PUT_DIRURL_named_bad",
851 self.GET, base + "/@@name=/blah.txt")
854 def test_GET_slash_file_bad(self):
855 d = self.shouldFail2(error.Error, "test_GET_slash_file_bad",
857 "/file must be followed by a file-cap and a name",
861 def test_GET_unhandled_URI_named(self):
862 contents, n, newuri = self.makefile(12)
863 verifier_cap = n.get_verify_cap().to_string()
864 base = "/file/%s" % urllib.quote(verifier_cap)
865 # client.create_node_from_uri() can't handle verify-caps
866 d = self.shouldFail2(error.Error, "GET_unhandled_URI_named",
867 "400 Bad Request", "is not a file-cap",
871 def test_GET_unhandled_URI(self):
872 contents, n, newuri = self.makefile(12)
873 verifier_cap = n.get_verify_cap().to_string()
874 base = "/uri/%s" % urllib.quote(verifier_cap)
875 # client.create_node_from_uri() can't handle verify-caps
876 d = self.shouldFail2(error.Error, "test_GET_unhandled_URI",
878 "GET unknown URI type: can only do t=info",
882 def test_GET_FILE_URI(self):
883 base = "/uri/%s" % urllib.quote(self._bar_txt_uri)
885 d.addCallback(self.failUnlessIsBarDotTxt)
888 def test_GET_FILE_URI_mdmf(self):
889 base = "/uri/%s" % urllib.quote(self._quux_txt_uri)
891 d.addCallback(self.failUnlessIsQuuxDotTxt)
894 def test_GET_FILE_URI_mdmf_extensions(self):
895 base = "/uri/%s" % urllib.quote("%s:RANDOMSTUFF" % self._quux_txt_uri)
897 d.addCallback(self.failUnlessIsQuuxDotTxt)
900 def test_GET_FILE_URI_mdmf_readonly(self):
901 base = "/uri/%s" % urllib.quote(self._quux_txt_readonly_uri)
903 d.addCallback(self.failUnlessIsQuuxDotTxt)
906 def test_GET_FILE_URI_badchild(self):
907 base = "/uri/%s/boguschild" % urllib.quote(self._bar_txt_uri)
908 errmsg = "Files have no children, certainly not named 'boguschild'"
909 d = self.shouldFail2(error.Error, "test_GET_FILE_URI_badchild",
910 "400 Bad Request", errmsg,
914 def test_PUT_FILE_URI_badchild(self):
915 base = "/uri/%s/boguschild" % urllib.quote(self._bar_txt_uri)
916 errmsg = "Cannot create directory 'boguschild', because its parent is a file, not a directory"
917 d = self.shouldFail2(error.Error, "test_GET_FILE_URI_badchild",
918 "400 Bad Request", errmsg,
922 def test_PUT_FILE_URI_mdmf(self):
923 base = "/uri/%s" % urllib.quote(self._quux_txt_uri)
924 self._quux_new_contents = "new_contents"
926 d.addCallback(lambda res:
927 self.failUnlessIsQuuxDotTxt(res))
928 d.addCallback(lambda ignored:
929 self.PUT(base, self._quux_new_contents))
930 d.addCallback(lambda ignored:
932 d.addCallback(lambda res:
933 self.failUnlessReallyEqual(res, self._quux_new_contents))
936 def test_PUT_FILE_URI_mdmf_extensions(self):
937 base = "/uri/%s" % urllib.quote("%s:EXTENSIONSTUFF" % self._quux_txt_uri)
938 self._quux_new_contents = "new_contents"
940 d.addCallback(lambda res: self.failUnlessIsQuuxDotTxt(res))
941 d.addCallback(lambda ignored: self.PUT(base, self._quux_new_contents))
942 d.addCallback(lambda ignored: self.GET(base))
943 d.addCallback(lambda res: self.failUnlessEqual(self._quux_new_contents,
947 def test_PUT_FILE_URI_mdmf_readonly(self):
948 # We're not allowed to PUT things to a readonly cap.
949 base = "/uri/%s" % self._quux_txt_readonly_uri
951 d.addCallback(lambda res:
952 self.failUnlessIsQuuxDotTxt(res))
953 # What should we get here? We get a 500 error now; that's not right.
954 d.addCallback(lambda ignored:
955 self.shouldFail2(error.Error, "test_PUT_FILE_URI_mdmf_readonly",
956 "400 Bad Request", "read-only cap",
957 self.PUT, base, "new data"))
960 def test_PUT_FILE_URI_sdmf_readonly(self):
961 # We're not allowed to put things to a readonly cap.
962 base = "/uri/%s" % self._baz_txt_readonly_uri
964 d.addCallback(lambda res:
965 self.failUnlessIsBazDotTxt(res))
966 d.addCallback(lambda ignored:
967 self.shouldFail2(error.Error, "test_PUT_FILE_URI_sdmf_readonly",
968 "400 Bad Request", "read-only cap",
969 self.PUT, base, "new_data"))
972 # TODO: version of this with a Unicode filename
973 def test_GET_FILEURL_save(self):
974 d = self.GET(self.public_url + "/foo/bar.txt?filename=bar.txt&save=true",
975 return_response=True)
976 def _got((res, statuscode, headers)):
977 content_disposition = headers["content-disposition"][0]
978 self.failUnless(content_disposition == 'attachment; filename="bar.txt"', content_disposition)
979 self.failUnlessIsBarDotTxt(res)
983 def test_GET_FILEURL_missing(self):
984 d = self.GET(self.public_url + "/foo/missing")
985 d.addBoth(self.should404, "test_GET_FILEURL_missing")
988 def test_GET_FILEURL_info_mdmf(self):
989 d = self.GET("/uri/%s?t=info" % self._quux_txt_uri)
991 self.failUnlessIn("mutable file (mdmf)", res)
992 self.failUnlessIn(self._quux_txt_uri, res)
993 self.failUnlessIn(self._quux_txt_readonly_uri, res)
997 def test_GET_FILEURL_info_mdmf_readonly(self):
998 d = self.GET("/uri/%s?t=info" % self._quux_txt_readonly_uri)
1000 self.failUnlessIn("mutable file (mdmf)", res)
1001 self.failIfIn(self._quux_txt_uri, res)
1002 self.failUnlessIn(self._quux_txt_readonly_uri, res)
1006 def test_GET_FILEURL_info_sdmf(self):
1007 d = self.GET("/uri/%s?t=info" % self._baz_txt_uri)
1009 self.failUnlessIn("mutable file (sdmf)", res)
1010 self.failUnlessIn(self._baz_txt_uri, res)
1014 def test_GET_FILEURL_info_mdmf_extensions(self):
1015 d = self.GET("/uri/%s:STUFF?t=info" % self._quux_txt_uri)
1017 self.failUnlessIn("mutable file (mdmf)", res)
1018 self.failUnlessIn(self._quux_txt_uri, res)
1019 self.failUnlessIn(self._quux_txt_readonly_uri, res)
1023 def test_PUT_overwrite_only_files(self):
1024 # create a directory, put a file in that directory.
1025 contents, n, filecap = self.makefile(8)
1026 d = self.PUT(self.public_url + "/foo/dir?t=mkdir", "")
1027 d.addCallback(lambda res:
1028 self.PUT(self.public_url + "/foo/dir/file1.txt",
1029 self.NEWFILE_CONTENTS))
1030 # try to overwrite the file with replace=only-files
1031 # (this should work)
1032 d.addCallback(lambda res:
1033 self.PUT(self.public_url + "/foo/dir/file1.txt?t=uri&replace=only-files",
1035 d.addCallback(lambda res:
1036 self.shouldFail2(error.Error, "PUT_bad_t", "409 Conflict",
1037 "There was already a child by that name, and you asked me "
1038 "to not replace it",
1039 self.PUT, self.public_url + "/foo/dir?t=uri&replace=only-files",
1043 def test_PUT_NEWFILEURL(self):
1044 d = self.PUT(self.public_url + "/foo/new.txt", self.NEWFILE_CONTENTS)
1045 # TODO: we lose the response code, so we can't check this
1046 #self.failUnlessReallyEqual(responsecode, 201)
1047 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"new.txt")
1048 d.addCallback(lambda res:
1049 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
1050 self.NEWFILE_CONTENTS))
1053 def test_PUT_NEWFILEURL_not_mutable(self):
1054 d = self.PUT(self.public_url + "/foo/new.txt?mutable=false",
1055 self.NEWFILE_CONTENTS)
1056 # TODO: we lose the response code, so we can't check this
1057 #self.failUnlessReallyEqual(responsecode, 201)
1058 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"new.txt")
1059 d.addCallback(lambda res:
1060 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
1061 self.NEWFILE_CONTENTS))
1064 def test_PUT_NEWFILEURL_unlinked_mdmf(self):
1065 # this should get us a few segments of an MDMF mutable file,
1066 # which we can then test for.
1067 contents = self.NEWFILE_CONTENTS * 300000
1068 d = self.PUT("/uri?format=mdmf",
1070 def _got_filecap(filecap):
1071 self.failUnless(filecap.startswith("URI:MDMF"))
1073 d.addCallback(_got_filecap)
1074 d.addCallback(lambda filecap: self.GET("/uri/%s?t=json" % filecap))
1075 d.addCallback(lambda json: self.failUnlessIn("MDMF", json))
1078 def test_PUT_NEWFILEURL_unlinked_sdmf(self):
1079 contents = self.NEWFILE_CONTENTS * 300000
1080 d = self.PUT("/uri?format=sdmf",
1082 d.addCallback(lambda filecap: self.GET("/uri/%s?t=json" % filecap))
1083 d.addCallback(lambda json: self.failUnlessIn("SDMF", json))
1086 def test_PUT_NEWFILEURL_unlinked_bad_format(self):
1087 contents = self.NEWFILE_CONTENTS * 300000
1088 return self.shouldHTTPError("PUT_NEWFILEURL_unlinked_bad_format",
1089 400, "Bad Request", "Unknown format: foo",
1090 self.PUT, "/uri?format=foo",
1093 def test_PUT_NEWFILEURL_range_bad(self):
1094 headers = {"content-range": "bytes 1-10/%d" % len(self.NEWFILE_CONTENTS)}
1095 target = self.public_url + "/foo/new.txt"
1096 d = self.shouldFail2(error.Error, "test_PUT_NEWFILEURL_range_bad",
1097 "501 Not Implemented",
1098 "Content-Range in PUT not yet supported",
1099 # (and certainly not for immutable files)
1100 self.PUT, target, self.NEWFILE_CONTENTS[1:11],
1102 d.addCallback(lambda res:
1103 self.failIfNodeHasChild(self._foo_node, u"new.txt"))
1106 def test_PUT_NEWFILEURL_mutable(self):
1107 d = self.PUT(self.public_url + "/foo/new.txt?mutable=true",
1108 self.NEWFILE_CONTENTS)
1109 # TODO: we lose the response code, so we can't check this
1110 #self.failUnlessReallyEqual(responsecode, 201)
1111 def _check_uri(res):
1112 u = uri.from_string_mutable_filenode(res)
1113 self.failUnless(u.is_mutable())
1114 self.failIf(u.is_readonly())
1116 d.addCallback(_check_uri)
1117 d.addCallback(self.failUnlessURIMatchesRWChild, self._foo_node, u"new.txt")
1118 d.addCallback(lambda res:
1119 self.failUnlessMutableChildContentsAre(self._foo_node,
1121 self.NEWFILE_CONTENTS))
1124 def test_PUT_NEWFILEURL_mutable_toobig(self):
1125 # It is okay to upload large mutable files, so we should be able
1127 d = self.PUT(self.public_url + "/foo/new.txt?mutable=true",
1128 "b" * (self.s.MUTABLE_SIZELIMIT + 1))
1131 def test_PUT_NEWFILEURL_replace(self):
1132 d = self.PUT(self.public_url + "/foo/bar.txt", self.NEWFILE_CONTENTS)
1133 # TODO: we lose the response code, so we can't check this
1134 #self.failUnlessReallyEqual(responsecode, 200)
1135 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"bar.txt")
1136 d.addCallback(lambda res:
1137 self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
1138 self.NEWFILE_CONTENTS))
1141 def test_PUT_NEWFILEURL_bad_t(self):
1142 d = self.shouldFail2(error.Error, "PUT_bad_t", "400 Bad Request",
1143 "PUT to a file: bad t=bogus",
1144 self.PUT, self.public_url + "/foo/bar.txt?t=bogus",
1148 def test_PUT_NEWFILEURL_no_replace(self):
1149 d = self.PUT(self.public_url + "/foo/bar.txt?replace=false",
1150 self.NEWFILE_CONTENTS)
1151 d.addBoth(self.shouldFail, error.Error, "PUT_NEWFILEURL_no_replace",
1153 "There was already a child by that name, and you asked me "
1154 "to not replace it")
1157 def test_PUT_NEWFILEURL_mkdirs(self):
1158 d = self.PUT(self.public_url + "/foo/newdir/new.txt", self.NEWFILE_CONTENTS)
1160 d.addCallback(self.failUnlessURIMatchesROChild, fn, u"newdir/new.txt")
1161 d.addCallback(lambda res: self.failIfNodeHasChild(fn, u"new.txt"))
1162 d.addCallback(lambda res: self.failUnlessNodeHasChild(fn, u"newdir"))
1163 d.addCallback(lambda res:
1164 self.failUnlessChildContentsAre(fn, u"newdir/new.txt",
1165 self.NEWFILE_CONTENTS))
1168 def test_PUT_NEWFILEURL_blocked(self):
1169 d = self.PUT(self.public_url + "/foo/blockingfile/new.txt",
1170 self.NEWFILE_CONTENTS)
1171 d.addBoth(self.shouldFail, error.Error, "PUT_NEWFILEURL_blocked",
1173 "Unable to create directory 'blockingfile': a file was in the way")
1176 def test_PUT_NEWFILEURL_emptyname(self):
1177 # an empty pathname component (i.e. a double-slash) is disallowed
1178 d = self.shouldFail2(error.Error, "test_PUT_NEWFILEURL_emptyname",
1180 "The webapi does not allow empty pathname components",
1181 self.PUT, self.public_url + "/foo//new.txt", "")
1184 def test_DELETE_FILEURL(self):
1185 d = self.DELETE(self.public_url + "/foo/bar.txt")
1186 d.addCallback(lambda res:
1187 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
1190 def test_DELETE_FILEURL_missing(self):
1191 d = self.DELETE(self.public_url + "/foo/missing")
1192 d.addBoth(self.should404, "test_DELETE_FILEURL_missing")
1195 def test_DELETE_FILEURL_missing2(self):
1196 d = self.DELETE(self.public_url + "/missing/missing")
1197 d.addBoth(self.should404, "test_DELETE_FILEURL_missing2")
1200 def failUnlessHasBarDotTxtMetadata(self, res):
1201 data = simplejson.loads(res)
1202 self.failUnless(isinstance(data, list))
1203 self.failUnlessIn("metadata", data[1])
1204 self.failUnlessIn("tahoe", data[1]["metadata"])
1205 self.failUnlessIn("linkcrtime", data[1]["metadata"]["tahoe"])
1206 self.failUnlessIn("linkmotime", data[1]["metadata"]["tahoe"])
1207 self.failUnlessReallyEqual(data[1]["metadata"]["tahoe"]["linkcrtime"],
1208 self._bar_txt_metadata["tahoe"]["linkcrtime"])
1210 def test_GET_FILEURL_json(self):
1211 # twisted.web.http.parse_qs ignores any query args without an '=', so
1212 # I can't do "GET /path?json", I have to do "GET /path/t=json"
1213 # instead. This may make it tricky to emulate the S3 interface
1215 d = self.GET(self.public_url + "/foo/bar.txt?t=json")
1217 self.failUnlessIsBarJSON(data)
1218 self.failUnlessHasBarDotTxtMetadata(data)
1220 d.addCallback(_check1)
1223 def test_GET_FILEURL_json_mutable_type(self):
1224 # The JSON should include format, which says whether the
1225 # file is SDMF or MDMF
1226 d = self.PUT("/uri?format=mdmf",
1227 self.NEWFILE_CONTENTS * 300000)
1228 d.addCallback(lambda filecap: self.GET("/uri/%s?t=json" % filecap))
1229 def _got_json(json, version):
1230 data = simplejson.loads(json)
1231 assert "filenode" == data[0]
1233 assert isinstance(data, dict)
1235 self.failUnlessIn("format", data)
1236 self.failUnlessEqual(data["format"], version)
1238 d.addCallback(_got_json, "MDMF")
1239 # Now make an SDMF file and check that it is reported correctly.
1240 d.addCallback(lambda ignored:
1241 self.PUT("/uri?format=sdmf",
1242 self.NEWFILE_CONTENTS * 300000))
1243 d.addCallback(lambda filecap: self.GET("/uri/%s?t=json" % filecap))
1244 d.addCallback(_got_json, "SDMF")
1247 def test_GET_FILEURL_json_mdmf(self):
1248 d = self.GET("/uri/%s?t=json" % urllib.quote(self._quux_txt_uri))
1249 d.addCallback(self.failUnlessIsQuuxJSON)
1252 def test_GET_FILEURL_json_missing(self):
1253 d = self.GET(self.public_url + "/foo/missing?json")
1254 d.addBoth(self.should404, "test_GET_FILEURL_json_missing")
1257 def test_GET_FILEURL_uri(self):
1258 d = self.GET(self.public_url + "/foo/bar.txt?t=uri")
1260 self.failUnlessReallyEqual(res, self._bar_txt_uri)
1261 d.addCallback(_check)
1262 d.addCallback(lambda res:
1263 self.GET(self.public_url + "/foo/bar.txt?t=readonly-uri"))
1265 # for now, for files, uris and readonly-uris are the same
1266 self.failUnlessReallyEqual(res, self._bar_txt_uri)
1267 d.addCallback(_check2)
1270 def test_GET_FILEURL_badtype(self):
1271 d = self.shouldHTTPError("GET t=bogus", 400, "Bad Request",
1274 self.public_url + "/foo/bar.txt?t=bogus")
1277 def test_CSS_FILE(self):
1278 d = self.GET("/tahoe_css", followRedirect=True)
1280 CSS_STYLE=re.compile('toolbar\s{.+text-align:\scenter.+toolbar-item.+display:\sinline',re.DOTALL)
1281 self.failUnless(CSS_STYLE.search(res), res)
1282 d.addCallback(_check)
1285 def test_GET_FILEURL_uri_missing(self):
1286 d = self.GET(self.public_url + "/foo/missing?t=uri")
1287 d.addBoth(self.should404, "test_GET_FILEURL_uri_missing")
1290 def _check_upload_and_mkdir_forms(self, html):
1291 # We should have a form to create a file, with radio buttons that allow
1292 # the user to toggle whether it is a CHK/LIT (default), SDMF, or MDMF file.
1293 self.failUnlessIn('name="t" value="upload"', html)
1294 self.failUnlessIn('input checked="checked" type="radio" id="upload-chk" value="chk" name="format"', html)
1295 self.failUnlessIn('input type="radio" id="upload-sdmf" value="sdmf" name="format"', html)
1296 self.failUnlessIn('input type="radio" id="upload-mdmf" value="mdmf" name="format"', html)
1298 # We should also have the ability to create a mutable directory, with
1299 # radio buttons that allow the user to toggle whether it is an SDMF (default)
1300 # or MDMF directory.
1301 self.failUnlessIn('name="t" value="mkdir"', html)
1302 self.failUnlessIn('input checked="checked" type="radio" id="mkdir-sdmf" value="sdmf" name="format"', html)
1303 self.failUnlessIn('input type="radio" id="mkdir-mdmf" value="mdmf" name="format"', html)
1305 def test_GET_DIRECTORY_html(self):
1306 d = self.GET(self.public_url + "/foo", followRedirect=True)
1308 self.failUnlessIn('<div class="toolbar-item"><a href="../../..">Return to Welcome page</a></div>', html)
1309 self._check_upload_and_mkdir_forms(html)
1310 self.failUnlessIn("quux", html)
1311 d.addCallback(_check)
1314 def test_GET_root_html(self):
1316 d.addCallback(self._check_upload_and_mkdir_forms)
1319 def test_GET_DIRURL(self):
1320 # the addSlash means we get a redirect here
1321 # from /uri/$URI/foo/ , we need ../../../ to get back to the root
1323 d = self.GET(self.public_url + "/foo", followRedirect=True)
1325 self.failUnless(('<a href="%s">Return to Welcome page' % ROOT)
1327 # the FILE reference points to a URI, but it should end in bar.txt
1328 bar_url = ("%s/file/%s/@@named=/bar.txt" %
1329 (ROOT, urllib.quote(self._bar_txt_uri)))
1330 get_bar = "".join([r'<td>FILE</td>',
1332 r'<a href="%s">bar.txt</a>' % bar_url,
1334 r'\s+<td align="right">%d</td>' % len(self.BAR_CONTENTS),
1336 self.failUnless(re.search(get_bar, res), res)
1337 for label in ['unlink', 'rename']:
1338 for line in res.split("\n"):
1339 # find the line that contains the relevant button for bar.txt
1340 if ("form action" in line and
1341 ('value="%s"' % (label,)) in line and
1342 'value="bar.txt"' in line):
1343 # the form target should use a relative URL
1344 foo_url = urllib.quote("%s/uri/%s/" % (ROOT, self._foo_uri))
1345 self.failUnlessIn('action="%s"' % foo_url, line)
1346 # and the when_done= should too
1347 #done_url = urllib.quote(???)
1348 #self.failUnlessIn('name="when_done" value="%s"' % done_url, line)
1350 # 'unlink' needs to use POST because it directly has a side effect
1351 if label == 'unlink':
1352 self.failUnlessIn('method="post"', line)
1355 self.fail("unable to find '%s bar.txt' line" % (label,), res)
1357 # the DIR reference just points to a URI
1358 sub_url = ("%s/uri/%s/" % (ROOT, urllib.quote(self._sub_uri)))
1359 get_sub = ((r'<td>DIR</td>')
1360 +r'\s+<td><a href="%s">sub</a></td>' % sub_url)
1361 self.failUnless(re.search(get_sub, res), res)
1362 d.addCallback(_check)
1364 # look at a readonly directory
1365 d.addCallback(lambda res:
1366 self.GET(self.public_url + "/reedownlee", followRedirect=True))
1368 self.failUnless("(read-only)" in res, res)
1369 self.failIf("Upload a file" in res, res)
1370 d.addCallback(_check2)
1372 # and at a directory that contains a readonly directory
1373 d.addCallback(lambda res:
1374 self.GET(self.public_url, followRedirect=True))
1376 self.failUnless(re.search('<td>DIR-RO</td>'
1377 r'\s+<td><a href="[\.\/]+/uri/URI%3ADIR2-RO%3A[^"]+">reedownlee</a></td>', res), res)
1378 d.addCallback(_check3)
1380 # and an empty directory
1381 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty/"))
1383 self.failUnless("directory is empty" in res, res)
1384 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)
1385 self.failUnless(MKDIR_BUTTON_RE.search(res), res)
1386 d.addCallback(_check4)
1388 # and at a literal directory
1389 tiny_litdir_uri = "URI:DIR2-LIT:gqytunj2onug64tufqzdcosvkjetutcjkq5gw4tvm5vwszdgnz5hgyzufqydulbshj5x2lbm" # contains one child which is itself also LIT
1390 d.addCallback(lambda res:
1391 self.GET("/uri/" + tiny_litdir_uri + "/", followRedirect=True))
1393 self.failUnless('(immutable)' in res, res)
1394 self.failUnless(re.search('<td>FILE</td>'
1395 r'\s+<td><a href="[\.\/]+/file/URI%3ALIT%3Akrugkidfnzsc4/@@named=/short">short</a></td>', res), res)
1396 d.addCallback(_check5)
1399 def test_GET_DIRURL_badtype(self):
1400 d = self.shouldHTTPError("test_GET_DIRURL_badtype",
1404 self.public_url + "/foo?t=bogus")
1407 def test_GET_DIRURL_json(self):
1408 d = self.GET(self.public_url + "/foo?t=json")
1409 d.addCallback(self.failUnlessIsFooJSON)
1412 def test_GET_DIRURL_json_format(self):
1413 d = self.PUT(self.public_url + \
1414 "/foo/sdmf.txt?format=sdmf",
1415 self.NEWFILE_CONTENTS * 300000)
1416 d.addCallback(lambda ignored:
1417 self.PUT(self.public_url + \
1418 "/foo/mdmf.txt?format=mdmf",
1419 self.NEWFILE_CONTENTS * 300000))
1420 # Now we have an MDMF and SDMF file in the directory. If we GET
1421 # its JSON, we should see their encodings.
1422 d.addCallback(lambda ignored:
1423 self.GET(self.public_url + "/foo?t=json"))
1424 def _got_json(json):
1425 data = simplejson.loads(json)
1426 assert data[0] == "dirnode"
1429 kids = data['children']
1431 mdmf_data = kids['mdmf.txt'][1]
1432 self.failUnlessIn("format", mdmf_data)
1433 self.failUnlessEqual(mdmf_data["format"], "MDMF")
1435 sdmf_data = kids['sdmf.txt'][1]
1436 self.failUnlessIn("format", sdmf_data)
1437 self.failUnlessEqual(sdmf_data["format"], "SDMF")
1438 d.addCallback(_got_json)
1442 def test_POST_DIRURL_manifest_no_ophandle(self):
1443 d = self.shouldFail2(error.Error,
1444 "test_POST_DIRURL_manifest_no_ophandle",
1446 "slow operation requires ophandle=",
1447 self.POST, self.public_url, t="start-manifest")
1450 def test_POST_DIRURL_manifest(self):
1451 d = defer.succeed(None)
1452 def getman(ignored, output):
1453 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=125",
1454 followRedirect=True)
1455 d.addCallback(self.wait_for_operation, "125")
1456 d.addCallback(self.get_operation_results, "125", output)
1458 d.addCallback(getman, None)
1459 def _got_html(manifest):
1460 self.failUnless("Manifest of SI=" in manifest)
1461 self.failUnless("<td>sub</td>" in manifest)
1462 self.failUnless(self._sub_uri in manifest)
1463 self.failUnless("<td>sub/baz.txt</td>" in manifest)
1464 d.addCallback(_got_html)
1466 # both t=status and unadorned GET should be identical
1467 d.addCallback(lambda res: self.GET("/operations/125"))
1468 d.addCallback(_got_html)
1470 d.addCallback(getman, "html")
1471 d.addCallback(_got_html)
1472 d.addCallback(getman, "text")
1473 def _got_text(manifest):
1474 self.failUnless("\nsub " + self._sub_uri + "\n" in manifest)
1475 self.failUnless("\nsub/baz.txt URI:CHK:" in manifest)
1476 d.addCallback(_got_text)
1477 d.addCallback(getman, "JSON")
1479 data = res["manifest"]
1481 for (path_list, cap) in data:
1482 got[tuple(path_list)] = cap
1483 self.failUnlessReallyEqual(to_str(got[(u"sub",)]), self._sub_uri)
1484 self.failUnless((u"sub",u"baz.txt") in got)
1485 self.failUnless("finished" in res)
1486 self.failUnless("origin" in res)
1487 self.failUnless("storage-index" in res)
1488 self.failUnless("verifycaps" in res)
1489 self.failUnless("stats" in res)
1490 d.addCallback(_got_json)
1493 def test_POST_DIRURL_deepsize_no_ophandle(self):
1494 d = self.shouldFail2(error.Error,
1495 "test_POST_DIRURL_deepsize_no_ophandle",
1497 "slow operation requires ophandle=",
1498 self.POST, self.public_url, t="start-deep-size")
1501 def test_POST_DIRURL_deepsize(self):
1502 d = self.POST(self.public_url + "/foo/?t=start-deep-size&ophandle=126",
1503 followRedirect=True)
1504 d.addCallback(self.wait_for_operation, "126")
1505 d.addCallback(self.get_operation_results, "126", "json")
1506 def _got_json(data):
1507 self.failUnlessReallyEqual(data["finished"], True)
1509 self.failUnless(size > 1000)
1510 d.addCallback(_got_json)
1511 d.addCallback(self.get_operation_results, "126", "text")
1513 mo = re.search(r'^size: (\d+)$', res, re.M)
1514 self.failUnless(mo, res)
1515 size = int(mo.group(1))
1516 # with directories, the size varies.
1517 self.failUnless(size > 1000)
1518 d.addCallback(_got_text)
1521 def test_POST_DIRURL_deepstats_no_ophandle(self):
1522 d = self.shouldFail2(error.Error,
1523 "test_POST_DIRURL_deepstats_no_ophandle",
1525 "slow operation requires ophandle=",
1526 self.POST, self.public_url, t="start-deep-stats")
1529 def test_POST_DIRURL_deepstats(self):
1530 d = self.POST(self.public_url + "/foo/?t=start-deep-stats&ophandle=127",
1531 followRedirect=True)
1532 d.addCallback(self.wait_for_operation, "127")
1533 d.addCallback(self.get_operation_results, "127", "json")
1534 def _got_json(stats):
1535 expected = {"count-immutable-files": 3,
1536 "count-mutable-files": 2,
1537 "count-literal-files": 0,
1539 "count-directories": 3,
1540 "size-immutable-files": 57,
1541 "size-literal-files": 0,
1542 #"size-directories": 1912, # varies
1543 #"largest-directory": 1590,
1544 "largest-directory-children": 7,
1545 "largest-immutable-file": 19,
1547 for k,v in expected.iteritems():
1548 self.failUnlessReallyEqual(stats[k], v,
1549 "stats[%s] was %s, not %s" %
1551 self.failUnlessReallyEqual(stats["size-files-histogram"],
1553 d.addCallback(_got_json)
1556 def test_POST_DIRURL_stream_manifest(self):
1557 d = self.POST(self.public_url + "/foo/?t=stream-manifest")
1559 self.failUnless(res.endswith("\n"))
1560 units = [simplejson.loads(t) for t in res[:-1].split("\n")]
1561 self.failUnlessReallyEqual(len(units), 9)
1562 self.failUnlessEqual(units[-1]["type"], "stats")
1564 self.failUnlessEqual(first["path"], [])
1565 self.failUnlessReallyEqual(to_str(first["cap"]), self._foo_uri)
1566 self.failUnlessEqual(first["type"], "directory")
1567 baz = [u for u in units[:-1] if to_str(u["cap"]) == self._baz_file_uri][0]
1568 self.failUnlessEqual(baz["path"], ["sub", "baz.txt"])
1569 self.failIfEqual(baz["storage-index"], None)
1570 self.failIfEqual(baz["verifycap"], None)
1571 self.failIfEqual(baz["repaircap"], None)
1572 # XXX: Add quux and baz to this test.
1574 d.addCallback(_check)
1577 def test_GET_DIRURL_uri(self):
1578 d = self.GET(self.public_url + "/foo?t=uri")
1580 self.failUnlessReallyEqual(to_str(res), self._foo_uri)
1581 d.addCallback(_check)
1584 def test_GET_DIRURL_readonly_uri(self):
1585 d = self.GET(self.public_url + "/foo?t=readonly-uri")
1587 self.failUnlessReallyEqual(to_str(res), self._foo_readonly_uri)
1588 d.addCallback(_check)
1591 def test_PUT_NEWDIRURL(self):
1592 d = self.PUT(self.public_url + "/foo/newdir?t=mkdir", "")
1593 d.addCallback(lambda res:
1594 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1595 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1596 d.addCallback(self.failUnlessNodeKeysAre, [])
1599 def test_PUT_NEWDIRURL_mdmf(self):
1600 d = self.PUT(self.public_url + "/foo/newdir?t=mkdir&format=mdmf", "")
1601 d.addCallback(lambda res:
1602 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1603 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1604 d.addCallback(lambda node:
1605 self.failUnlessEqual(node._node.get_version(), MDMF_VERSION))
1608 def test_PUT_NEWDIRURL_sdmf(self):
1609 d = self.PUT(self.public_url + "/foo/newdir?t=mkdir&format=sdmf",
1611 d.addCallback(lambda res:
1612 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1613 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1614 d.addCallback(lambda node:
1615 self.failUnlessEqual(node._node.get_version(), SDMF_VERSION))
1618 def test_PUT_NEWDIRURL_bad_format(self):
1619 return self.shouldHTTPError("PUT_NEWDIRURL_bad_format",
1620 400, "Bad Request", "Unknown format: foo",
1621 self.PUT, self.public_url +
1622 "/foo/newdir=?t=mkdir&format=foo", "")
1624 def test_POST_NEWDIRURL(self):
1625 d = self.POST2(self.public_url + "/foo/newdir?t=mkdir", "")
1626 d.addCallback(lambda res:
1627 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1628 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1629 d.addCallback(self.failUnlessNodeKeysAre, [])
1632 def test_POST_NEWDIRURL_mdmf(self):
1633 d = self.POST2(self.public_url + "/foo/newdir?t=mkdir&format=mdmf", "")
1634 d.addCallback(lambda res:
1635 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1636 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1637 d.addCallback(lambda node:
1638 self.failUnlessEqual(node._node.get_version(), MDMF_VERSION))
1641 def test_POST_NEWDIRURL_sdmf(self):
1642 d = self.POST2(self.public_url + "/foo/newdir?t=mkdir&format=sdmf", "")
1643 d.addCallback(lambda res:
1644 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1645 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1646 d.addCallback(lambda node:
1647 self.failUnlessEqual(node._node.get_version(), SDMF_VERSION))
1650 def test_POST_NEWDIRURL_bad_format(self):
1651 return self.shouldHTTPError("POST_NEWDIRURL_bad_format",
1652 400, "Bad Request", "Unknown format: foo",
1653 self.POST2, self.public_url + \
1654 "/foo/newdir?t=mkdir&format=foo", "")
1656 def test_POST_NEWDIRURL_emptyname(self):
1657 # an empty pathname component (i.e. a double-slash) is disallowed
1658 d = self.shouldFail2(error.Error, "POST_NEWDIRURL_emptyname",
1660 "The webapi does not allow empty pathname components, i.e. a double slash",
1661 self.POST, self.public_url + "//?t=mkdir")
1664 def _do_POST_NEWDIRURL_initial_children_test(self, version=None):
1665 (newkids, caps) = self._create_initial_children()
1666 query = "/foo/newdir?t=mkdir-with-children"
1667 if version == MDMF_VERSION:
1668 query += "&format=mdmf"
1669 elif version == SDMF_VERSION:
1670 query += "&format=sdmf"
1672 version = SDMF_VERSION # for later
1673 d = self.POST2(self.public_url + query,
1674 simplejson.dumps(newkids))
1676 n = self.s.create_node_from_uri(uri.strip())
1677 d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
1678 self.failUnlessEqual(n._node.get_version(), version)
1679 d2.addCallback(lambda ign:
1680 self.failUnlessROChildURIIs(n, u"child-imm",
1682 d2.addCallback(lambda ign:
1683 self.failUnlessRWChildURIIs(n, u"child-mutable",
1685 d2.addCallback(lambda ign:
1686 self.failUnlessROChildURIIs(n, u"child-mutable-ro",
1688 d2.addCallback(lambda ign:
1689 self.failUnlessROChildURIIs(n, u"unknownchild-ro",
1690 caps['unknown_rocap']))
1691 d2.addCallback(lambda ign:
1692 self.failUnlessRWChildURIIs(n, u"unknownchild-rw",
1693 caps['unknown_rwcap']))
1694 d2.addCallback(lambda ign:
1695 self.failUnlessROChildURIIs(n, u"unknownchild-imm",
1696 caps['unknown_immcap']))
1697 d2.addCallback(lambda ign:
1698 self.failUnlessRWChildURIIs(n, u"dirchild",
1700 d2.addCallback(lambda ign:
1701 self.failUnlessROChildURIIs(n, u"dirchild-lit",
1703 d2.addCallback(lambda ign:
1704 self.failUnlessROChildURIIs(n, u"dirchild-empty",
1705 caps['emptydircap']))
1707 d.addCallback(_check)
1708 d.addCallback(lambda res:
1709 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1710 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1711 d.addCallback(self.failUnlessNodeKeysAre, newkids.keys())
1712 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1713 d.addCallback(self.failUnlessROChildURIIs, u"child-imm", caps['filecap1'])
1716 def test_POST_NEWDIRURL_initial_children(self):
1717 return self._do_POST_NEWDIRURL_initial_children_test()
1719 def test_POST_NEWDIRURL_initial_children_mdmf(self):
1720 return self._do_POST_NEWDIRURL_initial_children_test(MDMF_VERSION)
1722 def test_POST_NEWDIRURL_initial_children_sdmf(self):
1723 return self._do_POST_NEWDIRURL_initial_children_test(SDMF_VERSION)
1725 def test_POST_NEWDIRURL_initial_children_bad_format(self):
1726 (newkids, caps) = self._create_initial_children()
1727 return self.shouldHTTPError("POST_NEWDIRURL_initial_children_bad_format",
1728 400, "Bad Request", "Unknown format: foo",
1729 self.POST2, self.public_url + \
1730 "/foo/newdir?t=mkdir-with-children&format=foo",
1731 simplejson.dumps(newkids))
1733 def test_POST_NEWDIRURL_immutable(self):
1734 (newkids, caps) = self._create_immutable_children()
1735 d = self.POST2(self.public_url + "/foo/newdir?t=mkdir-immutable",
1736 simplejson.dumps(newkids))
1738 n = self.s.create_node_from_uri(uri.strip())
1739 d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
1740 d2.addCallback(lambda ign:
1741 self.failUnlessROChildURIIs(n, u"child-imm",
1743 d2.addCallback(lambda ign:
1744 self.failUnlessROChildURIIs(n, u"unknownchild-imm",
1745 caps['unknown_immcap']))
1746 d2.addCallback(lambda ign:
1747 self.failUnlessROChildURIIs(n, u"dirchild-imm",
1749 d2.addCallback(lambda ign:
1750 self.failUnlessROChildURIIs(n, u"dirchild-lit",
1752 d2.addCallback(lambda ign:
1753 self.failUnlessROChildURIIs(n, u"dirchild-empty",
1754 caps['emptydircap']))
1756 d.addCallback(_check)
1757 d.addCallback(lambda res:
1758 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1759 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1760 d.addCallback(self.failUnlessNodeKeysAre, newkids.keys())
1761 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1762 d.addCallback(self.failUnlessROChildURIIs, u"child-imm", caps['filecap1'])
1763 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1764 d.addCallback(self.failUnlessROChildURIIs, u"unknownchild-imm", caps['unknown_immcap'])
1765 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1766 d.addCallback(self.failUnlessROChildURIIs, u"dirchild-imm", caps['immdircap'])
1767 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1768 d.addCallback(self.failUnlessROChildURIIs, u"dirchild-lit", caps['litdircap'])
1769 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1770 d.addCallback(self.failUnlessROChildURIIs, u"dirchild-empty", caps['emptydircap'])
1771 d.addErrback(self.explain_web_error)
1774 def test_POST_NEWDIRURL_immutable_bad(self):
1775 (newkids, caps) = self._create_initial_children()
1776 d = self.shouldFail2(error.Error, "test_POST_NEWDIRURL_immutable_bad",
1778 "needed to be immutable but was not",
1780 self.public_url + "/foo/newdir?t=mkdir-immutable",
1781 simplejson.dumps(newkids))
1784 def test_PUT_NEWDIRURL_exists(self):
1785 d = self.PUT(self.public_url + "/foo/sub?t=mkdir", "")
1786 d.addCallback(lambda res:
1787 self.failUnlessNodeHasChild(self._foo_node, u"sub"))
1788 d.addCallback(lambda res: self._foo_node.get(u"sub"))
1789 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
1792 def test_PUT_NEWDIRURL_blocked(self):
1793 d = self.shouldFail2(error.Error, "PUT_NEWDIRURL_blocked",
1794 "409 Conflict", "Unable to create directory 'bar.txt': a file was in the way",
1796 self.public_url + "/foo/bar.txt/sub?t=mkdir", "")
1797 d.addCallback(lambda res:
1798 self.failUnlessNodeHasChild(self._foo_node, u"sub"))
1799 d.addCallback(lambda res: self._foo_node.get(u"sub"))
1800 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
1803 def test_PUT_NEWDIRURL_mkdir_p(self):
1804 d = defer.succeed(None)
1805 d.addCallback(lambda res: self.POST(self.public_url + "/foo", t='mkdir', name='mkp'))
1806 d.addCallback(lambda res: self.failUnlessNodeHasChild(self._foo_node, u"mkp"))
1807 d.addCallback(lambda res: self._foo_node.get(u"mkp"))
1808 def mkdir_p(mkpnode):
1809 url = '/uri/%s?t=mkdir-p&path=/sub1/sub2' % urllib.quote(mkpnode.get_uri())
1811 def made_subsub(ssuri):
1812 d = self._foo_node.get_child_at_path(u"mkp/sub1/sub2")
1813 d.addCallback(lambda ssnode: self.failUnlessReallyEqual(ssnode.get_uri(), ssuri))
1815 d.addCallback(lambda uri2: self.failUnlessReallyEqual(uri2, ssuri))
1817 d.addCallback(made_subsub)
1819 d.addCallback(mkdir_p)
1822 def test_PUT_NEWDIRURL_mkdirs(self):
1823 d = self.PUT(self.public_url + "/foo/subdir/newdir?t=mkdir", "")
1824 d.addCallback(lambda res:
1825 self.failIfNodeHasChild(self._foo_node, u"newdir"))
1826 d.addCallback(lambda res:
1827 self.failUnlessNodeHasChild(self._foo_node, u"subdir"))
1828 d.addCallback(lambda res:
1829 self._foo_node.get_child_at_path(u"subdir/newdir"))
1830 d.addCallback(self.failUnlessNodeKeysAre, [])
1833 def test_PUT_NEWDIRURL_mkdirs_mdmf(self):
1834 d = self.PUT(self.public_url + "/foo/subdir/newdir?t=mkdir&format=mdmf", "")
1835 d.addCallback(lambda ignored:
1836 self.failUnlessNodeHasChild(self._foo_node, u"subdir"))
1837 d.addCallback(lambda ignored:
1838 self.failIfNodeHasChild(self._foo_node, u"newdir"))
1839 d.addCallback(lambda ignored:
1840 self._foo_node.get_child_at_path(u"subdir"))
1841 def _got_subdir(subdir):
1842 # XXX: What we want?
1843 #self.failUnlessEqual(subdir._node.get_version(), MDMF_VERSION)
1844 self.failUnlessNodeHasChild(subdir, u"newdir")
1845 return subdir.get_child_at_path(u"newdir")
1846 d.addCallback(_got_subdir)
1847 d.addCallback(lambda newdir:
1848 self.failUnlessEqual(newdir._node.get_version(), MDMF_VERSION))
1851 def test_PUT_NEWDIRURL_mkdirs_sdmf(self):
1852 d = self.PUT(self.public_url + "/foo/subdir/newdir?t=mkdir&format=sdmf", "")
1853 d.addCallback(lambda ignored:
1854 self.failUnlessNodeHasChild(self._foo_node, u"subdir"))
1855 d.addCallback(lambda ignored:
1856 self.failIfNodeHasChild(self._foo_node, u"newdir"))
1857 d.addCallback(lambda ignored:
1858 self._foo_node.get_child_at_path(u"subdir"))
1859 def _got_subdir(subdir):
1860 # XXX: What we want?
1861 #self.failUnlessEqual(subdir._node.get_version(), MDMF_VERSION)
1862 self.failUnlessNodeHasChild(subdir, u"newdir")
1863 return subdir.get_child_at_path(u"newdir")
1864 d.addCallback(_got_subdir)
1865 d.addCallback(lambda newdir:
1866 self.failUnlessEqual(newdir._node.get_version(), SDMF_VERSION))
1869 def test_PUT_NEWDIRURL_mkdirs_bad_format(self):
1870 return self.shouldHTTPError("PUT_NEWDIRURL_mkdirs_bad_format",
1871 400, "Bad Request", "Unknown format: foo",
1872 self.PUT, self.public_url + \
1873 "/foo/subdir/newdir?t=mkdir&format=foo",
1876 def test_DELETE_DIRURL(self):
1877 d = self.DELETE(self.public_url + "/foo")
1878 d.addCallback(lambda res:
1879 self.failIfNodeHasChild(self.public_root, u"foo"))
1882 def test_DELETE_DIRURL_missing(self):
1883 d = self.DELETE(self.public_url + "/foo/missing")
1884 d.addBoth(self.should404, "test_DELETE_DIRURL_missing")
1885 d.addCallback(lambda res:
1886 self.failUnlessNodeHasChild(self.public_root, u"foo"))
1889 def test_DELETE_DIRURL_missing2(self):
1890 d = self.DELETE(self.public_url + "/missing")
1891 d.addBoth(self.should404, "test_DELETE_DIRURL_missing2")
1894 def dump_root(self):
1896 w = webish.DirnodeWalkerMixin()
1897 def visitor(childpath, childnode, metadata):
1899 d = w.walk(self.public_root, visitor)
1902 def failUnlessNodeKeysAre(self, node, expected_keys):
1903 for k in expected_keys:
1904 assert isinstance(k, unicode)
1906 def _check(children):
1907 self.failUnlessReallyEqual(sorted(children.keys()), sorted(expected_keys))
1908 d.addCallback(_check)
1910 def failUnlessNodeHasChild(self, node, name):
1911 assert isinstance(name, unicode)
1913 def _check(children):
1914 self.failUnless(name in children)
1915 d.addCallback(_check)
1917 def failIfNodeHasChild(self, node, name):
1918 assert isinstance(name, unicode)
1920 def _check(children):
1921 self.failIf(name in children)
1922 d.addCallback(_check)
1925 def failUnlessChildContentsAre(self, node, name, expected_contents):
1926 assert isinstance(name, unicode)
1927 d = node.get_child_at_path(name)
1928 d.addCallback(lambda node: download_to_data(node))
1929 def _check(contents):
1930 self.failUnlessReallyEqual(contents, expected_contents)
1931 d.addCallback(_check)
1934 def failUnlessMutableChildContentsAre(self, node, name, expected_contents):
1935 assert isinstance(name, unicode)
1936 d = node.get_child_at_path(name)
1937 d.addCallback(lambda node: node.download_best_version())
1938 def _check(contents):
1939 self.failUnlessReallyEqual(contents, expected_contents)
1940 d.addCallback(_check)
1943 def failUnlessRWChildURIIs(self, node, name, expected_uri):
1944 assert isinstance(name, unicode)
1945 d = node.get_child_at_path(name)
1947 self.failUnless(child.is_unknown() or not child.is_readonly())
1948 self.failUnlessReallyEqual(child.get_uri(), expected_uri.strip())
1949 self.failUnlessReallyEqual(child.get_write_uri(), expected_uri.strip())
1950 expected_ro_uri = self._make_readonly(expected_uri)
1952 self.failUnlessReallyEqual(child.get_readonly_uri(), expected_ro_uri.strip())
1953 d.addCallback(_check)
1956 def failUnlessROChildURIIs(self, node, name, expected_uri):
1957 assert isinstance(name, unicode)
1958 d = node.get_child_at_path(name)
1960 self.failUnless(child.is_unknown() or child.is_readonly())
1961 self.failUnlessReallyEqual(child.get_write_uri(), None)
1962 self.failUnlessReallyEqual(child.get_uri(), expected_uri.strip())
1963 self.failUnlessReallyEqual(child.get_readonly_uri(), expected_uri.strip())
1964 d.addCallback(_check)
1967 def failUnlessURIMatchesRWChild(self, got_uri, node, name):
1968 assert isinstance(name, unicode)
1969 d = node.get_child_at_path(name)
1971 self.failUnless(child.is_unknown() or not child.is_readonly())
1972 self.failUnlessReallyEqual(child.get_uri(), got_uri.strip())
1973 self.failUnlessReallyEqual(child.get_write_uri(), got_uri.strip())
1974 expected_ro_uri = self._make_readonly(got_uri)
1976 self.failUnlessReallyEqual(child.get_readonly_uri(), expected_ro_uri.strip())
1977 d.addCallback(_check)
1980 def failUnlessURIMatchesROChild(self, got_uri, node, name):
1981 assert isinstance(name, unicode)
1982 d = node.get_child_at_path(name)
1984 self.failUnless(child.is_unknown() or child.is_readonly())
1985 self.failUnlessReallyEqual(child.get_write_uri(), None)
1986 self.failUnlessReallyEqual(got_uri.strip(), child.get_uri())
1987 self.failUnlessReallyEqual(got_uri.strip(), child.get_readonly_uri())
1988 d.addCallback(_check)
1991 def failUnlessCHKURIHasContents(self, got_uri, contents):
1992 self.failUnless(FakeCHKFileNode.all_contents[got_uri] == contents)
1994 def test_POST_upload(self):
1995 d = self.POST(self.public_url + "/foo", t="upload",
1996 file=("new.txt", self.NEWFILE_CONTENTS))
1998 d.addCallback(self.failUnlessURIMatchesROChild, fn, u"new.txt")
1999 d.addCallback(lambda res:
2000 self.failUnlessChildContentsAre(fn, u"new.txt",
2001 self.NEWFILE_CONTENTS))
2004 def test_POST_upload_unicode(self):
2005 filename = u"n\u00e9wer.txt" # n e-acute w e r . t x t
2006 d = self.POST(self.public_url + "/foo", t="upload",
2007 file=(filename, self.NEWFILE_CONTENTS))
2009 d.addCallback(self.failUnlessURIMatchesROChild, fn, filename)
2010 d.addCallback(lambda res:
2011 self.failUnlessChildContentsAre(fn, filename,
2012 self.NEWFILE_CONTENTS))
2013 target_url = self.public_url + "/foo/" + filename.encode("utf-8")
2014 d.addCallback(lambda res: self.GET(target_url))
2015 d.addCallback(lambda contents: self.failUnlessReallyEqual(contents,
2016 self.NEWFILE_CONTENTS,
2020 def test_POST_upload_unicode_named(self):
2021 filename = u"n\u00e9wer.txt" # n e-acute w e r . t x t
2022 d = self.POST(self.public_url + "/foo", t="upload",
2024 file=("overridden", self.NEWFILE_CONTENTS))
2026 d.addCallback(self.failUnlessURIMatchesROChild, fn, filename)
2027 d.addCallback(lambda res:
2028 self.failUnlessChildContentsAre(fn, filename,
2029 self.NEWFILE_CONTENTS))
2030 target_url = self.public_url + "/foo/" + filename.encode("utf-8")
2031 d.addCallback(lambda res: self.GET(target_url))
2032 d.addCallback(lambda contents: self.failUnlessReallyEqual(contents,
2033 self.NEWFILE_CONTENTS,
2037 def test_POST_upload_no_link(self):
2038 d = self.POST("/uri", t="upload",
2039 file=("new.txt", self.NEWFILE_CONTENTS))
2040 def _check_upload_results(page):
2041 # this should be a page which describes the results of the upload
2042 # that just finished.
2043 self.failUnless("Upload Results:" in page)
2044 self.failUnless("URI:" in page)
2045 uri_re = re.compile("URI: <tt><span>(.*)</span>")
2046 mo = uri_re.search(page)
2047 self.failUnless(mo, page)
2048 new_uri = mo.group(1)
2050 d.addCallback(_check_upload_results)
2051 d.addCallback(self.failUnlessCHKURIHasContents, self.NEWFILE_CONTENTS)
2054 def test_POST_upload_no_link_whendone(self):
2055 d = self.POST("/uri", t="upload", when_done="/",
2056 file=("new.txt", self.NEWFILE_CONTENTS))
2057 d.addBoth(self.shouldRedirect, "/")
2060 def shouldRedirect2(self, which, checker, callable, *args, **kwargs):
2061 d = defer.maybeDeferred(callable, *args, **kwargs)
2063 if isinstance(res, failure.Failure):
2064 res.trap(error.PageRedirect)
2065 statuscode = res.value.status
2066 target = res.value.location
2067 return checker(statuscode, target)
2068 self.fail("%s: callable was supposed to redirect, not return '%s'"
2073 def test_POST_upload_no_link_whendone_results(self):
2074 def check(statuscode, target):
2075 self.failUnlessReallyEqual(statuscode, str(http.FOUND))
2076 self.failUnless(target.startswith(self.webish_url), target)
2077 return client.getPage(target, method="GET")
2078 d = self.shouldRedirect2("test_POST_upload_no_link_whendone_results",
2080 self.POST, "/uri", t="upload",
2081 when_done="/uri/%(uri)s",
2082 file=("new.txt", self.NEWFILE_CONTENTS))
2083 d.addCallback(lambda res:
2084 self.failUnlessReallyEqual(res, self.NEWFILE_CONTENTS))
2087 def test_POST_upload_no_link_mutable(self):
2088 d = self.POST("/uri", t="upload", mutable="true",
2089 file=("new.txt", self.NEWFILE_CONTENTS))
2090 def _check(filecap):
2091 filecap = filecap.strip()
2092 self.failUnless(filecap.startswith("URI:SSK:"), filecap)
2093 self.filecap = filecap
2094 u = uri.WriteableSSKFileURI.init_from_string(filecap)
2095 self.failUnless(u.get_storage_index() in FakeMutableFileNode.all_contents)
2096 n = self.s.create_node_from_uri(filecap)
2097 return n.download_best_version()
2098 d.addCallback(_check)
2100 self.failUnlessReallyEqual(data, self.NEWFILE_CONTENTS)
2101 return self.GET("/uri/%s" % urllib.quote(self.filecap))
2102 d.addCallback(_check2)
2104 self.failUnlessReallyEqual(data, self.NEWFILE_CONTENTS)
2105 return self.GET("/file/%s" % urllib.quote(self.filecap))
2106 d.addCallback(_check3)
2108 self.failUnlessReallyEqual(data, self.NEWFILE_CONTENTS)
2109 d.addCallback(_check4)
2112 def test_POST_upload_no_link_mutable_toobig(self):
2113 # The SDMF size limit is no longer in place, so we should be
2114 # able to upload mutable files that are as large as we want them
2116 d = self.POST("/uri", t="upload", mutable="true",
2117 file=("new.txt", "b" * (self.s.MUTABLE_SIZELIMIT + 1)))
2121 def test_POST_upload_format_unlinked(self):
2122 def _check_upload_unlinked(ign, format, uri_prefix):
2123 filename = format + ".txt"
2124 d = self.POST("/uri?t=upload&format=" + format,
2125 file=(filename, self.NEWFILE_CONTENTS * 300000))
2126 def _got_results(results):
2127 if format.upper() in ("SDMF", "MDMF"):
2128 # webapi.rst says this returns a filecap
2131 # for immutable, it returns an "upload results page", and
2132 # the filecap is buried inside
2133 line = [l for l in results.split("\n") if "URI: " in l][0]
2134 mo = re.search(r'<span>([^<]+)</span>', line)
2135 filecap = mo.group(1)
2136 self.failUnless(filecap.startswith(uri_prefix),
2137 (uri_prefix, filecap))
2138 return self.GET("/uri/%s?t=json" % filecap)
2139 d.addCallback(_got_results)
2140 def _got_json(json):
2141 data = simplejson.loads(json)
2143 self.failUnlessIn("format", data)
2144 self.failUnlessEqual(data["format"], format.upper())
2145 d.addCallback(_got_json)
2147 d = defer.succeed(None)
2148 d.addCallback(_check_upload_unlinked, "chk", "URI:CHK")
2149 d.addCallback(_check_upload_unlinked, "CHK", "URI:CHK")
2150 d.addCallback(_check_upload_unlinked, "sdmf", "URI:SSK")
2151 d.addCallback(_check_upload_unlinked, "mdmf", "URI:MDMF")
2154 def test_POST_upload_bad_format_unlinked(self):
2155 return self.shouldHTTPError("POST_upload_bad_format_unlinked",
2156 400, "Bad Request", "Unknown format: foo",
2158 "/uri?t=upload&format=foo",
2159 file=("foo.txt", self.NEWFILE_CONTENTS * 300000))
2161 def test_POST_upload_format(self):
2162 def _check_upload(ign, format, uri_prefix, fn=None):
2163 filename = format + ".txt"
2164 d = self.POST(self.public_url +
2165 "/foo?t=upload&format=" + format,
2166 file=(filename, self.NEWFILE_CONTENTS * 300000))
2167 def _got_filecap(filecap):
2169 filenameu = unicode(filename)
2170 self.failUnlessURIMatchesRWChild(filecap, fn, filenameu)
2171 self.failUnless(filecap.startswith(uri_prefix))
2172 return self.GET(self.public_url + "/foo/%s?t=json" % filename)
2173 d.addCallback(_got_filecap)
2174 def _got_json(json):
2175 data = simplejson.loads(json)
2177 self.failUnlessIn("format", data)
2178 self.failUnlessEqual(data["format"], format.upper())
2179 d.addCallback(_got_json)
2182 d = defer.succeed(None)
2183 d.addCallback(_check_upload, "chk", "URI:CHK")
2184 d.addCallback(_check_upload, "sdmf", "URI:SSK", self._foo_node)
2185 d.addCallback(_check_upload, "mdmf", "URI:MDMF")
2186 d.addCallback(_check_upload, "MDMF", "URI:MDMF")
2189 def test_POST_upload_bad_format(self):
2190 return self.shouldHTTPError("POST_upload_bad_format",
2191 400, "Bad Request", "Unknown format: foo",
2192 self.POST, self.public_url + \
2193 "/foo?t=upload&format=foo",
2194 file=("foo.txt", self.NEWFILE_CONTENTS * 300000))
2196 def test_POST_upload_mutable(self):
2197 # this creates a mutable file
2198 d = self.POST(self.public_url + "/foo", t="upload", mutable="true",
2199 file=("new.txt", self.NEWFILE_CONTENTS))
2201 d.addCallback(self.failUnlessURIMatchesRWChild, fn, u"new.txt")
2202 d.addCallback(lambda res:
2203 self.failUnlessMutableChildContentsAre(fn, u"new.txt",
2204 self.NEWFILE_CONTENTS))
2205 d.addCallback(lambda res: self._foo_node.get(u"new.txt"))
2207 self.failUnless(IMutableFileNode.providedBy(newnode))
2208 self.failUnless(newnode.is_mutable())
2209 self.failIf(newnode.is_readonly())
2210 self._mutable_node = newnode
2211 self._mutable_uri = newnode.get_uri()
2214 # now upload it again and make sure that the URI doesn't change
2215 NEWER_CONTENTS = self.NEWFILE_CONTENTS + "newer\n"
2216 d.addCallback(lambda res:
2217 self.POST(self.public_url + "/foo", t="upload",
2219 file=("new.txt", NEWER_CONTENTS)))
2220 d.addCallback(self.failUnlessURIMatchesRWChild, fn, u"new.txt")
2221 d.addCallback(lambda res:
2222 self.failUnlessMutableChildContentsAre(fn, u"new.txt",
2224 d.addCallback(lambda res: self._foo_node.get(u"new.txt"))
2226 self.failUnless(IMutableFileNode.providedBy(newnode))
2227 self.failUnless(newnode.is_mutable())
2228 self.failIf(newnode.is_readonly())
2229 self.failUnlessReallyEqual(self._mutable_uri, newnode.get_uri())
2230 d.addCallback(_got2)
2232 # upload a second time, using PUT instead of POST
2233 NEW2_CONTENTS = NEWER_CONTENTS + "overwrite with PUT\n"
2234 d.addCallback(lambda res:
2235 self.PUT(self.public_url + "/foo/new.txt", NEW2_CONTENTS))
2236 d.addCallback(self.failUnlessURIMatchesRWChild, fn, u"new.txt")
2237 d.addCallback(lambda res:
2238 self.failUnlessMutableChildContentsAre(fn, u"new.txt",
2241 # finally list the directory, since mutable files are displayed
2242 # slightly differently
2244 d.addCallback(lambda res:
2245 self.GET(self.public_url + "/foo/",
2246 followRedirect=True))
2247 def _check_page(res):
2248 # TODO: assert more about the contents
2249 self.failUnless("SSK" in res)
2251 d.addCallback(_check_page)
2253 d.addCallback(lambda res: self._foo_node.get(u"new.txt"))
2255 self.failUnless(IMutableFileNode.providedBy(newnode))
2256 self.failUnless(newnode.is_mutable())
2257 self.failIf(newnode.is_readonly())
2258 self.failUnlessReallyEqual(self._mutable_uri, newnode.get_uri())
2259 d.addCallback(_got3)
2261 # look at the JSON form of the enclosing directory
2262 d.addCallback(lambda res:
2263 self.GET(self.public_url + "/foo/?t=json",
2264 followRedirect=True))
2265 def _check_page_json(res):
2266 parsed = simplejson.loads(res)
2267 self.failUnlessEqual(parsed[0], "dirnode")
2268 children = dict( [(unicode(name),value)
2270 in parsed[1]["children"].iteritems()] )
2271 self.failUnless(u"new.txt" in children)
2272 new_json = children[u"new.txt"]
2273 self.failUnlessEqual(new_json[0], "filenode")
2274 self.failUnless(new_json[1]["mutable"])
2275 self.failUnlessReallyEqual(to_str(new_json[1]["rw_uri"]), self._mutable_uri)
2276 ro_uri = self._mutable_node.get_readonly().to_string()
2277 self.failUnlessReallyEqual(to_str(new_json[1]["ro_uri"]), ro_uri)
2278 d.addCallback(_check_page_json)
2280 # and the JSON form of the file
2281 d.addCallback(lambda res:
2282 self.GET(self.public_url + "/foo/new.txt?t=json"))
2283 def _check_file_json(res):
2284 parsed = simplejson.loads(res)
2285 self.failUnlessEqual(parsed[0], "filenode")
2286 self.failUnless(parsed[1]["mutable"])
2287 self.failUnlessReallyEqual(to_str(parsed[1]["rw_uri"]), self._mutable_uri)
2288 ro_uri = self._mutable_node.get_readonly().to_string()
2289 self.failUnlessReallyEqual(to_str(parsed[1]["ro_uri"]), ro_uri)
2290 d.addCallback(_check_file_json)
2292 # and look at t=uri and t=readonly-uri
2293 d.addCallback(lambda res:
2294 self.GET(self.public_url + "/foo/new.txt?t=uri"))
2295 d.addCallback(lambda res: self.failUnlessReallyEqual(res, self._mutable_uri))
2296 d.addCallback(lambda res:
2297 self.GET(self.public_url + "/foo/new.txt?t=readonly-uri"))
2298 def _check_ro_uri(res):
2299 ro_uri = self._mutable_node.get_readonly().to_string()
2300 self.failUnlessReallyEqual(res, ro_uri)
2301 d.addCallback(_check_ro_uri)
2303 # make sure we can get to it from /uri/URI
2304 d.addCallback(lambda res:
2305 self.GET("/uri/%s" % urllib.quote(self._mutable_uri)))
2306 d.addCallback(lambda res:
2307 self.failUnlessReallyEqual(res, NEW2_CONTENTS))
2309 # and that HEAD computes the size correctly
2310 d.addCallback(lambda res:
2311 self.HEAD(self.public_url + "/foo/new.txt",
2312 return_response=True))
2313 def _got_headers((res, status, headers)):
2314 self.failUnlessReallyEqual(res, "")
2315 self.failUnlessReallyEqual(headers["content-length"][0],
2316 str(len(NEW2_CONTENTS)))
2317 self.failUnlessReallyEqual(headers["content-type"], ["text/plain"])
2318 d.addCallback(_got_headers)
2320 # make sure that outdated size limits aren't enforced anymore.
2321 d.addCallback(lambda ignored:
2322 self.POST(self.public_url + "/foo", t="upload",
2325 "b" * (self.s.MUTABLE_SIZELIMIT+1))))
2326 d.addErrback(self.dump_error)
2329 def test_POST_upload_mutable_toobig(self):
2330 # SDMF had a size limti that was removed a while ago. MDMF has
2331 # never had a size limit. Test to make sure that we do not
2332 # encounter errors when trying to upload large mutable files,
2333 # since there should be no coded prohibitions regarding large
2335 d = self.POST(self.public_url + "/foo",
2336 t="upload", mutable="true",
2337 file=("new.txt", "b" * (self.s.MUTABLE_SIZELIMIT + 1)))
2340 def dump_error(self, f):
2341 # if the web server returns an error code (like 400 Bad Request),
2342 # web.client.getPage puts the HTTP response body into the .response
2343 # attribute of the exception object that it gives back. It does not
2344 # appear in the Failure's repr(), so the ERROR that trial displays
2345 # will be rather terse and unhelpful. addErrback this method to the
2346 # end of your chain to get more information out of these errors.
2347 if f.check(error.Error):
2348 print "web.error.Error:"
2350 print f.value.response
2353 def test_POST_upload_replace(self):
2354 d = self.POST(self.public_url + "/foo", t="upload",
2355 file=("bar.txt", self.NEWFILE_CONTENTS))
2357 d.addCallback(self.failUnlessURIMatchesROChild, fn, u"bar.txt")
2358 d.addCallback(lambda res:
2359 self.failUnlessChildContentsAre(fn, u"bar.txt",
2360 self.NEWFILE_CONTENTS))
2363 def test_POST_upload_no_replace_ok(self):
2364 d = self.POST(self.public_url + "/foo?replace=false", t="upload",
2365 file=("new.txt", self.NEWFILE_CONTENTS))
2366 d.addCallback(lambda res: self.GET(self.public_url + "/foo/new.txt"))
2367 d.addCallback(lambda res: self.failUnlessReallyEqual(res,
2368 self.NEWFILE_CONTENTS))
2371 def test_POST_upload_no_replace_queryarg(self):
2372 d = self.POST(self.public_url + "/foo?replace=false", t="upload",
2373 file=("bar.txt", self.NEWFILE_CONTENTS))
2374 d.addBoth(self.shouldFail, error.Error,
2375 "POST_upload_no_replace_queryarg",
2377 "There was already a child by that name, and you asked me "
2378 "to not replace it")
2379 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
2380 d.addCallback(self.failUnlessIsBarDotTxt)
2383 def test_POST_upload_no_replace_field(self):
2384 d = self.POST(self.public_url + "/foo", t="upload", replace="false",
2385 file=("bar.txt", self.NEWFILE_CONTENTS))
2386 d.addBoth(self.shouldFail, error.Error, "POST_upload_no_replace_field",
2388 "There was already a child by that name, and you asked me "
2389 "to not replace it")
2390 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
2391 d.addCallback(self.failUnlessIsBarDotTxt)
2394 def test_POST_upload_whendone(self):
2395 d = self.POST(self.public_url + "/foo", t="upload", when_done="/THERE",
2396 file=("new.txt", self.NEWFILE_CONTENTS))
2397 d.addBoth(self.shouldRedirect, "/THERE")
2399 d.addCallback(lambda res:
2400 self.failUnlessChildContentsAre(fn, u"new.txt",
2401 self.NEWFILE_CONTENTS))
2404 def test_POST_upload_named(self):
2406 d = self.POST(self.public_url + "/foo", t="upload",
2407 name="new.txt", file=self.NEWFILE_CONTENTS)
2408 d.addCallback(self.failUnlessURIMatchesROChild, fn, u"new.txt")
2409 d.addCallback(lambda res:
2410 self.failUnlessChildContentsAre(fn, u"new.txt",
2411 self.NEWFILE_CONTENTS))
2414 def test_POST_upload_named_badfilename(self):
2415 d = self.POST(self.public_url + "/foo", t="upload",
2416 name="slashes/are/bad.txt", file=self.NEWFILE_CONTENTS)
2417 d.addBoth(self.shouldFail, error.Error,
2418 "test_POST_upload_named_badfilename",
2420 "name= may not contain a slash",
2422 # make sure that nothing was added
2423 d.addCallback(lambda res:
2424 self.failUnlessNodeKeysAre(self._foo_node,
2425 [u"bar.txt", u"baz.txt", u"blockingfile",
2426 u"empty", u"n\u00fc.txt", u"quux.txt",
2430 def test_POST_FILEURL_check(self):
2431 bar_url = self.public_url + "/foo/bar.txt"
2432 d = self.POST(bar_url, t="check")
2434 self.failUnless("Healthy :" in res)
2435 d.addCallback(_check)
2436 redir_url = "http://allmydata.org/TARGET"
2437 def _check2(statuscode, target):
2438 self.failUnlessReallyEqual(statuscode, str(http.FOUND))
2439 self.failUnlessReallyEqual(target, redir_url)
2440 d.addCallback(lambda res:
2441 self.shouldRedirect2("test_POST_FILEURL_check",
2445 when_done=redir_url))
2446 d.addCallback(lambda res:
2447 self.POST(bar_url, t="check", return_to=redir_url))
2449 self.failUnless("Healthy :" in res)
2450 self.failUnless("Return to file" in res)
2451 self.failUnless(redir_url in res)
2452 d.addCallback(_check3)
2454 d.addCallback(lambda res:
2455 self.POST(bar_url, t="check", output="JSON"))
2456 def _check_json(res):
2457 data = simplejson.loads(res)
2458 self.failUnless("storage-index" in data)
2459 self.failUnless(data["results"]["healthy"])
2460 d.addCallback(_check_json)
2464 def test_POST_FILEURL_check_and_repair(self):
2465 bar_url = self.public_url + "/foo/bar.txt"
2466 d = self.POST(bar_url, t="check", repair="true")
2468 self.failUnless("Healthy :" in res)
2469 d.addCallback(_check)
2470 redir_url = "http://allmydata.org/TARGET"
2471 def _check2(statuscode, target):
2472 self.failUnlessReallyEqual(statuscode, str(http.FOUND))
2473 self.failUnlessReallyEqual(target, redir_url)
2474 d.addCallback(lambda res:
2475 self.shouldRedirect2("test_POST_FILEURL_check_and_repair",
2478 t="check", repair="true",
2479 when_done=redir_url))
2480 d.addCallback(lambda res:
2481 self.POST(bar_url, t="check", return_to=redir_url))
2483 self.failUnless("Healthy :" in res)
2484 self.failUnless("Return to file" in res)
2485 self.failUnless(redir_url in res)
2486 d.addCallback(_check3)
2489 def test_POST_DIRURL_check(self):
2490 foo_url = self.public_url + "/foo/"
2491 d = self.POST(foo_url, t="check")
2493 self.failUnless("Healthy :" in res, res)
2494 d.addCallback(_check)
2495 redir_url = "http://allmydata.org/TARGET"
2496 def _check2(statuscode, target):
2497 self.failUnlessReallyEqual(statuscode, str(http.FOUND))
2498 self.failUnlessReallyEqual(target, redir_url)
2499 d.addCallback(lambda res:
2500 self.shouldRedirect2("test_POST_DIRURL_check",
2504 when_done=redir_url))
2505 d.addCallback(lambda res:
2506 self.POST(foo_url, t="check", return_to=redir_url))
2508 self.failUnless("Healthy :" in res, res)
2509 self.failUnless("Return to file/directory" in res)
2510 self.failUnless(redir_url in res)
2511 d.addCallback(_check3)
2513 d.addCallback(lambda res:
2514 self.POST(foo_url, t="check", output="JSON"))
2515 def _check_json(res):
2516 data = simplejson.loads(res)
2517 self.failUnless("storage-index" in data)
2518 self.failUnless(data["results"]["healthy"])
2519 d.addCallback(_check_json)
2523 def test_POST_DIRURL_check_and_repair(self):
2524 foo_url = self.public_url + "/foo/"
2525 d = self.POST(foo_url, t="check", repair="true")
2527 self.failUnless("Healthy :" in res, res)
2528 d.addCallback(_check)
2529 redir_url = "http://allmydata.org/TARGET"
2530 def _check2(statuscode, target):
2531 self.failUnlessReallyEqual(statuscode, str(http.FOUND))
2532 self.failUnlessReallyEqual(target, redir_url)
2533 d.addCallback(lambda res:
2534 self.shouldRedirect2("test_POST_DIRURL_check_and_repair",
2537 t="check", repair="true",
2538 when_done=redir_url))
2539 d.addCallback(lambda res:
2540 self.POST(foo_url, t="check", return_to=redir_url))
2542 self.failUnless("Healthy :" in res)
2543 self.failUnless("Return to file/directory" in res)
2544 self.failUnless(redir_url in res)
2545 d.addCallback(_check3)
2548 def test_POST_FILEURL_mdmf_check(self):
2549 quux_url = "/uri/%s" % urllib.quote(self._quux_txt_uri)
2550 d = self.POST(quux_url, t="check")
2552 self.failUnlessIn("Healthy", res)
2553 d.addCallback(_check)
2554 quux_extension_url = "/uri/%s" % urllib.quote("%s:3:131073" % self._quux_txt_uri)
2555 d.addCallback(lambda ignored:
2556 self.POST(quux_extension_url, t="check"))
2557 d.addCallback(_check)
2560 def test_POST_FILEURL_mdmf_check_and_repair(self):
2561 quux_url = "/uri/%s" % urllib.quote(self._quux_txt_uri)
2562 d = self.POST(quux_url, t="check", repair="true")
2564 self.failUnlessIn("Healthy", res)
2565 d.addCallback(_check)
2566 quux_extension_url = "/uri/%s" %\
2567 urllib.quote("%s:3:131073" % self._quux_txt_uri)
2568 d.addCallback(lambda ignored:
2569 self.POST(quux_extension_url, t="check", repair="true"))
2570 d.addCallback(_check)
2573 def wait_for_operation(self, ignored, ophandle):
2574 url = "/operations/" + ophandle
2575 url += "?t=status&output=JSON"
2578 data = simplejson.loads(res)
2579 if not data["finished"]:
2580 d = self.stall(delay=1.0)
2581 d.addCallback(self.wait_for_operation, ophandle)
2587 def get_operation_results(self, ignored, ophandle, output=None):
2588 url = "/operations/" + ophandle
2591 url += "&output=" + output
2594 if output and output.lower() == "json":
2595 return simplejson.loads(res)
2600 def test_POST_DIRURL_deepcheck_no_ophandle(self):
2601 d = self.shouldFail2(error.Error,
2602 "test_POST_DIRURL_deepcheck_no_ophandle",
2604 "slow operation requires ophandle=",
2605 self.POST, self.public_url, t="start-deep-check")
2608 def test_POST_DIRURL_deepcheck(self):
2609 def _check_redirect(statuscode, target):
2610 self.failUnlessReallyEqual(statuscode, str(http.FOUND))
2611 self.failUnless(target.endswith("/operations/123"))
2612 d = self.shouldRedirect2("test_POST_DIRURL_deepcheck", _check_redirect,
2613 self.POST, self.public_url,
2614 t="start-deep-check", ophandle="123")
2615 d.addCallback(self.wait_for_operation, "123")
2616 def _check_json(data):
2617 self.failUnlessReallyEqual(data["finished"], True)
2618 self.failUnlessReallyEqual(data["count-objects-checked"], 10)
2619 self.failUnlessReallyEqual(data["count-objects-healthy"], 10)
2620 d.addCallback(_check_json)
2621 d.addCallback(self.get_operation_results, "123", "html")
2622 def _check_html(res):
2623 self.failUnless("Objects Checked: <span>10</span>" in res)
2624 self.failUnless("Objects Healthy: <span>10</span>" in res)
2625 d.addCallback(_check_html)
2627 d.addCallback(lambda res:
2628 self.GET("/operations/123/"))
2629 d.addCallback(_check_html) # should be the same as without the slash
2631 d.addCallback(lambda res:
2632 self.shouldFail2(error.Error, "one", "404 Not Found",
2633 "No detailed results for SI bogus",
2634 self.GET, "/operations/123/bogus"))
2636 foo_si = self._foo_node.get_storage_index()
2637 foo_si_s = base32.b2a(foo_si)
2638 d.addCallback(lambda res:
2639 self.GET("/operations/123/%s?output=JSON" % foo_si_s))
2640 def _check_foo_json(res):
2641 data = simplejson.loads(res)
2642 self.failUnlessEqual(data["storage-index"], foo_si_s)
2643 self.failUnless(data["results"]["healthy"])
2644 d.addCallback(_check_foo_json)
2647 def test_POST_DIRURL_deepcheck_and_repair(self):
2648 d = self.POST(self.public_url, t="start-deep-check", repair="true",
2649 ophandle="124", output="json", followRedirect=True)
2650 d.addCallback(self.wait_for_operation, "124")
2651 def _check_json(data):
2652 self.failUnlessReallyEqual(data["finished"], True)
2653 self.failUnlessReallyEqual(data["count-objects-checked"], 10)
2654 self.failUnlessReallyEqual(data["count-objects-healthy-pre-repair"], 10)
2655 self.failUnlessReallyEqual(data["count-objects-unhealthy-pre-repair"], 0)
2656 self.failUnlessReallyEqual(data["count-corrupt-shares-pre-repair"], 0)
2657 self.failUnlessReallyEqual(data["count-repairs-attempted"], 0)
2658 self.failUnlessReallyEqual(data["count-repairs-successful"], 0)
2659 self.failUnlessReallyEqual(data["count-repairs-unsuccessful"], 0)
2660 self.failUnlessReallyEqual(data["count-objects-healthy-post-repair"], 10)
2661 self.failUnlessReallyEqual(data["count-objects-unhealthy-post-repair"], 0)
2662 self.failUnlessReallyEqual(data["count-corrupt-shares-post-repair"], 0)
2663 d.addCallback(_check_json)
2664 d.addCallback(self.get_operation_results, "124", "html")
2665 def _check_html(res):
2666 self.failUnless("Objects Checked: <span>10</span>" in res)
2668 self.failUnless("Objects Healthy (before repair): <span>10</span>" in res)
2669 self.failUnless("Objects Unhealthy (before repair): <span>0</span>" in res)
2670 self.failUnless("Corrupt Shares (before repair): <span>0</span>" in res)
2672 self.failUnless("Repairs Attempted: <span>0</span>" in res)
2673 self.failUnless("Repairs Successful: <span>0</span>" in res)
2674 self.failUnless("Repairs Unsuccessful: <span>0</span>" in res)
2676 self.failUnless("Objects Healthy (after repair): <span>10</span>" in res)
2677 self.failUnless("Objects Unhealthy (after repair): <span>0</span>" in res)
2678 self.failUnless("Corrupt Shares (after repair): <span>0</span>" in res)
2679 d.addCallback(_check_html)
2682 def test_POST_FILEURL_bad_t(self):
2683 d = self.shouldFail2(error.Error, "POST_bad_t", "400 Bad Request",
2684 "POST to file: bad t=bogus",
2685 self.POST, self.public_url + "/foo/bar.txt",
2689 def test_POST_mkdir(self): # return value?
2690 d = self.POST(self.public_url + "/foo", t="mkdir", name="newdir")
2691 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2692 d.addCallback(self.failUnlessNodeKeysAre, [])
2695 def test_POST_mkdir_mdmf(self):
2696 d = self.POST(self.public_url + "/foo?t=mkdir&name=newdir&format=mdmf")
2697 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2698 d.addCallback(lambda node:
2699 self.failUnlessEqual(node._node.get_version(), MDMF_VERSION))
2702 def test_POST_mkdir_sdmf(self):
2703 d = self.POST(self.public_url + "/foo?t=mkdir&name=newdir&format=sdmf")
2704 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2705 d.addCallback(lambda node:
2706 self.failUnlessEqual(node._node.get_version(), SDMF_VERSION))
2709 def test_POST_mkdir_bad_format(self):
2710 return self.shouldHTTPError("POST_mkdir_bad_format",
2711 400, "Bad Request", "Unknown format: foo",
2712 self.POST, self.public_url +
2713 "/foo?t=mkdir&name=newdir&format=foo")
2715 def test_POST_mkdir_initial_children(self):
2716 (newkids, caps) = self._create_initial_children()
2717 d = self.POST2(self.public_url +
2718 "/foo?t=mkdir-with-children&name=newdir",
2719 simplejson.dumps(newkids))
2720 d.addCallback(lambda res:
2721 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
2722 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2723 d.addCallback(self.failUnlessNodeKeysAre, newkids.keys())
2724 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2725 d.addCallback(self.failUnlessROChildURIIs, u"child-imm", caps['filecap1'])
2728 def test_POST_mkdir_initial_children_mdmf(self):
2729 (newkids, caps) = self._create_initial_children()
2730 d = self.POST2(self.public_url +
2731 "/foo?t=mkdir-with-children&name=newdir&format=mdmf",
2732 simplejson.dumps(newkids))
2733 d.addCallback(lambda res:
2734 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
2735 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2736 d.addCallback(lambda node:
2737 self.failUnlessEqual(node._node.get_version(), MDMF_VERSION))
2738 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2739 d.addCallback(self.failUnlessROChildURIIs, u"child-imm",
2744 def test_POST_mkdir_initial_children_sdmf(self):
2745 (newkids, caps) = self._create_initial_children()
2746 d = self.POST2(self.public_url +
2747 "/foo?t=mkdir-with-children&name=newdir&format=sdmf",
2748 simplejson.dumps(newkids))
2749 d.addCallback(lambda res:
2750 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
2751 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2752 d.addCallback(lambda node:
2753 self.failUnlessEqual(node._node.get_version(), SDMF_VERSION))
2754 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2755 d.addCallback(self.failUnlessROChildURIIs, u"child-imm",
2759 def test_POST_mkdir_initial_children_bad_format(self):
2760 (newkids, caps) = self._create_initial_children()
2761 return self.shouldHTTPError("POST_mkdir_initial_children_bad_format",
2762 400, "Bad Request", "Unknown format: foo",
2763 self.POST, self.public_url + \
2764 "/foo?t=mkdir-with-children&name=newdir&format=foo",
2765 simplejson.dumps(newkids))
2767 def test_POST_mkdir_immutable(self):
2768 (newkids, caps) = self._create_immutable_children()
2769 d = self.POST2(self.public_url +
2770 "/foo?t=mkdir-immutable&name=newdir",
2771 simplejson.dumps(newkids))
2772 d.addCallback(lambda res:
2773 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
2774 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2775 d.addCallback(self.failUnlessNodeKeysAre, newkids.keys())
2776 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2777 d.addCallback(self.failUnlessROChildURIIs, u"child-imm", caps['filecap1'])
2778 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2779 d.addCallback(self.failUnlessROChildURIIs, u"unknownchild-imm", caps['unknown_immcap'])
2780 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2781 d.addCallback(self.failUnlessROChildURIIs, u"dirchild-imm", caps['immdircap'])
2782 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2783 d.addCallback(self.failUnlessROChildURIIs, u"dirchild-lit", caps['litdircap'])
2784 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2785 d.addCallback(self.failUnlessROChildURIIs, u"dirchild-empty", caps['emptydircap'])
2788 def test_POST_mkdir_immutable_bad(self):
2789 (newkids, caps) = self._create_initial_children()
2790 d = self.shouldFail2(error.Error, "POST_mkdir_immutable_bad",
2792 "needed to be immutable but was not",
2795 "/foo?t=mkdir-immutable&name=newdir",
2796 simplejson.dumps(newkids))
2799 def test_POST_mkdir_2(self):
2800 d = self.POST(self.public_url + "/foo/newdir?t=mkdir", "")
2801 d.addCallback(lambda res:
2802 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
2803 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2804 d.addCallback(self.failUnlessNodeKeysAre, [])
2807 def test_POST_mkdirs_2(self):
2808 d = self.POST(self.public_url + "/foo/bardir/newdir?t=mkdir", "")
2809 d.addCallback(lambda res:
2810 self.failUnlessNodeHasChild(self._foo_node, u"bardir"))
2811 d.addCallback(lambda res: self._foo_node.get(u"bardir"))
2812 d.addCallback(lambda bardirnode: bardirnode.get(u"newdir"))
2813 d.addCallback(self.failUnlessNodeKeysAre, [])
2816 def test_POST_mkdir_no_parentdir_noredirect(self):
2817 d = self.POST("/uri?t=mkdir")
2818 def _after_mkdir(res):
2819 uri.DirectoryURI.init_from_string(res)
2820 d.addCallback(_after_mkdir)
2823 def test_POST_mkdir_no_parentdir_noredirect_mdmf(self):
2824 d = self.POST("/uri?t=mkdir&format=mdmf")
2825 def _after_mkdir(res):
2826 u = uri.from_string(res)
2827 # Check that this is an MDMF writecap
2828 self.failUnlessIsInstance(u, uri.MDMFDirectoryURI)
2829 d.addCallback(_after_mkdir)
2832 def test_POST_mkdir_no_parentdir_noredirect_sdmf(self):
2833 d = self.POST("/uri?t=mkdir&format=sdmf")
2834 def _after_mkdir(res):
2835 u = uri.from_string(res)
2836 self.failUnlessIsInstance(u, uri.DirectoryURI)
2837 d.addCallback(_after_mkdir)
2840 def test_POST_mkdir_no_parentdir_noredirect_bad_format(self):
2841 return self.shouldHTTPError("POST_mkdir_no_parentdir_noredirect_bad_format",
2842 400, "Bad Request", "Unknown format: foo",
2843 self.POST, self.public_url +
2844 "/uri?t=mkdir&format=foo")
2846 def test_POST_mkdir_no_parentdir_noredirect2(self):
2847 # make sure form-based arguments (as on the welcome page) still work
2848 d = self.POST("/uri", t="mkdir")
2849 def _after_mkdir(res):
2850 uri.DirectoryURI.init_from_string(res)
2851 d.addCallback(_after_mkdir)
2852 d.addErrback(self.explain_web_error)
2855 def test_POST_mkdir_no_parentdir_redirect(self):
2856 d = self.POST("/uri?t=mkdir&redirect_to_result=true")
2857 d.addBoth(self.shouldRedirect, None, statuscode='303')
2858 def _check_target(target):
2859 target = urllib.unquote(target)
2860 self.failUnless(target.startswith("uri/URI:DIR2:"), target)
2861 d.addCallback(_check_target)
2864 def test_POST_mkdir_no_parentdir_redirect2(self):
2865 d = self.POST("/uri", t="mkdir", redirect_to_result="true")
2866 d.addBoth(self.shouldRedirect, None, statuscode='303')
2867 def _check_target(target):
2868 target = urllib.unquote(target)
2869 self.failUnless(target.startswith("uri/URI:DIR2:"), target)
2870 d.addCallback(_check_target)
2871 d.addErrback(self.explain_web_error)
2874 def _make_readonly(self, u):
2875 ro_uri = uri.from_string(u).get_readonly()
2878 return ro_uri.to_string()
2880 def _create_initial_children(self):
2881 contents, n, filecap1 = self.makefile(12)
2882 md1 = {"metakey1": "metavalue1"}
2883 filecap2 = make_mutable_file_uri()
2884 node3 = self.s.create_node_from_uri(make_mutable_file_uri())
2885 filecap3 = node3.get_readonly_uri()
2886 node4 = self.s.create_node_from_uri(make_mutable_file_uri())
2887 dircap = DirectoryNode(node4, None, None).get_uri()
2888 mdmfcap = make_mutable_file_uri(mdmf=True)
2889 litdircap = "URI:DIR2-LIT:ge3dumj2mewdcotyfqydulbshj5x2lbm"
2890 emptydircap = "URI:DIR2-LIT:"
2891 newkids = {u"child-imm": ["filenode", {"rw_uri": filecap1,
2892 "ro_uri": self._make_readonly(filecap1),
2893 "metadata": md1, }],
2894 u"child-mutable": ["filenode", {"rw_uri": filecap2,
2895 "ro_uri": self._make_readonly(filecap2)}],
2896 u"child-mutable-ro": ["filenode", {"ro_uri": filecap3}],
2897 u"unknownchild-rw": ["unknown", {"rw_uri": unknown_rwcap,
2898 "ro_uri": unknown_rocap}],
2899 u"unknownchild-ro": ["unknown", {"ro_uri": unknown_rocap}],
2900 u"unknownchild-imm": ["unknown", {"ro_uri": unknown_immcap}],
2901 u"dirchild": ["dirnode", {"rw_uri": dircap,
2902 "ro_uri": self._make_readonly(dircap)}],
2903 u"dirchild-lit": ["dirnode", {"ro_uri": litdircap}],
2904 u"dirchild-empty": ["dirnode", {"ro_uri": emptydircap}],
2905 u"child-mutable-mdmf": ["filenode", {"rw_uri": mdmfcap,
2906 "ro_uri": self._make_readonly(mdmfcap)}],
2908 return newkids, {'filecap1': filecap1,
2909 'filecap2': filecap2,
2910 'filecap3': filecap3,
2911 'unknown_rwcap': unknown_rwcap,
2912 'unknown_rocap': unknown_rocap,
2913 'unknown_immcap': unknown_immcap,
2915 'litdircap': litdircap,
2916 'emptydircap': emptydircap,
2919 def _create_immutable_children(self):
2920 contents, n, filecap1 = self.makefile(12)
2921 md1 = {"metakey1": "metavalue1"}
2922 tnode = create_chk_filenode("immutable directory contents\n"*10)
2923 dnode = DirectoryNode(tnode, None, None)
2924 assert not dnode.is_mutable()
2925 immdircap = dnode.get_uri()
2926 litdircap = "URI:DIR2-LIT:ge3dumj2mewdcotyfqydulbshj5x2lbm"
2927 emptydircap = "URI:DIR2-LIT:"
2928 newkids = {u"child-imm": ["filenode", {"ro_uri": filecap1,
2929 "metadata": md1, }],
2930 u"unknownchild-imm": ["unknown", {"ro_uri": unknown_immcap}],
2931 u"dirchild-imm": ["dirnode", {"ro_uri": immdircap}],
2932 u"dirchild-lit": ["dirnode", {"ro_uri": litdircap}],
2933 u"dirchild-empty": ["dirnode", {"ro_uri": emptydircap}],
2935 return newkids, {'filecap1': filecap1,
2936 'unknown_immcap': unknown_immcap,
2937 'immdircap': immdircap,
2938 'litdircap': litdircap,
2939 'emptydircap': emptydircap}
2941 def test_POST_mkdir_no_parentdir_initial_children(self):
2942 (newkids, caps) = self._create_initial_children()
2943 d = self.POST2("/uri?t=mkdir-with-children", simplejson.dumps(newkids))
2944 def _after_mkdir(res):
2945 self.failUnless(res.startswith("URI:DIR"), res)
2946 n = self.s.create_node_from_uri(res)
2947 d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
2948 d2.addCallback(lambda ign:
2949 self.failUnlessROChildURIIs(n, u"child-imm",
2951 d2.addCallback(lambda ign:
2952 self.failUnlessRWChildURIIs(n, u"child-mutable",
2954 d2.addCallback(lambda ign:
2955 self.failUnlessROChildURIIs(n, u"child-mutable-ro",
2957 d2.addCallback(lambda ign:
2958 self.failUnlessRWChildURIIs(n, u"unknownchild-rw",
2959 caps['unknown_rwcap']))
2960 d2.addCallback(lambda ign:
2961 self.failUnlessROChildURIIs(n, u"unknownchild-ro",
2962 caps['unknown_rocap']))
2963 d2.addCallback(lambda ign:
2964 self.failUnlessROChildURIIs(n, u"unknownchild-imm",
2965 caps['unknown_immcap']))
2966 d2.addCallback(lambda ign:
2967 self.failUnlessRWChildURIIs(n, u"dirchild",
2970 d.addCallback(_after_mkdir)
2973 def test_POST_mkdir_no_parentdir_unexpected_children(self):
2974 # the regular /uri?t=mkdir operation is specified to ignore its body.
2975 # Only t=mkdir-with-children pays attention to it.
2976 (newkids, caps) = self._create_initial_children()
2977 d = self.shouldHTTPError("POST_mkdir_no_parentdir_unexpected_children",
2979 "t=mkdir does not accept children=, "
2980 "try t=mkdir-with-children instead",
2981 self.POST2, "/uri?t=mkdir", # without children
2982 simplejson.dumps(newkids))
2985 def test_POST_noparent_bad(self):
2986 d = self.shouldHTTPError("POST_noparent_bad",
2988 "/uri accepts only PUT, PUT?t=mkdir, "
2989 "POST?t=upload, and POST?t=mkdir",
2990 self.POST, "/uri?t=bogus")
2993 def test_POST_mkdir_no_parentdir_immutable(self):
2994 (newkids, caps) = self._create_immutable_children()
2995 d = self.POST2("/uri?t=mkdir-immutable", simplejson.dumps(newkids))
2996 def _after_mkdir(res):
2997 self.failUnless(res.startswith("URI:DIR"), res)
2998 n = self.s.create_node_from_uri(res)
2999 d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
3000 d2.addCallback(lambda ign:
3001 self.failUnlessROChildURIIs(n, u"child-imm",
3003 d2.addCallback(lambda ign:
3004 self.failUnlessROChildURIIs(n, u"unknownchild-imm",
3005 caps['unknown_immcap']))
3006 d2.addCallback(lambda ign:
3007 self.failUnlessROChildURIIs(n, u"dirchild-imm",
3009 d2.addCallback(lambda ign:
3010 self.failUnlessROChildURIIs(n, u"dirchild-lit",
3012 d2.addCallback(lambda ign:
3013 self.failUnlessROChildURIIs(n, u"dirchild-empty",
3014 caps['emptydircap']))
3016 d.addCallback(_after_mkdir)
3019 def test_POST_mkdir_no_parentdir_immutable_bad(self):
3020 (newkids, caps) = self._create_initial_children()
3021 d = self.shouldFail2(error.Error,
3022 "test_POST_mkdir_no_parentdir_immutable_bad",
3024 "needed to be immutable but was not",
3026 "/uri?t=mkdir-immutable",
3027 simplejson.dumps(newkids))
3030 def test_welcome_page_mkdir_button(self):
3031 # Fetch the welcome page.
3033 def _after_get_welcome_page(res):
3034 MKDIR_BUTTON_RE = re.compile(
3035 '<form action="([^"]*)" method="post".*?'
3036 '<input type="hidden" name="t" value="([^"]*)" />'
3037 '<input type="hidden" name="([^"]*)" value="([^"]*)" />'
3038 '<input type="submit" value="Create a directory" />',
3040 mo = MKDIR_BUTTON_RE.search(res)
3041 formaction = mo.group(1)
3043 formaname = mo.group(3)
3044 formavalue = mo.group(4)
3045 return (formaction, formt, formaname, formavalue)
3046 d.addCallback(_after_get_welcome_page)
3047 def _after_parse_form(res):
3048 (formaction, formt, formaname, formavalue) = res
3049 return self.POST("/%s?t=%s&%s=%s" % (formaction, formt, formaname, formavalue))
3050 d.addCallback(_after_parse_form)
3051 d.addBoth(self.shouldRedirect, None, statuscode='303')
3054 def test_POST_mkdir_replace(self): # return value?
3055 d = self.POST(self.public_url + "/foo", t="mkdir", name="sub")
3056 d.addCallback(lambda res: self._foo_node.get(u"sub"))
3057 d.addCallback(self.failUnlessNodeKeysAre, [])
3060 def test_POST_mkdir_no_replace_queryarg(self): # return value?
3061 d = self.POST(self.public_url + "/foo?replace=false", t="mkdir", name="sub")
3062 d.addBoth(self.shouldFail, error.Error,
3063 "POST_mkdir_no_replace_queryarg",
3065 "There was already a child by that name, and you asked me "
3066 "to not replace it")
3067 d.addCallback(lambda res: self._foo_node.get(u"sub"))
3068 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
3071 def test_POST_mkdir_no_replace_field(self): # return value?
3072 d = self.POST(self.public_url + "/foo", t="mkdir", name="sub",
3074 d.addBoth(self.shouldFail, error.Error, "POST_mkdir_no_replace_field",
3076 "There was already a child by that name, and you asked me "
3077 "to not replace it")
3078 d.addCallback(lambda res: self._foo_node.get(u"sub"))
3079 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
3082 def test_POST_mkdir_whendone_field(self):
3083 d = self.POST(self.public_url + "/foo",
3084 t="mkdir", name="newdir", when_done="/THERE")
3085 d.addBoth(self.shouldRedirect, "/THERE")
3086 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
3087 d.addCallback(self.failUnlessNodeKeysAre, [])
3090 def test_POST_mkdir_whendone_queryarg(self):
3091 d = self.POST(self.public_url + "/foo?when_done=/THERE",
3092 t="mkdir", name="newdir")
3093 d.addBoth(self.shouldRedirect, "/THERE")
3094 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
3095 d.addCallback(self.failUnlessNodeKeysAre, [])
3098 def test_POST_bad_t(self):
3099 d = self.shouldFail2(error.Error, "POST_bad_t",
3101 "POST to a directory with bad t=BOGUS",
3102 self.POST, self.public_url + "/foo", t="BOGUS")
3105 def test_POST_set_children(self, command_name="set_children"):
3106 contents9, n9, newuri9 = self.makefile(9)
3107 contents10, n10, newuri10 = self.makefile(10)
3108 contents11, n11, newuri11 = self.makefile(11)
3111 "atomic_added_1": [ "filenode", { "rw_uri": "%s",
3114 "ctime": 1002777696.7564139,
3115 "mtime": 1002777696.7564139
3118 "atomic_added_2": [ "filenode", { "rw_uri": "%s",
3121 "ctime": 1002777696.7564139,
3122 "mtime": 1002777696.7564139
3125 "atomic_added_3": [ "filenode", { "rw_uri": "%s",
3128 "ctime": 1002777696.7564139,
3129 "mtime": 1002777696.7564139
3132 }""" % (newuri9, newuri10, newuri11)
3134 url = self.webish_url + self.public_url + "/foo" + "?t=" + command_name
3136 d = client.getPage(url, method="POST", postdata=reqbody)
3138 self.failUnlessURIMatchesROChild(newuri9, self._foo_node, u"atomic_added_1")
3139 self.failUnlessURIMatchesROChild(newuri10, self._foo_node, u"atomic_added_2")
3140 self.failUnlessURIMatchesROChild(newuri11, self._foo_node, u"atomic_added_3")
3142 d.addCallback(_then)
3143 d.addErrback(self.dump_error)
3146 def test_POST_set_children_with_hyphen(self):
3147 return self.test_POST_set_children(command_name="set-children")
3149 def test_POST_link_uri(self):
3150 contents, n, newuri = self.makefile(8)
3151 d = self.POST(self.public_url + "/foo", t="uri", name="new.txt", uri=newuri)
3152 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"new.txt")
3153 d.addCallback(lambda res:
3154 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
3158 def test_POST_link_uri_replace(self):
3159 contents, n, newuri = self.makefile(8)
3160 d = self.POST(self.public_url + "/foo", t="uri", name="bar.txt", uri=newuri)
3161 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"bar.txt")
3162 d.addCallback(lambda res:
3163 self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
3167 def test_POST_link_uri_unknown_bad(self):
3168 d = self.POST(self.public_url + "/foo", t="uri", name="future.txt", uri=unknown_rwcap)
3169 d.addBoth(self.shouldFail, error.Error,
3170 "POST_link_uri_unknown_bad",
3172 "unknown cap in a write slot")
3175 def test_POST_link_uri_unknown_ro_good(self):
3176 d = self.POST(self.public_url + "/foo", t="uri", name="future-ro.txt", uri=unknown_rocap)
3177 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"future-ro.txt")
3180 def test_POST_link_uri_unknown_imm_good(self):
3181 d = self.POST(self.public_url + "/foo", t="uri", name="future-imm.txt", uri=unknown_immcap)
3182 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"future-imm.txt")
3185 def test_POST_link_uri_no_replace_queryarg(self):
3186 contents, n, newuri = self.makefile(8)
3187 d = self.POST(self.public_url + "/foo?replace=false", t="uri",
3188 name="bar.txt", uri=newuri)
3189 d.addBoth(self.shouldFail, error.Error,
3190 "POST_link_uri_no_replace_queryarg",
3192 "There was already a child by that name, and you asked me "
3193 "to not replace it")
3194 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
3195 d.addCallback(self.failUnlessIsBarDotTxt)
3198 def test_POST_link_uri_no_replace_field(self):
3199 contents, n, newuri = self.makefile(8)
3200 d = self.POST(self.public_url + "/foo", t="uri", replace="false",
3201 name="bar.txt", uri=newuri)
3202 d.addBoth(self.shouldFail, error.Error,
3203 "POST_link_uri_no_replace_field",
3205 "There was already a child by that name, and you asked me "
3206 "to not replace it")
3207 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
3208 d.addCallback(self.failUnlessIsBarDotTxt)
3211 def test_POST_delete(self, command_name='delete'):
3212 d = self._foo_node.list()
3213 def _check_before(children):
3214 self.failUnless(u"bar.txt" in children)
3215 d.addCallback(_check_before)
3216 d.addCallback(lambda res: self.POST(self.public_url + "/foo", t=command_name, name="bar.txt"))
3217 d.addCallback(lambda res: self._foo_node.list())
3218 def _check_after(children):
3219 self.failIf(u"bar.txt" in children)
3220 d.addCallback(_check_after)
3223 def test_POST_unlink(self):
3224 return self.test_POST_delete(command_name='unlink')
3226 def test_POST_rename_file(self):
3227 d = self.POST(self.public_url + "/foo", t="rename",
3228 from_name="bar.txt", to_name='wibble.txt')
3229 d.addCallback(lambda res:
3230 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
3231 d.addCallback(lambda res:
3232 self.failUnlessNodeHasChild(self._foo_node, u"wibble.txt"))
3233 d.addCallback(lambda res: self.GET(self.public_url + "/foo/wibble.txt"))
3234 d.addCallback(self.failUnlessIsBarDotTxt)
3235 d.addCallback(lambda res: self.GET(self.public_url + "/foo/wibble.txt?t=json"))
3236 d.addCallback(self.failUnlessIsBarJSON)
3239 def test_POST_rename_file_redundant(self):
3240 d = self.POST(self.public_url + "/foo", t="rename",
3241 from_name="bar.txt", to_name='bar.txt')
3242 d.addCallback(lambda res:
3243 self.failUnlessNodeHasChild(self._foo_node, u"bar.txt"))
3244 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
3245 d.addCallback(self.failUnlessIsBarDotTxt)
3246 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt?t=json"))
3247 d.addCallback(self.failUnlessIsBarJSON)
3250 def test_POST_rename_file_replace(self):
3251 # rename a file and replace a directory with it
3252 d = self.POST(self.public_url + "/foo", t="rename",
3253 from_name="bar.txt", to_name='empty')
3254 d.addCallback(lambda res:
3255 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
3256 d.addCallback(lambda res:
3257 self.failUnlessNodeHasChild(self._foo_node, u"empty"))
3258 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty"))
3259 d.addCallback(self.failUnlessIsBarDotTxt)
3260 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
3261 d.addCallback(self.failUnlessIsBarJSON)
3264 def test_POST_rename_file_no_replace_queryarg(self):
3265 # rename a file and replace a directory with it
3266 d = self.POST(self.public_url + "/foo?replace=false", t="rename",
3267 from_name="bar.txt", to_name='empty')
3268 d.addBoth(self.shouldFail, error.Error,
3269 "POST_rename_file_no_replace_queryarg",
3271 "There was already a child by that name, and you asked me "
3272 "to not replace it")
3273 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
3274 d.addCallback(self.failUnlessIsEmptyJSON)
3277 def test_POST_rename_file_no_replace_field(self):
3278 # rename a file and replace a directory with it
3279 d = self.POST(self.public_url + "/foo", t="rename", replace="false",
3280 from_name="bar.txt", to_name='empty')
3281 d.addBoth(self.shouldFail, error.Error,
3282 "POST_rename_file_no_replace_field",
3284 "There was already a child by that name, and you asked me "
3285 "to not replace it")
3286 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
3287 d.addCallback(self.failUnlessIsEmptyJSON)
3290 def failUnlessIsEmptyJSON(self, res):
3291 data = simplejson.loads(res)
3292 self.failUnlessEqual(data[0], "dirnode", data)
3293 self.failUnlessReallyEqual(len(data[1]["children"]), 0)
3295 def test_POST_rename_file_slash_fail(self):
3296 d = self.POST(self.public_url + "/foo", t="rename",
3297 from_name="bar.txt", to_name='kirk/spock.txt')
3298 d.addBoth(self.shouldFail, error.Error,
3299 "test_POST_rename_file_slash_fail",
3301 "to_name= may not contain a slash",
3303 d.addCallback(lambda res:
3304 self.failUnlessNodeHasChild(self._foo_node, u"bar.txt"))
3307 def test_POST_rename_dir(self):
3308 d = self.POST(self.public_url, t="rename",
3309 from_name="foo", to_name='plunk')
3310 d.addCallback(lambda res:
3311 self.failIfNodeHasChild(self.public_root, u"foo"))
3312 d.addCallback(lambda res:
3313 self.failUnlessNodeHasChild(self.public_root, u"plunk"))
3314 d.addCallback(lambda res: self.GET(self.public_url + "/plunk?t=json"))
3315 d.addCallback(self.failUnlessIsFooJSON)
3318 def shouldRedirect(self, res, target=None, statuscode=None, which=""):
3319 """ If target is not None then the redirection has to go to target. If
3320 statuscode is not None then the redirection has to be accomplished with
3321 that HTTP status code."""
3322 if not isinstance(res, failure.Failure):
3323 to_where = (target is None) and "somewhere" or ("to " + target)
3324 self.fail("%s: we were expecting to get redirected %s, not get an"
3325 " actual page: %s" % (which, to_where, res))
3326 res.trap(error.PageRedirect)
3327 if statuscode is not None:
3328 self.failUnlessReallyEqual(res.value.status, statuscode,
3329 "%s: not a redirect" % which)
3330 if target is not None:
3331 # the PageRedirect does not seem to capture the uri= query arg
3332 # properly, so we can't check for it.
3333 realtarget = self.webish_url + target
3334 self.failUnlessReallyEqual(res.value.location, realtarget,
3335 "%s: wrong target" % which)
3336 return res.value.location
3338 def test_GET_URI_form(self):
3339 base = "/uri?uri=%s" % self._bar_txt_uri
3340 # this is supposed to give us a redirect to /uri/$URI, plus arguments
3341 targetbase = "/uri/%s" % urllib.quote(self._bar_txt_uri)
3343 d.addBoth(self.shouldRedirect, targetbase)
3344 d.addCallback(lambda res: self.GET(base+"&filename=bar.txt"))
3345 d.addBoth(self.shouldRedirect, targetbase+"?filename=bar.txt")
3346 d.addCallback(lambda res: self.GET(base+"&t=json"))
3347 d.addBoth(self.shouldRedirect, targetbase+"?t=json")
3348 d.addCallback(self.log, "about to get file by uri")
3349 d.addCallback(lambda res: self.GET(base, followRedirect=True))
3350 d.addCallback(self.failUnlessIsBarDotTxt)
3351 d.addCallback(self.log, "got file by uri, about to get dir by uri")
3352 d.addCallback(lambda res: self.GET("/uri?uri=%s&t=json" % self._foo_uri,
3353 followRedirect=True))
3354 d.addCallback(self.failUnlessIsFooJSON)
3355 d.addCallback(self.log, "got dir by uri")
3359 def test_GET_URI_form_bad(self):
3360 d = self.shouldFail2(error.Error, "test_GET_URI_form_bad",
3361 "400 Bad Request", "GET /uri requires uri=",
3365 def test_GET_rename_form(self):
3366 d = self.GET(self.public_url + "/foo?t=rename-form&name=bar.txt",
3367 followRedirect=True)
3369 self.failUnless('name="when_done" value="."' in res, res)
3370 self.failUnless(re.search(r'name="from_name" value="bar\.txt"', res))
3371 d.addCallback(_check)
3374 def log(self, res, msg):
3375 #print "MSG: %s RES: %s" % (msg, res)
3379 def test_GET_URI_URL(self):
3380 base = "/uri/%s" % self._bar_txt_uri
3382 d.addCallback(self.failUnlessIsBarDotTxt)
3383 d.addCallback(lambda res: self.GET(base+"?filename=bar.txt"))
3384 d.addCallback(self.failUnlessIsBarDotTxt)
3385 d.addCallback(lambda res: self.GET(base+"?filename=bar.txt&save=true"))
3386 d.addCallback(self.failUnlessIsBarDotTxt)
3389 def test_GET_URI_URL_dir(self):
3390 base = "/uri/%s?t=json" % self._foo_uri
3392 d.addCallback(self.failUnlessIsFooJSON)
3395 def test_GET_URI_URL_missing(self):
3396 base = "/uri/%s" % self._bad_file_uri
3397 d = self.shouldHTTPError("test_GET_URI_URL_missing",
3398 http.GONE, None, "NotEnoughSharesError",
3400 # TODO: how can we exercise both sides of WebDownloadTarget.fail
3401 # here? we must arrange for a download to fail after target.open()
3402 # has been called, and then inspect the response to see that it is
3403 # shorter than we expected.
3406 def test_PUT_DIRURL_uri(self):
3407 d = self.s.create_dirnode()
3409 new_uri = dn.get_uri()
3410 # replace /foo with a new (empty) directory
3411 d = self.PUT(self.public_url + "/foo?t=uri", new_uri)
3412 d.addCallback(lambda res:
3413 self.failUnlessReallyEqual(res.strip(), new_uri))
3414 d.addCallback(lambda res:
3415 self.failUnlessRWChildURIIs(self.public_root,
3419 d.addCallback(_made_dir)
3422 def test_PUT_DIRURL_uri_noreplace(self):
3423 d = self.s.create_dirnode()
3425 new_uri = dn.get_uri()
3426 # replace /foo with a new (empty) directory, but ask that
3427 # replace=false, so it should fail
3428 d = self.shouldFail2(error.Error, "test_PUT_DIRURL_uri_noreplace",
3429 "409 Conflict", "There was already a child by that name, and you asked me to not replace it",
3431 self.public_url + "/foo?t=uri&replace=false",
3433 d.addCallback(lambda res:
3434 self.failUnlessRWChildURIIs(self.public_root,
3438 d.addCallback(_made_dir)
3441 def test_PUT_DIRURL_bad_t(self):
3442 d = self.shouldFail2(error.Error, "test_PUT_DIRURL_bad_t",
3443 "400 Bad Request", "PUT to a directory",
3444 self.PUT, self.public_url + "/foo?t=BOGUS", "")
3445 d.addCallback(lambda res:
3446 self.failUnlessRWChildURIIs(self.public_root,
3451 def test_PUT_NEWFILEURL_uri(self):
3452 contents, n, new_uri = self.makefile(8)
3453 d = self.PUT(self.public_url + "/foo/new.txt?t=uri", new_uri)
3454 d.addCallback(lambda res: self.failUnlessReallyEqual(res.strip(), new_uri))
3455 d.addCallback(lambda res:
3456 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
3460 def test_PUT_NEWFILEURL_mdmf(self):
3461 new_contents = self.NEWFILE_CONTENTS * 300000
3462 d = self.PUT(self.public_url + \
3463 "/foo/mdmf.txt?format=mdmf",
3465 d.addCallback(lambda ignored:
3466 self.GET(self.public_url + "/foo/mdmf.txt?t=json"))
3467 def _got_json(json):
3468 data = simplejson.loads(json)
3470 self.failUnlessIn("format", data)
3471 self.failUnlessEqual(data["format"], "MDMF")
3472 self.failUnless(data['rw_uri'].startswith("URI:MDMF"))
3473 self.failUnless(data['ro_uri'].startswith("URI:MDMF"))
3474 d.addCallback(_got_json)
3477 def test_PUT_NEWFILEURL_sdmf(self):
3478 new_contents = self.NEWFILE_CONTENTS * 300000
3479 d = self.PUT(self.public_url + \
3480 "/foo/sdmf.txt?format=sdmf",
3482 d.addCallback(lambda ignored:
3483 self.GET(self.public_url + "/foo/sdmf.txt?t=json"))
3484 def _got_json(json):
3485 data = simplejson.loads(json)
3487 self.failUnlessIn("format", data)
3488 self.failUnlessEqual(data["format"], "SDMF")
3489 d.addCallback(_got_json)
3492 def test_PUT_NEWFILEURL_bad_format(self):
3493 new_contents = self.NEWFILE_CONTENTS * 300000
3494 return self.shouldHTTPError("PUT_NEWFILEURL_bad_format",
3495 400, "Bad Request", "Unknown format: foo",
3496 self.PUT, self.public_url + \
3497 "/foo/foo.txt?format=foo",
3500 def test_PUT_NEWFILEURL_uri_replace(self):
3501 contents, n, new_uri = self.makefile(8)
3502 d = self.PUT(self.public_url + "/foo/bar.txt?t=uri", new_uri)
3503 d.addCallback(lambda res: self.failUnlessReallyEqual(res.strip(), new_uri))
3504 d.addCallback(lambda res:
3505 self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
3509 def test_PUT_NEWFILEURL_uri_no_replace(self):
3510 contents, n, new_uri = self.makefile(8)
3511 d = self.PUT(self.public_url + "/foo/bar.txt?t=uri&replace=false", new_uri)
3512 d.addBoth(self.shouldFail, error.Error,
3513 "PUT_NEWFILEURL_uri_no_replace",
3515 "There was already a child by that name, and you asked me "
3516 "to not replace it")
3519 def test_PUT_NEWFILEURL_uri_unknown_bad(self):
3520 d = self.PUT(self.public_url + "/foo/put-future.txt?t=uri", unknown_rwcap)
3521 d.addBoth(self.shouldFail, error.Error,
3522 "POST_put_uri_unknown_bad",
3524 "unknown cap in a write slot")
3527 def test_PUT_NEWFILEURL_uri_unknown_ro_good(self):
3528 d = self.PUT(self.public_url + "/foo/put-future-ro.txt?t=uri", unknown_rocap)
3529 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node,
3530 u"put-future-ro.txt")
3533 def test_PUT_NEWFILEURL_uri_unknown_imm_good(self):
3534 d = self.PUT(self.public_url + "/foo/put-future-imm.txt?t=uri", unknown_immcap)
3535 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node,
3536 u"put-future-imm.txt")
3539 def test_PUT_NEWFILE_URI(self):
3540 file_contents = "New file contents here\n"
3541 d = self.PUT("/uri", file_contents)
3543 assert isinstance(uri, str), uri
3544 self.failUnless(uri in FakeCHKFileNode.all_contents)
3545 self.failUnlessReallyEqual(FakeCHKFileNode.all_contents[uri],
3547 return self.GET("/uri/%s" % uri)
3548 d.addCallback(_check)
3550 self.failUnlessReallyEqual(res, file_contents)
3551 d.addCallback(_check2)
3554 def test_PUT_NEWFILE_URI_not_mutable(self):
3555 file_contents = "New file contents here\n"
3556 d = self.PUT("/uri?mutable=false", file_contents)
3558 assert isinstance(uri, str), uri
3559 self.failUnless(uri in FakeCHKFileNode.all_contents)
3560 self.failUnlessReallyEqual(FakeCHKFileNode.all_contents[uri],
3562 return self.GET("/uri/%s" % uri)
3563 d.addCallback(_check)
3565 self.failUnlessReallyEqual(res, file_contents)
3566 d.addCallback(_check2)
3569 def test_PUT_NEWFILE_URI_only_PUT(self):
3570 d = self.PUT("/uri?t=bogus", "")
3571 d.addBoth(self.shouldFail, error.Error,
3572 "PUT_NEWFILE_URI_only_PUT",
3574 "/uri accepts only PUT, PUT?t=mkdir, POST?t=upload, and POST?t=mkdir")
3577 def test_PUT_NEWFILE_URI_mutable(self):
3578 file_contents = "New file contents here\n"
3579 d = self.PUT("/uri?mutable=true", file_contents)
3580 def _check1(filecap):
3581 filecap = filecap.strip()
3582 self.failUnless(filecap.startswith("URI:SSK:"), filecap)
3583 self.filecap = filecap
3584 u = uri.WriteableSSKFileURI.init_from_string(filecap)
3585 self.failUnless(u.get_storage_index() in FakeMutableFileNode.all_contents)
3586 n = self.s.create_node_from_uri(filecap)
3587 return n.download_best_version()
3588 d.addCallback(_check1)
3590 self.failUnlessReallyEqual(data, file_contents)
3591 return self.GET("/uri/%s" % urllib.quote(self.filecap))
3592 d.addCallback(_check2)
3594 self.failUnlessReallyEqual(res, file_contents)
3595 d.addCallback(_check3)
3598 def test_PUT_mkdir(self):
3599 d = self.PUT("/uri?t=mkdir", "")
3601 n = self.s.create_node_from_uri(uri.strip())
3602 d2 = self.failUnlessNodeKeysAre(n, [])
3603 d2.addCallback(lambda res:
3604 self.GET("/uri/%s?t=json" % uri))
3606 d.addCallback(_check)
3607 d.addCallback(self.failUnlessIsEmptyJSON)
3610 def test_PUT_mkdir_mdmf(self):
3611 d = self.PUT("/uri?t=mkdir&format=mdmf", "")
3613 u = uri.from_string(res)
3614 # Check that this is an MDMF writecap
3615 self.failUnlessIsInstance(u, uri.MDMFDirectoryURI)
3619 def test_PUT_mkdir_sdmf(self):
3620 d = self.PUT("/uri?t=mkdir&format=sdmf", "")
3622 u = uri.from_string(res)
3623 self.failUnlessIsInstance(u, uri.DirectoryURI)
3627 def test_PUT_mkdir_bad_format(self):
3628 return self.shouldHTTPError("PUT_mkdir_bad_format",
3629 400, "Bad Request", "Unknown format: foo",
3630 self.PUT, "/uri?t=mkdir&format=foo",
3633 def test_POST_check(self):
3634 d = self.POST(self.public_url + "/foo", t="check", name="bar.txt")
3636 # this returns a string form of the results, which are probably
3637 # None since we're using fake filenodes.
3638 # TODO: verify that the check actually happened, by changing
3639 # FakeCHKFileNode to count how many times .check() has been
3642 d.addCallback(_done)
3646 def test_PUT_update_at_offset(self):
3647 file_contents = "test file" * 100000 # about 900 KiB
3648 d = self.PUT("/uri?mutable=true", file_contents)
3650 self.filecap = filecap
3651 new_data = file_contents[:100]
3652 new = "replaced and so on"
3654 new_data += file_contents[len(new_data):]
3655 assert len(new_data) == len(file_contents)
3656 self.new_data = new_data
3657 d.addCallback(_then)
3658 d.addCallback(lambda ignored:
3659 self.PUT("/uri/%s?replace=True&offset=100" % self.filecap,
3660 "replaced and so on"))
3661 def _get_data(filecap):
3662 n = self.s.create_node_from_uri(filecap)
3663 return n.download_best_version()
3664 d.addCallback(_get_data)
3665 d.addCallback(lambda results:
3666 self.failUnlessEqual(results, self.new_data))
3667 # Now try appending things to the file
3668 d.addCallback(lambda ignored:
3669 self.PUT("/uri/%s?offset=%d" % (self.filecap, len(self.new_data)),
3671 d.addCallback(_get_data)
3672 d.addCallback(lambda results:
3673 self.failUnlessEqual(results, self.new_data + ("puppies" * 100)))
3674 # and try replacing the beginning of the file
3675 d.addCallback(lambda ignored:
3676 self.PUT("/uri/%s?offset=0" % self.filecap, "begin"))
3677 d.addCallback(_get_data)
3678 d.addCallback(lambda results:
3679 self.failUnlessEqual(results, "begin"+self.new_data[len("begin"):]+("puppies"*100)))
3682 def test_PUT_update_at_invalid_offset(self):
3683 file_contents = "test file" * 100000 # about 900 KiB
3684 d = self.PUT("/uri?mutable=true", file_contents)
3686 self.filecap = filecap
3687 d.addCallback(_then)
3688 # Negative offsets should cause an error.
3689 d.addCallback(lambda ignored:
3690 self.shouldHTTPError("PUT_update_at_invalid_offset",
3694 "/uri/%s?offset=-1" % self.filecap,
3698 def test_PUT_update_at_offset_immutable(self):
3699 file_contents = "Test file" * 100000
3700 d = self.PUT("/uri", file_contents)
3702 self.filecap = filecap
3703 d.addCallback(_then)
3704 d.addCallback(lambda ignored:
3705 self.shouldHTTPError("PUT_update_at_offset_immutable",
3709 "/uri/%s?offset=50" % self.filecap,
3714 def test_bad_method(self):
3715 url = self.webish_url + self.public_url + "/foo/bar.txt"
3716 d = self.shouldHTTPError("bad_method",
3717 501, "Not Implemented",
3718 "I don't know how to treat a BOGUS request.",
3719 client.getPage, url, method="BOGUS")
3722 def test_short_url(self):
3723 url = self.webish_url + "/uri"
3724 d = self.shouldHTTPError("short_url", 501, "Not Implemented",
3725 "I don't know how to treat a DELETE request.",
3726 client.getPage, url, method="DELETE")
3729 def test_ophandle_bad(self):
3730 url = self.webish_url + "/operations/bogus?t=status"
3731 d = self.shouldHTTPError("ophandle_bad", 404, "404 Not Found",
3732 "unknown/expired handle 'bogus'",
3733 client.getPage, url)
3736 def test_ophandle_cancel(self):
3737 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=128",
3738 followRedirect=True)
3739 d.addCallback(lambda ignored:
3740 self.GET("/operations/128?t=status&output=JSON"))
3742 data = simplejson.loads(res)
3743 self.failUnless("finished" in data, res)
3744 monitor = self.ws.root.child_operations.handles["128"][0]
3745 d = self.POST("/operations/128?t=cancel&output=JSON")
3747 data = simplejson.loads(res)
3748 self.failUnless("finished" in data, res)
3749 # t=cancel causes the handle to be forgotten
3750 self.failUnless(monitor.is_cancelled())
3751 d.addCallback(_check2)
3753 d.addCallback(_check1)
3754 d.addCallback(lambda ignored:
3755 self.shouldHTTPError("ophandle_cancel",
3756 404, "404 Not Found",
3757 "unknown/expired handle '128'",
3759 "/operations/128?t=status&output=JSON"))
3762 def test_ophandle_retainfor(self):
3763 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=129&retain-for=60",
3764 followRedirect=True)
3765 d.addCallback(lambda ignored:
3766 self.GET("/operations/129?t=status&output=JSON&retain-for=0"))
3768 data = simplejson.loads(res)
3769 self.failUnless("finished" in data, res)
3770 d.addCallback(_check1)
3771 # the retain-for=0 will cause the handle to be expired very soon
3772 d.addCallback(lambda ign:
3773 self.clock.advance(2.0))
3774 d.addCallback(lambda ignored:
3775 self.shouldHTTPError("ophandle_retainfor",
3776 404, "404 Not Found",
3777 "unknown/expired handle '129'",
3779 "/operations/129?t=status&output=JSON"))
3782 def test_ophandle_release_after_complete(self):
3783 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=130",
3784 followRedirect=True)
3785 d.addCallback(self.wait_for_operation, "130")
3786 d.addCallback(lambda ignored:
3787 self.GET("/operations/130?t=status&output=JSON&release-after-complete=true"))
3788 # the release-after-complete=true will cause the handle to be expired
3789 d.addCallback(lambda ignored:
3790 self.shouldHTTPError("ophandle_release_after_complete",
3791 404, "404 Not Found",
3792 "unknown/expired handle '130'",
3794 "/operations/130?t=status&output=JSON"))
3797 def test_uncollected_ophandle_expiration(self):
3798 # uncollected ophandles should expire after 4 days
3799 def _make_uncollected_ophandle(ophandle):
3800 d = self.POST(self.public_url +
3801 "/foo/?t=start-manifest&ophandle=%d" % ophandle,
3802 followRedirect=False)
3803 # When we start the operation, the webapi server will want
3804 # to redirect us to the page for the ophandle, so we get
3805 # confirmation that the operation has started. If the
3806 # manifest operation has finished by the time we get there,
3807 # following that redirect (by setting followRedirect=True
3808 # above) has the side effect of collecting the ophandle that
3809 # we've just created, which means that we can't use the
3810 # ophandle to test the uncollected timeout anymore. So,
3811 # instead, catch the 302 here and don't follow it.
3812 d.addBoth(self.should302, "uncollected_ophandle_creation")
3814 # Create an ophandle, don't collect it, then advance the clock by
3815 # 4 days - 1 second and make sure that the ophandle is still there.
3816 d = _make_uncollected_ophandle(131)
3817 d.addCallback(lambda ign:
3818 self.clock.advance((96*60*60) - 1)) # 96 hours = 4 days
3819 d.addCallback(lambda ign:
3820 self.GET("/operations/131?t=status&output=JSON"))
3822 data = simplejson.loads(res)
3823 self.failUnless("finished" in data, res)
3824 d.addCallback(_check1)
3825 # Create an ophandle, don't collect it, then try to collect it
3826 # after 4 days. It should be gone.
3827 d.addCallback(lambda ign:
3828 _make_uncollected_ophandle(132))
3829 d.addCallback(lambda ign:
3830 self.clock.advance(96*60*60))
3831 d.addCallback(lambda ign:
3832 self.shouldHTTPError("uncollected_ophandle_expired_after_100_hours",
3833 404, "404 Not Found",
3834 "unknown/expired handle '132'",
3836 "/operations/132?t=status&output=JSON"))
3839 def test_collected_ophandle_expiration(self):
3840 # collected ophandles should expire after 1 day
3841 def _make_collected_ophandle(ophandle):
3842 d = self.POST(self.public_url +
3843 "/foo/?t=start-manifest&ophandle=%d" % ophandle,
3844 followRedirect=True)
3845 # By following the initial redirect, we collect the ophandle
3846 # we've just created.
3848 # Create a collected ophandle, then collect it after 23 hours
3849 # and 59 seconds to make sure that it is still there.
3850 d = _make_collected_ophandle(133)
3851 d.addCallback(lambda ign:
3852 self.clock.advance((24*60*60) - 1))
3853 d.addCallback(lambda ign:
3854 self.GET("/operations/133?t=status&output=JSON"))
3856 data = simplejson.loads(res)
3857 self.failUnless("finished" in data, res)
3858 d.addCallback(_check1)
3859 # Create another uncollected ophandle, then try to collect it
3860 # after 24 hours to make sure that it is gone.
3861 d.addCallback(lambda ign:
3862 _make_collected_ophandle(134))
3863 d.addCallback(lambda ign:
3864 self.clock.advance(24*60*60))
3865 d.addCallback(lambda ign:
3866 self.shouldHTTPError("collected_ophandle_expired_after_1_day",
3867 404, "404 Not Found",
3868 "unknown/expired handle '134'",
3870 "/operations/134?t=status&output=JSON"))
3873 def test_incident(self):
3874 d = self.POST("/report_incident", details="eek")
3876 self.failUnless("Thank you for your report!" in res, res)
3877 d.addCallback(_done)
3880 def test_static(self):
3881 webdir = os.path.join(self.staticdir, "subdir")
3882 fileutil.make_dirs(webdir)
3883 f = open(os.path.join(webdir, "hello.txt"), "wb")
3887 d = self.GET("/static/subdir/hello.txt")
3889 self.failUnlessReallyEqual(res, "hello")
3890 d.addCallback(_check)
3894 class Util(ShouldFailMixin, testutil.ReallyEqualMixin, unittest.TestCase):
3895 def test_load_file(self):
3896 # This will raise an exception unless a well-formed XML file is found under that name.
3897 common.getxmlfile('directory.xhtml').load()
3899 def test_parse_replace_arg(self):
3900 self.failUnlessReallyEqual(common.parse_replace_arg("true"), True)
3901 self.failUnlessReallyEqual(common.parse_replace_arg("false"), False)
3902 self.failUnlessReallyEqual(common.parse_replace_arg("only-files"),
3904 self.shouldFail(AssertionError, "test_parse_replace_arg", "",
3905 common.parse_replace_arg, "only_fles")
3907 def test_abbreviate_time(self):
3908 self.failUnlessReallyEqual(common.abbreviate_time(None), "")
3909 self.failUnlessReallyEqual(common.abbreviate_time(1.234), "1.23s")
3910 self.failUnlessReallyEqual(common.abbreviate_time(0.123), "123ms")
3911 self.failUnlessReallyEqual(common.abbreviate_time(0.00123), "1.2ms")
3912 self.failUnlessReallyEqual(common.abbreviate_time(0.000123), "123us")
3913 self.failUnlessReallyEqual(common.abbreviate_time(-123000), "-123000000000us")
3915 def test_compute_rate(self):
3916 self.failUnlessReallyEqual(common.compute_rate(None, None), None)
3917 self.failUnlessReallyEqual(common.compute_rate(None, 1), None)
3918 self.failUnlessReallyEqual(common.compute_rate(250000, None), None)
3919 self.failUnlessReallyEqual(common.compute_rate(250000, 0), None)
3920 self.failUnlessReallyEqual(common.compute_rate(250000, 10), 25000.0)
3921 self.failUnlessReallyEqual(common.compute_rate(0, 10), 0.0)
3922 self.shouldFail(AssertionError, "test_compute_rate", "",
3923 common.compute_rate, -100, 10)
3924 self.shouldFail(AssertionError, "test_compute_rate", "",
3925 common.compute_rate, 100, -10)
3928 rate = common.compute_rate(10*1000*1000, 1)
3929 self.failUnlessReallyEqual(common.abbreviate_rate(rate), "10.00MBps")
3931 def test_abbreviate_rate(self):
3932 self.failUnlessReallyEqual(common.abbreviate_rate(None), "")
3933 self.failUnlessReallyEqual(common.abbreviate_rate(1234000), "1.23MBps")
3934 self.failUnlessReallyEqual(common.abbreviate_rate(12340), "12.3kBps")
3935 self.failUnlessReallyEqual(common.abbreviate_rate(123), "123Bps")
3937 def test_abbreviate_size(self):
3938 self.failUnlessReallyEqual(common.abbreviate_size(None), "")
3939 self.failUnlessReallyEqual(common.abbreviate_size(1.23*1000*1000*1000), "1.23GB")
3940 self.failUnlessReallyEqual(common.abbreviate_size(1.23*1000*1000), "1.23MB")
3941 self.failUnlessReallyEqual(common.abbreviate_size(1230), "1.2kB")
3942 self.failUnlessReallyEqual(common.abbreviate_size(123), "123B")
3944 def test_plural(self):
3946 return "%d second%s" % (s, status.plural(s))
3947 self.failUnlessReallyEqual(convert(0), "0 seconds")
3948 self.failUnlessReallyEqual(convert(1), "1 second")
3949 self.failUnlessReallyEqual(convert(2), "2 seconds")
3951 return "has share%s: %s" % (status.plural(s), ",".join(s))
3952 self.failUnlessReallyEqual(convert2([]), "has shares: ")
3953 self.failUnlessReallyEqual(convert2(["1"]), "has share: 1")
3954 self.failUnlessReallyEqual(convert2(["1","2"]), "has shares: 1,2")
3957 class Grid(GridTestMixin, WebErrorMixin, ShouldFailMixin, testutil.ReallyEqualMixin, unittest.TestCase):
3959 def CHECK(self, ign, which, args, clientnum=0):
3960 fileurl = self.fileurls[which]
3961 url = fileurl + "?" + args
3962 return self.GET(url, method="POST", clientnum=clientnum)
3964 def test_filecheck(self):
3965 self.basedir = "web/Grid/filecheck"
3967 c0 = self.g.clients[0]
3970 d = c0.upload(upload.Data(DATA, convergence=""))
3971 def _stash_uri(ur, which):
3972 self.uris[which] = ur.uri
3973 d.addCallback(_stash_uri, "good")
3974 d.addCallback(lambda ign:
3975 c0.upload(upload.Data(DATA+"1", convergence="")))
3976 d.addCallback(_stash_uri, "sick")
3977 d.addCallback(lambda ign:
3978 c0.upload(upload.Data(DATA+"2", convergence="")))
3979 d.addCallback(_stash_uri, "dead")
3980 def _stash_mutable_uri(n, which):
3981 self.uris[which] = n.get_uri()
3982 assert isinstance(self.uris[which], str)
3983 d.addCallback(lambda ign:
3984 c0.create_mutable_file(publish.MutableData(DATA+"3")))
3985 d.addCallback(_stash_mutable_uri, "corrupt")
3986 d.addCallback(lambda ign:
3987 c0.upload(upload.Data("literal", convergence="")))
3988 d.addCallback(_stash_uri, "small")
3989 d.addCallback(lambda ign: c0.create_immutable_dirnode({}))
3990 d.addCallback(_stash_mutable_uri, "smalldir")
3992 def _compute_fileurls(ignored):
3994 for which in self.uris:
3995 self.fileurls[which] = "uri/" + urllib.quote(self.uris[which])
3996 d.addCallback(_compute_fileurls)
3998 def _clobber_shares(ignored):
3999 good_shares = self.find_uri_shares(self.uris["good"])
4000 self.failUnlessReallyEqual(len(good_shares), 10)
4001 sick_shares = self.find_uri_shares(self.uris["sick"])
4002 os.unlink(sick_shares[0][2])
4003 dead_shares = self.find_uri_shares(self.uris["dead"])
4004 for i in range(1, 10):
4005 os.unlink(dead_shares[i][2])
4006 c_shares = self.find_uri_shares(self.uris["corrupt"])
4007 cso = CorruptShareOptions()
4008 cso.stdout = StringIO()
4009 cso.parseOptions([c_shares[0][2]])
4011 d.addCallback(_clobber_shares)
4013 d.addCallback(self.CHECK, "good", "t=check")
4014 def _got_html_good(res):
4015 self.failUnless("Healthy" in res, res)
4016 self.failIf("Not Healthy" in res, res)
4017 d.addCallback(_got_html_good)
4018 d.addCallback(self.CHECK, "good", "t=check&return_to=somewhere")
4019 def _got_html_good_return_to(res):
4020 self.failUnless("Healthy" in res, res)
4021 self.failIf("Not Healthy" in res, res)
4022 self.failUnless('<a href="somewhere">Return to file'
4024 d.addCallback(_got_html_good_return_to)
4025 d.addCallback(self.CHECK, "good", "t=check&output=json")
4026 def _got_json_good(res):
4027 r = simplejson.loads(res)
4028 self.failUnlessEqual(r["summary"], "Healthy")
4029 self.failUnless(r["results"]["healthy"])
4030 self.failIf(r["results"]["needs-rebalancing"])
4031 self.failUnless(r["results"]["recoverable"])
4032 d.addCallback(_got_json_good)
4034 d.addCallback(self.CHECK, "small", "t=check")
4035 def _got_html_small(res):
4036 self.failUnless("Literal files are always healthy" in res, res)
4037 self.failIf("Not Healthy" in res, res)
4038 d.addCallback(_got_html_small)
4039 d.addCallback(self.CHECK, "small", "t=check&return_to=somewhere")
4040 def _got_html_small_return_to(res):
4041 self.failUnless("Literal files are always healthy" in res, res)
4042 self.failIf("Not Healthy" in res, res)
4043 self.failUnless('<a href="somewhere">Return to file'
4045 d.addCallback(_got_html_small_return_to)
4046 d.addCallback(self.CHECK, "small", "t=check&output=json")
4047 def _got_json_small(res):
4048 r = simplejson.loads(res)
4049 self.failUnlessEqual(r["storage-index"], "")
4050 self.failUnless(r["results"]["healthy"])
4051 d.addCallback(_got_json_small)
4053 d.addCallback(self.CHECK, "smalldir", "t=check")
4054 def _got_html_smalldir(res):
4055 self.failUnless("Literal files are always healthy" in res, res)
4056 self.failIf("Not Healthy" in res, res)
4057 d.addCallback(_got_html_smalldir)
4058 d.addCallback(self.CHECK, "smalldir", "t=check&output=json")
4059 def _got_json_smalldir(res):
4060 r = simplejson.loads(res)
4061 self.failUnlessEqual(r["storage-index"], "")
4062 self.failUnless(r["results"]["healthy"])
4063 d.addCallback(_got_json_smalldir)
4065 d.addCallback(self.CHECK, "sick", "t=check")
4066 def _got_html_sick(res):
4067 self.failUnless("Not Healthy" in res, res)
4068 d.addCallback(_got_html_sick)
4069 d.addCallback(self.CHECK, "sick", "t=check&output=json")
4070 def _got_json_sick(res):
4071 r = simplejson.loads(res)
4072 self.failUnlessEqual(r["summary"],
4073 "Not Healthy: 9 shares (enc 3-of-10)")
4074 self.failIf(r["results"]["healthy"])
4075 self.failIf(r["results"]["needs-rebalancing"])
4076 self.failUnless(r["results"]["recoverable"])
4077 d.addCallback(_got_json_sick)
4079 d.addCallback(self.CHECK, "dead", "t=check")
4080 def _got_html_dead(res):
4081 self.failUnless("Not Healthy" in res, res)
4082 d.addCallback(_got_html_dead)
4083 d.addCallback(self.CHECK, "dead", "t=check&output=json")
4084 def _got_json_dead(res):
4085 r = simplejson.loads(res)
4086 self.failUnlessEqual(r["summary"],
4087 "Not Healthy: 1 shares (enc 3-of-10)")
4088 self.failIf(r["results"]["healthy"])
4089 self.failIf(r["results"]["needs-rebalancing"])
4090 self.failIf(r["results"]["recoverable"])
4091 d.addCallback(_got_json_dead)
4093 d.addCallback(self.CHECK, "corrupt", "t=check&verify=true")
4094 def _got_html_corrupt(res):
4095 self.failUnless("Not Healthy! : Unhealthy" in res, res)
4096 d.addCallback(_got_html_corrupt)
4097 d.addCallback(self.CHECK, "corrupt", "t=check&verify=true&output=json")
4098 def _got_json_corrupt(res):
4099 r = simplejson.loads(res)
4100 self.failUnless("Unhealthy: 9 shares (enc 3-of-10)" in r["summary"],
4102 self.failIf(r["results"]["healthy"])
4103 self.failUnless(r["results"]["recoverable"])
4104 self.failUnlessReallyEqual(r["results"]["count-shares-good"], 9)
4105 self.failUnlessReallyEqual(r["results"]["count-corrupt-shares"], 1)
4106 d.addCallback(_got_json_corrupt)
4108 d.addErrback(self.explain_web_error)
4111 def test_repair_html(self):
4112 self.basedir = "web/Grid/repair_html"
4114 c0 = self.g.clients[0]
4117 d = c0.upload(upload.Data(DATA, convergence=""))
4118 def _stash_uri(ur, which):
4119 self.uris[which] = ur.uri
4120 d.addCallback(_stash_uri, "good")
4121 d.addCallback(lambda ign:
4122 c0.upload(upload.Data(DATA+"1", convergence="")))
4123 d.addCallback(_stash_uri, "sick")
4124 d.addCallback(lambda ign:
4125 c0.upload(upload.Data(DATA+"2", convergence="")))
4126 d.addCallback(_stash_uri, "dead")
4127 def _stash_mutable_uri(n, which):
4128 self.uris[which] = n.get_uri()
4129 assert isinstance(self.uris[which], str)
4130 d.addCallback(lambda ign:
4131 c0.create_mutable_file(publish.MutableData(DATA+"3")))
4132 d.addCallback(_stash_mutable_uri, "corrupt")
4134 def _compute_fileurls(ignored):
4136 for which in self.uris:
4137 self.fileurls[which] = "uri/" + urllib.quote(self.uris[which])
4138 d.addCallback(_compute_fileurls)
4140 def _clobber_shares(ignored):
4141 good_shares = self.find_uri_shares(self.uris["good"])
4142 self.failUnlessReallyEqual(len(good_shares), 10)
4143 sick_shares = self.find_uri_shares(self.uris["sick"])
4144 os.unlink(sick_shares[0][2])
4145 dead_shares = self.find_uri_shares(self.uris["dead"])
4146 for i in range(1, 10):
4147 os.unlink(dead_shares[i][2])
4148 c_shares = self.find_uri_shares(self.uris["corrupt"])
4149 cso = CorruptShareOptions()
4150 cso.stdout = StringIO()
4151 cso.parseOptions([c_shares[0][2]])
4153 d.addCallback(_clobber_shares)
4155 d.addCallback(self.CHECK, "good", "t=check&repair=true")
4156 def _got_html_good(res):
4157 self.failUnless("Healthy" in res, res)
4158 self.failIf("Not Healthy" in res, res)
4159 self.failUnless("No repair necessary" in res, res)
4160 d.addCallback(_got_html_good)
4162 d.addCallback(self.CHECK, "sick", "t=check&repair=true")
4163 def _got_html_sick(res):
4164 self.failUnless("Healthy : healthy" in res, res)
4165 self.failIf("Not Healthy" in res, res)
4166 self.failUnless("Repair successful" in res, res)
4167 d.addCallback(_got_html_sick)
4169 # repair of a dead file will fail, of course, but it isn't yet
4170 # clear how this should be reported. Right now it shows up as
4173 #d.addCallback(self.CHECK, "dead", "t=check&repair=true")
4174 #def _got_html_dead(res):
4176 # self.failUnless("Healthy : healthy" in res, res)
4177 # self.failIf("Not Healthy" in res, res)
4178 # self.failUnless("No repair necessary" in res, res)
4179 #d.addCallback(_got_html_dead)
4181 d.addCallback(self.CHECK, "corrupt", "t=check&verify=true&repair=true")
4182 def _got_html_corrupt(res):
4183 self.failUnless("Healthy : Healthy" in res, res)
4184 self.failIf("Not Healthy" in res, res)
4185 self.failUnless("Repair successful" in res, res)
4186 d.addCallback(_got_html_corrupt)
4188 d.addErrback(self.explain_web_error)
4191 def test_repair_json(self):
4192 self.basedir = "web/Grid/repair_json"
4194 c0 = self.g.clients[0]
4197 d = c0.upload(upload.Data(DATA+"1", convergence=""))
4198 def _stash_uri(ur, which):
4199 self.uris[which] = ur.uri
4200 d.addCallback(_stash_uri, "sick")
4202 def _compute_fileurls(ignored):
4204 for which in self.uris:
4205 self.fileurls[which] = "uri/" + urllib.quote(self.uris[which])
4206 d.addCallback(_compute_fileurls)
4208 def _clobber_shares(ignored):
4209 sick_shares = self.find_uri_shares(self.uris["sick"])
4210 os.unlink(sick_shares[0][2])
4211 d.addCallback(_clobber_shares)
4213 d.addCallback(self.CHECK, "sick", "t=check&repair=true&output=json")
4214 def _got_json_sick(res):
4215 r = simplejson.loads(res)
4216 self.failUnlessReallyEqual(r["repair-attempted"], True)
4217 self.failUnlessReallyEqual(r["repair-successful"], True)
4218 self.failUnlessEqual(r["pre-repair-results"]["summary"],
4219 "Not Healthy: 9 shares (enc 3-of-10)")
4220 self.failIf(r["pre-repair-results"]["results"]["healthy"])
4221 self.failUnlessEqual(r["post-repair-results"]["summary"], "healthy")
4222 self.failUnless(r["post-repair-results"]["results"]["healthy"])
4223 d.addCallback(_got_json_sick)
4225 d.addErrback(self.explain_web_error)
4228 def test_unknown(self, immutable=False):
4229 self.basedir = "web/Grid/unknown"
4231 self.basedir = "web/Grid/unknown-immutable"
4234 c0 = self.g.clients[0]
4238 # the future cap format may contain slashes, which must be tolerated
4239 expected_info_url = "uri/%s?t=info" % urllib.quote(unknown_rwcap,
4243 name = u"future-imm"
4244 future_node = UnknownNode(None, unknown_immcap, deep_immutable=True)
4245 d = c0.create_immutable_dirnode({name: (future_node, {})})
4248 future_node = UnknownNode(unknown_rwcap, unknown_rocap)
4249 d = c0.create_dirnode()
4251 def _stash_root_and_create_file(n):
4253 self.rooturl = "uri/" + urllib.quote(n.get_uri()) + "/"
4254 self.rourl = "uri/" + urllib.quote(n.get_readonly_uri()) + "/"
4256 return self.rootnode.set_node(name, future_node)
4257 d.addCallback(_stash_root_and_create_file)
4259 # make sure directory listing tolerates unknown nodes
4260 d.addCallback(lambda ign: self.GET(self.rooturl))
4261 def _check_directory_html(res, expected_type_suffix):
4262 pattern = re.compile(r'<td>\?%s</td>[ \t\n\r]*'
4263 '<td>%s</td>' % (expected_type_suffix, str(name)),
4265 self.failUnless(re.search(pattern, res), res)
4266 # find the More Info link for name, should be relative
4267 mo = re.search(r'<a href="([^"]+)">More Info</a>', res)
4268 info_url = mo.group(1)
4269 self.failUnlessReallyEqual(info_url, "%s?t=info" % (str(name),))
4271 d.addCallback(_check_directory_html, "-IMM")
4273 d.addCallback(_check_directory_html, "")
4275 d.addCallback(lambda ign: self.GET(self.rooturl+"?t=json"))
4276 def _check_directory_json(res, expect_rw_uri):
4277 data = simplejson.loads(res)
4278 self.failUnlessEqual(data[0], "dirnode")
4279 f = data[1]["children"][name]
4280 self.failUnlessEqual(f[0], "unknown")
4282 self.failUnlessReallyEqual(to_str(f[1]["rw_uri"]), unknown_rwcap, data)
4284 self.failIfIn("rw_uri", f[1])
4286 self.failUnlessReallyEqual(to_str(f[1]["ro_uri"]), unknown_immcap, data)
4288 self.failUnlessReallyEqual(to_str(f[1]["ro_uri"]), unknown_rocap, data)
4289 self.failUnless("metadata" in f[1])
4290 d.addCallback(_check_directory_json, expect_rw_uri=not immutable)
4292 def _check_info(res, expect_rw_uri, expect_ro_uri):
4293 self.failUnlessIn("Object Type: <span>unknown</span>", res)
4295 self.failUnlessIn(unknown_rwcap, res)
4298 self.failUnlessIn(unknown_immcap, res)
4300 self.failUnlessIn(unknown_rocap, res)
4302 self.failIfIn(unknown_rocap, res)
4303 self.failIfIn("Raw data as", res)
4304 self.failIfIn("Directory writecap", res)
4305 self.failIfIn("Checker Operations", res)
4306 self.failIfIn("Mutable File Operations", res)
4307 self.failIfIn("Directory Operations", res)
4309 # FIXME: these should have expect_rw_uri=not immutable; I don't know
4310 # why they fail. Possibly related to ticket #922.
4312 d.addCallback(lambda ign: self.GET(expected_info_url))
4313 d.addCallback(_check_info, expect_rw_uri=False, expect_ro_uri=False)
4314 d.addCallback(lambda ign: self.GET("%s%s?t=info" % (self.rooturl, str(name))))
4315 d.addCallback(_check_info, expect_rw_uri=False, expect_ro_uri=True)
4317 def _check_json(res, expect_rw_uri):
4318 data = simplejson.loads(res)
4319 self.failUnlessEqual(data[0], "unknown")
4321 self.failUnlessReallyEqual(to_str(data[1]["rw_uri"]), unknown_rwcap, data)
4323 self.failIfIn("rw_uri", data[1])
4326 self.failUnlessReallyEqual(to_str(data[1]["ro_uri"]), unknown_immcap, data)
4327 self.failUnlessReallyEqual(data[1]["mutable"], False)
4329 self.failUnlessReallyEqual(to_str(data[1]["ro_uri"]), unknown_rocap, data)
4330 self.failUnlessReallyEqual(data[1]["mutable"], True)
4332 self.failUnlessReallyEqual(to_str(data[1]["ro_uri"]), unknown_rocap, data)
4333 self.failIf("mutable" in data[1], data[1])
4335 # TODO: check metadata contents
4336 self.failUnless("metadata" in data[1])
4338 d.addCallback(lambda ign: self.GET("%s%s?t=json" % (self.rooturl, str(name))))
4339 d.addCallback(_check_json, expect_rw_uri=not immutable)
4341 # and make sure that a read-only version of the directory can be
4342 # rendered too. This version will not have unknown_rwcap, whether
4343 # or not future_node was immutable.
4344 d.addCallback(lambda ign: self.GET(self.rourl))
4346 d.addCallback(_check_directory_html, "-IMM")
4348 d.addCallback(_check_directory_html, "-RO")
4350 d.addCallback(lambda ign: self.GET(self.rourl+"?t=json"))
4351 d.addCallback(_check_directory_json, expect_rw_uri=False)
4353 d.addCallback(lambda ign: self.GET("%s%s?t=json" % (self.rourl, str(name))))
4354 d.addCallback(_check_json, expect_rw_uri=False)
4356 # TODO: check that getting t=info from the Info link in the ro directory
4357 # works, and does not include the writecap URI.
4360 def test_immutable_unknown(self):
4361 return self.test_unknown(immutable=True)
4363 def test_mutant_dirnodes_are_omitted(self):
4364 self.basedir = "web/Grid/mutant_dirnodes_are_omitted"
4367 c = self.g.clients[0]
4372 lonely_uri = "URI:LIT:n5xgk" # LIT for "one"
4373 mut_write_uri = "URI:SSK:vfvcbdfbszyrsaxchgevhmmlii:euw4iw7bbnkrrwpzuburbhppuxhc3gwxv26f6imekhz7zyw2ojnq"
4374 mut_read_uri = "URI:SSK-RO:e3mdrzfwhoq42hy5ubcz6rp3o4:ybyibhnp3vvwuq2vaw2ckjmesgkklfs6ghxleztqidihjyofgw7q"
4376 # This method tests mainly dirnode, but we'd have to duplicate code in order to
4377 # test the dirnode and web layers separately.
4379 # 'lonely' is a valid LIT child, 'ro' is a mutant child with an SSK-RO readcap,
4380 # and 'write-in-ro' is a mutant child with an SSK writecap in the ro_uri field.
4381 # When the directory is read, the mutants should be silently disposed of, leaving
4382 # their lonely sibling.
4383 # We don't test the case of a retrieving a cap from the encrypted rw_uri field,
4384 # because immutable directories don't have a writecap and therefore that field
4385 # isn't (and can't be) decrypted.
4386 # TODO: The field still exists in the netstring. Technically we should check what
4387 # happens if something is put there (_unpack_contents should raise ValueError),
4388 # but that can wait.
4390 lonely_child = nm.create_from_cap(lonely_uri)
4391 mutant_ro_child = nm.create_from_cap(mut_read_uri)
4392 mutant_write_in_ro_child = nm.create_from_cap(mut_write_uri)
4394 def _by_hook_or_by_crook():
4396 for n in [mutant_ro_child, mutant_write_in_ro_child]:
4397 n.is_allowed_in_immutable_directory = _by_hook_or_by_crook
4399 mutant_write_in_ro_child.get_write_uri = lambda: None
4400 mutant_write_in_ro_child.get_readonly_uri = lambda: mut_write_uri
4402 kids = {u"lonely": (lonely_child, {}),
4403 u"ro": (mutant_ro_child, {}),
4404 u"write-in-ro": (mutant_write_in_ro_child, {}),
4406 d = c.create_immutable_dirnode(kids)
4409 self.failUnless(isinstance(dn, dirnode.DirectoryNode))
4410 self.failIf(dn.is_mutable())
4411 self.failUnless(dn.is_readonly())
4412 # This checks that if we somehow ended up calling dn._decrypt_rwcapdata, it would fail.
4413 self.failIf(hasattr(dn._node, 'get_writekey'))
4415 self.failUnless("RO-IMM" in rep)
4417 self.failUnlessIn("CHK", cap.to_string())
4420 self.rooturl = "uri/" + urllib.quote(dn.get_uri()) + "/"
4421 return download_to_data(dn._node)
4422 d.addCallback(_created)
4424 def _check_data(data):
4425 # Decode the netstring representation of the directory to check that all children
4426 # are present. This is a bit of an abstraction violation, but there's not really
4427 # any other way to do it given that the real DirectoryNode._unpack_contents would
4428 # strip the mutant children out (which is what we're trying to test, later).
4431 while position < len(data):
4432 entries, position = split_netstring(data, 1, position)
4434 (name_utf8, ro_uri, rwcapdata, metadata_s), subpos = split_netstring(entry, 4)
4435 name = name_utf8.decode("utf-8")
4436 self.failUnless(rwcapdata == "")
4437 self.failUnless(name in kids)
4438 (expected_child, ign) = kids[name]
4439 self.failUnlessReallyEqual(ro_uri, expected_child.get_readonly_uri())
4442 self.failUnlessReallyEqual(numkids, 3)
4443 return self.rootnode.list()
4444 d.addCallback(_check_data)
4446 # Now when we use the real directory listing code, the mutants should be absent.
4447 def _check_kids(children):
4448 self.failUnlessReallyEqual(sorted(children.keys()), [u"lonely"])
4449 lonely_node, lonely_metadata = children[u"lonely"]
4451 self.failUnlessReallyEqual(lonely_node.get_write_uri(), None)
4452 self.failUnlessReallyEqual(lonely_node.get_readonly_uri(), lonely_uri)
4453 d.addCallback(_check_kids)
4455 d.addCallback(lambda ign: nm.create_from_cap(self.cap.to_string()))
4456 d.addCallback(lambda n: n.list())
4457 d.addCallback(_check_kids) # again with dirnode recreated from cap
4459 # Make sure the lonely child can be listed in HTML...
4460 d.addCallback(lambda ign: self.GET(self.rooturl))
4461 def _check_html(res):
4462 self.failIfIn("URI:SSK", res)
4463 get_lonely = "".join([r'<td>FILE</td>',
4465 r'<a href="[^"]+%s[^"]+">lonely</a>' % (urllib.quote(lonely_uri),),
4467 r'\s+<td align="right">%d</td>' % len("one"),
4469 self.failUnless(re.search(get_lonely, res), res)
4471 # find the More Info link for name, should be relative
4472 mo = re.search(r'<a href="([^"]+)">More Info</a>', res)
4473 info_url = mo.group(1)
4474 self.failUnless(info_url.endswith(urllib.quote(lonely_uri) + "?t=info"), info_url)
4475 d.addCallback(_check_html)
4478 d.addCallback(lambda ign: self.GET(self.rooturl+"?t=json"))
4479 def _check_json(res):
4480 data = simplejson.loads(res)
4481 self.failUnlessEqual(data[0], "dirnode")
4482 listed_children = data[1]["children"]
4483 self.failUnlessReallyEqual(sorted(listed_children.keys()), [u"lonely"])
4484 ll_type, ll_data = listed_children[u"lonely"]
4485 self.failUnlessEqual(ll_type, "filenode")
4486 self.failIf("rw_uri" in ll_data)
4487 self.failUnlessReallyEqual(to_str(ll_data["ro_uri"]), lonely_uri)
4488 d.addCallback(_check_json)
4491 def test_deep_check(self):
4492 self.basedir = "web/Grid/deep_check"
4494 c0 = self.g.clients[0]
4498 d = c0.create_dirnode()
4499 def _stash_root_and_create_file(n):
4501 self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri()) + "/"
4502 return n.add_file(u"good", upload.Data(DATA, convergence=""))
4503 d.addCallback(_stash_root_and_create_file)
4504 def _stash_uri(fn, which):
4505 self.uris[which] = fn.get_uri()
4507 d.addCallback(_stash_uri, "good")
4508 d.addCallback(lambda ign:
4509 self.rootnode.add_file(u"small",
4510 upload.Data("literal",
4512 d.addCallback(_stash_uri, "small")
4513 d.addCallback(lambda ign:
4514 self.rootnode.add_file(u"sick",
4515 upload.Data(DATA+"1",
4517 d.addCallback(_stash_uri, "sick")
4519 # this tests that deep-check and stream-manifest will ignore
4520 # UnknownNode instances. Hopefully this will also cover deep-stats.
4521 future_node = UnknownNode(unknown_rwcap, unknown_rocap)
4522 d.addCallback(lambda ign: self.rootnode.set_node(u"future", future_node))
4524 def _clobber_shares(ignored):
4525 self.delete_shares_numbered(self.uris["sick"], [0,1])
4526 d.addCallback(_clobber_shares)
4534 d.addCallback(self.CHECK, "root", "t=stream-deep-check")
4537 units = [simplejson.loads(line)
4538 for line in res.splitlines()
4541 print "response is:", res
4542 print "undecodeable line was '%s'" % line
4544 self.failUnlessReallyEqual(len(units), 5+1)
4545 # should be parent-first
4547 self.failUnlessEqual(u0["path"], [])
4548 self.failUnlessEqual(u0["type"], "directory")
4549 self.failUnlessReallyEqual(to_str(u0["cap"]), self.rootnode.get_uri())
4550 u0cr = u0["check-results"]
4551 self.failUnlessReallyEqual(u0cr["results"]["count-shares-good"], 10)
4553 ugood = [u for u in units
4554 if u["type"] == "file" and u["path"] == [u"good"]][0]
4555 self.failUnlessReallyEqual(to_str(ugood["cap"]), self.uris["good"])
4556 ugoodcr = ugood["check-results"]
4557 self.failUnlessReallyEqual(ugoodcr["results"]["count-shares-good"], 10)
4560 self.failUnlessEqual(stats["type"], "stats")
4562 self.failUnlessReallyEqual(s["count-immutable-files"], 2)
4563 self.failUnlessReallyEqual(s["count-literal-files"], 1)
4564 self.failUnlessReallyEqual(s["count-directories"], 1)
4565 self.failUnlessReallyEqual(s["count-unknown"], 1)
4566 d.addCallback(_done)
4568 d.addCallback(self.CHECK, "root", "t=stream-manifest")
4569 def _check_manifest(res):
4570 self.failUnless(res.endswith("\n"))
4571 units = [simplejson.loads(t) for t in res[:-1].split("\n")]
4572 self.failUnlessReallyEqual(len(units), 5+1)
4573 self.failUnlessEqual(units[-1]["type"], "stats")
4575 self.failUnlessEqual(first["path"], [])
4576 self.failUnlessEqual(to_str(first["cap"]), self.rootnode.get_uri())
4577 self.failUnlessEqual(first["type"], "directory")
4578 stats = units[-1]["stats"]
4579 self.failUnlessReallyEqual(stats["count-immutable-files"], 2)
4580 self.failUnlessReallyEqual(stats["count-literal-files"], 1)
4581 self.failUnlessReallyEqual(stats["count-mutable-files"], 0)
4582 self.failUnlessReallyEqual(stats["count-immutable-files"], 2)
4583 self.failUnlessReallyEqual(stats["count-unknown"], 1)
4584 d.addCallback(_check_manifest)
4586 # now add root/subdir and root/subdir/grandchild, then make subdir
4587 # unrecoverable, then see what happens
4589 d.addCallback(lambda ign:
4590 self.rootnode.create_subdirectory(u"subdir"))
4591 d.addCallback(_stash_uri, "subdir")
4592 d.addCallback(lambda subdir_node:
4593 subdir_node.add_file(u"grandchild",
4594 upload.Data(DATA+"2",
4596 d.addCallback(_stash_uri, "grandchild")
4598 d.addCallback(lambda ign:
4599 self.delete_shares_numbered(self.uris["subdir"],
4607 # root/subdir [unrecoverable]
4608 # root/subdir/grandchild
4610 # how should a streaming-JSON API indicate fatal error?
4611 # answer: emit ERROR: instead of a JSON string
4613 d.addCallback(self.CHECK, "root", "t=stream-manifest")
4614 def _check_broken_manifest(res):
4615 lines = res.splitlines()
4617 for (i,line) in enumerate(lines)
4618 if line.startswith("ERROR:")]
4620 self.fail("no ERROR: in output: %s" % (res,))
4621 first_error = error_lines[0]
4622 error_line = lines[first_error]
4623 error_msg = lines[first_error+1:]
4624 error_msg_s = "\n".join(error_msg) + "\n"
4625 self.failUnlessIn("ERROR: UnrecoverableFileError(no recoverable versions)",
4627 self.failUnless(len(error_msg) > 2, error_msg_s) # some traceback
4628 units = [simplejson.loads(line) for line in lines[:first_error]]
4629 self.failUnlessReallyEqual(len(units), 6) # includes subdir
4630 last_unit = units[-1]
4631 self.failUnlessEqual(last_unit["path"], ["subdir"])
4632 d.addCallback(_check_broken_manifest)
4634 d.addCallback(self.CHECK, "root", "t=stream-deep-check")
4635 def _check_broken_deepcheck(res):
4636 lines = res.splitlines()
4638 for (i,line) in enumerate(lines)
4639 if line.startswith("ERROR:")]
4641 self.fail("no ERROR: in output: %s" % (res,))
4642 first_error = error_lines[0]
4643 error_line = lines[first_error]
4644 error_msg = lines[first_error+1:]
4645 error_msg_s = "\n".join(error_msg) + "\n"
4646 self.failUnlessIn("ERROR: UnrecoverableFileError(no recoverable versions)",
4648 self.failUnless(len(error_msg) > 2, error_msg_s) # some traceback
4649 units = [simplejson.loads(line) for line in lines[:first_error]]
4650 self.failUnlessReallyEqual(len(units), 6) # includes subdir
4651 last_unit = units[-1]
4652 self.failUnlessEqual(last_unit["path"], ["subdir"])
4653 r = last_unit["check-results"]["results"]
4654 self.failUnlessReallyEqual(r["count-recoverable-versions"], 0)
4655 self.failUnlessReallyEqual(r["count-shares-good"], 1)
4656 self.failUnlessReallyEqual(r["recoverable"], False)
4657 d.addCallback(_check_broken_deepcheck)
4659 d.addErrback(self.explain_web_error)
4662 def test_deep_check_and_repair(self):
4663 self.basedir = "web/Grid/deep_check_and_repair"
4665 c0 = self.g.clients[0]
4669 d = c0.create_dirnode()
4670 def _stash_root_and_create_file(n):
4672 self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri()) + "/"
4673 return n.add_file(u"good", upload.Data(DATA, convergence=""))
4674 d.addCallback(_stash_root_and_create_file)
4675 def _stash_uri(fn, which):
4676 self.uris[which] = fn.get_uri()
4677 d.addCallback(_stash_uri, "good")
4678 d.addCallback(lambda ign:
4679 self.rootnode.add_file(u"small",
4680 upload.Data("literal",
4682 d.addCallback(_stash_uri, "small")
4683 d.addCallback(lambda ign:
4684 self.rootnode.add_file(u"sick",
4685 upload.Data(DATA+"1",
4687 d.addCallback(_stash_uri, "sick")
4688 #d.addCallback(lambda ign:
4689 # self.rootnode.add_file(u"dead",
4690 # upload.Data(DATA+"2",
4692 #d.addCallback(_stash_uri, "dead")
4694 #d.addCallback(lambda ign: c0.create_mutable_file("mutable"))
4695 #d.addCallback(lambda fn: self.rootnode.set_node(u"corrupt", fn))
4696 #d.addCallback(_stash_uri, "corrupt")
4698 def _clobber_shares(ignored):
4699 good_shares = self.find_uri_shares(self.uris["good"])
4700 self.failUnlessReallyEqual(len(good_shares), 10)
4701 sick_shares = self.find_uri_shares(self.uris["sick"])
4702 os.unlink(sick_shares[0][2])
4703 #dead_shares = self.find_uri_shares(self.uris["dead"])
4704 #for i in range(1, 10):
4705 # os.unlink(dead_shares[i][2])
4707 #c_shares = self.find_uri_shares(self.uris["corrupt"])
4708 #cso = CorruptShareOptions()
4709 #cso.stdout = StringIO()
4710 #cso.parseOptions([c_shares[0][2]])
4712 d.addCallback(_clobber_shares)
4715 # root/good CHK, 10 shares
4717 # root/sick CHK, 9 shares
4719 d.addCallback(self.CHECK, "root", "t=stream-deep-check&repair=true")
4721 units = [simplejson.loads(line)
4722 for line in res.splitlines()
4724 self.failUnlessReallyEqual(len(units), 4+1)
4725 # should be parent-first
4727 self.failUnlessEqual(u0["path"], [])
4728 self.failUnlessEqual(u0["type"], "directory")
4729 self.failUnlessReallyEqual(to_str(u0["cap"]), self.rootnode.get_uri())
4730 u0crr = u0["check-and-repair-results"]
4731 self.failUnlessReallyEqual(u0crr["repair-attempted"], False)
4732 self.failUnlessReallyEqual(u0crr["pre-repair-results"]["results"]["count-shares-good"], 10)
4734 ugood = [u for u in units
4735 if u["type"] == "file" and u["path"] == [u"good"]][0]
4736 self.failUnlessEqual(to_str(ugood["cap"]), self.uris["good"])
4737 ugoodcrr = ugood["check-and-repair-results"]
4738 self.failUnlessReallyEqual(ugoodcrr["repair-attempted"], False)
4739 self.failUnlessReallyEqual(ugoodcrr["pre-repair-results"]["results"]["count-shares-good"], 10)
4741 usick = [u for u in units
4742 if u["type"] == "file" and u["path"] == [u"sick"]][0]
4743 self.failUnlessReallyEqual(to_str(usick["cap"]), self.uris["sick"])
4744 usickcrr = usick["check-and-repair-results"]
4745 self.failUnlessReallyEqual(usickcrr["repair-attempted"], True)
4746 self.failUnlessReallyEqual(usickcrr["repair-successful"], True)
4747 self.failUnlessReallyEqual(usickcrr["pre-repair-results"]["results"]["count-shares-good"], 9)
4748 self.failUnlessReallyEqual(usickcrr["post-repair-results"]["results"]["count-shares-good"], 10)
4751 self.failUnlessEqual(stats["type"], "stats")
4753 self.failUnlessReallyEqual(s["count-immutable-files"], 2)
4754 self.failUnlessReallyEqual(s["count-literal-files"], 1)
4755 self.failUnlessReallyEqual(s["count-directories"], 1)
4756 d.addCallback(_done)
4758 d.addErrback(self.explain_web_error)
4761 def _count_leases(self, ignored, which):
4762 u = self.uris[which]
4763 shares = self.find_uri_shares(u)
4765 for shnum, serverid, fn in shares:
4766 sf = get_share_file(fn)
4767 num_leases = len(list(sf.get_leases()))
4768 lease_counts.append( (fn, num_leases) )
4771 def _assert_leasecount(self, lease_counts, expected):
4772 for (fn, num_leases) in lease_counts:
4773 if num_leases != expected:
4774 self.fail("expected %d leases, have %d, on %s" %
4775 (expected, num_leases, fn))
4777 def test_add_lease(self):
4778 self.basedir = "web/Grid/add_lease"
4779 self.set_up_grid(num_clients=2)
4780 c0 = self.g.clients[0]
4783 d = c0.upload(upload.Data(DATA, convergence=""))
4784 def _stash_uri(ur, which):
4785 self.uris[which] = ur.uri
4786 d.addCallback(_stash_uri, "one")
4787 d.addCallback(lambda ign:
4788 c0.upload(upload.Data(DATA+"1", convergence="")))
4789 d.addCallback(_stash_uri, "two")
4790 def _stash_mutable_uri(n, which):
4791 self.uris[which] = n.get_uri()
4792 assert isinstance(self.uris[which], str)
4793 d.addCallback(lambda ign:
4794 c0.create_mutable_file(publish.MutableData(DATA+"2")))
4795 d.addCallback(_stash_mutable_uri, "mutable")
4797 def _compute_fileurls(ignored):
4799 for which in self.uris:
4800 self.fileurls[which] = "uri/" + urllib.quote(self.uris[which])
4801 d.addCallback(_compute_fileurls)
4803 d.addCallback(self._count_leases, "one")
4804 d.addCallback(self._assert_leasecount, 1)
4805 d.addCallback(self._count_leases, "two")
4806 d.addCallback(self._assert_leasecount, 1)
4807 d.addCallback(self._count_leases, "mutable")
4808 d.addCallback(self._assert_leasecount, 1)
4810 d.addCallback(self.CHECK, "one", "t=check") # no add-lease
4811 def _got_html_good(res):
4812 self.failUnless("Healthy" in res, res)
4813 self.failIf("Not Healthy" in res, res)
4814 d.addCallback(_got_html_good)
4816 d.addCallback(self._count_leases, "one")
4817 d.addCallback(self._assert_leasecount, 1)
4818 d.addCallback(self._count_leases, "two")
4819 d.addCallback(self._assert_leasecount, 1)
4820 d.addCallback(self._count_leases, "mutable")
4821 d.addCallback(self._assert_leasecount, 1)
4823 # this CHECK uses the original client, which uses the same
4824 # lease-secrets, so it will just renew the original lease
4825 d.addCallback(self.CHECK, "one", "t=check&add-lease=true")
4826 d.addCallback(_got_html_good)
4828 d.addCallback(self._count_leases, "one")
4829 d.addCallback(self._assert_leasecount, 1)
4830 d.addCallback(self._count_leases, "two")
4831 d.addCallback(self._assert_leasecount, 1)
4832 d.addCallback(self._count_leases, "mutable")
4833 d.addCallback(self._assert_leasecount, 1)
4835 # this CHECK uses an alternate client, which adds a second lease
4836 d.addCallback(self.CHECK, "one", "t=check&add-lease=true", clientnum=1)
4837 d.addCallback(_got_html_good)
4839 d.addCallback(self._count_leases, "one")
4840 d.addCallback(self._assert_leasecount, 2)
4841 d.addCallback(self._count_leases, "two")
4842 d.addCallback(self._assert_leasecount, 1)
4843 d.addCallback(self._count_leases, "mutable")
4844 d.addCallback(self._assert_leasecount, 1)
4846 d.addCallback(self.CHECK, "mutable", "t=check&add-lease=true")
4847 d.addCallback(_got_html_good)
4849 d.addCallback(self._count_leases, "one")
4850 d.addCallback(self._assert_leasecount, 2)
4851 d.addCallback(self._count_leases, "two")
4852 d.addCallback(self._assert_leasecount, 1)
4853 d.addCallback(self._count_leases, "mutable")
4854 d.addCallback(self._assert_leasecount, 1)
4856 d.addCallback(self.CHECK, "mutable", "t=check&add-lease=true",
4858 d.addCallback(_got_html_good)
4860 d.addCallback(self._count_leases, "one")
4861 d.addCallback(self._assert_leasecount, 2)
4862 d.addCallback(self._count_leases, "two")
4863 d.addCallback(self._assert_leasecount, 1)
4864 d.addCallback(self._count_leases, "mutable")
4865 d.addCallback(self._assert_leasecount, 2)
4867 d.addErrback(self.explain_web_error)
4870 def test_deep_add_lease(self):
4871 self.basedir = "web/Grid/deep_add_lease"
4872 self.set_up_grid(num_clients=2)
4873 c0 = self.g.clients[0]
4877 d = c0.create_dirnode()
4878 def _stash_root_and_create_file(n):
4880 self.uris["root"] = n.get_uri()
4881 self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri()) + "/"
4882 return n.add_file(u"one", upload.Data(DATA, convergence=""))
4883 d.addCallback(_stash_root_and_create_file)
4884 def _stash_uri(fn, which):
4885 self.uris[which] = fn.get_uri()
4886 d.addCallback(_stash_uri, "one")
4887 d.addCallback(lambda ign:
4888 self.rootnode.add_file(u"small",
4889 upload.Data("literal",
4891 d.addCallback(_stash_uri, "small")
4893 d.addCallback(lambda ign:
4894 c0.create_mutable_file(publish.MutableData("mutable")))
4895 d.addCallback(lambda fn: self.rootnode.set_node(u"mutable", fn))
4896 d.addCallback(_stash_uri, "mutable")
4898 d.addCallback(self.CHECK, "root", "t=stream-deep-check") # no add-lease
4900 units = [simplejson.loads(line)
4901 for line in res.splitlines()
4903 # root, one, small, mutable, stats
4904 self.failUnlessReallyEqual(len(units), 4+1)
4905 d.addCallback(_done)
4907 d.addCallback(self._count_leases, "root")
4908 d.addCallback(self._assert_leasecount, 1)
4909 d.addCallback(self._count_leases, "one")
4910 d.addCallback(self._assert_leasecount, 1)
4911 d.addCallback(self._count_leases, "mutable")
4912 d.addCallback(self._assert_leasecount, 1)
4914 d.addCallback(self.CHECK, "root", "t=stream-deep-check&add-lease=true")
4915 d.addCallback(_done)
4917 d.addCallback(self._count_leases, "root")
4918 d.addCallback(self._assert_leasecount, 1)
4919 d.addCallback(self._count_leases, "one")
4920 d.addCallback(self._assert_leasecount, 1)
4921 d.addCallback(self._count_leases, "mutable")
4922 d.addCallback(self._assert_leasecount, 1)
4924 d.addCallback(self.CHECK, "root", "t=stream-deep-check&add-lease=true",
4926 d.addCallback(_done)
4928 d.addCallback(self._count_leases, "root")
4929 d.addCallback(self._assert_leasecount, 2)
4930 d.addCallback(self._count_leases, "one")
4931 d.addCallback(self._assert_leasecount, 2)
4932 d.addCallback(self._count_leases, "mutable")
4933 d.addCallback(self._assert_leasecount, 2)
4935 d.addErrback(self.explain_web_error)
4939 def test_exceptions(self):
4940 self.basedir = "web/Grid/exceptions"
4941 self.set_up_grid(num_clients=1, num_servers=2)
4942 c0 = self.g.clients[0]
4943 c0.DEFAULT_ENCODING_PARAMETERS['happy'] = 2
4946 d = c0.create_dirnode()
4948 self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri()) + "/"
4949 self.fileurls["imaginary"] = self.fileurls["root"] + "imaginary"
4951 d.addCallback(_stash_root)
4952 d.addCallback(lambda ign: c0.upload(upload.Data(DATA, convergence="")))
4954 self.fileurls["1share"] = "uri/" + urllib.quote(ur.uri)
4955 self.delete_shares_numbered(ur.uri, range(1,10))
4957 u = uri.from_string(ur.uri)
4958 u.key = testutil.flip_bit(u.key, 0)
4959 baduri = u.to_string()
4960 self.fileurls["0shares"] = "uri/" + urllib.quote(baduri)
4961 d.addCallback(_stash_bad)
4962 d.addCallback(lambda ign: c0.create_dirnode())
4963 def _mangle_dirnode_1share(n):
4965 url = self.fileurls["dir-1share"] = "uri/" + urllib.quote(u) + "/"
4966 self.fileurls["dir-1share-json"] = url + "?t=json"
4967 self.delete_shares_numbered(u, range(1,10))
4968 d.addCallback(_mangle_dirnode_1share)
4969 d.addCallback(lambda ign: c0.create_dirnode())
4970 def _mangle_dirnode_0share(n):
4972 url = self.fileurls["dir-0share"] = "uri/" + urllib.quote(u) + "/"
4973 self.fileurls["dir-0share-json"] = url + "?t=json"
4974 self.delete_shares_numbered(u, range(0,10))
4975 d.addCallback(_mangle_dirnode_0share)
4977 # NotEnoughSharesError should be reported sensibly, with a
4978 # text/plain explanation of the problem, and perhaps some
4979 # information on which shares *could* be found.
4981 d.addCallback(lambda ignored:
4982 self.shouldHTTPError("GET unrecoverable",
4983 410, "Gone", "NoSharesError",
4984 self.GET, self.fileurls["0shares"]))
4985 def _check_zero_shares(body):
4986 self.failIf("<html>" in body, body)
4987 body = " ".join(body.strip().split())
4988 exp = ("NoSharesError: no shares could be found. "
4989 "Zero shares usually indicates a corrupt URI, or that "
4990 "no servers were connected, but it might also indicate "
4991 "severe corruption. You should perform a filecheck on "
4992 "this object to learn more. The full error message is: "
4993 "no shares (need 3). Last failure: None")
4994 self.failUnlessReallyEqual(exp, body)
4995 d.addCallback(_check_zero_shares)
4998 d.addCallback(lambda ignored:
4999 self.shouldHTTPError("GET 1share",
5000 410, "Gone", "NotEnoughSharesError",
5001 self.GET, self.fileurls["1share"]))
5002 def _check_one_share(body):
5003 self.failIf("<html>" in body, body)
5004 body = " ".join(body.strip().split())
5005 msgbase = ("NotEnoughSharesError: This indicates that some "
5006 "servers were unavailable, or that shares have been "
5007 "lost to server departure, hard drive failure, or disk "
5008 "corruption. You should perform a filecheck on "
5009 "this object to learn more. The full error message is:"
5011 msg1 = msgbase + (" ran out of shares:"
5014 " overdue= unused= need 3. Last failure: None")
5015 msg2 = msgbase + (" ran out of shares:"
5017 " pending=Share(sh0-on-xgru5)"
5018 " overdue= unused= need 3. Last failure: None")
5019 self.failUnless(body == msg1 or body == msg2, body)
5020 d.addCallback(_check_one_share)
5022 d.addCallback(lambda ignored:
5023 self.shouldHTTPError("GET imaginary",
5024 404, "Not Found", None,
5025 self.GET, self.fileurls["imaginary"]))
5026 def _missing_child(body):
5027 self.failUnless("No such child: imaginary" in body, body)
5028 d.addCallback(_missing_child)
5030 d.addCallback(lambda ignored: self.GET(self.fileurls["dir-0share"]))
5031 def _check_0shares_dir_html(body):
5032 self.failUnless("<html>" in body, body)
5033 # we should see the regular page, but without the child table or
5035 body = " ".join(body.strip().split())
5036 self.failUnlessIn('href="?t=info">More info on this directory',
5038 exp = ("UnrecoverableFileError: the directory (or mutable file) "
5039 "could not be retrieved, because there were insufficient "
5040 "good shares. This might indicate that no servers were "
5041 "connected, insufficient servers were connected, the URI "
5042 "was corrupt, or that shares have been lost due to server "
5043 "departure, hard drive failure, or disk corruption. You "
5044 "should perform a filecheck on this object to learn more.")
5045 self.failUnlessIn(exp, body)
5046 self.failUnlessIn("No upload forms: directory is unreadable", body)
5047 d.addCallback(_check_0shares_dir_html)
5049 d.addCallback(lambda ignored: self.GET(self.fileurls["dir-1share"]))
5050 def _check_1shares_dir_html(body):
5051 # at some point, we'll split UnrecoverableFileError into 0-shares
5052 # and some-shares like we did for immutable files (since there
5053 # are different sorts of advice to offer in each case). For now,
5054 # they present the same way.
5055 self.failUnless("<html>" in body, body)
5056 body = " ".join(body.strip().split())
5057 self.failUnlessIn('href="?t=info">More info on this directory',
5059 exp = ("UnrecoverableFileError: the directory (or mutable file) "
5060 "could not be retrieved, because there were insufficient "
5061 "good shares. This might indicate that no servers were "
5062 "connected, insufficient servers were connected, the URI "
5063 "was corrupt, or that shares have been lost due to server "
5064 "departure, hard drive failure, or disk corruption. You "
5065 "should perform a filecheck on this object to learn more.")
5066 self.failUnlessIn(exp, body)
5067 self.failUnlessIn("No upload forms: directory is unreadable", body)
5068 d.addCallback(_check_1shares_dir_html)
5070 d.addCallback(lambda ignored:
5071 self.shouldHTTPError("GET dir-0share-json",
5072 410, "Gone", "UnrecoverableFileError",
5074 self.fileurls["dir-0share-json"]))
5075 def _check_unrecoverable_file(body):
5076 self.failIf("<html>" in body, body)
5077 body = " ".join(body.strip().split())
5078 exp = ("UnrecoverableFileError: the directory (or mutable file) "
5079 "could not be retrieved, because there were insufficient "
5080 "good shares. This might indicate that no servers were "
5081 "connected, insufficient servers were connected, the URI "
5082 "was corrupt, or that shares have been lost due to server "
5083 "departure, hard drive failure, or disk corruption. You "
5084 "should perform a filecheck on this object to learn more.")
5085 self.failUnlessReallyEqual(exp, body)
5086 d.addCallback(_check_unrecoverable_file)
5088 d.addCallback(lambda ignored:
5089 self.shouldHTTPError("GET dir-1share-json",
5090 410, "Gone", "UnrecoverableFileError",
5092 self.fileurls["dir-1share-json"]))
5093 d.addCallback(_check_unrecoverable_file)
5095 d.addCallback(lambda ignored:
5096 self.shouldHTTPError("GET imaginary",
5097 404, "Not Found", None,
5098 self.GET, self.fileurls["imaginary"]))
5100 # attach a webapi child that throws a random error, to test how it
5102 w = c0.getServiceNamed("webish")
5103 w.root.putChild("ERRORBOOM", ErrorBoom())
5105 # "Accept: */*" : should get a text/html stack trace
5106 # "Accept: text/plain" : should get a text/plain stack trace
5107 # "Accept: text/plain, application/octet-stream" : text/plain (CLI)
5108 # no Accept header: should get a text/html stack trace
5110 d.addCallback(lambda ignored:
5111 self.shouldHTTPError("GET errorboom_html",
5112 500, "Internal Server Error", None,
5113 self.GET, "ERRORBOOM",
5114 headers={"accept": ["*/*"]}))
5115 def _internal_error_html1(body):
5116 self.failUnless("<html>" in body, "expected HTML, not '%s'" % body)
5117 d.addCallback(_internal_error_html1)
5119 d.addCallback(lambda ignored:
5120 self.shouldHTTPError("GET errorboom_text",
5121 500, "Internal Server Error", None,
5122 self.GET, "ERRORBOOM",
5123 headers={"accept": ["text/plain"]}))
5124 def _internal_error_text2(body):
5125 self.failIf("<html>" in body, body)
5126 self.failUnless(body.startswith("Traceback "), body)
5127 d.addCallback(_internal_error_text2)
5129 CLI_accepts = "text/plain, application/octet-stream"
5130 d.addCallback(lambda ignored:
5131 self.shouldHTTPError("GET errorboom_text",
5132 500, "Internal Server Error", None,
5133 self.GET, "ERRORBOOM",
5134 headers={"accept": [CLI_accepts]}))
5135 def _internal_error_text3(body):
5136 self.failIf("<html>" in body, body)
5137 self.failUnless(body.startswith("Traceback "), body)
5138 d.addCallback(_internal_error_text3)
5140 d.addCallback(lambda ignored:
5141 self.shouldHTTPError("GET errorboom_text",
5142 500, "Internal Server Error", None,
5143 self.GET, "ERRORBOOM"))
5144 def _internal_error_html4(body):
5145 self.failUnless("<html>" in body, "expected HTML, not '%s'" % body)
5146 d.addCallback(_internal_error_html4)
5148 def _flush_errors(res):
5149 # Trial: please ignore the CompletelyUnhandledError in the logs
5150 self.flushLoggedErrors(CompletelyUnhandledError)
5152 d.addBoth(_flush_errors)
5156 def test_blacklist(self):
5157 # download from a blacklisted URI, get an error
5158 self.basedir = "web/Grid/blacklist"
5160 c0 = self.g.clients[0]
5161 c0_basedir = c0.basedir
5162 fn = os.path.join(c0_basedir, "access.blacklist")
5164 DATA = "off-limits " * 50
5166 d = c0.upload(upload.Data(DATA, convergence=""))
5167 def _stash_uri_and_create_dir(ur):
5169 self.url = "uri/"+self.uri
5170 u = uri.from_string_filenode(self.uri)
5171 self.si = u.get_storage_index()
5172 childnode = c0.create_node_from_uri(self.uri, None)
5173 return c0.create_dirnode({u"blacklisted.txt": (childnode,{}) })
5174 d.addCallback(_stash_uri_and_create_dir)
5175 def _stash_dir(node):
5176 self.dir_node = node
5177 self.dir_uri = node.get_uri()
5178 self.dir_url = "uri/"+self.dir_uri
5179 d.addCallback(_stash_dir)
5180 d.addCallback(lambda ign: self.GET(self.dir_url, followRedirect=True))
5181 def _check_dir_html(body):
5182 self.failUnlessIn("<html>", body)
5183 self.failUnlessIn("blacklisted.txt</a>", body)
5184 d.addCallback(_check_dir_html)
5185 d.addCallback(lambda ign: self.GET(self.url))
5186 d.addCallback(lambda body: self.failUnlessEqual(DATA, body))
5188 def _blacklist(ign):
5190 f.write(" # this is a comment\n")
5192 f.write("\n") # also exercise blank lines
5193 f.write("%s %s\n" % (base32.b2a(self.si), "off-limits to you"))
5195 # clients should be checking the blacklist each time, so we don't
5196 # need to restart the client
5197 d.addCallback(_blacklist)
5198 d.addCallback(lambda ign: self.shouldHTTPError("get_from_blacklisted_uri",
5200 "Access Prohibited: off-limits",
5201 self.GET, self.url))
5203 # We should still be able to list the parent directory, in HTML...
5204 d.addCallback(lambda ign: self.GET(self.dir_url, followRedirect=True))
5205 def _check_dir_html2(body):
5206 self.failUnlessIn("<html>", body)
5207 self.failUnlessIn("blacklisted.txt</strike>", body)
5208 d.addCallback(_check_dir_html2)
5210 # ... and in JSON (used by CLI).
5211 d.addCallback(lambda ign: self.GET(self.dir_url+"?t=json", followRedirect=True))
5212 def _check_dir_json(res):
5213 data = simplejson.loads(res)
5214 self.failUnless(isinstance(data, list), data)
5215 self.failUnlessEqual(data[0], "dirnode")
5216 self.failUnless(isinstance(data[1], dict), data)
5217 self.failUnlessIn("children", data[1])
5218 self.failUnlessIn("blacklisted.txt", data[1]["children"])
5219 childdata = data[1]["children"]["blacklisted.txt"]
5220 self.failUnless(isinstance(childdata, list), data)
5221 self.failUnlessEqual(childdata[0], "filenode")
5222 self.failUnless(isinstance(childdata[1], dict), data)
5223 d.addCallback(_check_dir_json)
5225 def _unblacklist(ign):
5226 open(fn, "w").close()
5227 # the Blacklist object watches mtime to tell when the file has
5228 # changed, but on windows this test will run faster than the
5229 # filesystem's mtime resolution. So we edit Blacklist.last_mtime
5230 # to force a reload.
5231 self.g.clients[0].blacklist.last_mtime -= 2.0
5232 d.addCallback(_unblacklist)
5234 # now a read should work
5235 d.addCallback(lambda ign: self.GET(self.url))
5236 d.addCallback(lambda body: self.failUnlessEqual(DATA, body))
5238 # read again to exercise the blacklist-is-unchanged logic
5239 d.addCallback(lambda ign: self.GET(self.url))
5240 d.addCallback(lambda body: self.failUnlessEqual(DATA, body))
5242 # now add a blacklisted directory, and make sure files under it are
5245 childnode = c0.create_node_from_uri(self.uri, None)
5246 return c0.create_dirnode({u"child": (childnode,{}) })
5247 d.addCallback(_add_dir)
5248 def _get_dircap(dn):
5249 self.dir_si_b32 = base32.b2a(dn.get_storage_index())
5250 self.dir_url_base = "uri/"+dn.get_write_uri()
5251 self.dir_url_json1 = "uri/"+dn.get_write_uri()+"?t=json"
5252 self.dir_url_json2 = "uri/"+dn.get_write_uri()+"/?t=json"
5253 self.dir_url_json_ro = "uri/"+dn.get_readonly_uri()+"/?t=json"
5254 self.child_url = "uri/"+dn.get_readonly_uri()+"/child"
5255 d.addCallback(_get_dircap)
5256 d.addCallback(lambda ign: self.GET(self.dir_url_base, followRedirect=True))
5257 d.addCallback(lambda body: self.failUnlessIn("<html>", body))
5258 d.addCallback(lambda ign: self.GET(self.dir_url_json1))
5259 d.addCallback(lambda res: simplejson.loads(res)) # just check it decodes
5260 d.addCallback(lambda ign: self.GET(self.dir_url_json2))
5261 d.addCallback(lambda res: simplejson.loads(res)) # just check it decodes
5262 d.addCallback(lambda ign: self.GET(self.dir_url_json_ro))
5263 d.addCallback(lambda res: simplejson.loads(res)) # just check it decodes
5264 d.addCallback(lambda ign: self.GET(self.child_url))
5265 d.addCallback(lambda body: self.failUnlessEqual(DATA, body))
5267 def _block_dir(ign):
5269 f.write("%s %s\n" % (self.dir_si_b32, "dir-off-limits to you"))
5271 self.g.clients[0].blacklist.last_mtime -= 2.0
5272 d.addCallback(_block_dir)
5273 d.addCallback(lambda ign: self.shouldHTTPError("get_from_blacklisted_dir base",
5275 "Access Prohibited: dir-off-limits",
5276 self.GET, self.dir_url_base))
5277 d.addCallback(lambda ign: self.shouldHTTPError("get_from_blacklisted_dir json1",
5279 "Access Prohibited: dir-off-limits",
5280 self.GET, self.dir_url_json1))
5281 d.addCallback(lambda ign: self.shouldHTTPError("get_from_blacklisted_dir json2",
5283 "Access Prohibited: dir-off-limits",
5284 self.GET, self.dir_url_json2))
5285 d.addCallback(lambda ign: self.shouldHTTPError("get_from_blacklisted_dir json_ro",
5287 "Access Prohibited: dir-off-limits",
5288 self.GET, self.dir_url_json_ro))
5289 d.addCallback(lambda ign: self.shouldHTTPError("get_from_blacklisted_dir child",
5291 "Access Prohibited: dir-off-limits",
5292 self.GET, self.child_url))
5296 class CompletelyUnhandledError(Exception):
5298 class ErrorBoom(rend.Page):
5299 def beforeRender(self, ctx):
5300 raise CompletelyUnhandledError("whoops")