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 test_GET_DIRECTORY_html(self):
1291 d = self.GET(self.public_url + "/foo", followRedirect=True)
1293 self.failUnlessIn('<div class="toolbar-item"><a href="../../..">Return to Welcome page</a></div>',res)
1294 # These are radio buttons that allow a user to toggle
1295 # whether a particular mutable file is SDMF or MDMF.
1296 self.failUnlessIn("mutable-type-mdmf", res)
1297 self.failUnlessIn("mutable-type-sdmf", res)
1298 # Similarly, these toggle whether a particular directory
1299 # should be MDMF or SDMF.
1300 self.failUnlessIn("mutable-directory-mdmf", res)
1301 self.failUnlessIn("mutable-directory-sdmf", res)
1302 self.failUnlessIn("quux", res)
1303 d.addCallback(_check)
1306 def test_GET_root_html(self):
1307 # make sure that we have the option to upload an unlinked
1308 # mutable file in SDMF and MDMF formats.
1310 def _got_html(html):
1311 # These are radio buttons that allow the user to toggle
1312 # whether a particular mutable file is MDMF or SDMF.
1313 self.failUnlessIn("mutable-type-mdmf", html)
1314 self.failUnlessIn("mutable-type-sdmf", html)
1315 # We should also have the ability to create a mutable directory.
1316 self.failUnlessIn("mkdir", html)
1317 # ...and we should have the ability to say whether that's an
1318 # MDMF or SDMF directory
1319 self.failUnlessIn("mutable-directory-mdmf", html)
1320 self.failUnlessIn("mutable-directory-sdmf", html)
1321 d.addCallback(_got_html)
1324 def test_mutable_type_defaults(self):
1325 # The checked="checked" attribute of the inputs corresponding to
1326 # the mutable-type parameter should change as expected with the
1327 # value configured in tahoe.cfg.
1329 # By default, the value configured with the client is
1330 # SDMF_VERSION, so that should be checked.
1331 assert self.s.mutable_file_default == SDMF_VERSION
1334 def _got_html(html, value):
1335 i = 'input checked="checked" type="radio" id="mutable-type-%s"'
1336 self.failUnlessIn(i % value, html)
1337 d.addCallback(_got_html, "sdmf")
1338 d.addCallback(lambda ignored:
1339 self.GET(self.public_url + "/foo", followRedirect=True))
1340 d.addCallback(_got_html, "sdmf")
1341 # Now switch the configuration value to MDMF. The MDMF radio
1342 # buttons should now be checked on these pages.
1343 def _swap_values(ignored):
1344 self.s.mutable_file_default = MDMF_VERSION
1345 d.addCallback(_swap_values)
1346 d.addCallback(lambda ignored: self.GET("/"))
1347 d.addCallback(_got_html, "mdmf")
1348 d.addCallback(lambda ignored:
1349 self.GET(self.public_url + "/foo", followRedirect=True))
1350 d.addCallback(_got_html, "mdmf")
1353 def test_GET_DIRURL(self):
1354 # the addSlash means we get a redirect here
1355 # from /uri/$URI/foo/ , we need ../../../ to get back to the root
1357 d = self.GET(self.public_url + "/foo", followRedirect=True)
1359 self.failUnless(('<a href="%s">Return to Welcome page' % ROOT)
1361 # the FILE reference points to a URI, but it should end in bar.txt
1362 bar_url = ("%s/file/%s/@@named=/bar.txt" %
1363 (ROOT, urllib.quote(self._bar_txt_uri)))
1364 get_bar = "".join([r'<td>FILE</td>',
1366 r'<a href="%s">bar.txt</a>' % bar_url,
1368 r'\s+<td align="right">%d</td>' % len(self.BAR_CONTENTS),
1370 self.failUnless(re.search(get_bar, res), res)
1371 for label in ['unlink', 'rename']:
1372 for line in res.split("\n"):
1373 # find the line that contains the relevant button for bar.txt
1374 if ("form action" in line and
1375 ('value="%s"' % (label,)) in line and
1376 'value="bar.txt"' in line):
1377 # the form target should use a relative URL
1378 foo_url = urllib.quote("%s/uri/%s/" % (ROOT, self._foo_uri))
1379 self.failUnlessIn('action="%s"' % foo_url, line)
1380 # and the when_done= should too
1381 #done_url = urllib.quote(???)
1382 #self.failUnlessIn('name="when_done" value="%s"' % done_url, line)
1384 # 'unlink' needs to use POST because it directly has a side effect
1385 if label == 'unlink':
1386 self.failUnlessIn('method="post"', line)
1389 self.fail("unable to find '%s bar.txt' line" % (label,), res)
1391 # the DIR reference just points to a URI
1392 sub_url = ("%s/uri/%s/" % (ROOT, urllib.quote(self._sub_uri)))
1393 get_sub = ((r'<td>DIR</td>')
1394 +r'\s+<td><a href="%s">sub</a></td>' % sub_url)
1395 self.failUnless(re.search(get_sub, res), res)
1396 d.addCallback(_check)
1398 # look at a readonly directory
1399 d.addCallback(lambda res:
1400 self.GET(self.public_url + "/reedownlee", followRedirect=True))
1402 self.failUnless("(read-only)" in res, res)
1403 self.failIf("Upload a file" in res, res)
1404 d.addCallback(_check2)
1406 # and at a directory that contains a readonly directory
1407 d.addCallback(lambda res:
1408 self.GET(self.public_url, followRedirect=True))
1410 self.failUnless(re.search('<td>DIR-RO</td>'
1411 r'\s+<td><a href="[\.\/]+/uri/URI%3ADIR2-RO%3A[^"]+">reedownlee</a></td>', res), res)
1412 d.addCallback(_check3)
1414 # and an empty directory
1415 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty/"))
1417 self.failUnless("directory is empty" in res, res)
1418 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)
1419 self.failUnless(MKDIR_BUTTON_RE.search(res), res)
1420 d.addCallback(_check4)
1422 # and at a literal directory
1423 tiny_litdir_uri = "URI:DIR2-LIT:gqytunj2onug64tufqzdcosvkjetutcjkq5gw4tvm5vwszdgnz5hgyzufqydulbshj5x2lbm" # contains one child which is itself also LIT
1424 d.addCallback(lambda res:
1425 self.GET("/uri/" + tiny_litdir_uri + "/", followRedirect=True))
1427 self.failUnless('(immutable)' in res, res)
1428 self.failUnless(re.search('<td>FILE</td>'
1429 r'\s+<td><a href="[\.\/]+/file/URI%3ALIT%3Akrugkidfnzsc4/@@named=/short">short</a></td>', res), res)
1430 d.addCallback(_check5)
1433 def test_GET_DIRURL_badtype(self):
1434 d = self.shouldHTTPError("test_GET_DIRURL_badtype",
1438 self.public_url + "/foo?t=bogus")
1441 def test_GET_DIRURL_json(self):
1442 d = self.GET(self.public_url + "/foo?t=json")
1443 d.addCallback(self.failUnlessIsFooJSON)
1446 def test_GET_DIRURL_json_format(self):
1447 d = self.PUT(self.public_url + \
1448 "/foo/sdmf.txt?format=sdmf",
1449 self.NEWFILE_CONTENTS * 300000)
1450 d.addCallback(lambda ignored:
1451 self.PUT(self.public_url + \
1452 "/foo/mdmf.txt?format=mdmf",
1453 self.NEWFILE_CONTENTS * 300000))
1454 # Now we have an MDMF and SDMF file in the directory. If we GET
1455 # its JSON, we should see their encodings.
1456 d.addCallback(lambda ignored:
1457 self.GET(self.public_url + "/foo?t=json"))
1458 def _got_json(json):
1459 data = simplejson.loads(json)
1460 assert data[0] == "dirnode"
1463 kids = data['children']
1465 mdmf_data = kids['mdmf.txt'][1]
1466 self.failUnlessIn("format", mdmf_data)
1467 self.failUnlessEqual(mdmf_data["format"], "mdmf")
1469 sdmf_data = kids['sdmf.txt'][1]
1470 self.failUnlessIn("format", sdmf_data)
1471 self.failUnlessEqual(sdmf_data["format"], "sdmf")
1472 d.addCallback(_got_json)
1476 def test_POST_DIRURL_manifest_no_ophandle(self):
1477 d = self.shouldFail2(error.Error,
1478 "test_POST_DIRURL_manifest_no_ophandle",
1480 "slow operation requires ophandle=",
1481 self.POST, self.public_url, t="start-manifest")
1484 def test_POST_DIRURL_manifest(self):
1485 d = defer.succeed(None)
1486 def getman(ignored, output):
1487 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=125",
1488 followRedirect=True)
1489 d.addCallback(self.wait_for_operation, "125")
1490 d.addCallback(self.get_operation_results, "125", output)
1492 d.addCallback(getman, None)
1493 def _got_html(manifest):
1494 self.failUnless("Manifest of SI=" in manifest)
1495 self.failUnless("<td>sub</td>" in manifest)
1496 self.failUnless(self._sub_uri in manifest)
1497 self.failUnless("<td>sub/baz.txt</td>" in manifest)
1498 d.addCallback(_got_html)
1500 # both t=status and unadorned GET should be identical
1501 d.addCallback(lambda res: self.GET("/operations/125"))
1502 d.addCallback(_got_html)
1504 d.addCallback(getman, "html")
1505 d.addCallback(_got_html)
1506 d.addCallback(getman, "text")
1507 def _got_text(manifest):
1508 self.failUnless("\nsub " + self._sub_uri + "\n" in manifest)
1509 self.failUnless("\nsub/baz.txt URI:CHK:" in manifest)
1510 d.addCallback(_got_text)
1511 d.addCallback(getman, "JSON")
1513 data = res["manifest"]
1515 for (path_list, cap) in data:
1516 got[tuple(path_list)] = cap
1517 self.failUnlessReallyEqual(to_str(got[(u"sub",)]), self._sub_uri)
1518 self.failUnless((u"sub",u"baz.txt") in got)
1519 self.failUnless("finished" in res)
1520 self.failUnless("origin" in res)
1521 self.failUnless("storage-index" in res)
1522 self.failUnless("verifycaps" in res)
1523 self.failUnless("stats" in res)
1524 d.addCallback(_got_json)
1527 def test_POST_DIRURL_deepsize_no_ophandle(self):
1528 d = self.shouldFail2(error.Error,
1529 "test_POST_DIRURL_deepsize_no_ophandle",
1531 "slow operation requires ophandle=",
1532 self.POST, self.public_url, t="start-deep-size")
1535 def test_POST_DIRURL_deepsize(self):
1536 d = self.POST(self.public_url + "/foo/?t=start-deep-size&ophandle=126",
1537 followRedirect=True)
1538 d.addCallback(self.wait_for_operation, "126")
1539 d.addCallback(self.get_operation_results, "126", "json")
1540 def _got_json(data):
1541 self.failUnlessReallyEqual(data["finished"], True)
1543 self.failUnless(size > 1000)
1544 d.addCallback(_got_json)
1545 d.addCallback(self.get_operation_results, "126", "text")
1547 mo = re.search(r'^size: (\d+)$', res, re.M)
1548 self.failUnless(mo, res)
1549 size = int(mo.group(1))
1550 # with directories, the size varies.
1551 self.failUnless(size > 1000)
1552 d.addCallback(_got_text)
1555 def test_POST_DIRURL_deepstats_no_ophandle(self):
1556 d = self.shouldFail2(error.Error,
1557 "test_POST_DIRURL_deepstats_no_ophandle",
1559 "slow operation requires ophandle=",
1560 self.POST, self.public_url, t="start-deep-stats")
1563 def test_POST_DIRURL_deepstats(self):
1564 d = self.POST(self.public_url + "/foo/?t=start-deep-stats&ophandle=127",
1565 followRedirect=True)
1566 d.addCallback(self.wait_for_operation, "127")
1567 d.addCallback(self.get_operation_results, "127", "json")
1568 def _got_json(stats):
1569 expected = {"count-immutable-files": 3,
1570 "count-mutable-files": 2,
1571 "count-literal-files": 0,
1573 "count-directories": 3,
1574 "size-immutable-files": 57,
1575 "size-literal-files": 0,
1576 #"size-directories": 1912, # varies
1577 #"largest-directory": 1590,
1578 "largest-directory-children": 7,
1579 "largest-immutable-file": 19,
1581 for k,v in expected.iteritems():
1582 self.failUnlessReallyEqual(stats[k], v,
1583 "stats[%s] was %s, not %s" %
1585 self.failUnlessReallyEqual(stats["size-files-histogram"],
1587 d.addCallback(_got_json)
1590 def test_POST_DIRURL_stream_manifest(self):
1591 d = self.POST(self.public_url + "/foo/?t=stream-manifest")
1593 self.failUnless(res.endswith("\n"))
1594 units = [simplejson.loads(t) for t in res[:-1].split("\n")]
1595 self.failUnlessReallyEqual(len(units), 9)
1596 self.failUnlessEqual(units[-1]["type"], "stats")
1598 self.failUnlessEqual(first["path"], [])
1599 self.failUnlessReallyEqual(to_str(first["cap"]), self._foo_uri)
1600 self.failUnlessEqual(first["type"], "directory")
1601 baz = [u for u in units[:-1] if to_str(u["cap"]) == self._baz_file_uri][0]
1602 self.failUnlessEqual(baz["path"], ["sub", "baz.txt"])
1603 self.failIfEqual(baz["storage-index"], None)
1604 self.failIfEqual(baz["verifycap"], None)
1605 self.failIfEqual(baz["repaircap"], None)
1606 # XXX: Add quux and baz to this test.
1608 d.addCallback(_check)
1611 def test_GET_DIRURL_uri(self):
1612 d = self.GET(self.public_url + "/foo?t=uri")
1614 self.failUnlessReallyEqual(to_str(res), self._foo_uri)
1615 d.addCallback(_check)
1618 def test_GET_DIRURL_readonly_uri(self):
1619 d = self.GET(self.public_url + "/foo?t=readonly-uri")
1621 self.failUnlessReallyEqual(to_str(res), self._foo_readonly_uri)
1622 d.addCallback(_check)
1625 def test_PUT_NEWDIRURL(self):
1626 d = self.PUT(self.public_url + "/foo/newdir?t=mkdir", "")
1627 d.addCallback(lambda res:
1628 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1629 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1630 d.addCallback(self.failUnlessNodeKeysAre, [])
1633 def test_PUT_NEWDIRURL_mdmf(self):
1634 d = self.PUT(self.public_url + "/foo/newdir?t=mkdir&format=mdmf", "")
1635 d.addCallback(lambda res:
1636 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1637 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1638 d.addCallback(lambda node:
1639 self.failUnlessEqual(node._node.get_version(), MDMF_VERSION))
1642 def test_PUT_NEWDIRURL_sdmf(self):
1643 d = self.PUT(self.public_url + "/foo/newdir?t=mkdir&format=sdmf",
1645 d.addCallback(lambda res:
1646 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1647 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1648 d.addCallback(lambda node:
1649 self.failUnlessEqual(node._node.get_version(), SDMF_VERSION))
1652 def test_PUT_NEWDIRURL_bad_format(self):
1653 return self.shouldHTTPError("PUT_NEWDIRURL_bad_format",
1654 400, "Bad Request", "Unknown format: foo",
1655 self.PUT, self.public_url +
1656 "/foo/newdir=?t=mkdir&format=foo", "")
1658 def test_POST_NEWDIRURL(self):
1659 d = self.POST2(self.public_url + "/foo/newdir?t=mkdir", "")
1660 d.addCallback(lambda res:
1661 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1662 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1663 d.addCallback(self.failUnlessNodeKeysAre, [])
1666 def test_POST_NEWDIRURL_mdmf(self):
1667 d = self.POST2(self.public_url + "/foo/newdir?t=mkdir&format=mdmf", "")
1668 d.addCallback(lambda res:
1669 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1670 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1671 d.addCallback(lambda node:
1672 self.failUnlessEqual(node._node.get_version(), MDMF_VERSION))
1675 def test_POST_NEWDIRURL_sdmf(self):
1676 d = self.POST2(self.public_url + "/foo/newdir?t=mkdir&format=sdmf", "")
1677 d.addCallback(lambda res:
1678 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1679 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1680 d.addCallback(lambda node:
1681 self.failUnlessEqual(node._node.get_version(), SDMF_VERSION))
1684 def test_POST_NEWDIRURL_bad_format(self):
1685 return self.shouldHTTPError("POST_NEWDIRURL_bad_format",
1686 400, "Bad Request", "Unknown format: foo",
1687 self.POST2, self.public_url + \
1688 "/foo/newdir?t=mkdir&format=foo", "")
1690 def test_POST_NEWDIRURL_emptyname(self):
1691 # an empty pathname component (i.e. a double-slash) is disallowed
1692 d = self.shouldFail2(error.Error, "POST_NEWDIRURL_emptyname",
1694 "The webapi does not allow empty pathname components, i.e. a double slash",
1695 self.POST, self.public_url + "//?t=mkdir")
1698 def _do_POST_NEWDIRURL_initial_children_test(self, version=None):
1699 (newkids, caps) = self._create_initial_children()
1700 query = "/foo/newdir?t=mkdir-with-children"
1701 if version == MDMF_VERSION:
1702 query += "&format=mdmf"
1703 elif version == SDMF_VERSION:
1704 query += "&format=sdmf"
1706 version = SDMF_VERSION # for later
1707 d = self.POST2(self.public_url + query,
1708 simplejson.dumps(newkids))
1710 n = self.s.create_node_from_uri(uri.strip())
1711 d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
1712 self.failUnlessEqual(n._node.get_version(), version)
1713 d2.addCallback(lambda ign:
1714 self.failUnlessROChildURIIs(n, u"child-imm",
1716 d2.addCallback(lambda ign:
1717 self.failUnlessRWChildURIIs(n, u"child-mutable",
1719 d2.addCallback(lambda ign:
1720 self.failUnlessROChildURIIs(n, u"child-mutable-ro",
1722 d2.addCallback(lambda ign:
1723 self.failUnlessROChildURIIs(n, u"unknownchild-ro",
1724 caps['unknown_rocap']))
1725 d2.addCallback(lambda ign:
1726 self.failUnlessRWChildURIIs(n, u"unknownchild-rw",
1727 caps['unknown_rwcap']))
1728 d2.addCallback(lambda ign:
1729 self.failUnlessROChildURIIs(n, u"unknownchild-imm",
1730 caps['unknown_immcap']))
1731 d2.addCallback(lambda ign:
1732 self.failUnlessRWChildURIIs(n, u"dirchild",
1734 d2.addCallback(lambda ign:
1735 self.failUnlessROChildURIIs(n, u"dirchild-lit",
1737 d2.addCallback(lambda ign:
1738 self.failUnlessROChildURIIs(n, u"dirchild-empty",
1739 caps['emptydircap']))
1741 d.addCallback(_check)
1742 d.addCallback(lambda res:
1743 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1744 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1745 d.addCallback(self.failUnlessNodeKeysAre, newkids.keys())
1746 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1747 d.addCallback(self.failUnlessROChildURIIs, u"child-imm", caps['filecap1'])
1750 def test_POST_NEWDIRURL_initial_children(self):
1751 return self._do_POST_NEWDIRURL_initial_children_test()
1753 def test_POST_NEWDIRURL_initial_children_mdmf(self):
1754 return self._do_POST_NEWDIRURL_initial_children_test(MDMF_VERSION)
1756 def test_POST_NEWDIRURL_initial_children_sdmf(self):
1757 return self._do_POST_NEWDIRURL_initial_children_test(SDMF_VERSION)
1759 def test_POST_NEWDIRURL_initial_children_bad_format(self):
1760 (newkids, caps) = self._create_initial_children()
1761 return self.shouldHTTPError("POST_NEWDIRURL_initial_children_bad_format",
1762 400, "Bad Request", "Unknown format: foo",
1763 self.POST2, self.public_url + \
1764 "/foo/newdir?t=mkdir-with-children&format=foo",
1765 simplejson.dumps(newkids))
1767 def test_POST_NEWDIRURL_immutable(self):
1768 (newkids, caps) = self._create_immutable_children()
1769 d = self.POST2(self.public_url + "/foo/newdir?t=mkdir-immutable",
1770 simplejson.dumps(newkids))
1772 n = self.s.create_node_from_uri(uri.strip())
1773 d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
1774 d2.addCallback(lambda ign:
1775 self.failUnlessROChildURIIs(n, u"child-imm",
1777 d2.addCallback(lambda ign:
1778 self.failUnlessROChildURIIs(n, u"unknownchild-imm",
1779 caps['unknown_immcap']))
1780 d2.addCallback(lambda ign:
1781 self.failUnlessROChildURIIs(n, u"dirchild-imm",
1783 d2.addCallback(lambda ign:
1784 self.failUnlessROChildURIIs(n, u"dirchild-lit",
1786 d2.addCallback(lambda ign:
1787 self.failUnlessROChildURIIs(n, u"dirchild-empty",
1788 caps['emptydircap']))
1790 d.addCallback(_check)
1791 d.addCallback(lambda res:
1792 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1793 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1794 d.addCallback(self.failUnlessNodeKeysAre, newkids.keys())
1795 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1796 d.addCallback(self.failUnlessROChildURIIs, u"child-imm", caps['filecap1'])
1797 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1798 d.addCallback(self.failUnlessROChildURIIs, u"unknownchild-imm", caps['unknown_immcap'])
1799 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1800 d.addCallback(self.failUnlessROChildURIIs, u"dirchild-imm", caps['immdircap'])
1801 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1802 d.addCallback(self.failUnlessROChildURIIs, u"dirchild-lit", caps['litdircap'])
1803 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1804 d.addCallback(self.failUnlessROChildURIIs, u"dirchild-empty", caps['emptydircap'])
1805 d.addErrback(self.explain_web_error)
1808 def test_POST_NEWDIRURL_immutable_bad(self):
1809 (newkids, caps) = self._create_initial_children()
1810 d = self.shouldFail2(error.Error, "test_POST_NEWDIRURL_immutable_bad",
1812 "needed to be immutable but was not",
1814 self.public_url + "/foo/newdir?t=mkdir-immutable",
1815 simplejson.dumps(newkids))
1818 def test_PUT_NEWDIRURL_exists(self):
1819 d = self.PUT(self.public_url + "/foo/sub?t=mkdir", "")
1820 d.addCallback(lambda res:
1821 self.failUnlessNodeHasChild(self._foo_node, u"sub"))
1822 d.addCallback(lambda res: self._foo_node.get(u"sub"))
1823 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
1826 def test_PUT_NEWDIRURL_blocked(self):
1827 d = self.shouldFail2(error.Error, "PUT_NEWDIRURL_blocked",
1828 "409 Conflict", "Unable to create directory 'bar.txt': a file was in the way",
1830 self.public_url + "/foo/bar.txt/sub?t=mkdir", "")
1831 d.addCallback(lambda res:
1832 self.failUnlessNodeHasChild(self._foo_node, u"sub"))
1833 d.addCallback(lambda res: self._foo_node.get(u"sub"))
1834 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
1837 def test_PUT_NEWDIRURL_mkdir_p(self):
1838 d = defer.succeed(None)
1839 d.addCallback(lambda res: self.POST(self.public_url + "/foo", t='mkdir', name='mkp'))
1840 d.addCallback(lambda res: self.failUnlessNodeHasChild(self._foo_node, u"mkp"))
1841 d.addCallback(lambda res: self._foo_node.get(u"mkp"))
1842 def mkdir_p(mkpnode):
1843 url = '/uri/%s?t=mkdir-p&path=/sub1/sub2' % urllib.quote(mkpnode.get_uri())
1845 def made_subsub(ssuri):
1846 d = self._foo_node.get_child_at_path(u"mkp/sub1/sub2")
1847 d.addCallback(lambda ssnode: self.failUnlessReallyEqual(ssnode.get_uri(), ssuri))
1849 d.addCallback(lambda uri2: self.failUnlessReallyEqual(uri2, ssuri))
1851 d.addCallback(made_subsub)
1853 d.addCallback(mkdir_p)
1856 def test_PUT_NEWDIRURL_mkdirs(self):
1857 d = self.PUT(self.public_url + "/foo/subdir/newdir?t=mkdir", "")
1858 d.addCallback(lambda res:
1859 self.failIfNodeHasChild(self._foo_node, u"newdir"))
1860 d.addCallback(lambda res:
1861 self.failUnlessNodeHasChild(self._foo_node, u"subdir"))
1862 d.addCallback(lambda res:
1863 self._foo_node.get_child_at_path(u"subdir/newdir"))
1864 d.addCallback(self.failUnlessNodeKeysAre, [])
1867 def test_PUT_NEWDIRURL_mkdirs_mdmf(self):
1868 d = self.PUT(self.public_url + "/foo/subdir/newdir?t=mkdir&format=mdmf", "")
1869 d.addCallback(lambda ignored:
1870 self.failUnlessNodeHasChild(self._foo_node, u"subdir"))
1871 d.addCallback(lambda ignored:
1872 self.failIfNodeHasChild(self._foo_node, u"newdir"))
1873 d.addCallback(lambda ignored:
1874 self._foo_node.get_child_at_path(u"subdir"))
1875 def _got_subdir(subdir):
1876 # XXX: What we want?
1877 #self.failUnlessEqual(subdir._node.get_version(), MDMF_VERSION)
1878 self.failUnlessNodeHasChild(subdir, u"newdir")
1879 return subdir.get_child_at_path(u"newdir")
1880 d.addCallback(_got_subdir)
1881 d.addCallback(lambda newdir:
1882 self.failUnlessEqual(newdir._node.get_version(), MDMF_VERSION))
1885 def test_PUT_NEWDIRURL_mkdirs_sdmf(self):
1886 d = self.PUT(self.public_url + "/foo/subdir/newdir?t=mkdir&format=sdmf", "")
1887 d.addCallback(lambda ignored:
1888 self.failUnlessNodeHasChild(self._foo_node, u"subdir"))
1889 d.addCallback(lambda ignored:
1890 self.failIfNodeHasChild(self._foo_node, u"newdir"))
1891 d.addCallback(lambda ignored:
1892 self._foo_node.get_child_at_path(u"subdir"))
1893 def _got_subdir(subdir):
1894 # XXX: What we want?
1895 #self.failUnlessEqual(subdir._node.get_version(), MDMF_VERSION)
1896 self.failUnlessNodeHasChild(subdir, u"newdir")
1897 return subdir.get_child_at_path(u"newdir")
1898 d.addCallback(_got_subdir)
1899 d.addCallback(lambda newdir:
1900 self.failUnlessEqual(newdir._node.get_version(), SDMF_VERSION))
1903 def test_PUT_NEWDIRURL_mkdirs_bad_format(self):
1904 return self.shouldHTTPError("PUT_NEWDIRURL_mkdirs_bad_format",
1905 400, "Bad Request", "Unknown format: foo",
1906 self.PUT, self.public_url + \
1907 "/foo/subdir/newdir?t=mkdir&format=foo",
1910 def test_DELETE_DIRURL(self):
1911 d = self.DELETE(self.public_url + "/foo")
1912 d.addCallback(lambda res:
1913 self.failIfNodeHasChild(self.public_root, u"foo"))
1916 def test_DELETE_DIRURL_missing(self):
1917 d = self.DELETE(self.public_url + "/foo/missing")
1918 d.addBoth(self.should404, "test_DELETE_DIRURL_missing")
1919 d.addCallback(lambda res:
1920 self.failUnlessNodeHasChild(self.public_root, u"foo"))
1923 def test_DELETE_DIRURL_missing2(self):
1924 d = self.DELETE(self.public_url + "/missing")
1925 d.addBoth(self.should404, "test_DELETE_DIRURL_missing2")
1928 def dump_root(self):
1930 w = webish.DirnodeWalkerMixin()
1931 def visitor(childpath, childnode, metadata):
1933 d = w.walk(self.public_root, visitor)
1936 def failUnlessNodeKeysAre(self, node, expected_keys):
1937 for k in expected_keys:
1938 assert isinstance(k, unicode)
1940 def _check(children):
1941 self.failUnlessReallyEqual(sorted(children.keys()), sorted(expected_keys))
1942 d.addCallback(_check)
1944 def failUnlessNodeHasChild(self, node, name):
1945 assert isinstance(name, unicode)
1947 def _check(children):
1948 self.failUnless(name in children)
1949 d.addCallback(_check)
1951 def failIfNodeHasChild(self, node, name):
1952 assert isinstance(name, unicode)
1954 def _check(children):
1955 self.failIf(name in children)
1956 d.addCallback(_check)
1959 def failUnlessChildContentsAre(self, node, name, expected_contents):
1960 assert isinstance(name, unicode)
1961 d = node.get_child_at_path(name)
1962 d.addCallback(lambda node: download_to_data(node))
1963 def _check(contents):
1964 self.failUnlessReallyEqual(contents, expected_contents)
1965 d.addCallback(_check)
1968 def failUnlessMutableChildContentsAre(self, node, name, expected_contents):
1969 assert isinstance(name, unicode)
1970 d = node.get_child_at_path(name)
1971 d.addCallback(lambda node: node.download_best_version())
1972 def _check(contents):
1973 self.failUnlessReallyEqual(contents, expected_contents)
1974 d.addCallback(_check)
1977 def failUnlessRWChildURIIs(self, node, name, expected_uri):
1978 assert isinstance(name, unicode)
1979 d = node.get_child_at_path(name)
1981 self.failUnless(child.is_unknown() or not child.is_readonly())
1982 self.failUnlessReallyEqual(child.get_uri(), expected_uri.strip())
1983 self.failUnlessReallyEqual(child.get_write_uri(), expected_uri.strip())
1984 expected_ro_uri = self._make_readonly(expected_uri)
1986 self.failUnlessReallyEqual(child.get_readonly_uri(), expected_ro_uri.strip())
1987 d.addCallback(_check)
1990 def failUnlessROChildURIIs(self, node, name, expected_uri):
1991 assert isinstance(name, unicode)
1992 d = node.get_child_at_path(name)
1994 self.failUnless(child.is_unknown() or child.is_readonly())
1995 self.failUnlessReallyEqual(child.get_write_uri(), None)
1996 self.failUnlessReallyEqual(child.get_uri(), expected_uri.strip())
1997 self.failUnlessReallyEqual(child.get_readonly_uri(), expected_uri.strip())
1998 d.addCallback(_check)
2001 def failUnlessURIMatchesRWChild(self, got_uri, node, name):
2002 assert isinstance(name, unicode)
2003 d = node.get_child_at_path(name)
2005 self.failUnless(child.is_unknown() or not child.is_readonly())
2006 self.failUnlessReallyEqual(child.get_uri(), got_uri.strip())
2007 self.failUnlessReallyEqual(child.get_write_uri(), got_uri.strip())
2008 expected_ro_uri = self._make_readonly(got_uri)
2010 self.failUnlessReallyEqual(child.get_readonly_uri(), expected_ro_uri.strip())
2011 d.addCallback(_check)
2014 def failUnlessURIMatchesROChild(self, got_uri, node, name):
2015 assert isinstance(name, unicode)
2016 d = node.get_child_at_path(name)
2018 self.failUnless(child.is_unknown() or child.is_readonly())
2019 self.failUnlessReallyEqual(child.get_write_uri(), None)
2020 self.failUnlessReallyEqual(got_uri.strip(), child.get_uri())
2021 self.failUnlessReallyEqual(got_uri.strip(), child.get_readonly_uri())
2022 d.addCallback(_check)
2025 def failUnlessCHKURIHasContents(self, got_uri, contents):
2026 self.failUnless(FakeCHKFileNode.all_contents[got_uri] == contents)
2028 def test_POST_upload(self):
2029 d = self.POST(self.public_url + "/foo", t="upload",
2030 file=("new.txt", self.NEWFILE_CONTENTS))
2032 d.addCallback(self.failUnlessURIMatchesROChild, fn, u"new.txt")
2033 d.addCallback(lambda res:
2034 self.failUnlessChildContentsAre(fn, u"new.txt",
2035 self.NEWFILE_CONTENTS))
2038 def test_POST_upload_unicode(self):
2039 filename = u"n\u00e9wer.txt" # n e-acute w e r . t x t
2040 d = self.POST(self.public_url + "/foo", t="upload",
2041 file=(filename, self.NEWFILE_CONTENTS))
2043 d.addCallback(self.failUnlessURIMatchesROChild, fn, filename)
2044 d.addCallback(lambda res:
2045 self.failUnlessChildContentsAre(fn, filename,
2046 self.NEWFILE_CONTENTS))
2047 target_url = self.public_url + "/foo/" + filename.encode("utf-8")
2048 d.addCallback(lambda res: self.GET(target_url))
2049 d.addCallback(lambda contents: self.failUnlessReallyEqual(contents,
2050 self.NEWFILE_CONTENTS,
2054 def test_POST_upload_unicode_named(self):
2055 filename = u"n\u00e9wer.txt" # n e-acute w e r . t x t
2056 d = self.POST(self.public_url + "/foo", t="upload",
2058 file=("overridden", self.NEWFILE_CONTENTS))
2060 d.addCallback(self.failUnlessURIMatchesROChild, fn, filename)
2061 d.addCallback(lambda res:
2062 self.failUnlessChildContentsAre(fn, filename,
2063 self.NEWFILE_CONTENTS))
2064 target_url = self.public_url + "/foo/" + filename.encode("utf-8")
2065 d.addCallback(lambda res: self.GET(target_url))
2066 d.addCallback(lambda contents: self.failUnlessReallyEqual(contents,
2067 self.NEWFILE_CONTENTS,
2071 def test_POST_upload_no_link(self):
2072 d = self.POST("/uri", t="upload",
2073 file=("new.txt", self.NEWFILE_CONTENTS))
2074 def _check_upload_results(page):
2075 # this should be a page which describes the results of the upload
2076 # that just finished.
2077 self.failUnless("Upload Results:" in page)
2078 self.failUnless("URI:" in page)
2079 uri_re = re.compile("URI: <tt><span>(.*)</span>")
2080 mo = uri_re.search(page)
2081 self.failUnless(mo, page)
2082 new_uri = mo.group(1)
2084 d.addCallback(_check_upload_results)
2085 d.addCallback(self.failUnlessCHKURIHasContents, self.NEWFILE_CONTENTS)
2088 def test_POST_upload_no_link_whendone(self):
2089 d = self.POST("/uri", t="upload", when_done="/",
2090 file=("new.txt", self.NEWFILE_CONTENTS))
2091 d.addBoth(self.shouldRedirect, "/")
2094 def shouldRedirect2(self, which, checker, callable, *args, **kwargs):
2095 d = defer.maybeDeferred(callable, *args, **kwargs)
2097 if isinstance(res, failure.Failure):
2098 res.trap(error.PageRedirect)
2099 statuscode = res.value.status
2100 target = res.value.location
2101 return checker(statuscode, target)
2102 self.fail("%s: callable was supposed to redirect, not return '%s'"
2107 def test_POST_upload_no_link_whendone_results(self):
2108 def check(statuscode, target):
2109 self.failUnlessReallyEqual(statuscode, str(http.FOUND))
2110 self.failUnless(target.startswith(self.webish_url), target)
2111 return client.getPage(target, method="GET")
2112 d = self.shouldRedirect2("test_POST_upload_no_link_whendone_results",
2114 self.POST, "/uri", t="upload",
2115 when_done="/uri/%(uri)s",
2116 file=("new.txt", self.NEWFILE_CONTENTS))
2117 d.addCallback(lambda res:
2118 self.failUnlessReallyEqual(res, self.NEWFILE_CONTENTS))
2121 def test_POST_upload_no_link_mutable(self):
2122 d = self.POST("/uri", t="upload", mutable="true",
2123 file=("new.txt", self.NEWFILE_CONTENTS))
2124 def _check(filecap):
2125 filecap = filecap.strip()
2126 self.failUnless(filecap.startswith("URI:SSK:"), filecap)
2127 self.filecap = filecap
2128 u = uri.WriteableSSKFileURI.init_from_string(filecap)
2129 self.failUnless(u.get_storage_index() in FakeMutableFileNode.all_contents)
2130 n = self.s.create_node_from_uri(filecap)
2131 return n.download_best_version()
2132 d.addCallback(_check)
2134 self.failUnlessReallyEqual(data, self.NEWFILE_CONTENTS)
2135 return self.GET("/uri/%s" % urllib.quote(self.filecap))
2136 d.addCallback(_check2)
2138 self.failUnlessReallyEqual(data, self.NEWFILE_CONTENTS)
2139 return self.GET("/file/%s" % urllib.quote(self.filecap))
2140 d.addCallback(_check3)
2142 self.failUnlessReallyEqual(data, self.NEWFILE_CONTENTS)
2143 d.addCallback(_check4)
2146 def test_POST_upload_no_link_mutable_toobig(self):
2147 # The SDMF size limit is no longer in place, so we should be
2148 # able to upload mutable files that are as large as we want them
2150 d = self.POST("/uri", t="upload", mutable="true",
2151 file=("new.txt", "b" * (self.s.MUTABLE_SIZELIMIT + 1)))
2155 def test_POST_upload_format_unlinked(self):
2156 def _check_upload_unlinked(ign, format, uri_prefix):
2157 filename = format + ".txt"
2158 d = self.POST("/uri?t=upload&format=" + format,
2159 file=(filename, self.NEWFILE_CONTENTS * 300000))
2160 def _got_filecap(filecap):
2161 self.failUnless(filecap.startswith(uri_prefix))
2162 return self.GET("/uri/%s?t=json" % filecap)
2163 d.addCallback(_got_filecap)
2164 def _got_json(json):
2165 data = simplejson.loads(json)
2167 self.failUnlessIn("format", data)
2168 self.failUnlessEqual(data["format"], format)
2169 d.addCallback(_got_json)
2171 d = defer.succeed(None)
2172 d.addCallback(_check_upload_unlinked, "chk", "URI:CHK")
2173 d.addCallback(_check_upload_unlinked, "CHK", "URI:CHK")
2174 d.addCallback(_check_upload_unlinked, "sdmf", "URI:SSK")
2175 d.addCallback(_check_upload_unlinked, "mdmf", "URI:MDMF")
2178 def test_POST_upload_bad_format_unlinked(self):
2179 return self.shouldHTTPError("POST_upload_bad_format_unlinked",
2180 400, "Bad Request", "Unknown format: foo",
2182 "/uri?t=upload&format=foo",
2183 file=("foo.txt", self.NEWFILE_CONTENTS * 300000))
2185 def test_POST_upload_format(self):
2186 def _check_upload(ign, format, uri_prefix, fn=None):
2187 filename = format + ".txt"
2188 d = self.POST(self.public_url +
2189 "/foo?t=upload&format=" + format,
2190 file=(filename, self.NEWFILE_CONTENTS * 300000))
2191 def _got_filecap(filecap):
2193 filenameu = unicode(filename)
2194 self.failUnlessURIMatchesRWChild(filecap, fn, filenameu)
2195 self.failUnless(filecap.startswith(uri_prefix))
2196 return self.GET(self.public_url + "/foo/%s?t=json" % filename)
2197 d.addCallback(_got_filecap)
2198 def _got_json(json):
2199 data = simplejson.loads(json)
2201 self.failUnlessIn("format", data)
2202 self.failUnlessEqual(data["format"], format)
2203 d.addCallback(_got_json)
2205 d = defer.succeed(None)
2206 d.addCallback(_check_upload, "chk", "URI:CHK")
2207 d.addCallback(_check_upload, "sdmf", "URI:SSK", self._foo_node)
2208 d.addCallback(_check_upload, "mdmf", "URI:MDMF")
2209 d.addCallback(_check_upload, "MDMF", "URI:MDMF")
2212 def test_POST_upload_bad_format(self):
2213 return self.shouldHTTPError("POST_upload_bad_format",
2214 400, "Bad Request", "Unknown format: foo",
2215 self.POST, self.public_url + \
2216 "/foo?t=upload&format=foo",
2217 file=("foo.txt", self.NEWFILE_CONTENTS * 300000))
2219 def test_POST_upload_mutable(self):
2220 # this creates a mutable file
2221 d = self.POST(self.public_url + "/foo", t="upload", mutable="true",
2222 file=("new.txt", self.NEWFILE_CONTENTS))
2224 d.addCallback(self.failUnlessURIMatchesRWChild, fn, u"new.txt")
2225 d.addCallback(lambda res:
2226 self.failUnlessMutableChildContentsAre(fn, u"new.txt",
2227 self.NEWFILE_CONTENTS))
2228 d.addCallback(lambda res: self._foo_node.get(u"new.txt"))
2230 self.failUnless(IMutableFileNode.providedBy(newnode))
2231 self.failUnless(newnode.is_mutable())
2232 self.failIf(newnode.is_readonly())
2233 self._mutable_node = newnode
2234 self._mutable_uri = newnode.get_uri()
2237 # now upload it again and make sure that the URI doesn't change
2238 NEWER_CONTENTS = self.NEWFILE_CONTENTS + "newer\n"
2239 d.addCallback(lambda res:
2240 self.POST(self.public_url + "/foo", t="upload",
2242 file=("new.txt", NEWER_CONTENTS)))
2243 d.addCallback(self.failUnlessURIMatchesRWChild, fn, u"new.txt")
2244 d.addCallback(lambda res:
2245 self.failUnlessMutableChildContentsAre(fn, u"new.txt",
2247 d.addCallback(lambda res: self._foo_node.get(u"new.txt"))
2249 self.failUnless(IMutableFileNode.providedBy(newnode))
2250 self.failUnless(newnode.is_mutable())
2251 self.failIf(newnode.is_readonly())
2252 self.failUnlessReallyEqual(self._mutable_uri, newnode.get_uri())
2253 d.addCallback(_got2)
2255 # upload a second time, using PUT instead of POST
2256 NEW2_CONTENTS = NEWER_CONTENTS + "overwrite with PUT\n"
2257 d.addCallback(lambda res:
2258 self.PUT(self.public_url + "/foo/new.txt", NEW2_CONTENTS))
2259 d.addCallback(self.failUnlessURIMatchesRWChild, fn, u"new.txt")
2260 d.addCallback(lambda res:
2261 self.failUnlessMutableChildContentsAre(fn, u"new.txt",
2264 # finally list the directory, since mutable files are displayed
2265 # slightly differently
2267 d.addCallback(lambda res:
2268 self.GET(self.public_url + "/foo/",
2269 followRedirect=True))
2270 def _check_page(res):
2271 # TODO: assert more about the contents
2272 self.failUnless("SSK" in res)
2274 d.addCallback(_check_page)
2276 d.addCallback(lambda res: self._foo_node.get(u"new.txt"))
2278 self.failUnless(IMutableFileNode.providedBy(newnode))
2279 self.failUnless(newnode.is_mutable())
2280 self.failIf(newnode.is_readonly())
2281 self.failUnlessReallyEqual(self._mutable_uri, newnode.get_uri())
2282 d.addCallback(_got3)
2284 # look at the JSON form of the enclosing directory
2285 d.addCallback(lambda res:
2286 self.GET(self.public_url + "/foo/?t=json",
2287 followRedirect=True))
2288 def _check_page_json(res):
2289 parsed = simplejson.loads(res)
2290 self.failUnlessEqual(parsed[0], "dirnode")
2291 children = dict( [(unicode(name),value)
2293 in parsed[1]["children"].iteritems()] )
2294 self.failUnless(u"new.txt" in children)
2295 new_json = children[u"new.txt"]
2296 self.failUnlessEqual(new_json[0], "filenode")
2297 self.failUnless(new_json[1]["mutable"])
2298 self.failUnlessReallyEqual(to_str(new_json[1]["rw_uri"]), self._mutable_uri)
2299 ro_uri = self._mutable_node.get_readonly().to_string()
2300 self.failUnlessReallyEqual(to_str(new_json[1]["ro_uri"]), ro_uri)
2301 d.addCallback(_check_page_json)
2303 # and the JSON form of the file
2304 d.addCallback(lambda res:
2305 self.GET(self.public_url + "/foo/new.txt?t=json"))
2306 def _check_file_json(res):
2307 parsed = simplejson.loads(res)
2308 self.failUnlessEqual(parsed[0], "filenode")
2309 self.failUnless(parsed[1]["mutable"])
2310 self.failUnlessReallyEqual(to_str(parsed[1]["rw_uri"]), self._mutable_uri)
2311 ro_uri = self._mutable_node.get_readonly().to_string()
2312 self.failUnlessReallyEqual(to_str(parsed[1]["ro_uri"]), ro_uri)
2313 d.addCallback(_check_file_json)
2315 # and look at t=uri and t=readonly-uri
2316 d.addCallback(lambda res:
2317 self.GET(self.public_url + "/foo/new.txt?t=uri"))
2318 d.addCallback(lambda res: self.failUnlessReallyEqual(res, self._mutable_uri))
2319 d.addCallback(lambda res:
2320 self.GET(self.public_url + "/foo/new.txt?t=readonly-uri"))
2321 def _check_ro_uri(res):
2322 ro_uri = self._mutable_node.get_readonly().to_string()
2323 self.failUnlessReallyEqual(res, ro_uri)
2324 d.addCallback(_check_ro_uri)
2326 # make sure we can get to it from /uri/URI
2327 d.addCallback(lambda res:
2328 self.GET("/uri/%s" % urllib.quote(self._mutable_uri)))
2329 d.addCallback(lambda res:
2330 self.failUnlessReallyEqual(res, NEW2_CONTENTS))
2332 # and that HEAD computes the size correctly
2333 d.addCallback(lambda res:
2334 self.HEAD(self.public_url + "/foo/new.txt",
2335 return_response=True))
2336 def _got_headers((res, status, headers)):
2337 self.failUnlessReallyEqual(res, "")
2338 self.failUnlessReallyEqual(headers["content-length"][0],
2339 str(len(NEW2_CONTENTS)))
2340 self.failUnlessReallyEqual(headers["content-type"], ["text/plain"])
2341 d.addCallback(_got_headers)
2343 # make sure that outdated size limits aren't enforced anymore.
2344 d.addCallback(lambda ignored:
2345 self.POST(self.public_url + "/foo", t="upload",
2348 "b" * (self.s.MUTABLE_SIZELIMIT+1))))
2349 d.addErrback(self.dump_error)
2352 def test_POST_upload_mutable_toobig(self):
2353 # SDMF had a size limti that was removed a while ago. MDMF has
2354 # never had a size limit. Test to make sure that we do not
2355 # encounter errors when trying to upload large mutable files,
2356 # since there should be no coded prohibitions regarding large
2358 d = self.POST(self.public_url + "/foo",
2359 t="upload", mutable="true",
2360 file=("new.txt", "b" * (self.s.MUTABLE_SIZELIMIT + 1)))
2363 def dump_error(self, f):
2364 # if the web server returns an error code (like 400 Bad Request),
2365 # web.client.getPage puts the HTTP response body into the .response
2366 # attribute of the exception object that it gives back. It does not
2367 # appear in the Failure's repr(), so the ERROR that trial displays
2368 # will be rather terse and unhelpful. addErrback this method to the
2369 # end of your chain to get more information out of these errors.
2370 if f.check(error.Error):
2371 print "web.error.Error:"
2373 print f.value.response
2376 def test_POST_upload_replace(self):
2377 d = self.POST(self.public_url + "/foo", t="upload",
2378 file=("bar.txt", self.NEWFILE_CONTENTS))
2380 d.addCallback(self.failUnlessURIMatchesROChild, fn, u"bar.txt")
2381 d.addCallback(lambda res:
2382 self.failUnlessChildContentsAre(fn, u"bar.txt",
2383 self.NEWFILE_CONTENTS))
2386 def test_POST_upload_no_replace_ok(self):
2387 d = self.POST(self.public_url + "/foo?replace=false", t="upload",
2388 file=("new.txt", self.NEWFILE_CONTENTS))
2389 d.addCallback(lambda res: self.GET(self.public_url + "/foo/new.txt"))
2390 d.addCallback(lambda res: self.failUnlessReallyEqual(res,
2391 self.NEWFILE_CONTENTS))
2394 def test_POST_upload_no_replace_queryarg(self):
2395 d = self.POST(self.public_url + "/foo?replace=false", t="upload",
2396 file=("bar.txt", self.NEWFILE_CONTENTS))
2397 d.addBoth(self.shouldFail, error.Error,
2398 "POST_upload_no_replace_queryarg",
2400 "There was already a child by that name, and you asked me "
2401 "to not replace it")
2402 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
2403 d.addCallback(self.failUnlessIsBarDotTxt)
2406 def test_POST_upload_no_replace_field(self):
2407 d = self.POST(self.public_url + "/foo", t="upload", replace="false",
2408 file=("bar.txt", self.NEWFILE_CONTENTS))
2409 d.addBoth(self.shouldFail, error.Error, "POST_upload_no_replace_field",
2411 "There was already a child by that name, and you asked me "
2412 "to not replace it")
2413 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
2414 d.addCallback(self.failUnlessIsBarDotTxt)
2417 def test_POST_upload_whendone(self):
2418 d = self.POST(self.public_url + "/foo", t="upload", when_done="/THERE",
2419 file=("new.txt", self.NEWFILE_CONTENTS))
2420 d.addBoth(self.shouldRedirect, "/THERE")
2422 d.addCallback(lambda res:
2423 self.failUnlessChildContentsAre(fn, u"new.txt",
2424 self.NEWFILE_CONTENTS))
2427 def test_POST_upload_named(self):
2429 d = self.POST(self.public_url + "/foo", t="upload",
2430 name="new.txt", file=self.NEWFILE_CONTENTS)
2431 d.addCallback(self.failUnlessURIMatchesROChild, fn, u"new.txt")
2432 d.addCallback(lambda res:
2433 self.failUnlessChildContentsAre(fn, u"new.txt",
2434 self.NEWFILE_CONTENTS))
2437 def test_POST_upload_named_badfilename(self):
2438 d = self.POST(self.public_url + "/foo", t="upload",
2439 name="slashes/are/bad.txt", file=self.NEWFILE_CONTENTS)
2440 d.addBoth(self.shouldFail, error.Error,
2441 "test_POST_upload_named_badfilename",
2443 "name= may not contain a slash",
2445 # make sure that nothing was added
2446 d.addCallback(lambda res:
2447 self.failUnlessNodeKeysAre(self._foo_node,
2448 [u"bar.txt", u"baz.txt", u"blockingfile",
2449 u"empty", u"n\u00fc.txt", u"quux.txt",
2453 def test_POST_FILEURL_check(self):
2454 bar_url = self.public_url + "/foo/bar.txt"
2455 d = self.POST(bar_url, t="check")
2457 self.failUnless("Healthy :" in res)
2458 d.addCallback(_check)
2459 redir_url = "http://allmydata.org/TARGET"
2460 def _check2(statuscode, target):
2461 self.failUnlessReallyEqual(statuscode, str(http.FOUND))
2462 self.failUnlessReallyEqual(target, redir_url)
2463 d.addCallback(lambda res:
2464 self.shouldRedirect2("test_POST_FILEURL_check",
2468 when_done=redir_url))
2469 d.addCallback(lambda res:
2470 self.POST(bar_url, t="check", return_to=redir_url))
2472 self.failUnless("Healthy :" in res)
2473 self.failUnless("Return to file" in res)
2474 self.failUnless(redir_url in res)
2475 d.addCallback(_check3)
2477 d.addCallback(lambda res:
2478 self.POST(bar_url, t="check", output="JSON"))
2479 def _check_json(res):
2480 data = simplejson.loads(res)
2481 self.failUnless("storage-index" in data)
2482 self.failUnless(data["results"]["healthy"])
2483 d.addCallback(_check_json)
2487 def test_POST_FILEURL_check_and_repair(self):
2488 bar_url = self.public_url + "/foo/bar.txt"
2489 d = self.POST(bar_url, t="check", repair="true")
2491 self.failUnless("Healthy :" in res)
2492 d.addCallback(_check)
2493 redir_url = "http://allmydata.org/TARGET"
2494 def _check2(statuscode, target):
2495 self.failUnlessReallyEqual(statuscode, str(http.FOUND))
2496 self.failUnlessReallyEqual(target, redir_url)
2497 d.addCallback(lambda res:
2498 self.shouldRedirect2("test_POST_FILEURL_check_and_repair",
2501 t="check", repair="true",
2502 when_done=redir_url))
2503 d.addCallback(lambda res:
2504 self.POST(bar_url, t="check", return_to=redir_url))
2506 self.failUnless("Healthy :" in res)
2507 self.failUnless("Return to file" in res)
2508 self.failUnless(redir_url in res)
2509 d.addCallback(_check3)
2512 def test_POST_DIRURL_check(self):
2513 foo_url = self.public_url + "/foo/"
2514 d = self.POST(foo_url, t="check")
2516 self.failUnless("Healthy :" in res, res)
2517 d.addCallback(_check)
2518 redir_url = "http://allmydata.org/TARGET"
2519 def _check2(statuscode, target):
2520 self.failUnlessReallyEqual(statuscode, str(http.FOUND))
2521 self.failUnlessReallyEqual(target, redir_url)
2522 d.addCallback(lambda res:
2523 self.shouldRedirect2("test_POST_DIRURL_check",
2527 when_done=redir_url))
2528 d.addCallback(lambda res:
2529 self.POST(foo_url, t="check", return_to=redir_url))
2531 self.failUnless("Healthy :" in res, res)
2532 self.failUnless("Return to file/directory" in res)
2533 self.failUnless(redir_url in res)
2534 d.addCallback(_check3)
2536 d.addCallback(lambda res:
2537 self.POST(foo_url, t="check", output="JSON"))
2538 def _check_json(res):
2539 data = simplejson.loads(res)
2540 self.failUnless("storage-index" in data)
2541 self.failUnless(data["results"]["healthy"])
2542 d.addCallback(_check_json)
2546 def test_POST_DIRURL_check_and_repair(self):
2547 foo_url = self.public_url + "/foo/"
2548 d = self.POST(foo_url, t="check", repair="true")
2550 self.failUnless("Healthy :" in res, res)
2551 d.addCallback(_check)
2552 redir_url = "http://allmydata.org/TARGET"
2553 def _check2(statuscode, target):
2554 self.failUnlessReallyEqual(statuscode, str(http.FOUND))
2555 self.failUnlessReallyEqual(target, redir_url)
2556 d.addCallback(lambda res:
2557 self.shouldRedirect2("test_POST_DIRURL_check_and_repair",
2560 t="check", repair="true",
2561 when_done=redir_url))
2562 d.addCallback(lambda res:
2563 self.POST(foo_url, t="check", return_to=redir_url))
2565 self.failUnless("Healthy :" in res)
2566 self.failUnless("Return to file/directory" in res)
2567 self.failUnless(redir_url in res)
2568 d.addCallback(_check3)
2571 def test_POST_FILEURL_mdmf_check(self):
2572 quux_url = "/uri/%s" % urllib.quote(self._quux_txt_uri)
2573 d = self.POST(quux_url, t="check")
2575 self.failUnlessIn("Healthy", res)
2576 d.addCallback(_check)
2577 quux_extension_url = "/uri/%s" % urllib.quote("%s:3:131073" % self._quux_txt_uri)
2578 d.addCallback(lambda ignored:
2579 self.POST(quux_extension_url, t="check"))
2580 d.addCallback(_check)
2583 def test_POST_FILEURL_mdmf_check_and_repair(self):
2584 quux_url = "/uri/%s" % urllib.quote(self._quux_txt_uri)
2585 d = self.POST(quux_url, t="check", repair="true")
2587 self.failUnlessIn("Healthy", res)
2588 d.addCallback(_check)
2589 quux_extension_url = "/uri/%s" %\
2590 urllib.quote("%s:3:131073" % self._quux_txt_uri)
2591 d.addCallback(lambda ignored:
2592 self.POST(quux_extension_url, t="check", repair="true"))
2593 d.addCallback(_check)
2596 def wait_for_operation(self, ignored, ophandle):
2597 url = "/operations/" + ophandle
2598 url += "?t=status&output=JSON"
2601 data = simplejson.loads(res)
2602 if not data["finished"]:
2603 d = self.stall(delay=1.0)
2604 d.addCallback(self.wait_for_operation, ophandle)
2610 def get_operation_results(self, ignored, ophandle, output=None):
2611 url = "/operations/" + ophandle
2614 url += "&output=" + output
2617 if output and output.lower() == "json":
2618 return simplejson.loads(res)
2623 def test_POST_DIRURL_deepcheck_no_ophandle(self):
2624 d = self.shouldFail2(error.Error,
2625 "test_POST_DIRURL_deepcheck_no_ophandle",
2627 "slow operation requires ophandle=",
2628 self.POST, self.public_url, t="start-deep-check")
2631 def test_POST_DIRURL_deepcheck(self):
2632 def _check_redirect(statuscode, target):
2633 self.failUnlessReallyEqual(statuscode, str(http.FOUND))
2634 self.failUnless(target.endswith("/operations/123"))
2635 d = self.shouldRedirect2("test_POST_DIRURL_deepcheck", _check_redirect,
2636 self.POST, self.public_url,
2637 t="start-deep-check", ophandle="123")
2638 d.addCallback(self.wait_for_operation, "123")
2639 def _check_json(data):
2640 self.failUnlessReallyEqual(data["finished"], True)
2641 self.failUnlessReallyEqual(data["count-objects-checked"], 10)
2642 self.failUnlessReallyEqual(data["count-objects-healthy"], 10)
2643 d.addCallback(_check_json)
2644 d.addCallback(self.get_operation_results, "123", "html")
2645 def _check_html(res):
2646 self.failUnless("Objects Checked: <span>10</span>" in res)
2647 self.failUnless("Objects Healthy: <span>10</span>" in res)
2648 d.addCallback(_check_html)
2650 d.addCallback(lambda res:
2651 self.GET("/operations/123/"))
2652 d.addCallback(_check_html) # should be the same as without the slash
2654 d.addCallback(lambda res:
2655 self.shouldFail2(error.Error, "one", "404 Not Found",
2656 "No detailed results for SI bogus",
2657 self.GET, "/operations/123/bogus"))
2659 foo_si = self._foo_node.get_storage_index()
2660 foo_si_s = base32.b2a(foo_si)
2661 d.addCallback(lambda res:
2662 self.GET("/operations/123/%s?output=JSON" % foo_si_s))
2663 def _check_foo_json(res):
2664 data = simplejson.loads(res)
2665 self.failUnlessEqual(data["storage-index"], foo_si_s)
2666 self.failUnless(data["results"]["healthy"])
2667 d.addCallback(_check_foo_json)
2670 def test_POST_DIRURL_deepcheck_and_repair(self):
2671 d = self.POST(self.public_url, t="start-deep-check", repair="true",
2672 ophandle="124", output="json", followRedirect=True)
2673 d.addCallback(self.wait_for_operation, "124")
2674 def _check_json(data):
2675 self.failUnlessReallyEqual(data["finished"], True)
2676 self.failUnlessReallyEqual(data["count-objects-checked"], 10)
2677 self.failUnlessReallyEqual(data["count-objects-healthy-pre-repair"], 10)
2678 self.failUnlessReallyEqual(data["count-objects-unhealthy-pre-repair"], 0)
2679 self.failUnlessReallyEqual(data["count-corrupt-shares-pre-repair"], 0)
2680 self.failUnlessReallyEqual(data["count-repairs-attempted"], 0)
2681 self.failUnlessReallyEqual(data["count-repairs-successful"], 0)
2682 self.failUnlessReallyEqual(data["count-repairs-unsuccessful"], 0)
2683 self.failUnlessReallyEqual(data["count-objects-healthy-post-repair"], 10)
2684 self.failUnlessReallyEqual(data["count-objects-unhealthy-post-repair"], 0)
2685 self.failUnlessReallyEqual(data["count-corrupt-shares-post-repair"], 0)
2686 d.addCallback(_check_json)
2687 d.addCallback(self.get_operation_results, "124", "html")
2688 def _check_html(res):
2689 self.failUnless("Objects Checked: <span>10</span>" in res)
2691 self.failUnless("Objects Healthy (before repair): <span>10</span>" in res)
2692 self.failUnless("Objects Unhealthy (before repair): <span>0</span>" in res)
2693 self.failUnless("Corrupt Shares (before repair): <span>0</span>" in res)
2695 self.failUnless("Repairs Attempted: <span>0</span>" in res)
2696 self.failUnless("Repairs Successful: <span>0</span>" in res)
2697 self.failUnless("Repairs Unsuccessful: <span>0</span>" in res)
2699 self.failUnless("Objects Healthy (after repair): <span>10</span>" in res)
2700 self.failUnless("Objects Unhealthy (after repair): <span>0</span>" in res)
2701 self.failUnless("Corrupt Shares (after repair): <span>0</span>" in res)
2702 d.addCallback(_check_html)
2705 def test_POST_FILEURL_bad_t(self):
2706 d = self.shouldFail2(error.Error, "POST_bad_t", "400 Bad Request",
2707 "POST to file: bad t=bogus",
2708 self.POST, self.public_url + "/foo/bar.txt",
2712 def test_POST_mkdir(self): # return value?
2713 d = self.POST(self.public_url + "/foo", t="mkdir", name="newdir")
2714 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2715 d.addCallback(self.failUnlessNodeKeysAre, [])
2718 def test_POST_mkdir_mdmf(self):
2719 d = self.POST(self.public_url + "/foo?t=mkdir&name=newdir&format=mdmf")
2720 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2721 d.addCallback(lambda node:
2722 self.failUnlessEqual(node._node.get_version(), MDMF_VERSION))
2725 def test_POST_mkdir_sdmf(self):
2726 d = self.POST(self.public_url + "/foo?t=mkdir&name=newdir&format=sdmf")
2727 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2728 d.addCallback(lambda node:
2729 self.failUnlessEqual(node._node.get_version(), SDMF_VERSION))
2732 def test_POST_mkdir_bad_format(self):
2733 return self.shouldHTTPError("POST_mkdir_bad_format",
2734 400, "Bad Request", "Unknown format: foo",
2735 self.POST, self.public_url +
2736 "/foo?t=mkdir&name=newdir&format=foo")
2738 def test_POST_mkdir_initial_children(self):
2739 (newkids, caps) = self._create_initial_children()
2740 d = self.POST2(self.public_url +
2741 "/foo?t=mkdir-with-children&name=newdir",
2742 simplejson.dumps(newkids))
2743 d.addCallback(lambda res:
2744 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
2745 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2746 d.addCallback(self.failUnlessNodeKeysAre, newkids.keys())
2747 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2748 d.addCallback(self.failUnlessROChildURIIs, u"child-imm", caps['filecap1'])
2751 def test_POST_mkdir_initial_children_mdmf(self):
2752 (newkids, caps) = self._create_initial_children()
2753 d = self.POST2(self.public_url +
2754 "/foo?t=mkdir-with-children&name=newdir&format=mdmf",
2755 simplejson.dumps(newkids))
2756 d.addCallback(lambda res:
2757 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
2758 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2759 d.addCallback(lambda node:
2760 self.failUnlessEqual(node._node.get_version(), MDMF_VERSION))
2761 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2762 d.addCallback(self.failUnlessROChildURIIs, u"child-imm",
2767 def test_POST_mkdir_initial_children_sdmf(self):
2768 (newkids, caps) = self._create_initial_children()
2769 d = self.POST2(self.public_url +
2770 "/foo?t=mkdir-with-children&name=newdir&format=sdmf",
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(lambda node:
2776 self.failUnlessEqual(node._node.get_version(), SDMF_VERSION))
2777 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2778 d.addCallback(self.failUnlessROChildURIIs, u"child-imm",
2782 def test_POST_mkdir_initial_children_bad_format(self):
2783 (newkids, caps) = self._create_initial_children()
2784 return self.shouldHTTPError("POST_mkdir_initial_children_bad_format",
2785 400, "Bad Request", "Unknown format: foo",
2786 self.POST, self.public_url + \
2787 "/foo?t=mkdir-with-children&name=newdir&format=foo",
2788 simplejson.dumps(newkids))
2790 def test_POST_mkdir_immutable(self):
2791 (newkids, caps) = self._create_immutable_children()
2792 d = self.POST2(self.public_url +
2793 "/foo?t=mkdir-immutable&name=newdir",
2794 simplejson.dumps(newkids))
2795 d.addCallback(lambda res:
2796 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
2797 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2798 d.addCallback(self.failUnlessNodeKeysAre, newkids.keys())
2799 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2800 d.addCallback(self.failUnlessROChildURIIs, u"child-imm", caps['filecap1'])
2801 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2802 d.addCallback(self.failUnlessROChildURIIs, u"unknownchild-imm", caps['unknown_immcap'])
2803 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2804 d.addCallback(self.failUnlessROChildURIIs, u"dirchild-imm", caps['immdircap'])
2805 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2806 d.addCallback(self.failUnlessROChildURIIs, u"dirchild-lit", caps['litdircap'])
2807 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2808 d.addCallback(self.failUnlessROChildURIIs, u"dirchild-empty", caps['emptydircap'])
2811 def test_POST_mkdir_immutable_bad(self):
2812 (newkids, caps) = self._create_initial_children()
2813 d = self.shouldFail2(error.Error, "POST_mkdir_immutable_bad",
2815 "needed to be immutable but was not",
2818 "/foo?t=mkdir-immutable&name=newdir",
2819 simplejson.dumps(newkids))
2822 def test_POST_mkdir_2(self):
2823 d = self.POST(self.public_url + "/foo/newdir?t=mkdir", "")
2824 d.addCallback(lambda res:
2825 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
2826 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2827 d.addCallback(self.failUnlessNodeKeysAre, [])
2830 def test_POST_mkdirs_2(self):
2831 d = self.POST(self.public_url + "/foo/bardir/newdir?t=mkdir", "")
2832 d.addCallback(lambda res:
2833 self.failUnlessNodeHasChild(self._foo_node, u"bardir"))
2834 d.addCallback(lambda res: self._foo_node.get(u"bardir"))
2835 d.addCallback(lambda bardirnode: bardirnode.get(u"newdir"))
2836 d.addCallback(self.failUnlessNodeKeysAre, [])
2839 def test_POST_mkdir_no_parentdir_noredirect(self):
2840 d = self.POST("/uri?t=mkdir")
2841 def _after_mkdir(res):
2842 uri.DirectoryURI.init_from_string(res)
2843 d.addCallback(_after_mkdir)
2846 def test_POST_mkdir_no_parentdir_noredirect_mdmf(self):
2847 d = self.POST("/uri?t=mkdir&format=mdmf")
2848 def _after_mkdir(res):
2849 u = uri.from_string(res)
2850 # Check that this is an MDMF writecap
2851 self.failUnlessIsInstance(u, uri.MDMFDirectoryURI)
2852 d.addCallback(_after_mkdir)
2855 def test_POST_mkdir_no_parentdir_noredirect_sdmf(self):
2856 d = self.POST("/uri?t=mkdir&format=sdmf")
2857 def _after_mkdir(res):
2858 u = uri.from_string(res)
2859 self.failUnlessIsInstance(u, uri.DirectoryURI)
2860 d.addCallback(_after_mkdir)
2863 def test_POST_mkdir_no_parentdir_noredirect_bad_format(self):
2864 return self.shouldHTTPError("POST_mkdir_no_parentdir_noredirect_bad_format",
2865 400, "Bad Request", "Unknown format: foo",
2866 self.POST, self.public_url +
2867 "/uri?t=mkdir&format=foo")
2869 def test_POST_mkdir_no_parentdir_noredirect2(self):
2870 # make sure form-based arguments (as on the welcome page) still work
2871 d = self.POST("/uri", t="mkdir")
2872 def _after_mkdir(res):
2873 uri.DirectoryURI.init_from_string(res)
2874 d.addCallback(_after_mkdir)
2875 d.addErrback(self.explain_web_error)
2878 def test_POST_mkdir_no_parentdir_redirect(self):
2879 d = self.POST("/uri?t=mkdir&redirect_to_result=true")
2880 d.addBoth(self.shouldRedirect, None, statuscode='303')
2881 def _check_target(target):
2882 target = urllib.unquote(target)
2883 self.failUnless(target.startswith("uri/URI:DIR2:"), target)
2884 d.addCallback(_check_target)
2887 def test_POST_mkdir_no_parentdir_redirect2(self):
2888 d = self.POST("/uri", t="mkdir", redirect_to_result="true")
2889 d.addBoth(self.shouldRedirect, None, statuscode='303')
2890 def _check_target(target):
2891 target = urllib.unquote(target)
2892 self.failUnless(target.startswith("uri/URI:DIR2:"), target)
2893 d.addCallback(_check_target)
2894 d.addErrback(self.explain_web_error)
2897 def _make_readonly(self, u):
2898 ro_uri = uri.from_string(u).get_readonly()
2901 return ro_uri.to_string()
2903 def _create_initial_children(self):
2904 contents, n, filecap1 = self.makefile(12)
2905 md1 = {"metakey1": "metavalue1"}
2906 filecap2 = make_mutable_file_uri()
2907 node3 = self.s.create_node_from_uri(make_mutable_file_uri())
2908 filecap3 = node3.get_readonly_uri()
2909 node4 = self.s.create_node_from_uri(make_mutable_file_uri())
2910 dircap = DirectoryNode(node4, None, None).get_uri()
2911 mdmfcap = make_mutable_file_uri(mdmf=True)
2912 litdircap = "URI:DIR2-LIT:ge3dumj2mewdcotyfqydulbshj5x2lbm"
2913 emptydircap = "URI:DIR2-LIT:"
2914 newkids = {u"child-imm": ["filenode", {"rw_uri": filecap1,
2915 "ro_uri": self._make_readonly(filecap1),
2916 "metadata": md1, }],
2917 u"child-mutable": ["filenode", {"rw_uri": filecap2,
2918 "ro_uri": self._make_readonly(filecap2)}],
2919 u"child-mutable-ro": ["filenode", {"ro_uri": filecap3}],
2920 u"unknownchild-rw": ["unknown", {"rw_uri": unknown_rwcap,
2921 "ro_uri": unknown_rocap}],
2922 u"unknownchild-ro": ["unknown", {"ro_uri": unknown_rocap}],
2923 u"unknownchild-imm": ["unknown", {"ro_uri": unknown_immcap}],
2924 u"dirchild": ["dirnode", {"rw_uri": dircap,
2925 "ro_uri": self._make_readonly(dircap)}],
2926 u"dirchild-lit": ["dirnode", {"ro_uri": litdircap}],
2927 u"dirchild-empty": ["dirnode", {"ro_uri": emptydircap}],
2928 u"child-mutable-mdmf": ["filenode", {"rw_uri": mdmfcap,
2929 "ro_uri": self._make_readonly(mdmfcap)}],
2931 return newkids, {'filecap1': filecap1,
2932 'filecap2': filecap2,
2933 'filecap3': filecap3,
2934 'unknown_rwcap': unknown_rwcap,
2935 'unknown_rocap': unknown_rocap,
2936 'unknown_immcap': unknown_immcap,
2938 'litdircap': litdircap,
2939 'emptydircap': emptydircap,
2942 def _create_immutable_children(self):
2943 contents, n, filecap1 = self.makefile(12)
2944 md1 = {"metakey1": "metavalue1"}
2945 tnode = create_chk_filenode("immutable directory contents\n"*10)
2946 dnode = DirectoryNode(tnode, None, None)
2947 assert not dnode.is_mutable()
2948 immdircap = dnode.get_uri()
2949 litdircap = "URI:DIR2-LIT:ge3dumj2mewdcotyfqydulbshj5x2lbm"
2950 emptydircap = "URI:DIR2-LIT:"
2951 newkids = {u"child-imm": ["filenode", {"ro_uri": filecap1,
2952 "metadata": md1, }],
2953 u"unknownchild-imm": ["unknown", {"ro_uri": unknown_immcap}],
2954 u"dirchild-imm": ["dirnode", {"ro_uri": immdircap}],
2955 u"dirchild-lit": ["dirnode", {"ro_uri": litdircap}],
2956 u"dirchild-empty": ["dirnode", {"ro_uri": emptydircap}],
2958 return newkids, {'filecap1': filecap1,
2959 'unknown_immcap': unknown_immcap,
2960 'immdircap': immdircap,
2961 'litdircap': litdircap,
2962 'emptydircap': emptydircap}
2964 def test_POST_mkdir_no_parentdir_initial_children(self):
2965 (newkids, caps) = self._create_initial_children()
2966 d = self.POST2("/uri?t=mkdir-with-children", simplejson.dumps(newkids))
2967 def _after_mkdir(res):
2968 self.failUnless(res.startswith("URI:DIR"), res)
2969 n = self.s.create_node_from_uri(res)
2970 d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
2971 d2.addCallback(lambda ign:
2972 self.failUnlessROChildURIIs(n, u"child-imm",
2974 d2.addCallback(lambda ign:
2975 self.failUnlessRWChildURIIs(n, u"child-mutable",
2977 d2.addCallback(lambda ign:
2978 self.failUnlessROChildURIIs(n, u"child-mutable-ro",
2980 d2.addCallback(lambda ign:
2981 self.failUnlessRWChildURIIs(n, u"unknownchild-rw",
2982 caps['unknown_rwcap']))
2983 d2.addCallback(lambda ign:
2984 self.failUnlessROChildURIIs(n, u"unknownchild-ro",
2985 caps['unknown_rocap']))
2986 d2.addCallback(lambda ign:
2987 self.failUnlessROChildURIIs(n, u"unknownchild-imm",
2988 caps['unknown_immcap']))
2989 d2.addCallback(lambda ign:
2990 self.failUnlessRWChildURIIs(n, u"dirchild",
2993 d.addCallback(_after_mkdir)
2996 def test_POST_mkdir_no_parentdir_unexpected_children(self):
2997 # the regular /uri?t=mkdir operation is specified to ignore its body.
2998 # Only t=mkdir-with-children pays attention to it.
2999 (newkids, caps) = self._create_initial_children()
3000 d = self.shouldHTTPError("POST_mkdir_no_parentdir_unexpected_children",
3002 "t=mkdir does not accept children=, "
3003 "try t=mkdir-with-children instead",
3004 self.POST2, "/uri?t=mkdir", # without children
3005 simplejson.dumps(newkids))
3008 def test_POST_noparent_bad(self):
3009 d = self.shouldHTTPError("POST_noparent_bad",
3011 "/uri accepts only PUT, PUT?t=mkdir, "
3012 "POST?t=upload, and POST?t=mkdir",
3013 self.POST, "/uri?t=bogus")
3016 def test_POST_mkdir_no_parentdir_immutable(self):
3017 (newkids, caps) = self._create_immutable_children()
3018 d = self.POST2("/uri?t=mkdir-immutable", simplejson.dumps(newkids))
3019 def _after_mkdir(res):
3020 self.failUnless(res.startswith("URI:DIR"), res)
3021 n = self.s.create_node_from_uri(res)
3022 d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
3023 d2.addCallback(lambda ign:
3024 self.failUnlessROChildURIIs(n, u"child-imm",
3026 d2.addCallback(lambda ign:
3027 self.failUnlessROChildURIIs(n, u"unknownchild-imm",
3028 caps['unknown_immcap']))
3029 d2.addCallback(lambda ign:
3030 self.failUnlessROChildURIIs(n, u"dirchild-imm",
3032 d2.addCallback(lambda ign:
3033 self.failUnlessROChildURIIs(n, u"dirchild-lit",
3035 d2.addCallback(lambda ign:
3036 self.failUnlessROChildURIIs(n, u"dirchild-empty",
3037 caps['emptydircap']))
3039 d.addCallback(_after_mkdir)
3042 def test_POST_mkdir_no_parentdir_immutable_bad(self):
3043 (newkids, caps) = self._create_initial_children()
3044 d = self.shouldFail2(error.Error,
3045 "test_POST_mkdir_no_parentdir_immutable_bad",
3047 "needed to be immutable but was not",
3049 "/uri?t=mkdir-immutable",
3050 simplejson.dumps(newkids))
3053 def test_welcome_page_mkdir_button(self):
3054 # Fetch the welcome page.
3056 def _after_get_welcome_page(res):
3057 MKDIR_BUTTON_RE = re.compile(
3058 '<form action="([^"]*)" method="post".*?'
3059 '<input type="hidden" name="t" value="([^"]*)" />'
3060 '<input type="hidden" name="([^"]*)" value="([^"]*)" />'
3061 '<input type="submit" value="Create a directory" />',
3063 mo = MKDIR_BUTTON_RE.search(res)
3064 formaction = mo.group(1)
3066 formaname = mo.group(3)
3067 formavalue = mo.group(4)
3068 return (formaction, formt, formaname, formavalue)
3069 d.addCallback(_after_get_welcome_page)
3070 def _after_parse_form(res):
3071 (formaction, formt, formaname, formavalue) = res
3072 return self.POST("/%s?t=%s&%s=%s" % (formaction, formt, formaname, formavalue))
3073 d.addCallback(_after_parse_form)
3074 d.addBoth(self.shouldRedirect, None, statuscode='303')
3077 def test_POST_mkdir_replace(self): # return value?
3078 d = self.POST(self.public_url + "/foo", t="mkdir", name="sub")
3079 d.addCallback(lambda res: self._foo_node.get(u"sub"))
3080 d.addCallback(self.failUnlessNodeKeysAre, [])
3083 def test_POST_mkdir_no_replace_queryarg(self): # return value?
3084 d = self.POST(self.public_url + "/foo?replace=false", t="mkdir", name="sub")
3085 d.addBoth(self.shouldFail, error.Error,
3086 "POST_mkdir_no_replace_queryarg",
3088 "There was already a child by that name, and you asked me "
3089 "to not replace it")
3090 d.addCallback(lambda res: self._foo_node.get(u"sub"))
3091 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
3094 def test_POST_mkdir_no_replace_field(self): # return value?
3095 d = self.POST(self.public_url + "/foo", t="mkdir", name="sub",
3097 d.addBoth(self.shouldFail, error.Error, "POST_mkdir_no_replace_field",
3099 "There was already a child by that name, and you asked me "
3100 "to not replace it")
3101 d.addCallback(lambda res: self._foo_node.get(u"sub"))
3102 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
3105 def test_POST_mkdir_whendone_field(self):
3106 d = self.POST(self.public_url + "/foo",
3107 t="mkdir", name="newdir", when_done="/THERE")
3108 d.addBoth(self.shouldRedirect, "/THERE")
3109 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
3110 d.addCallback(self.failUnlessNodeKeysAre, [])
3113 def test_POST_mkdir_whendone_queryarg(self):
3114 d = self.POST(self.public_url + "/foo?when_done=/THERE",
3115 t="mkdir", name="newdir")
3116 d.addBoth(self.shouldRedirect, "/THERE")
3117 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
3118 d.addCallback(self.failUnlessNodeKeysAre, [])
3121 def test_POST_bad_t(self):
3122 d = self.shouldFail2(error.Error, "POST_bad_t",
3124 "POST to a directory with bad t=BOGUS",
3125 self.POST, self.public_url + "/foo", t="BOGUS")
3128 def test_POST_set_children(self, command_name="set_children"):
3129 contents9, n9, newuri9 = self.makefile(9)
3130 contents10, n10, newuri10 = self.makefile(10)
3131 contents11, n11, newuri11 = self.makefile(11)
3134 "atomic_added_1": [ "filenode", { "rw_uri": "%s",
3137 "ctime": 1002777696.7564139,
3138 "mtime": 1002777696.7564139
3141 "atomic_added_2": [ "filenode", { "rw_uri": "%s",
3144 "ctime": 1002777696.7564139,
3145 "mtime": 1002777696.7564139
3148 "atomic_added_3": [ "filenode", { "rw_uri": "%s",
3151 "ctime": 1002777696.7564139,
3152 "mtime": 1002777696.7564139
3155 }""" % (newuri9, newuri10, newuri11)
3157 url = self.webish_url + self.public_url + "/foo" + "?t=" + command_name
3159 d = client.getPage(url, method="POST", postdata=reqbody)
3161 self.failUnlessURIMatchesROChild(newuri9, self._foo_node, u"atomic_added_1")
3162 self.failUnlessURIMatchesROChild(newuri10, self._foo_node, u"atomic_added_2")
3163 self.failUnlessURIMatchesROChild(newuri11, self._foo_node, u"atomic_added_3")
3165 d.addCallback(_then)
3166 d.addErrback(self.dump_error)
3169 def test_POST_set_children_with_hyphen(self):
3170 return self.test_POST_set_children(command_name="set-children")
3172 def test_POST_link_uri(self):
3173 contents, n, newuri = self.makefile(8)
3174 d = self.POST(self.public_url + "/foo", t="uri", name="new.txt", uri=newuri)
3175 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"new.txt")
3176 d.addCallback(lambda res:
3177 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
3181 def test_POST_link_uri_replace(self):
3182 contents, n, newuri = self.makefile(8)
3183 d = self.POST(self.public_url + "/foo", t="uri", name="bar.txt", uri=newuri)
3184 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"bar.txt")
3185 d.addCallback(lambda res:
3186 self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
3190 def test_POST_link_uri_unknown_bad(self):
3191 d = self.POST(self.public_url + "/foo", t="uri", name="future.txt", uri=unknown_rwcap)
3192 d.addBoth(self.shouldFail, error.Error,
3193 "POST_link_uri_unknown_bad",
3195 "unknown cap in a write slot")
3198 def test_POST_link_uri_unknown_ro_good(self):
3199 d = self.POST(self.public_url + "/foo", t="uri", name="future-ro.txt", uri=unknown_rocap)
3200 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"future-ro.txt")
3203 def test_POST_link_uri_unknown_imm_good(self):
3204 d = self.POST(self.public_url + "/foo", t="uri", name="future-imm.txt", uri=unknown_immcap)
3205 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"future-imm.txt")
3208 def test_POST_link_uri_no_replace_queryarg(self):
3209 contents, n, newuri = self.makefile(8)
3210 d = self.POST(self.public_url + "/foo?replace=false", t="uri",
3211 name="bar.txt", uri=newuri)
3212 d.addBoth(self.shouldFail, error.Error,
3213 "POST_link_uri_no_replace_queryarg",
3215 "There was already a child by that name, and you asked me "
3216 "to not replace it")
3217 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
3218 d.addCallback(self.failUnlessIsBarDotTxt)
3221 def test_POST_link_uri_no_replace_field(self):
3222 contents, n, newuri = self.makefile(8)
3223 d = self.POST(self.public_url + "/foo", t="uri", replace="false",
3224 name="bar.txt", uri=newuri)
3225 d.addBoth(self.shouldFail, error.Error,
3226 "POST_link_uri_no_replace_field",
3228 "There was already a child by that name, and you asked me "
3229 "to not replace it")
3230 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
3231 d.addCallback(self.failUnlessIsBarDotTxt)
3234 def test_POST_delete(self, command_name='delete'):
3235 d = self._foo_node.list()
3236 def _check_before(children):
3237 self.failUnless(u"bar.txt" in children)
3238 d.addCallback(_check_before)
3239 d.addCallback(lambda res: self.POST(self.public_url + "/foo", t=command_name, name="bar.txt"))
3240 d.addCallback(lambda res: self._foo_node.list())
3241 def _check_after(children):
3242 self.failIf(u"bar.txt" in children)
3243 d.addCallback(_check_after)
3246 def test_POST_unlink(self):
3247 return self.test_POST_delete(command_name='unlink')
3249 def test_POST_rename_file(self):
3250 d = self.POST(self.public_url + "/foo", t="rename",
3251 from_name="bar.txt", to_name='wibble.txt')
3252 d.addCallback(lambda res:
3253 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
3254 d.addCallback(lambda res:
3255 self.failUnlessNodeHasChild(self._foo_node, u"wibble.txt"))
3256 d.addCallback(lambda res: self.GET(self.public_url + "/foo/wibble.txt"))
3257 d.addCallback(self.failUnlessIsBarDotTxt)
3258 d.addCallback(lambda res: self.GET(self.public_url + "/foo/wibble.txt?t=json"))
3259 d.addCallback(self.failUnlessIsBarJSON)
3262 def test_POST_rename_file_redundant(self):
3263 d = self.POST(self.public_url + "/foo", t="rename",
3264 from_name="bar.txt", to_name='bar.txt')
3265 d.addCallback(lambda res:
3266 self.failUnlessNodeHasChild(self._foo_node, u"bar.txt"))
3267 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
3268 d.addCallback(self.failUnlessIsBarDotTxt)
3269 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt?t=json"))
3270 d.addCallback(self.failUnlessIsBarJSON)
3273 def test_POST_rename_file_replace(self):
3274 # rename a file and replace a directory with it
3275 d = self.POST(self.public_url + "/foo", t="rename",
3276 from_name="bar.txt", to_name='empty')
3277 d.addCallback(lambda res:
3278 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
3279 d.addCallback(lambda res:
3280 self.failUnlessNodeHasChild(self._foo_node, u"empty"))
3281 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty"))
3282 d.addCallback(self.failUnlessIsBarDotTxt)
3283 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
3284 d.addCallback(self.failUnlessIsBarJSON)
3287 def test_POST_rename_file_no_replace_queryarg(self):
3288 # rename a file and replace a directory with it
3289 d = self.POST(self.public_url + "/foo?replace=false", t="rename",
3290 from_name="bar.txt", to_name='empty')
3291 d.addBoth(self.shouldFail, error.Error,
3292 "POST_rename_file_no_replace_queryarg",
3294 "There was already a child by that name, and you asked me "
3295 "to not replace it")
3296 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
3297 d.addCallback(self.failUnlessIsEmptyJSON)
3300 def test_POST_rename_file_no_replace_field(self):
3301 # rename a file and replace a directory with it
3302 d = self.POST(self.public_url + "/foo", t="rename", replace="false",
3303 from_name="bar.txt", to_name='empty')
3304 d.addBoth(self.shouldFail, error.Error,
3305 "POST_rename_file_no_replace_field",
3307 "There was already a child by that name, and you asked me "
3308 "to not replace it")
3309 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
3310 d.addCallback(self.failUnlessIsEmptyJSON)
3313 def failUnlessIsEmptyJSON(self, res):
3314 data = simplejson.loads(res)
3315 self.failUnlessEqual(data[0], "dirnode", data)
3316 self.failUnlessReallyEqual(len(data[1]["children"]), 0)
3318 def test_POST_rename_file_slash_fail(self):
3319 d = self.POST(self.public_url + "/foo", t="rename",
3320 from_name="bar.txt", to_name='kirk/spock.txt')
3321 d.addBoth(self.shouldFail, error.Error,
3322 "test_POST_rename_file_slash_fail",
3324 "to_name= may not contain a slash",
3326 d.addCallback(lambda res:
3327 self.failUnlessNodeHasChild(self._foo_node, u"bar.txt"))
3330 def test_POST_rename_dir(self):
3331 d = self.POST(self.public_url, t="rename",
3332 from_name="foo", to_name='plunk')
3333 d.addCallback(lambda res:
3334 self.failIfNodeHasChild(self.public_root, u"foo"))
3335 d.addCallback(lambda res:
3336 self.failUnlessNodeHasChild(self.public_root, u"plunk"))
3337 d.addCallback(lambda res: self.GET(self.public_url + "/plunk?t=json"))
3338 d.addCallback(self.failUnlessIsFooJSON)
3341 def shouldRedirect(self, res, target=None, statuscode=None, which=""):
3342 """ If target is not None then the redirection has to go to target. If
3343 statuscode is not None then the redirection has to be accomplished with
3344 that HTTP status code."""
3345 if not isinstance(res, failure.Failure):
3346 to_where = (target is None) and "somewhere" or ("to " + target)
3347 self.fail("%s: we were expecting to get redirected %s, not get an"
3348 " actual page: %s" % (which, to_where, res))
3349 res.trap(error.PageRedirect)
3350 if statuscode is not None:
3351 self.failUnlessReallyEqual(res.value.status, statuscode,
3352 "%s: not a redirect" % which)
3353 if target is not None:
3354 # the PageRedirect does not seem to capture the uri= query arg
3355 # properly, so we can't check for it.
3356 realtarget = self.webish_url + target
3357 self.failUnlessReallyEqual(res.value.location, realtarget,
3358 "%s: wrong target" % which)
3359 return res.value.location
3361 def test_GET_URI_form(self):
3362 base = "/uri?uri=%s" % self._bar_txt_uri
3363 # this is supposed to give us a redirect to /uri/$URI, plus arguments
3364 targetbase = "/uri/%s" % urllib.quote(self._bar_txt_uri)
3366 d.addBoth(self.shouldRedirect, targetbase)
3367 d.addCallback(lambda res: self.GET(base+"&filename=bar.txt"))
3368 d.addBoth(self.shouldRedirect, targetbase+"?filename=bar.txt")
3369 d.addCallback(lambda res: self.GET(base+"&t=json"))
3370 d.addBoth(self.shouldRedirect, targetbase+"?t=json")
3371 d.addCallback(self.log, "about to get file by uri")
3372 d.addCallback(lambda res: self.GET(base, followRedirect=True))
3373 d.addCallback(self.failUnlessIsBarDotTxt)
3374 d.addCallback(self.log, "got file by uri, about to get dir by uri")
3375 d.addCallback(lambda res: self.GET("/uri?uri=%s&t=json" % self._foo_uri,
3376 followRedirect=True))
3377 d.addCallback(self.failUnlessIsFooJSON)
3378 d.addCallback(self.log, "got dir by uri")
3382 def test_GET_URI_form_bad(self):
3383 d = self.shouldFail2(error.Error, "test_GET_URI_form_bad",
3384 "400 Bad Request", "GET /uri requires uri=",
3388 def test_GET_rename_form(self):
3389 d = self.GET(self.public_url + "/foo?t=rename-form&name=bar.txt",
3390 followRedirect=True)
3392 self.failUnless('name="when_done" value="."' in res, res)
3393 self.failUnless(re.search(r'name="from_name" value="bar\.txt"', res))
3394 d.addCallback(_check)
3397 def log(self, res, msg):
3398 #print "MSG: %s RES: %s" % (msg, res)
3402 def test_GET_URI_URL(self):
3403 base = "/uri/%s" % self._bar_txt_uri
3405 d.addCallback(self.failUnlessIsBarDotTxt)
3406 d.addCallback(lambda res: self.GET(base+"?filename=bar.txt"))
3407 d.addCallback(self.failUnlessIsBarDotTxt)
3408 d.addCallback(lambda res: self.GET(base+"?filename=bar.txt&save=true"))
3409 d.addCallback(self.failUnlessIsBarDotTxt)
3412 def test_GET_URI_URL_dir(self):
3413 base = "/uri/%s?t=json" % self._foo_uri
3415 d.addCallback(self.failUnlessIsFooJSON)
3418 def test_GET_URI_URL_missing(self):
3419 base = "/uri/%s" % self._bad_file_uri
3420 d = self.shouldHTTPError("test_GET_URI_URL_missing",
3421 http.GONE, None, "NotEnoughSharesError",
3423 # TODO: how can we exercise both sides of WebDownloadTarget.fail
3424 # here? we must arrange for a download to fail after target.open()
3425 # has been called, and then inspect the response to see that it is
3426 # shorter than we expected.
3429 def test_PUT_DIRURL_uri(self):
3430 d = self.s.create_dirnode()
3432 new_uri = dn.get_uri()
3433 # replace /foo with a new (empty) directory
3434 d = self.PUT(self.public_url + "/foo?t=uri", new_uri)
3435 d.addCallback(lambda res:
3436 self.failUnlessReallyEqual(res.strip(), new_uri))
3437 d.addCallback(lambda res:
3438 self.failUnlessRWChildURIIs(self.public_root,
3442 d.addCallback(_made_dir)
3445 def test_PUT_DIRURL_uri_noreplace(self):
3446 d = self.s.create_dirnode()
3448 new_uri = dn.get_uri()
3449 # replace /foo with a new (empty) directory, but ask that
3450 # replace=false, so it should fail
3451 d = self.shouldFail2(error.Error, "test_PUT_DIRURL_uri_noreplace",
3452 "409 Conflict", "There was already a child by that name, and you asked me to not replace it",
3454 self.public_url + "/foo?t=uri&replace=false",
3456 d.addCallback(lambda res:
3457 self.failUnlessRWChildURIIs(self.public_root,
3461 d.addCallback(_made_dir)
3464 def test_PUT_DIRURL_bad_t(self):
3465 d = self.shouldFail2(error.Error, "test_PUT_DIRURL_bad_t",
3466 "400 Bad Request", "PUT to a directory",
3467 self.PUT, self.public_url + "/foo?t=BOGUS", "")
3468 d.addCallback(lambda res:
3469 self.failUnlessRWChildURIIs(self.public_root,
3474 def test_PUT_NEWFILEURL_uri(self):
3475 contents, n, new_uri = self.makefile(8)
3476 d = self.PUT(self.public_url + "/foo/new.txt?t=uri", new_uri)
3477 d.addCallback(lambda res: self.failUnlessReallyEqual(res.strip(), new_uri))
3478 d.addCallback(lambda res:
3479 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
3483 def test_PUT_NEWFILEURL_mdmf(self):
3484 new_contents = self.NEWFILE_CONTENTS * 300000
3485 d = self.PUT(self.public_url + \
3486 "/foo/mdmf.txt?format=mdmf",
3488 d.addCallback(lambda ignored:
3489 self.GET(self.public_url + "/foo/mdmf.txt?t=json"))
3490 def _got_json(json):
3491 data = simplejson.loads(json)
3493 self.failUnlessIn("format", data)
3494 self.failUnlessEqual(data["format"], "mdmf")
3495 self.failUnless(data['rw_uri'].startswith("URI:MDMF"))
3496 self.failUnless(data['ro_uri'].startswith("URI:MDMF"))
3497 d.addCallback(_got_json)
3500 def test_PUT_NEWFILEURL_sdmf(self):
3501 new_contents = self.NEWFILE_CONTENTS * 300000
3502 d = self.PUT(self.public_url + \
3503 "/foo/sdmf.txt?format=sdmf",
3505 d.addCallback(lambda ignored:
3506 self.GET(self.public_url + "/foo/sdmf.txt?t=json"))
3507 def _got_json(json):
3508 data = simplejson.loads(json)
3510 self.failUnlessIn("format", data)
3511 self.failUnlessEqual(data["format"], "sdmf")
3512 d.addCallback(_got_json)
3515 def test_PUT_NEWFILEURL_bad_format(self):
3516 new_contents = self.NEWFILE_CONTENTS * 300000
3517 return self.shouldHTTPError("PUT_NEWFILEURL_bad_format",
3518 400, "Bad Request", "Unknown format: foo",
3519 self.PUT, self.public_url + \
3520 "/foo/foo.txt?format=foo",
3523 def test_PUT_NEWFILEURL_uri_replace(self):
3524 contents, n, new_uri = self.makefile(8)
3525 d = self.PUT(self.public_url + "/foo/bar.txt?t=uri", new_uri)
3526 d.addCallback(lambda res: self.failUnlessReallyEqual(res.strip(), new_uri))
3527 d.addCallback(lambda res:
3528 self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
3532 def test_PUT_NEWFILEURL_uri_no_replace(self):
3533 contents, n, new_uri = self.makefile(8)
3534 d = self.PUT(self.public_url + "/foo/bar.txt?t=uri&replace=false", new_uri)
3535 d.addBoth(self.shouldFail, error.Error,
3536 "PUT_NEWFILEURL_uri_no_replace",
3538 "There was already a child by that name, and you asked me "
3539 "to not replace it")
3542 def test_PUT_NEWFILEURL_uri_unknown_bad(self):
3543 d = self.PUT(self.public_url + "/foo/put-future.txt?t=uri", unknown_rwcap)
3544 d.addBoth(self.shouldFail, error.Error,
3545 "POST_put_uri_unknown_bad",
3547 "unknown cap in a write slot")
3550 def test_PUT_NEWFILEURL_uri_unknown_ro_good(self):
3551 d = self.PUT(self.public_url + "/foo/put-future-ro.txt?t=uri", unknown_rocap)
3552 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node,
3553 u"put-future-ro.txt")
3556 def test_PUT_NEWFILEURL_uri_unknown_imm_good(self):
3557 d = self.PUT(self.public_url + "/foo/put-future-imm.txt?t=uri", unknown_immcap)
3558 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node,
3559 u"put-future-imm.txt")
3562 def test_PUT_NEWFILE_URI(self):
3563 file_contents = "New file contents here\n"
3564 d = self.PUT("/uri", file_contents)
3566 assert isinstance(uri, str), uri
3567 self.failUnless(uri in FakeCHKFileNode.all_contents)
3568 self.failUnlessReallyEqual(FakeCHKFileNode.all_contents[uri],
3570 return self.GET("/uri/%s" % uri)
3571 d.addCallback(_check)
3573 self.failUnlessReallyEqual(res, file_contents)
3574 d.addCallback(_check2)
3577 def test_PUT_NEWFILE_URI_not_mutable(self):
3578 file_contents = "New file contents here\n"
3579 d = self.PUT("/uri?mutable=false", file_contents)
3581 assert isinstance(uri, str), uri
3582 self.failUnless(uri in FakeCHKFileNode.all_contents)
3583 self.failUnlessReallyEqual(FakeCHKFileNode.all_contents[uri],
3585 return self.GET("/uri/%s" % uri)
3586 d.addCallback(_check)
3588 self.failUnlessReallyEqual(res, file_contents)
3589 d.addCallback(_check2)
3592 def test_PUT_NEWFILE_URI_only_PUT(self):
3593 d = self.PUT("/uri?t=bogus", "")
3594 d.addBoth(self.shouldFail, error.Error,
3595 "PUT_NEWFILE_URI_only_PUT",
3597 "/uri accepts only PUT, PUT?t=mkdir, POST?t=upload, and POST?t=mkdir")
3600 def test_PUT_NEWFILE_URI_mutable(self):
3601 file_contents = "New file contents here\n"
3602 d = self.PUT("/uri?mutable=true", file_contents)
3603 def _check1(filecap):
3604 filecap = filecap.strip()
3605 self.failUnless(filecap.startswith("URI:SSK:"), filecap)
3606 self.filecap = filecap
3607 u = uri.WriteableSSKFileURI.init_from_string(filecap)
3608 self.failUnless(u.get_storage_index() in FakeMutableFileNode.all_contents)
3609 n = self.s.create_node_from_uri(filecap)
3610 return n.download_best_version()
3611 d.addCallback(_check1)
3613 self.failUnlessReallyEqual(data, file_contents)
3614 return self.GET("/uri/%s" % urllib.quote(self.filecap))
3615 d.addCallback(_check2)
3617 self.failUnlessReallyEqual(res, file_contents)
3618 d.addCallback(_check3)
3621 def test_PUT_mkdir(self):
3622 d = self.PUT("/uri?t=mkdir", "")
3624 n = self.s.create_node_from_uri(uri.strip())
3625 d2 = self.failUnlessNodeKeysAre(n, [])
3626 d2.addCallback(lambda res:
3627 self.GET("/uri/%s?t=json" % uri))
3629 d.addCallback(_check)
3630 d.addCallback(self.failUnlessIsEmptyJSON)
3633 def test_PUT_mkdir_mdmf(self):
3634 d = self.PUT("/uri?t=mkdir&format=mdmf", "")
3636 u = uri.from_string(res)
3637 # Check that this is an MDMF writecap
3638 self.failUnlessIsInstance(u, uri.MDMFDirectoryURI)
3642 def test_PUT_mkdir_sdmf(self):
3643 d = self.PUT("/uri?t=mkdir&format=sdmf", "")
3645 u = uri.from_string(res)
3646 self.failUnlessIsInstance(u, uri.DirectoryURI)
3650 def test_PUT_mkdir_bad_format(self):
3651 return self.shouldHTTPError("PUT_mkdir_bad_format",
3652 400, "Bad Request", "Unknown format: foo",
3653 self.PUT, "/uri?t=mkdir&format=foo",
3656 def test_POST_check(self):
3657 d = self.POST(self.public_url + "/foo", t="check", name="bar.txt")
3659 # this returns a string form of the results, which are probably
3660 # None since we're using fake filenodes.
3661 # TODO: verify that the check actually happened, by changing
3662 # FakeCHKFileNode to count how many times .check() has been
3665 d.addCallback(_done)
3669 def test_PUT_update_at_offset(self):
3670 file_contents = "test file" * 100000 # about 900 KiB
3671 d = self.PUT("/uri?mutable=true", file_contents)
3673 self.filecap = filecap
3674 new_data = file_contents[:100]
3675 new = "replaced and so on"
3677 new_data += file_contents[len(new_data):]
3678 assert len(new_data) == len(file_contents)
3679 self.new_data = new_data
3680 d.addCallback(_then)
3681 d.addCallback(lambda ignored:
3682 self.PUT("/uri/%s?replace=True&offset=100" % self.filecap,
3683 "replaced and so on"))
3684 def _get_data(filecap):
3685 n = self.s.create_node_from_uri(filecap)
3686 return n.download_best_version()
3687 d.addCallback(_get_data)
3688 d.addCallback(lambda results:
3689 self.failUnlessEqual(results, self.new_data))
3690 # Now try appending things to the file
3691 d.addCallback(lambda ignored:
3692 self.PUT("/uri/%s?offset=%d" % (self.filecap, len(self.new_data)),
3694 d.addCallback(_get_data)
3695 d.addCallback(lambda results:
3696 self.failUnlessEqual(results, self.new_data + ("puppies" * 100)))
3697 # and try replacing the beginning of the file
3698 d.addCallback(lambda ignored:
3699 self.PUT("/uri/%s?offset=0" % self.filecap, "begin"))
3700 d.addCallback(_get_data)
3701 d.addCallback(lambda results:
3702 self.failUnlessEqual(results, "begin"+self.new_data[len("begin"):]+("puppies"*100)))
3705 def test_PUT_update_at_invalid_offset(self):
3706 file_contents = "test file" * 100000 # about 900 KiB
3707 d = self.PUT("/uri?mutable=true", file_contents)
3709 self.filecap = filecap
3710 d.addCallback(_then)
3711 # Negative offsets should cause an error.
3712 d.addCallback(lambda ignored:
3713 self.shouldHTTPError("PUT_update_at_invalid_offset",
3717 "/uri/%s?offset=-1" % self.filecap,
3721 def test_PUT_update_at_offset_immutable(self):
3722 file_contents = "Test file" * 100000
3723 d = self.PUT("/uri", file_contents)
3725 self.filecap = filecap
3726 d.addCallback(_then)
3727 d.addCallback(lambda ignored:
3728 self.shouldHTTPError("PUT_update_at_offset_immutable",
3732 "/uri/%s?offset=50" % self.filecap,
3737 def test_bad_method(self):
3738 url = self.webish_url + self.public_url + "/foo/bar.txt"
3739 d = self.shouldHTTPError("bad_method",
3740 501, "Not Implemented",
3741 "I don't know how to treat a BOGUS request.",
3742 client.getPage, url, method="BOGUS")
3745 def test_short_url(self):
3746 url = self.webish_url + "/uri"
3747 d = self.shouldHTTPError("short_url", 501, "Not Implemented",
3748 "I don't know how to treat a DELETE request.",
3749 client.getPage, url, method="DELETE")
3752 def test_ophandle_bad(self):
3753 url = self.webish_url + "/operations/bogus?t=status"
3754 d = self.shouldHTTPError("ophandle_bad", 404, "404 Not Found",
3755 "unknown/expired handle 'bogus'",
3756 client.getPage, url)
3759 def test_ophandle_cancel(self):
3760 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=128",
3761 followRedirect=True)
3762 d.addCallback(lambda ignored:
3763 self.GET("/operations/128?t=status&output=JSON"))
3765 data = simplejson.loads(res)
3766 self.failUnless("finished" in data, res)
3767 monitor = self.ws.root.child_operations.handles["128"][0]
3768 d = self.POST("/operations/128?t=cancel&output=JSON")
3770 data = simplejson.loads(res)
3771 self.failUnless("finished" in data, res)
3772 # t=cancel causes the handle to be forgotten
3773 self.failUnless(monitor.is_cancelled())
3774 d.addCallback(_check2)
3776 d.addCallback(_check1)
3777 d.addCallback(lambda ignored:
3778 self.shouldHTTPError("ophandle_cancel",
3779 404, "404 Not Found",
3780 "unknown/expired handle '128'",
3782 "/operations/128?t=status&output=JSON"))
3785 def test_ophandle_retainfor(self):
3786 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=129&retain-for=60",
3787 followRedirect=True)
3788 d.addCallback(lambda ignored:
3789 self.GET("/operations/129?t=status&output=JSON&retain-for=0"))
3791 data = simplejson.loads(res)
3792 self.failUnless("finished" in data, res)
3793 d.addCallback(_check1)
3794 # the retain-for=0 will cause the handle to be expired very soon
3795 d.addCallback(lambda ign:
3796 self.clock.advance(2.0))
3797 d.addCallback(lambda ignored:
3798 self.shouldHTTPError("ophandle_retainfor",
3799 404, "404 Not Found",
3800 "unknown/expired handle '129'",
3802 "/operations/129?t=status&output=JSON"))
3805 def test_ophandle_release_after_complete(self):
3806 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=130",
3807 followRedirect=True)
3808 d.addCallback(self.wait_for_operation, "130")
3809 d.addCallback(lambda ignored:
3810 self.GET("/operations/130?t=status&output=JSON&release-after-complete=true"))
3811 # the release-after-complete=true will cause the handle to be expired
3812 d.addCallback(lambda ignored:
3813 self.shouldHTTPError("ophandle_release_after_complete",
3814 404, "404 Not Found",
3815 "unknown/expired handle '130'",
3817 "/operations/130?t=status&output=JSON"))
3820 def test_uncollected_ophandle_expiration(self):
3821 # uncollected ophandles should expire after 4 days
3822 def _make_uncollected_ophandle(ophandle):
3823 d = self.POST(self.public_url +
3824 "/foo/?t=start-manifest&ophandle=%d" % ophandle,
3825 followRedirect=False)
3826 # When we start the operation, the webapi server will want
3827 # to redirect us to the page for the ophandle, so we get
3828 # confirmation that the operation has started. If the
3829 # manifest operation has finished by the time we get there,
3830 # following that redirect (by setting followRedirect=True
3831 # above) has the side effect of collecting the ophandle that
3832 # we've just created, which means that we can't use the
3833 # ophandle to test the uncollected timeout anymore. So,
3834 # instead, catch the 302 here and don't follow it.
3835 d.addBoth(self.should302, "uncollected_ophandle_creation")
3837 # Create an ophandle, don't collect it, then advance the clock by
3838 # 4 days - 1 second and make sure that the ophandle is still there.
3839 d = _make_uncollected_ophandle(131)
3840 d.addCallback(lambda ign:
3841 self.clock.advance((96*60*60) - 1)) # 96 hours = 4 days
3842 d.addCallback(lambda ign:
3843 self.GET("/operations/131?t=status&output=JSON"))
3845 data = simplejson.loads(res)
3846 self.failUnless("finished" in data, res)
3847 d.addCallback(_check1)
3848 # Create an ophandle, don't collect it, then try to collect it
3849 # after 4 days. It should be gone.
3850 d.addCallback(lambda ign:
3851 _make_uncollected_ophandle(132))
3852 d.addCallback(lambda ign:
3853 self.clock.advance(96*60*60))
3854 d.addCallback(lambda ign:
3855 self.shouldHTTPError("uncollected_ophandle_expired_after_100_hours",
3856 404, "404 Not Found",
3857 "unknown/expired handle '132'",
3859 "/operations/132?t=status&output=JSON"))
3862 def test_collected_ophandle_expiration(self):
3863 # collected ophandles should expire after 1 day
3864 def _make_collected_ophandle(ophandle):
3865 d = self.POST(self.public_url +
3866 "/foo/?t=start-manifest&ophandle=%d" % ophandle,
3867 followRedirect=True)
3868 # By following the initial redirect, we collect the ophandle
3869 # we've just created.
3871 # Create a collected ophandle, then collect it after 23 hours
3872 # and 59 seconds to make sure that it is still there.
3873 d = _make_collected_ophandle(133)
3874 d.addCallback(lambda ign:
3875 self.clock.advance((24*60*60) - 1))
3876 d.addCallback(lambda ign:
3877 self.GET("/operations/133?t=status&output=JSON"))
3879 data = simplejson.loads(res)
3880 self.failUnless("finished" in data, res)
3881 d.addCallback(_check1)
3882 # Create another uncollected ophandle, then try to collect it
3883 # after 24 hours to make sure that it is gone.
3884 d.addCallback(lambda ign:
3885 _make_collected_ophandle(134))
3886 d.addCallback(lambda ign:
3887 self.clock.advance(24*60*60))
3888 d.addCallback(lambda ign:
3889 self.shouldHTTPError("collected_ophandle_expired_after_1_day",
3890 404, "404 Not Found",
3891 "unknown/expired handle '134'",
3893 "/operations/134?t=status&output=JSON"))
3896 def test_incident(self):
3897 d = self.POST("/report_incident", details="eek")
3899 self.failUnless("Thank you for your report!" in res, res)
3900 d.addCallback(_done)
3903 def test_static(self):
3904 webdir = os.path.join(self.staticdir, "subdir")
3905 fileutil.make_dirs(webdir)
3906 f = open(os.path.join(webdir, "hello.txt"), "wb")
3910 d = self.GET("/static/subdir/hello.txt")
3912 self.failUnlessReallyEqual(res, "hello")
3913 d.addCallback(_check)
3917 class Util(ShouldFailMixin, testutil.ReallyEqualMixin, unittest.TestCase):
3918 def test_load_file(self):
3919 # This will raise an exception unless a well-formed XML file is found under that name.
3920 common.getxmlfile('directory.xhtml').load()
3922 def test_parse_replace_arg(self):
3923 self.failUnlessReallyEqual(common.parse_replace_arg("true"), True)
3924 self.failUnlessReallyEqual(common.parse_replace_arg("false"), False)
3925 self.failUnlessReallyEqual(common.parse_replace_arg("only-files"),
3927 self.shouldFail(AssertionError, "test_parse_replace_arg", "",
3928 common.parse_replace_arg, "only_fles")
3930 def test_abbreviate_time(self):
3931 self.failUnlessReallyEqual(common.abbreviate_time(None), "")
3932 self.failUnlessReallyEqual(common.abbreviate_time(1.234), "1.23s")
3933 self.failUnlessReallyEqual(common.abbreviate_time(0.123), "123ms")
3934 self.failUnlessReallyEqual(common.abbreviate_time(0.00123), "1.2ms")
3935 self.failUnlessReallyEqual(common.abbreviate_time(0.000123), "123us")
3936 self.failUnlessReallyEqual(common.abbreviate_time(-123000), "-123000000000us")
3938 def test_compute_rate(self):
3939 self.failUnlessReallyEqual(common.compute_rate(None, None), None)
3940 self.failUnlessReallyEqual(common.compute_rate(None, 1), None)
3941 self.failUnlessReallyEqual(common.compute_rate(250000, None), None)
3942 self.failUnlessReallyEqual(common.compute_rate(250000, 0), None)
3943 self.failUnlessReallyEqual(common.compute_rate(250000, 10), 25000.0)
3944 self.failUnlessReallyEqual(common.compute_rate(0, 10), 0.0)
3945 self.shouldFail(AssertionError, "test_compute_rate", "",
3946 common.compute_rate, -100, 10)
3947 self.shouldFail(AssertionError, "test_compute_rate", "",
3948 common.compute_rate, 100, -10)
3951 rate = common.compute_rate(10*1000*1000, 1)
3952 self.failUnlessReallyEqual(common.abbreviate_rate(rate), "10.00MBps")
3954 def test_abbreviate_rate(self):
3955 self.failUnlessReallyEqual(common.abbreviate_rate(None), "")
3956 self.failUnlessReallyEqual(common.abbreviate_rate(1234000), "1.23MBps")
3957 self.failUnlessReallyEqual(common.abbreviate_rate(12340), "12.3kBps")
3958 self.failUnlessReallyEqual(common.abbreviate_rate(123), "123Bps")
3960 def test_abbreviate_size(self):
3961 self.failUnlessReallyEqual(common.abbreviate_size(None), "")
3962 self.failUnlessReallyEqual(common.abbreviate_size(1.23*1000*1000*1000), "1.23GB")
3963 self.failUnlessReallyEqual(common.abbreviate_size(1.23*1000*1000), "1.23MB")
3964 self.failUnlessReallyEqual(common.abbreviate_size(1230), "1.2kB")
3965 self.failUnlessReallyEqual(common.abbreviate_size(123), "123B")
3967 def test_plural(self):
3969 return "%d second%s" % (s, status.plural(s))
3970 self.failUnlessReallyEqual(convert(0), "0 seconds")
3971 self.failUnlessReallyEqual(convert(1), "1 second")
3972 self.failUnlessReallyEqual(convert(2), "2 seconds")
3974 return "has share%s: %s" % (status.plural(s), ",".join(s))
3975 self.failUnlessReallyEqual(convert2([]), "has shares: ")
3976 self.failUnlessReallyEqual(convert2(["1"]), "has share: 1")
3977 self.failUnlessReallyEqual(convert2(["1","2"]), "has shares: 1,2")
3980 class Grid(GridTestMixin, WebErrorMixin, ShouldFailMixin, testutil.ReallyEqualMixin, unittest.TestCase):
3982 def CHECK(self, ign, which, args, clientnum=0):
3983 fileurl = self.fileurls[which]
3984 url = fileurl + "?" + args
3985 return self.GET(url, method="POST", clientnum=clientnum)
3987 def test_filecheck(self):
3988 self.basedir = "web/Grid/filecheck"
3990 c0 = self.g.clients[0]
3993 d = c0.upload(upload.Data(DATA, convergence=""))
3994 def _stash_uri(ur, which):
3995 self.uris[which] = ur.uri
3996 d.addCallback(_stash_uri, "good")
3997 d.addCallback(lambda ign:
3998 c0.upload(upload.Data(DATA+"1", convergence="")))
3999 d.addCallback(_stash_uri, "sick")
4000 d.addCallback(lambda ign:
4001 c0.upload(upload.Data(DATA+"2", convergence="")))
4002 d.addCallback(_stash_uri, "dead")
4003 def _stash_mutable_uri(n, which):
4004 self.uris[which] = n.get_uri()
4005 assert isinstance(self.uris[which], str)
4006 d.addCallback(lambda ign:
4007 c0.create_mutable_file(publish.MutableData(DATA+"3")))
4008 d.addCallback(_stash_mutable_uri, "corrupt")
4009 d.addCallback(lambda ign:
4010 c0.upload(upload.Data("literal", convergence="")))
4011 d.addCallback(_stash_uri, "small")
4012 d.addCallback(lambda ign: c0.create_immutable_dirnode({}))
4013 d.addCallback(_stash_mutable_uri, "smalldir")
4015 def _compute_fileurls(ignored):
4017 for which in self.uris:
4018 self.fileurls[which] = "uri/" + urllib.quote(self.uris[which])
4019 d.addCallback(_compute_fileurls)
4021 def _clobber_shares(ignored):
4022 good_shares = self.find_uri_shares(self.uris["good"])
4023 self.failUnlessReallyEqual(len(good_shares), 10)
4024 sick_shares = self.find_uri_shares(self.uris["sick"])
4025 os.unlink(sick_shares[0][2])
4026 dead_shares = self.find_uri_shares(self.uris["dead"])
4027 for i in range(1, 10):
4028 os.unlink(dead_shares[i][2])
4029 c_shares = self.find_uri_shares(self.uris["corrupt"])
4030 cso = CorruptShareOptions()
4031 cso.stdout = StringIO()
4032 cso.parseOptions([c_shares[0][2]])
4034 d.addCallback(_clobber_shares)
4036 d.addCallback(self.CHECK, "good", "t=check")
4037 def _got_html_good(res):
4038 self.failUnless("Healthy" in res, res)
4039 self.failIf("Not Healthy" in res, res)
4040 d.addCallback(_got_html_good)
4041 d.addCallback(self.CHECK, "good", "t=check&return_to=somewhere")
4042 def _got_html_good_return_to(res):
4043 self.failUnless("Healthy" in res, res)
4044 self.failIf("Not Healthy" in res, res)
4045 self.failUnless('<a href="somewhere">Return to file'
4047 d.addCallback(_got_html_good_return_to)
4048 d.addCallback(self.CHECK, "good", "t=check&output=json")
4049 def _got_json_good(res):
4050 r = simplejson.loads(res)
4051 self.failUnlessEqual(r["summary"], "Healthy")
4052 self.failUnless(r["results"]["healthy"])
4053 self.failIf(r["results"]["needs-rebalancing"])
4054 self.failUnless(r["results"]["recoverable"])
4055 d.addCallback(_got_json_good)
4057 d.addCallback(self.CHECK, "small", "t=check")
4058 def _got_html_small(res):
4059 self.failUnless("Literal files are always healthy" in res, res)
4060 self.failIf("Not Healthy" in res, res)
4061 d.addCallback(_got_html_small)
4062 d.addCallback(self.CHECK, "small", "t=check&return_to=somewhere")
4063 def _got_html_small_return_to(res):
4064 self.failUnless("Literal files are always healthy" in res, res)
4065 self.failIf("Not Healthy" in res, res)
4066 self.failUnless('<a href="somewhere">Return to file'
4068 d.addCallback(_got_html_small_return_to)
4069 d.addCallback(self.CHECK, "small", "t=check&output=json")
4070 def _got_json_small(res):
4071 r = simplejson.loads(res)
4072 self.failUnlessEqual(r["storage-index"], "")
4073 self.failUnless(r["results"]["healthy"])
4074 d.addCallback(_got_json_small)
4076 d.addCallback(self.CHECK, "smalldir", "t=check")
4077 def _got_html_smalldir(res):
4078 self.failUnless("Literal files are always healthy" in res, res)
4079 self.failIf("Not Healthy" in res, res)
4080 d.addCallback(_got_html_smalldir)
4081 d.addCallback(self.CHECK, "smalldir", "t=check&output=json")
4082 def _got_json_smalldir(res):
4083 r = simplejson.loads(res)
4084 self.failUnlessEqual(r["storage-index"], "")
4085 self.failUnless(r["results"]["healthy"])
4086 d.addCallback(_got_json_smalldir)
4088 d.addCallback(self.CHECK, "sick", "t=check")
4089 def _got_html_sick(res):
4090 self.failUnless("Not Healthy" in res, res)
4091 d.addCallback(_got_html_sick)
4092 d.addCallback(self.CHECK, "sick", "t=check&output=json")
4093 def _got_json_sick(res):
4094 r = simplejson.loads(res)
4095 self.failUnlessEqual(r["summary"],
4096 "Not Healthy: 9 shares (enc 3-of-10)")
4097 self.failIf(r["results"]["healthy"])
4098 self.failIf(r["results"]["needs-rebalancing"])
4099 self.failUnless(r["results"]["recoverable"])
4100 d.addCallback(_got_json_sick)
4102 d.addCallback(self.CHECK, "dead", "t=check")
4103 def _got_html_dead(res):
4104 self.failUnless("Not Healthy" in res, res)
4105 d.addCallback(_got_html_dead)
4106 d.addCallback(self.CHECK, "dead", "t=check&output=json")
4107 def _got_json_dead(res):
4108 r = simplejson.loads(res)
4109 self.failUnlessEqual(r["summary"],
4110 "Not Healthy: 1 shares (enc 3-of-10)")
4111 self.failIf(r["results"]["healthy"])
4112 self.failIf(r["results"]["needs-rebalancing"])
4113 self.failIf(r["results"]["recoverable"])
4114 d.addCallback(_got_json_dead)
4116 d.addCallback(self.CHECK, "corrupt", "t=check&verify=true")
4117 def _got_html_corrupt(res):
4118 self.failUnless("Not Healthy! : Unhealthy" in res, res)
4119 d.addCallback(_got_html_corrupt)
4120 d.addCallback(self.CHECK, "corrupt", "t=check&verify=true&output=json")
4121 def _got_json_corrupt(res):
4122 r = simplejson.loads(res)
4123 self.failUnless("Unhealthy: 9 shares (enc 3-of-10)" in r["summary"],
4125 self.failIf(r["results"]["healthy"])
4126 self.failUnless(r["results"]["recoverable"])
4127 self.failUnlessReallyEqual(r["results"]["count-shares-good"], 9)
4128 self.failUnlessReallyEqual(r["results"]["count-corrupt-shares"], 1)
4129 d.addCallback(_got_json_corrupt)
4131 d.addErrback(self.explain_web_error)
4134 def test_repair_html(self):
4135 self.basedir = "web/Grid/repair_html"
4137 c0 = self.g.clients[0]
4140 d = c0.upload(upload.Data(DATA, convergence=""))
4141 def _stash_uri(ur, which):
4142 self.uris[which] = ur.uri
4143 d.addCallback(_stash_uri, "good")
4144 d.addCallback(lambda ign:
4145 c0.upload(upload.Data(DATA+"1", convergence="")))
4146 d.addCallback(_stash_uri, "sick")
4147 d.addCallback(lambda ign:
4148 c0.upload(upload.Data(DATA+"2", convergence="")))
4149 d.addCallback(_stash_uri, "dead")
4150 def _stash_mutable_uri(n, which):
4151 self.uris[which] = n.get_uri()
4152 assert isinstance(self.uris[which], str)
4153 d.addCallback(lambda ign:
4154 c0.create_mutable_file(publish.MutableData(DATA+"3")))
4155 d.addCallback(_stash_mutable_uri, "corrupt")
4157 def _compute_fileurls(ignored):
4159 for which in self.uris:
4160 self.fileurls[which] = "uri/" + urllib.quote(self.uris[which])
4161 d.addCallback(_compute_fileurls)
4163 def _clobber_shares(ignored):
4164 good_shares = self.find_uri_shares(self.uris["good"])
4165 self.failUnlessReallyEqual(len(good_shares), 10)
4166 sick_shares = self.find_uri_shares(self.uris["sick"])
4167 os.unlink(sick_shares[0][2])
4168 dead_shares = self.find_uri_shares(self.uris["dead"])
4169 for i in range(1, 10):
4170 os.unlink(dead_shares[i][2])
4171 c_shares = self.find_uri_shares(self.uris["corrupt"])
4172 cso = CorruptShareOptions()
4173 cso.stdout = StringIO()
4174 cso.parseOptions([c_shares[0][2]])
4176 d.addCallback(_clobber_shares)
4178 d.addCallback(self.CHECK, "good", "t=check&repair=true")
4179 def _got_html_good(res):
4180 self.failUnless("Healthy" in res, res)
4181 self.failIf("Not Healthy" in res, res)
4182 self.failUnless("No repair necessary" in res, res)
4183 d.addCallback(_got_html_good)
4185 d.addCallback(self.CHECK, "sick", "t=check&repair=true")
4186 def _got_html_sick(res):
4187 self.failUnless("Healthy : healthy" in res, res)
4188 self.failIf("Not Healthy" in res, res)
4189 self.failUnless("Repair successful" in res, res)
4190 d.addCallback(_got_html_sick)
4192 # repair of a dead file will fail, of course, but it isn't yet
4193 # clear how this should be reported. Right now it shows up as
4196 #d.addCallback(self.CHECK, "dead", "t=check&repair=true")
4197 #def _got_html_dead(res):
4199 # self.failUnless("Healthy : healthy" in res, res)
4200 # self.failIf("Not Healthy" in res, res)
4201 # self.failUnless("No repair necessary" in res, res)
4202 #d.addCallback(_got_html_dead)
4204 d.addCallback(self.CHECK, "corrupt", "t=check&verify=true&repair=true")
4205 def _got_html_corrupt(res):
4206 self.failUnless("Healthy : Healthy" in res, res)
4207 self.failIf("Not Healthy" in res, res)
4208 self.failUnless("Repair successful" in res, res)
4209 d.addCallback(_got_html_corrupt)
4211 d.addErrback(self.explain_web_error)
4214 def test_repair_json(self):
4215 self.basedir = "web/Grid/repair_json"
4217 c0 = self.g.clients[0]
4220 d = c0.upload(upload.Data(DATA+"1", convergence=""))
4221 def _stash_uri(ur, which):
4222 self.uris[which] = ur.uri
4223 d.addCallback(_stash_uri, "sick")
4225 def _compute_fileurls(ignored):
4227 for which in self.uris:
4228 self.fileurls[which] = "uri/" + urllib.quote(self.uris[which])
4229 d.addCallback(_compute_fileurls)
4231 def _clobber_shares(ignored):
4232 sick_shares = self.find_uri_shares(self.uris["sick"])
4233 os.unlink(sick_shares[0][2])
4234 d.addCallback(_clobber_shares)
4236 d.addCallback(self.CHECK, "sick", "t=check&repair=true&output=json")
4237 def _got_json_sick(res):
4238 r = simplejson.loads(res)
4239 self.failUnlessReallyEqual(r["repair-attempted"], True)
4240 self.failUnlessReallyEqual(r["repair-successful"], True)
4241 self.failUnlessEqual(r["pre-repair-results"]["summary"],
4242 "Not Healthy: 9 shares (enc 3-of-10)")
4243 self.failIf(r["pre-repair-results"]["results"]["healthy"])
4244 self.failUnlessEqual(r["post-repair-results"]["summary"], "healthy")
4245 self.failUnless(r["post-repair-results"]["results"]["healthy"])
4246 d.addCallback(_got_json_sick)
4248 d.addErrback(self.explain_web_error)
4251 def test_unknown(self, immutable=False):
4252 self.basedir = "web/Grid/unknown"
4254 self.basedir = "web/Grid/unknown-immutable"
4257 c0 = self.g.clients[0]
4261 # the future cap format may contain slashes, which must be tolerated
4262 expected_info_url = "uri/%s?t=info" % urllib.quote(unknown_rwcap,
4266 name = u"future-imm"
4267 future_node = UnknownNode(None, unknown_immcap, deep_immutable=True)
4268 d = c0.create_immutable_dirnode({name: (future_node, {})})
4271 future_node = UnknownNode(unknown_rwcap, unknown_rocap)
4272 d = c0.create_dirnode()
4274 def _stash_root_and_create_file(n):
4276 self.rooturl = "uri/" + urllib.quote(n.get_uri()) + "/"
4277 self.rourl = "uri/" + urllib.quote(n.get_readonly_uri()) + "/"
4279 return self.rootnode.set_node(name, future_node)
4280 d.addCallback(_stash_root_and_create_file)
4282 # make sure directory listing tolerates unknown nodes
4283 d.addCallback(lambda ign: self.GET(self.rooturl))
4284 def _check_directory_html(res, expected_type_suffix):
4285 pattern = re.compile(r'<td>\?%s</td>[ \t\n\r]*'
4286 '<td>%s</td>' % (expected_type_suffix, str(name)),
4288 self.failUnless(re.search(pattern, res), res)
4289 # find the More Info link for name, should be relative
4290 mo = re.search(r'<a href="([^"]+)">More Info</a>', res)
4291 info_url = mo.group(1)
4292 self.failUnlessReallyEqual(info_url, "%s?t=info" % (str(name),))
4294 d.addCallback(_check_directory_html, "-IMM")
4296 d.addCallback(_check_directory_html, "")
4298 d.addCallback(lambda ign: self.GET(self.rooturl+"?t=json"))
4299 def _check_directory_json(res, expect_rw_uri):
4300 data = simplejson.loads(res)
4301 self.failUnlessEqual(data[0], "dirnode")
4302 f = data[1]["children"][name]
4303 self.failUnlessEqual(f[0], "unknown")
4305 self.failUnlessReallyEqual(to_str(f[1]["rw_uri"]), unknown_rwcap, data)
4307 self.failIfIn("rw_uri", f[1])
4309 self.failUnlessReallyEqual(to_str(f[1]["ro_uri"]), unknown_immcap, data)
4311 self.failUnlessReallyEqual(to_str(f[1]["ro_uri"]), unknown_rocap, data)
4312 self.failUnless("metadata" in f[1])
4313 d.addCallback(_check_directory_json, expect_rw_uri=not immutable)
4315 def _check_info(res, expect_rw_uri, expect_ro_uri):
4316 self.failUnlessIn("Object Type: <span>unknown</span>", res)
4318 self.failUnlessIn(unknown_rwcap, res)
4321 self.failUnlessIn(unknown_immcap, res)
4323 self.failUnlessIn(unknown_rocap, res)
4325 self.failIfIn(unknown_rocap, res)
4326 self.failIfIn("Raw data as", res)
4327 self.failIfIn("Directory writecap", res)
4328 self.failIfIn("Checker Operations", res)
4329 self.failIfIn("Mutable File Operations", res)
4330 self.failIfIn("Directory Operations", res)
4332 # FIXME: these should have expect_rw_uri=not immutable; I don't know
4333 # why they fail. Possibly related to ticket #922.
4335 d.addCallback(lambda ign: self.GET(expected_info_url))
4336 d.addCallback(_check_info, expect_rw_uri=False, expect_ro_uri=False)
4337 d.addCallback(lambda ign: self.GET("%s%s?t=info" % (self.rooturl, str(name))))
4338 d.addCallback(_check_info, expect_rw_uri=False, expect_ro_uri=True)
4340 def _check_json(res, expect_rw_uri):
4341 data = simplejson.loads(res)
4342 self.failUnlessEqual(data[0], "unknown")
4344 self.failUnlessReallyEqual(to_str(data[1]["rw_uri"]), unknown_rwcap, data)
4346 self.failIfIn("rw_uri", data[1])
4349 self.failUnlessReallyEqual(to_str(data[1]["ro_uri"]), unknown_immcap, data)
4350 self.failUnlessReallyEqual(data[1]["mutable"], False)
4352 self.failUnlessReallyEqual(to_str(data[1]["ro_uri"]), unknown_rocap, data)
4353 self.failUnlessReallyEqual(data[1]["mutable"], True)
4355 self.failUnlessReallyEqual(to_str(data[1]["ro_uri"]), unknown_rocap, data)
4356 self.failIf("mutable" in data[1], data[1])
4358 # TODO: check metadata contents
4359 self.failUnless("metadata" in data[1])
4361 d.addCallback(lambda ign: self.GET("%s%s?t=json" % (self.rooturl, str(name))))
4362 d.addCallback(_check_json, expect_rw_uri=not immutable)
4364 # and make sure that a read-only version of the directory can be
4365 # rendered too. This version will not have unknown_rwcap, whether
4366 # or not future_node was immutable.
4367 d.addCallback(lambda ign: self.GET(self.rourl))
4369 d.addCallback(_check_directory_html, "-IMM")
4371 d.addCallback(_check_directory_html, "-RO")
4373 d.addCallback(lambda ign: self.GET(self.rourl+"?t=json"))
4374 d.addCallback(_check_directory_json, expect_rw_uri=False)
4376 d.addCallback(lambda ign: self.GET("%s%s?t=json" % (self.rourl, str(name))))
4377 d.addCallback(_check_json, expect_rw_uri=False)
4379 # TODO: check that getting t=info from the Info link in the ro directory
4380 # works, and does not include the writecap URI.
4383 def test_immutable_unknown(self):
4384 return self.test_unknown(immutable=True)
4386 def test_mutant_dirnodes_are_omitted(self):
4387 self.basedir = "web/Grid/mutant_dirnodes_are_omitted"
4390 c = self.g.clients[0]
4395 lonely_uri = "URI:LIT:n5xgk" # LIT for "one"
4396 mut_write_uri = "URI:SSK:vfvcbdfbszyrsaxchgevhmmlii:euw4iw7bbnkrrwpzuburbhppuxhc3gwxv26f6imekhz7zyw2ojnq"
4397 mut_read_uri = "URI:SSK-RO:e3mdrzfwhoq42hy5ubcz6rp3o4:ybyibhnp3vvwuq2vaw2ckjmesgkklfs6ghxleztqidihjyofgw7q"
4399 # This method tests mainly dirnode, but we'd have to duplicate code in order to
4400 # test the dirnode and web layers separately.
4402 # 'lonely' is a valid LIT child, 'ro' is a mutant child with an SSK-RO readcap,
4403 # and 'write-in-ro' is a mutant child with an SSK writecap in the ro_uri field.
4404 # When the directory is read, the mutants should be silently disposed of, leaving
4405 # their lonely sibling.
4406 # We don't test the case of a retrieving a cap from the encrypted rw_uri field,
4407 # because immutable directories don't have a writecap and therefore that field
4408 # isn't (and can't be) decrypted.
4409 # TODO: The field still exists in the netstring. Technically we should check what
4410 # happens if something is put there (_unpack_contents should raise ValueError),
4411 # but that can wait.
4413 lonely_child = nm.create_from_cap(lonely_uri)
4414 mutant_ro_child = nm.create_from_cap(mut_read_uri)
4415 mutant_write_in_ro_child = nm.create_from_cap(mut_write_uri)
4417 def _by_hook_or_by_crook():
4419 for n in [mutant_ro_child, mutant_write_in_ro_child]:
4420 n.is_allowed_in_immutable_directory = _by_hook_or_by_crook
4422 mutant_write_in_ro_child.get_write_uri = lambda: None
4423 mutant_write_in_ro_child.get_readonly_uri = lambda: mut_write_uri
4425 kids = {u"lonely": (lonely_child, {}),
4426 u"ro": (mutant_ro_child, {}),
4427 u"write-in-ro": (mutant_write_in_ro_child, {}),
4429 d = c.create_immutable_dirnode(kids)
4432 self.failUnless(isinstance(dn, dirnode.DirectoryNode))
4433 self.failIf(dn.is_mutable())
4434 self.failUnless(dn.is_readonly())
4435 # This checks that if we somehow ended up calling dn._decrypt_rwcapdata, it would fail.
4436 self.failIf(hasattr(dn._node, 'get_writekey'))
4438 self.failUnless("RO-IMM" in rep)
4440 self.failUnlessIn("CHK", cap.to_string())
4443 self.rooturl = "uri/" + urllib.quote(dn.get_uri()) + "/"
4444 return download_to_data(dn._node)
4445 d.addCallback(_created)
4447 def _check_data(data):
4448 # Decode the netstring representation of the directory to check that all children
4449 # are present. This is a bit of an abstraction violation, but there's not really
4450 # any other way to do it given that the real DirectoryNode._unpack_contents would
4451 # strip the mutant children out (which is what we're trying to test, later).
4454 while position < len(data):
4455 entries, position = split_netstring(data, 1, position)
4457 (name_utf8, ro_uri, rwcapdata, metadata_s), subpos = split_netstring(entry, 4)
4458 name = name_utf8.decode("utf-8")
4459 self.failUnless(rwcapdata == "")
4460 self.failUnless(name in kids)
4461 (expected_child, ign) = kids[name]
4462 self.failUnlessReallyEqual(ro_uri, expected_child.get_readonly_uri())
4465 self.failUnlessReallyEqual(numkids, 3)
4466 return self.rootnode.list()
4467 d.addCallback(_check_data)
4469 # Now when we use the real directory listing code, the mutants should be absent.
4470 def _check_kids(children):
4471 self.failUnlessReallyEqual(sorted(children.keys()), [u"lonely"])
4472 lonely_node, lonely_metadata = children[u"lonely"]
4474 self.failUnlessReallyEqual(lonely_node.get_write_uri(), None)
4475 self.failUnlessReallyEqual(lonely_node.get_readonly_uri(), lonely_uri)
4476 d.addCallback(_check_kids)
4478 d.addCallback(lambda ign: nm.create_from_cap(self.cap.to_string()))
4479 d.addCallback(lambda n: n.list())
4480 d.addCallback(_check_kids) # again with dirnode recreated from cap
4482 # Make sure the lonely child can be listed in HTML...
4483 d.addCallback(lambda ign: self.GET(self.rooturl))
4484 def _check_html(res):
4485 self.failIfIn("URI:SSK", res)
4486 get_lonely = "".join([r'<td>FILE</td>',
4488 r'<a href="[^"]+%s[^"]+">lonely</a>' % (urllib.quote(lonely_uri),),
4490 r'\s+<td align="right">%d</td>' % len("one"),
4492 self.failUnless(re.search(get_lonely, res), res)
4494 # find the More Info link for name, should be relative
4495 mo = re.search(r'<a href="([^"]+)">More Info</a>', res)
4496 info_url = mo.group(1)
4497 self.failUnless(info_url.endswith(urllib.quote(lonely_uri) + "?t=info"), info_url)
4498 d.addCallback(_check_html)
4501 d.addCallback(lambda ign: self.GET(self.rooturl+"?t=json"))
4502 def _check_json(res):
4503 data = simplejson.loads(res)
4504 self.failUnlessEqual(data[0], "dirnode")
4505 listed_children = data[1]["children"]
4506 self.failUnlessReallyEqual(sorted(listed_children.keys()), [u"lonely"])
4507 ll_type, ll_data = listed_children[u"lonely"]
4508 self.failUnlessEqual(ll_type, "filenode")
4509 self.failIf("rw_uri" in ll_data)
4510 self.failUnlessReallyEqual(to_str(ll_data["ro_uri"]), lonely_uri)
4511 d.addCallback(_check_json)
4514 def test_deep_check(self):
4515 self.basedir = "web/Grid/deep_check"
4517 c0 = self.g.clients[0]
4521 d = c0.create_dirnode()
4522 def _stash_root_and_create_file(n):
4524 self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri()) + "/"
4525 return n.add_file(u"good", upload.Data(DATA, convergence=""))
4526 d.addCallback(_stash_root_and_create_file)
4527 def _stash_uri(fn, which):
4528 self.uris[which] = fn.get_uri()
4530 d.addCallback(_stash_uri, "good")
4531 d.addCallback(lambda ign:
4532 self.rootnode.add_file(u"small",
4533 upload.Data("literal",
4535 d.addCallback(_stash_uri, "small")
4536 d.addCallback(lambda ign:
4537 self.rootnode.add_file(u"sick",
4538 upload.Data(DATA+"1",
4540 d.addCallback(_stash_uri, "sick")
4542 # this tests that deep-check and stream-manifest will ignore
4543 # UnknownNode instances. Hopefully this will also cover deep-stats.
4544 future_node = UnknownNode(unknown_rwcap, unknown_rocap)
4545 d.addCallback(lambda ign: self.rootnode.set_node(u"future", future_node))
4547 def _clobber_shares(ignored):
4548 self.delete_shares_numbered(self.uris["sick"], [0,1])
4549 d.addCallback(_clobber_shares)
4557 d.addCallback(self.CHECK, "root", "t=stream-deep-check")
4560 units = [simplejson.loads(line)
4561 for line in res.splitlines()
4564 print "response is:", res
4565 print "undecodeable line was '%s'" % line
4567 self.failUnlessReallyEqual(len(units), 5+1)
4568 # should be parent-first
4570 self.failUnlessEqual(u0["path"], [])
4571 self.failUnlessEqual(u0["type"], "directory")
4572 self.failUnlessReallyEqual(to_str(u0["cap"]), self.rootnode.get_uri())
4573 u0cr = u0["check-results"]
4574 self.failUnlessReallyEqual(u0cr["results"]["count-shares-good"], 10)
4576 ugood = [u for u in units
4577 if u["type"] == "file" and u["path"] == [u"good"]][0]
4578 self.failUnlessReallyEqual(to_str(ugood["cap"]), self.uris["good"])
4579 ugoodcr = ugood["check-results"]
4580 self.failUnlessReallyEqual(ugoodcr["results"]["count-shares-good"], 10)
4583 self.failUnlessEqual(stats["type"], "stats")
4585 self.failUnlessReallyEqual(s["count-immutable-files"], 2)
4586 self.failUnlessReallyEqual(s["count-literal-files"], 1)
4587 self.failUnlessReallyEqual(s["count-directories"], 1)
4588 self.failUnlessReallyEqual(s["count-unknown"], 1)
4589 d.addCallback(_done)
4591 d.addCallback(self.CHECK, "root", "t=stream-manifest")
4592 def _check_manifest(res):
4593 self.failUnless(res.endswith("\n"))
4594 units = [simplejson.loads(t) for t in res[:-1].split("\n")]
4595 self.failUnlessReallyEqual(len(units), 5+1)
4596 self.failUnlessEqual(units[-1]["type"], "stats")
4598 self.failUnlessEqual(first["path"], [])
4599 self.failUnlessEqual(to_str(first["cap"]), self.rootnode.get_uri())
4600 self.failUnlessEqual(first["type"], "directory")
4601 stats = units[-1]["stats"]
4602 self.failUnlessReallyEqual(stats["count-immutable-files"], 2)
4603 self.failUnlessReallyEqual(stats["count-literal-files"], 1)
4604 self.failUnlessReallyEqual(stats["count-mutable-files"], 0)
4605 self.failUnlessReallyEqual(stats["count-immutable-files"], 2)
4606 self.failUnlessReallyEqual(stats["count-unknown"], 1)
4607 d.addCallback(_check_manifest)
4609 # now add root/subdir and root/subdir/grandchild, then make subdir
4610 # unrecoverable, then see what happens
4612 d.addCallback(lambda ign:
4613 self.rootnode.create_subdirectory(u"subdir"))
4614 d.addCallback(_stash_uri, "subdir")
4615 d.addCallback(lambda subdir_node:
4616 subdir_node.add_file(u"grandchild",
4617 upload.Data(DATA+"2",
4619 d.addCallback(_stash_uri, "grandchild")
4621 d.addCallback(lambda ign:
4622 self.delete_shares_numbered(self.uris["subdir"],
4630 # root/subdir [unrecoverable]
4631 # root/subdir/grandchild
4633 # how should a streaming-JSON API indicate fatal error?
4634 # answer: emit ERROR: instead of a JSON string
4636 d.addCallback(self.CHECK, "root", "t=stream-manifest")
4637 def _check_broken_manifest(res):
4638 lines = res.splitlines()
4640 for (i,line) in enumerate(lines)
4641 if line.startswith("ERROR:")]
4643 self.fail("no ERROR: in output: %s" % (res,))
4644 first_error = error_lines[0]
4645 error_line = lines[first_error]
4646 error_msg = lines[first_error+1:]
4647 error_msg_s = "\n".join(error_msg) + "\n"
4648 self.failUnlessIn("ERROR: UnrecoverableFileError(no recoverable versions)",
4650 self.failUnless(len(error_msg) > 2, error_msg_s) # some traceback
4651 units = [simplejson.loads(line) for line in lines[:first_error]]
4652 self.failUnlessReallyEqual(len(units), 6) # includes subdir
4653 last_unit = units[-1]
4654 self.failUnlessEqual(last_unit["path"], ["subdir"])
4655 d.addCallback(_check_broken_manifest)
4657 d.addCallback(self.CHECK, "root", "t=stream-deep-check")
4658 def _check_broken_deepcheck(res):
4659 lines = res.splitlines()
4661 for (i,line) in enumerate(lines)
4662 if line.startswith("ERROR:")]
4664 self.fail("no ERROR: in output: %s" % (res,))
4665 first_error = error_lines[0]
4666 error_line = lines[first_error]
4667 error_msg = lines[first_error+1:]
4668 error_msg_s = "\n".join(error_msg) + "\n"
4669 self.failUnlessIn("ERROR: UnrecoverableFileError(no recoverable versions)",
4671 self.failUnless(len(error_msg) > 2, error_msg_s) # some traceback
4672 units = [simplejson.loads(line) for line in lines[:first_error]]
4673 self.failUnlessReallyEqual(len(units), 6) # includes subdir
4674 last_unit = units[-1]
4675 self.failUnlessEqual(last_unit["path"], ["subdir"])
4676 r = last_unit["check-results"]["results"]
4677 self.failUnlessReallyEqual(r["count-recoverable-versions"], 0)
4678 self.failUnlessReallyEqual(r["count-shares-good"], 1)
4679 self.failUnlessReallyEqual(r["recoverable"], False)
4680 d.addCallback(_check_broken_deepcheck)
4682 d.addErrback(self.explain_web_error)
4685 def test_deep_check_and_repair(self):
4686 self.basedir = "web/Grid/deep_check_and_repair"
4688 c0 = self.g.clients[0]
4692 d = c0.create_dirnode()
4693 def _stash_root_and_create_file(n):
4695 self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri()) + "/"
4696 return n.add_file(u"good", upload.Data(DATA, convergence=""))
4697 d.addCallback(_stash_root_and_create_file)
4698 def _stash_uri(fn, which):
4699 self.uris[which] = fn.get_uri()
4700 d.addCallback(_stash_uri, "good")
4701 d.addCallback(lambda ign:
4702 self.rootnode.add_file(u"small",
4703 upload.Data("literal",
4705 d.addCallback(_stash_uri, "small")
4706 d.addCallback(lambda ign:
4707 self.rootnode.add_file(u"sick",
4708 upload.Data(DATA+"1",
4710 d.addCallback(_stash_uri, "sick")
4711 #d.addCallback(lambda ign:
4712 # self.rootnode.add_file(u"dead",
4713 # upload.Data(DATA+"2",
4715 #d.addCallback(_stash_uri, "dead")
4717 #d.addCallback(lambda ign: c0.create_mutable_file("mutable"))
4718 #d.addCallback(lambda fn: self.rootnode.set_node(u"corrupt", fn))
4719 #d.addCallback(_stash_uri, "corrupt")
4721 def _clobber_shares(ignored):
4722 good_shares = self.find_uri_shares(self.uris["good"])
4723 self.failUnlessReallyEqual(len(good_shares), 10)
4724 sick_shares = self.find_uri_shares(self.uris["sick"])
4725 os.unlink(sick_shares[0][2])
4726 #dead_shares = self.find_uri_shares(self.uris["dead"])
4727 #for i in range(1, 10):
4728 # os.unlink(dead_shares[i][2])
4730 #c_shares = self.find_uri_shares(self.uris["corrupt"])
4731 #cso = CorruptShareOptions()
4732 #cso.stdout = StringIO()
4733 #cso.parseOptions([c_shares[0][2]])
4735 d.addCallback(_clobber_shares)
4738 # root/good CHK, 10 shares
4740 # root/sick CHK, 9 shares
4742 d.addCallback(self.CHECK, "root", "t=stream-deep-check&repair=true")
4744 units = [simplejson.loads(line)
4745 for line in res.splitlines()
4747 self.failUnlessReallyEqual(len(units), 4+1)
4748 # should be parent-first
4750 self.failUnlessEqual(u0["path"], [])
4751 self.failUnlessEqual(u0["type"], "directory")
4752 self.failUnlessReallyEqual(to_str(u0["cap"]), self.rootnode.get_uri())
4753 u0crr = u0["check-and-repair-results"]
4754 self.failUnlessReallyEqual(u0crr["repair-attempted"], False)
4755 self.failUnlessReallyEqual(u0crr["pre-repair-results"]["results"]["count-shares-good"], 10)
4757 ugood = [u for u in units
4758 if u["type"] == "file" and u["path"] == [u"good"]][0]
4759 self.failUnlessEqual(to_str(ugood["cap"]), self.uris["good"])
4760 ugoodcrr = ugood["check-and-repair-results"]
4761 self.failUnlessReallyEqual(ugoodcrr["repair-attempted"], False)
4762 self.failUnlessReallyEqual(ugoodcrr["pre-repair-results"]["results"]["count-shares-good"], 10)
4764 usick = [u for u in units
4765 if u["type"] == "file" and u["path"] == [u"sick"]][0]
4766 self.failUnlessReallyEqual(to_str(usick["cap"]), self.uris["sick"])
4767 usickcrr = usick["check-and-repair-results"]
4768 self.failUnlessReallyEqual(usickcrr["repair-attempted"], True)
4769 self.failUnlessReallyEqual(usickcrr["repair-successful"], True)
4770 self.failUnlessReallyEqual(usickcrr["pre-repair-results"]["results"]["count-shares-good"], 9)
4771 self.failUnlessReallyEqual(usickcrr["post-repair-results"]["results"]["count-shares-good"], 10)
4774 self.failUnlessEqual(stats["type"], "stats")
4776 self.failUnlessReallyEqual(s["count-immutable-files"], 2)
4777 self.failUnlessReallyEqual(s["count-literal-files"], 1)
4778 self.failUnlessReallyEqual(s["count-directories"], 1)
4779 d.addCallback(_done)
4781 d.addErrback(self.explain_web_error)
4784 def _count_leases(self, ignored, which):
4785 u = self.uris[which]
4786 shares = self.find_uri_shares(u)
4788 for shnum, serverid, fn in shares:
4789 sf = get_share_file(fn)
4790 num_leases = len(list(sf.get_leases()))
4791 lease_counts.append( (fn, num_leases) )
4794 def _assert_leasecount(self, lease_counts, expected):
4795 for (fn, num_leases) in lease_counts:
4796 if num_leases != expected:
4797 self.fail("expected %d leases, have %d, on %s" %
4798 (expected, num_leases, fn))
4800 def test_add_lease(self):
4801 self.basedir = "web/Grid/add_lease"
4802 self.set_up_grid(num_clients=2)
4803 c0 = self.g.clients[0]
4806 d = c0.upload(upload.Data(DATA, convergence=""))
4807 def _stash_uri(ur, which):
4808 self.uris[which] = ur.uri
4809 d.addCallback(_stash_uri, "one")
4810 d.addCallback(lambda ign:
4811 c0.upload(upload.Data(DATA+"1", convergence="")))
4812 d.addCallback(_stash_uri, "two")
4813 def _stash_mutable_uri(n, which):
4814 self.uris[which] = n.get_uri()
4815 assert isinstance(self.uris[which], str)
4816 d.addCallback(lambda ign:
4817 c0.create_mutable_file(publish.MutableData(DATA+"2")))
4818 d.addCallback(_stash_mutable_uri, "mutable")
4820 def _compute_fileurls(ignored):
4822 for which in self.uris:
4823 self.fileurls[which] = "uri/" + urllib.quote(self.uris[which])
4824 d.addCallback(_compute_fileurls)
4826 d.addCallback(self._count_leases, "one")
4827 d.addCallback(self._assert_leasecount, 1)
4828 d.addCallback(self._count_leases, "two")
4829 d.addCallback(self._assert_leasecount, 1)
4830 d.addCallback(self._count_leases, "mutable")
4831 d.addCallback(self._assert_leasecount, 1)
4833 d.addCallback(self.CHECK, "one", "t=check") # no add-lease
4834 def _got_html_good(res):
4835 self.failUnless("Healthy" in res, res)
4836 self.failIf("Not Healthy" in res, res)
4837 d.addCallback(_got_html_good)
4839 d.addCallback(self._count_leases, "one")
4840 d.addCallback(self._assert_leasecount, 1)
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 # this CHECK uses the original client, which uses the same
4847 # lease-secrets, so it will just renew the original lease
4848 d.addCallback(self.CHECK, "one", "t=check&add-lease=true")
4849 d.addCallback(_got_html_good)
4851 d.addCallback(self._count_leases, "one")
4852 d.addCallback(self._assert_leasecount, 1)
4853 d.addCallback(self._count_leases, "two")
4854 d.addCallback(self._assert_leasecount, 1)
4855 d.addCallback(self._count_leases, "mutable")
4856 d.addCallback(self._assert_leasecount, 1)
4858 # this CHECK uses an alternate client, which adds a second lease
4859 d.addCallback(self.CHECK, "one", "t=check&add-lease=true", clientnum=1)
4860 d.addCallback(_got_html_good)
4862 d.addCallback(self._count_leases, "one")
4863 d.addCallback(self._assert_leasecount, 2)
4864 d.addCallback(self._count_leases, "two")
4865 d.addCallback(self._assert_leasecount, 1)
4866 d.addCallback(self._count_leases, "mutable")
4867 d.addCallback(self._assert_leasecount, 1)
4869 d.addCallback(self.CHECK, "mutable", "t=check&add-lease=true")
4870 d.addCallback(_got_html_good)
4872 d.addCallback(self._count_leases, "one")
4873 d.addCallback(self._assert_leasecount, 2)
4874 d.addCallback(self._count_leases, "two")
4875 d.addCallback(self._assert_leasecount, 1)
4876 d.addCallback(self._count_leases, "mutable")
4877 d.addCallback(self._assert_leasecount, 1)
4879 d.addCallback(self.CHECK, "mutable", "t=check&add-lease=true",
4881 d.addCallback(_got_html_good)
4883 d.addCallback(self._count_leases, "one")
4884 d.addCallback(self._assert_leasecount, 2)
4885 d.addCallback(self._count_leases, "two")
4886 d.addCallback(self._assert_leasecount, 1)
4887 d.addCallback(self._count_leases, "mutable")
4888 d.addCallback(self._assert_leasecount, 2)
4890 d.addErrback(self.explain_web_error)
4893 def test_deep_add_lease(self):
4894 self.basedir = "web/Grid/deep_add_lease"
4895 self.set_up_grid(num_clients=2)
4896 c0 = self.g.clients[0]
4900 d = c0.create_dirnode()
4901 def _stash_root_and_create_file(n):
4903 self.uris["root"] = n.get_uri()
4904 self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri()) + "/"
4905 return n.add_file(u"one", upload.Data(DATA, convergence=""))
4906 d.addCallback(_stash_root_and_create_file)
4907 def _stash_uri(fn, which):
4908 self.uris[which] = fn.get_uri()
4909 d.addCallback(_stash_uri, "one")
4910 d.addCallback(lambda ign:
4911 self.rootnode.add_file(u"small",
4912 upload.Data("literal",
4914 d.addCallback(_stash_uri, "small")
4916 d.addCallback(lambda ign:
4917 c0.create_mutable_file(publish.MutableData("mutable")))
4918 d.addCallback(lambda fn: self.rootnode.set_node(u"mutable", fn))
4919 d.addCallback(_stash_uri, "mutable")
4921 d.addCallback(self.CHECK, "root", "t=stream-deep-check") # no add-lease
4923 units = [simplejson.loads(line)
4924 for line in res.splitlines()
4926 # root, one, small, mutable, stats
4927 self.failUnlessReallyEqual(len(units), 4+1)
4928 d.addCallback(_done)
4930 d.addCallback(self._count_leases, "root")
4931 d.addCallback(self._assert_leasecount, 1)
4932 d.addCallback(self._count_leases, "one")
4933 d.addCallback(self._assert_leasecount, 1)
4934 d.addCallback(self._count_leases, "mutable")
4935 d.addCallback(self._assert_leasecount, 1)
4937 d.addCallback(self.CHECK, "root", "t=stream-deep-check&add-lease=true")
4938 d.addCallback(_done)
4940 d.addCallback(self._count_leases, "root")
4941 d.addCallback(self._assert_leasecount, 1)
4942 d.addCallback(self._count_leases, "one")
4943 d.addCallback(self._assert_leasecount, 1)
4944 d.addCallback(self._count_leases, "mutable")
4945 d.addCallback(self._assert_leasecount, 1)
4947 d.addCallback(self.CHECK, "root", "t=stream-deep-check&add-lease=true",
4949 d.addCallback(_done)
4951 d.addCallback(self._count_leases, "root")
4952 d.addCallback(self._assert_leasecount, 2)
4953 d.addCallback(self._count_leases, "one")
4954 d.addCallback(self._assert_leasecount, 2)
4955 d.addCallback(self._count_leases, "mutable")
4956 d.addCallback(self._assert_leasecount, 2)
4958 d.addErrback(self.explain_web_error)
4962 def test_exceptions(self):
4963 self.basedir = "web/Grid/exceptions"
4964 self.set_up_grid(num_clients=1, num_servers=2)
4965 c0 = self.g.clients[0]
4966 c0.DEFAULT_ENCODING_PARAMETERS['happy'] = 2
4969 d = c0.create_dirnode()
4971 self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri()) + "/"
4972 self.fileurls["imaginary"] = self.fileurls["root"] + "imaginary"
4974 d.addCallback(_stash_root)
4975 d.addCallback(lambda ign: c0.upload(upload.Data(DATA, convergence="")))
4977 self.fileurls["1share"] = "uri/" + urllib.quote(ur.uri)
4978 self.delete_shares_numbered(ur.uri, range(1,10))
4980 u = uri.from_string(ur.uri)
4981 u.key = testutil.flip_bit(u.key, 0)
4982 baduri = u.to_string()
4983 self.fileurls["0shares"] = "uri/" + urllib.quote(baduri)
4984 d.addCallback(_stash_bad)
4985 d.addCallback(lambda ign: c0.create_dirnode())
4986 def _mangle_dirnode_1share(n):
4988 url = self.fileurls["dir-1share"] = "uri/" + urllib.quote(u) + "/"
4989 self.fileurls["dir-1share-json"] = url + "?t=json"
4990 self.delete_shares_numbered(u, range(1,10))
4991 d.addCallback(_mangle_dirnode_1share)
4992 d.addCallback(lambda ign: c0.create_dirnode())
4993 def _mangle_dirnode_0share(n):
4995 url = self.fileurls["dir-0share"] = "uri/" + urllib.quote(u) + "/"
4996 self.fileurls["dir-0share-json"] = url + "?t=json"
4997 self.delete_shares_numbered(u, range(0,10))
4998 d.addCallback(_mangle_dirnode_0share)
5000 # NotEnoughSharesError should be reported sensibly, with a
5001 # text/plain explanation of the problem, and perhaps some
5002 # information on which shares *could* be found.
5004 d.addCallback(lambda ignored:
5005 self.shouldHTTPError("GET unrecoverable",
5006 410, "Gone", "NoSharesError",
5007 self.GET, self.fileurls["0shares"]))
5008 def _check_zero_shares(body):
5009 self.failIf("<html>" in body, body)
5010 body = " ".join(body.strip().split())
5011 exp = ("NoSharesError: no shares could be found. "
5012 "Zero shares usually indicates a corrupt URI, or that "
5013 "no servers were connected, but it might also indicate "
5014 "severe corruption. You should perform a filecheck on "
5015 "this object to learn more. The full error message is: "
5016 "no shares (need 3). Last failure: None")
5017 self.failUnlessReallyEqual(exp, body)
5018 d.addCallback(_check_zero_shares)
5021 d.addCallback(lambda ignored:
5022 self.shouldHTTPError("GET 1share",
5023 410, "Gone", "NotEnoughSharesError",
5024 self.GET, self.fileurls["1share"]))
5025 def _check_one_share(body):
5026 self.failIf("<html>" in body, body)
5027 body = " ".join(body.strip().split())
5028 msgbase = ("NotEnoughSharesError: This indicates that some "
5029 "servers were unavailable, or that shares have been "
5030 "lost to server departure, hard drive failure, or disk "
5031 "corruption. You should perform a filecheck on "
5032 "this object to learn more. The full error message is:"
5034 msg1 = msgbase + (" ran out of shares:"
5037 " overdue= unused= need 3. Last failure: None")
5038 msg2 = msgbase + (" ran out of shares:"
5040 " pending=Share(sh0-on-xgru5)"
5041 " overdue= unused= need 3. Last failure: None")
5042 self.failUnless(body == msg1 or body == msg2, body)
5043 d.addCallback(_check_one_share)
5045 d.addCallback(lambda ignored:
5046 self.shouldHTTPError("GET imaginary",
5047 404, "Not Found", None,
5048 self.GET, self.fileurls["imaginary"]))
5049 def _missing_child(body):
5050 self.failUnless("No such child: imaginary" in body, body)
5051 d.addCallback(_missing_child)
5053 d.addCallback(lambda ignored: self.GET(self.fileurls["dir-0share"]))
5054 def _check_0shares_dir_html(body):
5055 self.failUnless("<html>" in body, body)
5056 # we should see the regular page, but without the child table or
5058 body = " ".join(body.strip().split())
5059 self.failUnlessIn('href="?t=info">More info on this directory',
5061 exp = ("UnrecoverableFileError: the directory (or mutable file) "
5062 "could not be retrieved, because there were insufficient "
5063 "good shares. This might indicate that no servers were "
5064 "connected, insufficient servers were connected, the URI "
5065 "was corrupt, or that shares have been lost due to server "
5066 "departure, hard drive failure, or disk corruption. You "
5067 "should perform a filecheck on this object to learn more.")
5068 self.failUnlessIn(exp, body)
5069 self.failUnlessIn("No upload forms: directory is unreadable", body)
5070 d.addCallback(_check_0shares_dir_html)
5072 d.addCallback(lambda ignored: self.GET(self.fileurls["dir-1share"]))
5073 def _check_1shares_dir_html(body):
5074 # at some point, we'll split UnrecoverableFileError into 0-shares
5075 # and some-shares like we did for immutable files (since there
5076 # are different sorts of advice to offer in each case). For now,
5077 # they present the same way.
5078 self.failUnless("<html>" in body, body)
5079 body = " ".join(body.strip().split())
5080 self.failUnlessIn('href="?t=info">More info on this directory',
5082 exp = ("UnrecoverableFileError: the directory (or mutable file) "
5083 "could not be retrieved, because there were insufficient "
5084 "good shares. This might indicate that no servers were "
5085 "connected, insufficient servers were connected, the URI "
5086 "was corrupt, or that shares have been lost due to server "
5087 "departure, hard drive failure, or disk corruption. You "
5088 "should perform a filecheck on this object to learn more.")
5089 self.failUnlessIn(exp, body)
5090 self.failUnlessIn("No upload forms: directory is unreadable", body)
5091 d.addCallback(_check_1shares_dir_html)
5093 d.addCallback(lambda ignored:
5094 self.shouldHTTPError("GET dir-0share-json",
5095 410, "Gone", "UnrecoverableFileError",
5097 self.fileurls["dir-0share-json"]))
5098 def _check_unrecoverable_file(body):
5099 self.failIf("<html>" in body, body)
5100 body = " ".join(body.strip().split())
5101 exp = ("UnrecoverableFileError: the directory (or mutable file) "
5102 "could not be retrieved, because there were insufficient "
5103 "good shares. This might indicate that no servers were "
5104 "connected, insufficient servers were connected, the URI "
5105 "was corrupt, or that shares have been lost due to server "
5106 "departure, hard drive failure, or disk corruption. You "
5107 "should perform a filecheck on this object to learn more.")
5108 self.failUnlessReallyEqual(exp, body)
5109 d.addCallback(_check_unrecoverable_file)
5111 d.addCallback(lambda ignored:
5112 self.shouldHTTPError("GET dir-1share-json",
5113 410, "Gone", "UnrecoverableFileError",
5115 self.fileurls["dir-1share-json"]))
5116 d.addCallback(_check_unrecoverable_file)
5118 d.addCallback(lambda ignored:
5119 self.shouldHTTPError("GET imaginary",
5120 404, "Not Found", None,
5121 self.GET, self.fileurls["imaginary"]))
5123 # attach a webapi child that throws a random error, to test how it
5125 w = c0.getServiceNamed("webish")
5126 w.root.putChild("ERRORBOOM", ErrorBoom())
5128 # "Accept: */*" : should get a text/html stack trace
5129 # "Accept: text/plain" : should get a text/plain stack trace
5130 # "Accept: text/plain, application/octet-stream" : text/plain (CLI)
5131 # no Accept header: should get a text/html stack trace
5133 d.addCallback(lambda ignored:
5134 self.shouldHTTPError("GET errorboom_html",
5135 500, "Internal Server Error", None,
5136 self.GET, "ERRORBOOM",
5137 headers={"accept": ["*/*"]}))
5138 def _internal_error_html1(body):
5139 self.failUnless("<html>" in body, "expected HTML, not '%s'" % body)
5140 d.addCallback(_internal_error_html1)
5142 d.addCallback(lambda ignored:
5143 self.shouldHTTPError("GET errorboom_text",
5144 500, "Internal Server Error", None,
5145 self.GET, "ERRORBOOM",
5146 headers={"accept": ["text/plain"]}))
5147 def _internal_error_text2(body):
5148 self.failIf("<html>" in body, body)
5149 self.failUnless(body.startswith("Traceback "), body)
5150 d.addCallback(_internal_error_text2)
5152 CLI_accepts = "text/plain, application/octet-stream"
5153 d.addCallback(lambda ignored:
5154 self.shouldHTTPError("GET errorboom_text",
5155 500, "Internal Server Error", None,
5156 self.GET, "ERRORBOOM",
5157 headers={"accept": [CLI_accepts]}))
5158 def _internal_error_text3(body):
5159 self.failIf("<html>" in body, body)
5160 self.failUnless(body.startswith("Traceback "), body)
5161 d.addCallback(_internal_error_text3)
5163 d.addCallback(lambda ignored:
5164 self.shouldHTTPError("GET errorboom_text",
5165 500, "Internal Server Error", None,
5166 self.GET, "ERRORBOOM"))
5167 def _internal_error_html4(body):
5168 self.failUnless("<html>" in body, "expected HTML, not '%s'" % body)
5169 d.addCallback(_internal_error_html4)
5171 def _flush_errors(res):
5172 # Trial: please ignore the CompletelyUnhandledError in the logs
5173 self.flushLoggedErrors(CompletelyUnhandledError)
5175 d.addBoth(_flush_errors)
5179 def test_blacklist(self):
5180 # download from a blacklisted URI, get an error
5181 self.basedir = "web/Grid/blacklist"
5183 c0 = self.g.clients[0]
5184 c0_basedir = c0.basedir
5185 fn = os.path.join(c0_basedir, "access.blacklist")
5187 DATA = "off-limits " * 50
5189 d = c0.upload(upload.Data(DATA, convergence=""))
5190 def _stash_uri_and_create_dir(ur):
5192 self.url = "uri/"+self.uri
5193 u = uri.from_string_filenode(self.uri)
5194 self.si = u.get_storage_index()
5195 childnode = c0.create_node_from_uri(self.uri, None)
5196 return c0.create_dirnode({u"blacklisted.txt": (childnode,{}) })
5197 d.addCallback(_stash_uri_and_create_dir)
5198 def _stash_dir(node):
5199 self.dir_node = node
5200 self.dir_uri = node.get_uri()
5201 self.dir_url = "uri/"+self.dir_uri
5202 d.addCallback(_stash_dir)
5203 d.addCallback(lambda ign: self.GET(self.dir_url, followRedirect=True))
5204 def _check_dir_html(body):
5205 self.failUnlessIn("<html>", body)
5206 self.failUnlessIn("blacklisted.txt</a>", body)
5207 d.addCallback(_check_dir_html)
5208 d.addCallback(lambda ign: self.GET(self.url))
5209 d.addCallback(lambda body: self.failUnlessEqual(DATA, body))
5211 def _blacklist(ign):
5213 f.write(" # this is a comment\n")
5215 f.write("\n") # also exercise blank lines
5216 f.write("%s %s\n" % (base32.b2a(self.si), "off-limits to you"))
5218 # clients should be checking the blacklist each time, so we don't
5219 # need to restart the client
5220 d.addCallback(_blacklist)
5221 d.addCallback(lambda ign: self.shouldHTTPError("get_from_blacklisted_uri",
5223 "Access Prohibited: off-limits",
5224 self.GET, self.url))
5226 # We should still be able to list the parent directory, in HTML...
5227 d.addCallback(lambda ign: self.GET(self.dir_url, followRedirect=True))
5228 def _check_dir_html2(body):
5229 self.failUnlessIn("<html>", body)
5230 self.failUnlessIn("blacklisted.txt</strike>", body)
5231 d.addCallback(_check_dir_html2)
5233 # ... and in JSON (used by CLI).
5234 d.addCallback(lambda ign: self.GET(self.dir_url+"?t=json", followRedirect=True))
5235 def _check_dir_json(res):
5236 data = simplejson.loads(res)
5237 self.failUnless(isinstance(data, list), data)
5238 self.failUnlessEqual(data[0], "dirnode")
5239 self.failUnless(isinstance(data[1], dict), data)
5240 self.failUnlessIn("children", data[1])
5241 self.failUnlessIn("blacklisted.txt", data[1]["children"])
5242 childdata = data[1]["children"]["blacklisted.txt"]
5243 self.failUnless(isinstance(childdata, list), data)
5244 self.failUnlessEqual(childdata[0], "filenode")
5245 self.failUnless(isinstance(childdata[1], dict), data)
5246 d.addCallback(_check_dir_json)
5248 def _unblacklist(ign):
5249 open(fn, "w").close()
5250 # the Blacklist object watches mtime to tell when the file has
5251 # changed, but on windows this test will run faster than the
5252 # filesystem's mtime resolution. So we edit Blacklist.last_mtime
5253 # to force a reload.
5254 self.g.clients[0].blacklist.last_mtime -= 2.0
5255 d.addCallback(_unblacklist)
5257 # now a read should work
5258 d.addCallback(lambda ign: self.GET(self.url))
5259 d.addCallback(lambda body: self.failUnlessEqual(DATA, body))
5261 # read again to exercise the blacklist-is-unchanged logic
5262 d.addCallback(lambda ign: self.GET(self.url))
5263 d.addCallback(lambda body: self.failUnlessEqual(DATA, body))
5265 # now add a blacklisted directory, and make sure files under it are
5268 childnode = c0.create_node_from_uri(self.uri, None)
5269 return c0.create_dirnode({u"child": (childnode,{}) })
5270 d.addCallback(_add_dir)
5271 def _get_dircap(dn):
5272 self.dir_si_b32 = base32.b2a(dn.get_storage_index())
5273 self.dir_url_base = "uri/"+dn.get_write_uri()
5274 self.dir_url_json1 = "uri/"+dn.get_write_uri()+"?t=json"
5275 self.dir_url_json2 = "uri/"+dn.get_write_uri()+"/?t=json"
5276 self.dir_url_json_ro = "uri/"+dn.get_readonly_uri()+"/?t=json"
5277 self.child_url = "uri/"+dn.get_readonly_uri()+"/child"
5278 d.addCallback(_get_dircap)
5279 d.addCallback(lambda ign: self.GET(self.dir_url_base, followRedirect=True))
5280 d.addCallback(lambda body: self.failUnlessIn("<html>", body))
5281 d.addCallback(lambda ign: self.GET(self.dir_url_json1))
5282 d.addCallback(lambda res: simplejson.loads(res)) # just check it decodes
5283 d.addCallback(lambda ign: self.GET(self.dir_url_json2))
5284 d.addCallback(lambda res: simplejson.loads(res)) # just check it decodes
5285 d.addCallback(lambda ign: self.GET(self.dir_url_json_ro))
5286 d.addCallback(lambda res: simplejson.loads(res)) # just check it decodes
5287 d.addCallback(lambda ign: self.GET(self.child_url))
5288 d.addCallback(lambda body: self.failUnlessEqual(DATA, body))
5290 def _block_dir(ign):
5292 f.write("%s %s\n" % (self.dir_si_b32, "dir-off-limits to you"))
5294 self.g.clients[0].blacklist.last_mtime -= 2.0
5295 d.addCallback(_block_dir)
5296 d.addCallback(lambda ign: self.shouldHTTPError("get_from_blacklisted_dir base",
5298 "Access Prohibited: dir-off-limits",
5299 self.GET, self.dir_url_base))
5300 d.addCallback(lambda ign: self.shouldHTTPError("get_from_blacklisted_dir json1",
5302 "Access Prohibited: dir-off-limits",
5303 self.GET, self.dir_url_json1))
5304 d.addCallback(lambda ign: self.shouldHTTPError("get_from_blacklisted_dir json2",
5306 "Access Prohibited: dir-off-limits",
5307 self.GET, self.dir_url_json2))
5308 d.addCallback(lambda ign: self.shouldHTTPError("get_from_blacklisted_dir json_ro",
5310 "Access Prohibited: dir-off-limits",
5311 self.GET, self.dir_url_json_ro))
5312 d.addCallback(lambda ign: self.shouldHTTPError("get_from_blacklisted_dir child",
5314 "Access Prohibited: dir-off-limits",
5315 self.GET, self.child_url))
5319 class CompletelyUnhandledError(Exception):
5321 class ErrorBoom(rend.Page):
5322 def beforeRender(self, ctx):
5323 raise CompletelyUnhandledError("whoops")