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, "test_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, "test_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 t=mkdir 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 /uri?t=bogus", 400, "Bad Request",
3010 "/uri accepts only PUT, PUT?t=mkdir, "
3011 "POST?t=upload, and POST?t=mkdir",
3012 self.POST, "/uri?t=bogus")
3015 def test_POST_mkdir_no_parentdir_immutable(self):
3016 (newkids, caps) = self._create_immutable_children()
3017 d = self.POST2("/uri?t=mkdir-immutable", simplejson.dumps(newkids))
3018 def _after_mkdir(res):
3019 self.failUnless(res.startswith("URI:DIR"), res)
3020 n = self.s.create_node_from_uri(res)
3021 d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
3022 d2.addCallback(lambda ign:
3023 self.failUnlessROChildURIIs(n, u"child-imm",
3025 d2.addCallback(lambda ign:
3026 self.failUnlessROChildURIIs(n, u"unknownchild-imm",
3027 caps['unknown_immcap']))
3028 d2.addCallback(lambda ign:
3029 self.failUnlessROChildURIIs(n, u"dirchild-imm",
3031 d2.addCallback(lambda ign:
3032 self.failUnlessROChildURIIs(n, u"dirchild-lit",
3034 d2.addCallback(lambda ign:
3035 self.failUnlessROChildURIIs(n, u"dirchild-empty",
3036 caps['emptydircap']))
3038 d.addCallback(_after_mkdir)
3041 def test_POST_mkdir_no_parentdir_immutable_bad(self):
3042 (newkids, caps) = self._create_initial_children()
3043 d = self.shouldFail2(error.Error,
3044 "test_POST_mkdir_no_parentdir_immutable_bad",
3046 "needed to be immutable but was not",
3048 "/uri?t=mkdir-immutable",
3049 simplejson.dumps(newkids))
3052 def test_welcome_page_mkdir_button(self):
3053 # Fetch the welcome page.
3055 def _after_get_welcome_page(res):
3056 MKDIR_BUTTON_RE = re.compile(
3057 '<form action="([^"]*)" method="post".*?'
3058 '<input type="hidden" name="t" value="([^"]*)" />'
3059 '<input type="hidden" name="([^"]*)" value="([^"]*)" />'
3060 '<input type="submit" value="Create a directory" />',
3062 mo = MKDIR_BUTTON_RE.search(res)
3063 formaction = mo.group(1)
3065 formaname = mo.group(3)
3066 formavalue = mo.group(4)
3067 return (formaction, formt, formaname, formavalue)
3068 d.addCallback(_after_get_welcome_page)
3069 def _after_parse_form(res):
3070 (formaction, formt, formaname, formavalue) = res
3071 return self.POST("/%s?t=%s&%s=%s" % (formaction, formt, formaname, formavalue))
3072 d.addCallback(_after_parse_form)
3073 d.addBoth(self.shouldRedirect, None, statuscode='303')
3076 def test_POST_mkdir_replace(self): # return value?
3077 d = self.POST(self.public_url + "/foo", t="mkdir", name="sub")
3078 d.addCallback(lambda res: self._foo_node.get(u"sub"))
3079 d.addCallback(self.failUnlessNodeKeysAre, [])
3082 def test_POST_mkdir_no_replace_queryarg(self): # return value?
3083 d = self.POST(self.public_url + "/foo?replace=false", t="mkdir", name="sub")
3084 d.addBoth(self.shouldFail, error.Error,
3085 "POST_mkdir_no_replace_queryarg",
3087 "There was already a child by that name, and you asked me "
3088 "to not replace it")
3089 d.addCallback(lambda res: self._foo_node.get(u"sub"))
3090 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
3093 def test_POST_mkdir_no_replace_field(self): # return value?
3094 d = self.POST(self.public_url + "/foo", t="mkdir", name="sub",
3096 d.addBoth(self.shouldFail, error.Error, "POST_mkdir_no_replace_field",
3098 "There was already a child by that name, and you asked me "
3099 "to not replace it")
3100 d.addCallback(lambda res: self._foo_node.get(u"sub"))
3101 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
3104 def test_POST_mkdir_whendone_field(self):
3105 d = self.POST(self.public_url + "/foo",
3106 t="mkdir", name="newdir", when_done="/THERE")
3107 d.addBoth(self.shouldRedirect, "/THERE")
3108 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
3109 d.addCallback(self.failUnlessNodeKeysAre, [])
3112 def test_POST_mkdir_whendone_queryarg(self):
3113 d = self.POST(self.public_url + "/foo?when_done=/THERE",
3114 t="mkdir", name="newdir")
3115 d.addBoth(self.shouldRedirect, "/THERE")
3116 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
3117 d.addCallback(self.failUnlessNodeKeysAre, [])
3120 def test_POST_bad_t(self):
3121 d = self.shouldFail2(error.Error, "POST_bad_t", "400 Bad Request",
3122 "POST to a directory with bad t=BOGUS",
3123 self.POST, self.public_url + "/foo", t="BOGUS")
3126 def test_POST_set_children(self, command_name="set_children"):
3127 contents9, n9, newuri9 = self.makefile(9)
3128 contents10, n10, newuri10 = self.makefile(10)
3129 contents11, n11, newuri11 = self.makefile(11)
3132 "atomic_added_1": [ "filenode", { "rw_uri": "%s",
3135 "ctime": 1002777696.7564139,
3136 "mtime": 1002777696.7564139
3139 "atomic_added_2": [ "filenode", { "rw_uri": "%s",
3142 "ctime": 1002777696.7564139,
3143 "mtime": 1002777696.7564139
3146 "atomic_added_3": [ "filenode", { "rw_uri": "%s",
3149 "ctime": 1002777696.7564139,
3150 "mtime": 1002777696.7564139
3153 }""" % (newuri9, newuri10, newuri11)
3155 url = self.webish_url + self.public_url + "/foo" + "?t=" + command_name
3157 d = client.getPage(url, method="POST", postdata=reqbody)
3159 self.failUnlessURIMatchesROChild(newuri9, self._foo_node, u"atomic_added_1")
3160 self.failUnlessURIMatchesROChild(newuri10, self._foo_node, u"atomic_added_2")
3161 self.failUnlessURIMatchesROChild(newuri11, self._foo_node, u"atomic_added_3")
3163 d.addCallback(_then)
3164 d.addErrback(self.dump_error)
3167 def test_POST_set_children_with_hyphen(self):
3168 return self.test_POST_set_children(command_name="set-children")
3170 def test_POST_link_uri(self):
3171 contents, n, newuri = self.makefile(8)
3172 d = self.POST(self.public_url + "/foo", t="uri", name="new.txt", uri=newuri)
3173 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"new.txt")
3174 d.addCallback(lambda res:
3175 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
3179 def test_POST_link_uri_replace(self):
3180 contents, n, newuri = self.makefile(8)
3181 d = self.POST(self.public_url + "/foo", t="uri", name="bar.txt", uri=newuri)
3182 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"bar.txt")
3183 d.addCallback(lambda res:
3184 self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
3188 def test_POST_link_uri_unknown_bad(self):
3189 d = self.POST(self.public_url + "/foo", t="uri", name="future.txt", uri=unknown_rwcap)
3190 d.addBoth(self.shouldFail, error.Error,
3191 "POST_link_uri_unknown_bad",
3193 "unknown cap in a write slot")
3196 def test_POST_link_uri_unknown_ro_good(self):
3197 d = self.POST(self.public_url + "/foo", t="uri", name="future-ro.txt", uri=unknown_rocap)
3198 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"future-ro.txt")
3201 def test_POST_link_uri_unknown_imm_good(self):
3202 d = self.POST(self.public_url + "/foo", t="uri", name="future-imm.txt", uri=unknown_immcap)
3203 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"future-imm.txt")
3206 def test_POST_link_uri_no_replace_queryarg(self):
3207 contents, n, newuri = self.makefile(8)
3208 d = self.POST(self.public_url + "/foo?replace=false", t="uri",
3209 name="bar.txt", uri=newuri)
3210 d.addBoth(self.shouldFail, error.Error,
3211 "POST_link_uri_no_replace_queryarg",
3213 "There was already a child by that name, and you asked me "
3214 "to not replace it")
3215 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
3216 d.addCallback(self.failUnlessIsBarDotTxt)
3219 def test_POST_link_uri_no_replace_field(self):
3220 contents, n, newuri = self.makefile(8)
3221 d = self.POST(self.public_url + "/foo", t="uri", replace="false",
3222 name="bar.txt", uri=newuri)
3223 d.addBoth(self.shouldFail, error.Error,
3224 "POST_link_uri_no_replace_field",
3226 "There was already a child by that name, and you asked me "
3227 "to not replace it")
3228 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
3229 d.addCallback(self.failUnlessIsBarDotTxt)
3232 def test_POST_delete(self, command_name='delete'):
3233 d = self._foo_node.list()
3234 def _check_before(children):
3235 self.failUnless(u"bar.txt" in children)
3236 d.addCallback(_check_before)
3237 d.addCallback(lambda res: self.POST(self.public_url + "/foo", t=command_name, name="bar.txt"))
3238 d.addCallback(lambda res: self._foo_node.list())
3239 def _check_after(children):
3240 self.failIf(u"bar.txt" in children)
3241 d.addCallback(_check_after)
3244 def test_POST_unlink(self):
3245 return self.test_POST_delete(command_name='unlink')
3247 def test_POST_rename_file(self):
3248 d = self.POST(self.public_url + "/foo", t="rename",
3249 from_name="bar.txt", to_name='wibble.txt')
3250 d.addCallback(lambda res:
3251 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
3252 d.addCallback(lambda res:
3253 self.failUnlessNodeHasChild(self._foo_node, u"wibble.txt"))
3254 d.addCallback(lambda res: self.GET(self.public_url + "/foo/wibble.txt"))
3255 d.addCallback(self.failUnlessIsBarDotTxt)
3256 d.addCallback(lambda res: self.GET(self.public_url + "/foo/wibble.txt?t=json"))
3257 d.addCallback(self.failUnlessIsBarJSON)
3260 def test_POST_rename_file_redundant(self):
3261 d = self.POST(self.public_url + "/foo", t="rename",
3262 from_name="bar.txt", to_name='bar.txt')
3263 d.addCallback(lambda res:
3264 self.failUnlessNodeHasChild(self._foo_node, u"bar.txt"))
3265 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
3266 d.addCallback(self.failUnlessIsBarDotTxt)
3267 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt?t=json"))
3268 d.addCallback(self.failUnlessIsBarJSON)
3271 def test_POST_rename_file_replace(self):
3272 # rename a file and replace a directory with it
3273 d = self.POST(self.public_url + "/foo", t="rename",
3274 from_name="bar.txt", to_name='empty')
3275 d.addCallback(lambda res:
3276 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
3277 d.addCallback(lambda res:
3278 self.failUnlessNodeHasChild(self._foo_node, u"empty"))
3279 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty"))
3280 d.addCallback(self.failUnlessIsBarDotTxt)
3281 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
3282 d.addCallback(self.failUnlessIsBarJSON)
3285 def test_POST_rename_file_no_replace_queryarg(self):
3286 # rename a file and replace a directory with it
3287 d = self.POST(self.public_url + "/foo?replace=false", t="rename",
3288 from_name="bar.txt", to_name='empty')
3289 d.addBoth(self.shouldFail, error.Error,
3290 "POST_rename_file_no_replace_queryarg",
3292 "There was already a child by that name, and you asked me "
3293 "to not replace it")
3294 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
3295 d.addCallback(self.failUnlessIsEmptyJSON)
3298 def test_POST_rename_file_no_replace_field(self):
3299 # rename a file and replace a directory with it
3300 d = self.POST(self.public_url + "/foo", t="rename", replace="false",
3301 from_name="bar.txt", to_name='empty')
3302 d.addBoth(self.shouldFail, error.Error,
3303 "POST_rename_file_no_replace_field",
3305 "There was already a child by that name, and you asked me "
3306 "to not replace it")
3307 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
3308 d.addCallback(self.failUnlessIsEmptyJSON)
3311 def failUnlessIsEmptyJSON(self, res):
3312 data = simplejson.loads(res)
3313 self.failUnlessEqual(data[0], "dirnode", data)
3314 self.failUnlessReallyEqual(len(data[1]["children"]), 0)
3316 def test_POST_rename_file_slash_fail(self):
3317 d = self.POST(self.public_url + "/foo", t="rename",
3318 from_name="bar.txt", to_name='kirk/spock.txt')
3319 d.addBoth(self.shouldFail, error.Error,
3320 "test_POST_rename_file_slash_fail",
3322 "to_name= may not contain a slash",
3324 d.addCallback(lambda res:
3325 self.failUnlessNodeHasChild(self._foo_node, u"bar.txt"))
3328 def test_POST_rename_dir(self):
3329 d = self.POST(self.public_url, t="rename",
3330 from_name="foo", to_name='plunk')
3331 d.addCallback(lambda res:
3332 self.failIfNodeHasChild(self.public_root, u"foo"))
3333 d.addCallback(lambda res:
3334 self.failUnlessNodeHasChild(self.public_root, u"plunk"))
3335 d.addCallback(lambda res: self.GET(self.public_url + "/plunk?t=json"))
3336 d.addCallback(self.failUnlessIsFooJSON)
3339 def shouldRedirect(self, res, target=None, statuscode=None, which=""):
3340 """ If target is not None then the redirection has to go to target. If
3341 statuscode is not None then the redirection has to be accomplished with
3342 that HTTP status code."""
3343 if not isinstance(res, failure.Failure):
3344 to_where = (target is None) and "somewhere" or ("to " + target)
3345 self.fail("%s: we were expecting to get redirected %s, not get an"
3346 " actual page: %s" % (which, to_where, res))
3347 res.trap(error.PageRedirect)
3348 if statuscode is not None:
3349 self.failUnlessReallyEqual(res.value.status, statuscode,
3350 "%s: not a redirect" % which)
3351 if target is not None:
3352 # the PageRedirect does not seem to capture the uri= query arg
3353 # properly, so we can't check for it.
3354 realtarget = self.webish_url + target
3355 self.failUnlessReallyEqual(res.value.location, realtarget,
3356 "%s: wrong target" % which)
3357 return res.value.location
3359 def test_GET_URI_form(self):
3360 base = "/uri?uri=%s" % self._bar_txt_uri
3361 # this is supposed to give us a redirect to /uri/$URI, plus arguments
3362 targetbase = "/uri/%s" % urllib.quote(self._bar_txt_uri)
3364 d.addBoth(self.shouldRedirect, targetbase)
3365 d.addCallback(lambda res: self.GET(base+"&filename=bar.txt"))
3366 d.addBoth(self.shouldRedirect, targetbase+"?filename=bar.txt")
3367 d.addCallback(lambda res: self.GET(base+"&t=json"))
3368 d.addBoth(self.shouldRedirect, targetbase+"?t=json")
3369 d.addCallback(self.log, "about to get file by uri")
3370 d.addCallback(lambda res: self.GET(base, followRedirect=True))
3371 d.addCallback(self.failUnlessIsBarDotTxt)
3372 d.addCallback(self.log, "got file by uri, about to get dir by uri")
3373 d.addCallback(lambda res: self.GET("/uri?uri=%s&t=json" % self._foo_uri,
3374 followRedirect=True))
3375 d.addCallback(self.failUnlessIsFooJSON)
3376 d.addCallback(self.log, "got dir by uri")
3380 def test_GET_URI_form_bad(self):
3381 d = self.shouldFail2(error.Error, "test_GET_URI_form_bad",
3382 "400 Bad Request", "GET /uri requires uri=",
3386 def test_GET_rename_form(self):
3387 d = self.GET(self.public_url + "/foo?t=rename-form&name=bar.txt",
3388 followRedirect=True)
3390 self.failUnless('name="when_done" value="."' in res, res)
3391 self.failUnless(re.search(r'name="from_name" value="bar\.txt"', res))
3392 d.addCallback(_check)
3395 def log(self, res, msg):
3396 #print "MSG: %s RES: %s" % (msg, res)
3400 def test_GET_URI_URL(self):
3401 base = "/uri/%s" % self._bar_txt_uri
3403 d.addCallback(self.failUnlessIsBarDotTxt)
3404 d.addCallback(lambda res: self.GET(base+"?filename=bar.txt"))
3405 d.addCallback(self.failUnlessIsBarDotTxt)
3406 d.addCallback(lambda res: self.GET(base+"?filename=bar.txt&save=true"))
3407 d.addCallback(self.failUnlessIsBarDotTxt)
3410 def test_GET_URI_URL_dir(self):
3411 base = "/uri/%s?t=json" % self._foo_uri
3413 d.addCallback(self.failUnlessIsFooJSON)
3416 def test_GET_URI_URL_missing(self):
3417 base = "/uri/%s" % self._bad_file_uri
3418 d = self.shouldHTTPError("test_GET_URI_URL_missing",
3419 http.GONE, None, "NotEnoughSharesError",
3421 # TODO: how can we exercise both sides of WebDownloadTarget.fail
3422 # here? we must arrange for a download to fail after target.open()
3423 # has been called, and then inspect the response to see that it is
3424 # shorter than we expected.
3427 def test_PUT_DIRURL_uri(self):
3428 d = self.s.create_dirnode()
3430 new_uri = dn.get_uri()
3431 # replace /foo with a new (empty) directory
3432 d = self.PUT(self.public_url + "/foo?t=uri", new_uri)
3433 d.addCallback(lambda res:
3434 self.failUnlessReallyEqual(res.strip(), new_uri))
3435 d.addCallback(lambda res:
3436 self.failUnlessRWChildURIIs(self.public_root,
3440 d.addCallback(_made_dir)
3443 def test_PUT_DIRURL_uri_noreplace(self):
3444 d = self.s.create_dirnode()
3446 new_uri = dn.get_uri()
3447 # replace /foo with a new (empty) directory, but ask that
3448 # replace=false, so it should fail
3449 d = self.shouldFail2(error.Error, "test_PUT_DIRURL_uri_noreplace",
3450 "409 Conflict", "There was already a child by that name, and you asked me to not replace it",
3452 self.public_url + "/foo?t=uri&replace=false",
3454 d.addCallback(lambda res:
3455 self.failUnlessRWChildURIIs(self.public_root,
3459 d.addCallback(_made_dir)
3462 def test_PUT_DIRURL_bad_t(self):
3463 d = self.shouldFail2(error.Error, "test_PUT_DIRURL_bad_t",
3464 "400 Bad Request", "PUT to a directory",
3465 self.PUT, self.public_url + "/foo?t=BOGUS", "")
3466 d.addCallback(lambda res:
3467 self.failUnlessRWChildURIIs(self.public_root,
3472 def test_PUT_NEWFILEURL_uri(self):
3473 contents, n, new_uri = self.makefile(8)
3474 d = self.PUT(self.public_url + "/foo/new.txt?t=uri", new_uri)
3475 d.addCallback(lambda res: self.failUnlessReallyEqual(res.strip(), new_uri))
3476 d.addCallback(lambda res:
3477 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
3481 def test_PUT_NEWFILEURL_mdmf(self):
3482 new_contents = self.NEWFILE_CONTENTS * 300000
3483 d = self.PUT(self.public_url + \
3484 "/foo/mdmf.txt?format=mdmf",
3486 d.addCallback(lambda ignored:
3487 self.GET(self.public_url + "/foo/mdmf.txt?t=json"))
3488 def _got_json(json):
3489 data = simplejson.loads(json)
3491 self.failUnlessIn("format", data)
3492 self.failUnlessEqual(data["format"], "mdmf")
3493 self.failUnless(data['rw_uri'].startswith("URI:MDMF"))
3494 self.failUnless(data['ro_uri'].startswith("URI:MDMF"))
3495 d.addCallback(_got_json)
3498 def test_PUT_NEWFILEURL_sdmf(self):
3499 new_contents = self.NEWFILE_CONTENTS * 300000
3500 d = self.PUT(self.public_url + \
3501 "/foo/sdmf.txt?format=sdmf",
3503 d.addCallback(lambda ignored:
3504 self.GET(self.public_url + "/foo/sdmf.txt?t=json"))
3505 def _got_json(json):
3506 data = simplejson.loads(json)
3508 self.failUnlessIn("format", data)
3509 self.failUnlessEqual(data["format"], "sdmf")
3510 d.addCallback(_got_json)
3513 def test_PUT_NEWFILEURL_bad_format(self):
3514 new_contents = self.NEWFILE_CONTENTS * 300000
3515 return self.shouldHTTPError("PUT_NEWFILEURL_bad_format",
3516 400, "Bad Request", "Unknown format: foo",
3517 self.PUT, self.public_url + \
3518 "/foo/foo.txt?format=foo",
3521 def test_PUT_NEWFILEURL_uri_replace(self):
3522 contents, n, new_uri = self.makefile(8)
3523 d = self.PUT(self.public_url + "/foo/bar.txt?t=uri", new_uri)
3524 d.addCallback(lambda res: self.failUnlessReallyEqual(res.strip(), new_uri))
3525 d.addCallback(lambda res:
3526 self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
3530 def test_PUT_NEWFILEURL_uri_no_replace(self):
3531 contents, n, new_uri = self.makefile(8)
3532 d = self.PUT(self.public_url + "/foo/bar.txt?t=uri&replace=false", new_uri)
3533 d.addBoth(self.shouldFail, error.Error, "PUT_NEWFILEURL_uri_no_replace",
3535 "There was already a child by that name, and you asked me "
3536 "to not replace it")
3539 def test_PUT_NEWFILEURL_uri_unknown_bad(self):
3540 d = self.PUT(self.public_url + "/foo/put-future.txt?t=uri", unknown_rwcap)
3541 d.addBoth(self.shouldFail, error.Error,
3542 "POST_put_uri_unknown_bad",
3544 "unknown cap in a write slot")
3547 def test_PUT_NEWFILEURL_uri_unknown_ro_good(self):
3548 d = self.PUT(self.public_url + "/foo/put-future-ro.txt?t=uri", unknown_rocap)
3549 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node,
3550 u"put-future-ro.txt")
3553 def test_PUT_NEWFILEURL_uri_unknown_imm_good(self):
3554 d = self.PUT(self.public_url + "/foo/put-future-imm.txt?t=uri", unknown_immcap)
3555 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node,
3556 u"put-future-imm.txt")
3559 def test_PUT_NEWFILE_URI(self):
3560 file_contents = "New file contents here\n"
3561 d = self.PUT("/uri", file_contents)
3563 assert isinstance(uri, str), uri
3564 self.failUnless(uri in FakeCHKFileNode.all_contents)
3565 self.failUnlessReallyEqual(FakeCHKFileNode.all_contents[uri],
3567 return self.GET("/uri/%s" % uri)
3568 d.addCallback(_check)
3570 self.failUnlessReallyEqual(res, file_contents)
3571 d.addCallback(_check2)
3574 def test_PUT_NEWFILE_URI_not_mutable(self):
3575 file_contents = "New file contents here\n"
3576 d = self.PUT("/uri?mutable=false", file_contents)
3578 assert isinstance(uri, str), uri
3579 self.failUnless(uri in FakeCHKFileNode.all_contents)
3580 self.failUnlessReallyEqual(FakeCHKFileNode.all_contents[uri],
3582 return self.GET("/uri/%s" % uri)
3583 d.addCallback(_check)
3585 self.failUnlessReallyEqual(res, file_contents)
3586 d.addCallback(_check2)
3589 def test_PUT_NEWFILE_URI_only_PUT(self):
3590 d = self.PUT("/uri?t=bogus", "")
3591 d.addBoth(self.shouldFail, error.Error,
3592 "PUT_NEWFILE_URI_only_PUT",
3594 "/uri accepts only PUT, PUT?t=mkdir, POST?t=upload, and POST?t=mkdir")
3597 def test_PUT_NEWFILE_URI_mutable(self):
3598 file_contents = "New file contents here\n"
3599 d = self.PUT("/uri?mutable=true", file_contents)
3600 def _check1(filecap):
3601 filecap = filecap.strip()
3602 self.failUnless(filecap.startswith("URI:SSK:"), filecap)
3603 self.filecap = filecap
3604 u = uri.WriteableSSKFileURI.init_from_string(filecap)
3605 self.failUnless(u.get_storage_index() in FakeMutableFileNode.all_contents)
3606 n = self.s.create_node_from_uri(filecap)
3607 return n.download_best_version()
3608 d.addCallback(_check1)
3610 self.failUnlessReallyEqual(data, file_contents)
3611 return self.GET("/uri/%s" % urllib.quote(self.filecap))
3612 d.addCallback(_check2)
3614 self.failUnlessReallyEqual(res, file_contents)
3615 d.addCallback(_check3)
3618 def test_PUT_mkdir(self):
3619 d = self.PUT("/uri?t=mkdir", "")
3621 n = self.s.create_node_from_uri(uri.strip())
3622 d2 = self.failUnlessNodeKeysAre(n, [])
3623 d2.addCallback(lambda res:
3624 self.GET("/uri/%s?t=json" % uri))
3626 d.addCallback(_check)
3627 d.addCallback(self.failUnlessIsEmptyJSON)
3630 def test_PUT_mkdir_mdmf(self):
3631 d = self.PUT("/uri?t=mkdir&format=mdmf", "")
3633 u = uri.from_string(res)
3634 # Check that this is an MDMF writecap
3635 self.failUnlessIsInstance(u, uri.MDMFDirectoryURI)
3639 def test_PUT_mkdir_sdmf(self):
3640 d = self.PUT("/uri?t=mkdir&format=sdmf", "")
3642 u = uri.from_string(res)
3643 self.failUnlessIsInstance(u, uri.DirectoryURI)
3647 def test_PUT_mkdir_bad_format(self):
3648 return self.shouldHTTPError("PUT_mkdir_bad_format",
3649 400, "Bad Request", "Unknown format: foo",
3650 self.PUT, "/uri?t=mkdir&format=foo",
3653 def test_POST_check(self):
3654 d = self.POST(self.public_url + "/foo", t="check", name="bar.txt")
3656 # this returns a string form of the results, which are probably
3657 # None since we're using fake filenodes.
3658 # TODO: verify that the check actually happened, by changing
3659 # FakeCHKFileNode to count how many times .check() has been
3662 d.addCallback(_done)
3666 def test_PUT_update_at_offset(self):
3667 file_contents = "test file" * 100000 # about 900 KiB
3668 d = self.PUT("/uri?mutable=true", file_contents)
3670 self.filecap = filecap
3671 new_data = file_contents[:100]
3672 new = "replaced and so on"
3674 new_data += file_contents[len(new_data):]
3675 assert len(new_data) == len(file_contents)
3676 self.new_data = new_data
3677 d.addCallback(_then)
3678 d.addCallback(lambda ignored:
3679 self.PUT("/uri/%s?replace=True&offset=100" % self.filecap,
3680 "replaced and so on"))
3681 def _get_data(filecap):
3682 n = self.s.create_node_from_uri(filecap)
3683 return n.download_best_version()
3684 d.addCallback(_get_data)
3685 d.addCallback(lambda results:
3686 self.failUnlessEqual(results, self.new_data))
3687 # Now try appending things to the file
3688 d.addCallback(lambda ignored:
3689 self.PUT("/uri/%s?offset=%d" % (self.filecap, len(self.new_data)),
3691 d.addCallback(_get_data)
3692 d.addCallback(lambda results:
3693 self.failUnlessEqual(results, self.new_data + ("puppies" * 100)))
3694 # and try replacing the beginning of the file
3695 d.addCallback(lambda ignored:
3696 self.PUT("/uri/%s?offset=0" % self.filecap, "begin"))
3697 d.addCallback(_get_data)
3698 d.addCallback(lambda results:
3699 self.failUnlessEqual(results, "begin"+self.new_data[len("begin"):]+("puppies"*100)))
3702 def test_PUT_update_at_invalid_offset(self):
3703 file_contents = "test file" * 100000 # about 900 KiB
3704 d = self.PUT("/uri?mutable=true", file_contents)
3706 self.filecap = filecap
3707 d.addCallback(_then)
3708 # Negative offsets should cause an error.
3709 d.addCallback(lambda ignored:
3710 self.shouldHTTPError("test mutable invalid offset negative",
3714 "/uri/%s?offset=-1" % self.filecap,
3718 def test_PUT_update_at_offset_immutable(self):
3719 file_contents = "Test file" * 100000
3720 d = self.PUT("/uri", file_contents)
3722 self.filecap = filecap
3723 d.addCallback(_then)
3724 d.addCallback(lambda ignored:
3725 self.shouldHTTPError("test immutable update",
3729 "/uri/%s?offset=50" % self.filecap,
3734 def test_bad_method(self):
3735 url = self.webish_url + self.public_url + "/foo/bar.txt"
3736 d = self.shouldHTTPError("test_bad_method",
3737 501, "Not Implemented",
3738 "I don't know how to treat a BOGUS request.",
3739 client.getPage, url, method="BOGUS")
3742 def test_short_url(self):
3743 url = self.webish_url + "/uri"
3744 d = self.shouldHTTPError("test_short_url", 501, "Not Implemented",
3745 "I don't know how to treat a DELETE request.",
3746 client.getPage, url, method="DELETE")
3749 def test_ophandle_bad(self):
3750 url = self.webish_url + "/operations/bogus?t=status"
3751 d = self.shouldHTTPError("test_ophandle_bad", 404, "404 Not Found",
3752 "unknown/expired handle 'bogus'",
3753 client.getPage, url)
3756 def test_ophandle_cancel(self):
3757 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=128",
3758 followRedirect=True)
3759 d.addCallback(lambda ignored:
3760 self.GET("/operations/128?t=status&output=JSON"))
3762 data = simplejson.loads(res)
3763 self.failUnless("finished" in data, res)
3764 monitor = self.ws.root.child_operations.handles["128"][0]
3765 d = self.POST("/operations/128?t=cancel&output=JSON")
3767 data = simplejson.loads(res)
3768 self.failUnless("finished" in data, res)
3769 # t=cancel causes the handle to be forgotten
3770 self.failUnless(monitor.is_cancelled())
3771 d.addCallback(_check2)
3773 d.addCallback(_check1)
3774 d.addCallback(lambda ignored:
3775 self.shouldHTTPError("test_ophandle_cancel",
3776 404, "404 Not Found",
3777 "unknown/expired handle '128'",
3779 "/operations/128?t=status&output=JSON"))
3782 def test_ophandle_retainfor(self):
3783 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=129&retain-for=60",
3784 followRedirect=True)
3785 d.addCallback(lambda ignored:
3786 self.GET("/operations/129?t=status&output=JSON&retain-for=0"))
3788 data = simplejson.loads(res)
3789 self.failUnless("finished" in data, res)
3790 d.addCallback(_check1)
3791 # the retain-for=0 will cause the handle to be expired very soon
3792 d.addCallback(lambda ign:
3793 self.clock.advance(2.0))
3794 d.addCallback(lambda ignored:
3795 self.shouldHTTPError("test_ophandle_retainfor",
3796 404, "404 Not Found",
3797 "unknown/expired handle '129'",
3799 "/operations/129?t=status&output=JSON"))
3802 def test_ophandle_release_after_complete(self):
3803 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=130",
3804 followRedirect=True)
3805 d.addCallback(self.wait_for_operation, "130")
3806 d.addCallback(lambda ignored:
3807 self.GET("/operations/130?t=status&output=JSON&release-after-complete=true"))
3808 # the release-after-complete=true will cause the handle to be expired
3809 d.addCallback(lambda ignored:
3810 self.shouldHTTPError("test_ophandle_release_after_complete",
3811 404, "404 Not Found",
3812 "unknown/expired handle '130'",
3814 "/operations/130?t=status&output=JSON"))
3817 def test_uncollected_ophandle_expiration(self):
3818 # uncollected ophandles should expire after 4 days
3819 def _make_uncollected_ophandle(ophandle):
3820 d = self.POST(self.public_url +
3821 "/foo/?t=start-manifest&ophandle=%d" % ophandle,
3822 followRedirect=False)
3823 # When we start the operation, the webapi server will want
3824 # to redirect us to the page for the ophandle, so we get
3825 # confirmation that the operation has started. If the
3826 # manifest operation has finished by the time we get there,
3827 # following that redirect (by setting followRedirect=True
3828 # above) has the side effect of collecting the ophandle that
3829 # we've just created, which means that we can't use the
3830 # ophandle to test the uncollected timeout anymore. So,
3831 # instead, catch the 302 here and don't follow it.
3832 d.addBoth(self.should302, "uncollected_ophandle_creation")
3834 # Create an ophandle, don't collect it, then advance the clock by
3835 # 4 days - 1 second and make sure that the ophandle is still there.
3836 d = _make_uncollected_ophandle(131)
3837 d.addCallback(lambda ign:
3838 self.clock.advance((96*60*60) - 1)) # 96 hours = 4 days
3839 d.addCallback(lambda ign:
3840 self.GET("/operations/131?t=status&output=JSON"))
3842 data = simplejson.loads(res)
3843 self.failUnless("finished" in data, res)
3844 d.addCallback(_check1)
3845 # Create an ophandle, don't collect it, then try to collect it
3846 # after 4 days. It should be gone.
3847 d.addCallback(lambda ign:
3848 _make_uncollected_ophandle(132))
3849 d.addCallback(lambda ign:
3850 self.clock.advance(96*60*60))
3851 d.addCallback(lambda ign:
3852 self.shouldHTTPError("test_uncollected_ophandle_expired_after_100_hours",
3853 404, "404 Not Found",
3854 "unknown/expired handle '132'",
3856 "/operations/132?t=status&output=JSON"))
3859 def test_collected_ophandle_expiration(self):
3860 # collected ophandles should expire after 1 day
3861 def _make_collected_ophandle(ophandle):
3862 d = self.POST(self.public_url +
3863 "/foo/?t=start-manifest&ophandle=%d" % ophandle,
3864 followRedirect=True)
3865 # By following the initial redirect, we collect the ophandle
3866 # we've just created.
3868 # Create a collected ophandle, then collect it after 23 hours
3869 # and 59 seconds to make sure that it is still there.
3870 d = _make_collected_ophandle(133)
3871 d.addCallback(lambda ign:
3872 self.clock.advance((24*60*60) - 1))
3873 d.addCallback(lambda ign:
3874 self.GET("/operations/133?t=status&output=JSON"))
3876 data = simplejson.loads(res)
3877 self.failUnless("finished" in data, res)
3878 d.addCallback(_check1)
3879 # Create another uncollected ophandle, then try to collect it
3880 # after 24 hours to make sure that it is gone.
3881 d.addCallback(lambda ign:
3882 _make_collected_ophandle(134))
3883 d.addCallback(lambda ign:
3884 self.clock.advance(24*60*60))
3885 d.addCallback(lambda ign:
3886 self.shouldHTTPError("test_collected_ophandle_expired_after_1000_minutes",
3887 404, "404 Not Found",
3888 "unknown/expired handle '134'",
3890 "/operations/134?t=status&output=JSON"))
3893 def test_incident(self):
3894 d = self.POST("/report_incident", details="eek")
3896 self.failUnless("Thank you for your report!" in res, res)
3897 d.addCallback(_done)
3900 def test_static(self):
3901 webdir = os.path.join(self.staticdir, "subdir")
3902 fileutil.make_dirs(webdir)
3903 f = open(os.path.join(webdir, "hello.txt"), "wb")
3907 d = self.GET("/static/subdir/hello.txt")
3909 self.failUnlessReallyEqual(res, "hello")
3910 d.addCallback(_check)
3914 class Util(ShouldFailMixin, testutil.ReallyEqualMixin, unittest.TestCase):
3915 def test_load_file(self):
3916 # This will raise an exception unless a well-formed XML file is found under that name.
3917 common.getxmlfile('directory.xhtml').load()
3919 def test_parse_replace_arg(self):
3920 self.failUnlessReallyEqual(common.parse_replace_arg("true"), True)
3921 self.failUnlessReallyEqual(common.parse_replace_arg("false"), False)
3922 self.failUnlessReallyEqual(common.parse_replace_arg("only-files"),
3924 self.shouldFail(AssertionError, "test_parse_replace_arg", "",
3925 common.parse_replace_arg, "only_fles")
3927 def test_abbreviate_time(self):
3928 self.failUnlessReallyEqual(common.abbreviate_time(None), "")
3929 self.failUnlessReallyEqual(common.abbreviate_time(1.234), "1.23s")
3930 self.failUnlessReallyEqual(common.abbreviate_time(0.123), "123ms")
3931 self.failUnlessReallyEqual(common.abbreviate_time(0.00123), "1.2ms")
3932 self.failUnlessReallyEqual(common.abbreviate_time(0.000123), "123us")
3933 self.failUnlessReallyEqual(common.abbreviate_time(-123000), "-123000000000us")
3935 def test_compute_rate(self):
3936 self.failUnlessReallyEqual(common.compute_rate(None, None), None)
3937 self.failUnlessReallyEqual(common.compute_rate(None, 1), None)
3938 self.failUnlessReallyEqual(common.compute_rate(250000, None), None)
3939 self.failUnlessReallyEqual(common.compute_rate(250000, 0), None)
3940 self.failUnlessReallyEqual(common.compute_rate(250000, 10), 25000.0)
3941 self.failUnlessReallyEqual(common.compute_rate(0, 10), 0.0)
3942 self.shouldFail(AssertionError, "test_compute_rate", "",
3943 common.compute_rate, -100, 10)
3944 self.shouldFail(AssertionError, "test_compute_rate", "",
3945 common.compute_rate, 100, -10)
3948 rate = common.compute_rate(10*1000*1000, 1)
3949 self.failUnlessReallyEqual(common.abbreviate_rate(rate), "10.00MBps")
3951 def test_abbreviate_rate(self):
3952 self.failUnlessReallyEqual(common.abbreviate_rate(None), "")
3953 self.failUnlessReallyEqual(common.abbreviate_rate(1234000), "1.23MBps")
3954 self.failUnlessReallyEqual(common.abbreviate_rate(12340), "12.3kBps")
3955 self.failUnlessReallyEqual(common.abbreviate_rate(123), "123Bps")
3957 def test_abbreviate_size(self):
3958 self.failUnlessReallyEqual(common.abbreviate_size(None), "")
3959 self.failUnlessReallyEqual(common.abbreviate_size(1.23*1000*1000*1000), "1.23GB")
3960 self.failUnlessReallyEqual(common.abbreviate_size(1.23*1000*1000), "1.23MB")
3961 self.failUnlessReallyEqual(common.abbreviate_size(1230), "1.2kB")
3962 self.failUnlessReallyEqual(common.abbreviate_size(123), "123B")
3964 def test_plural(self):
3966 return "%d second%s" % (s, status.plural(s))
3967 self.failUnlessReallyEqual(convert(0), "0 seconds")
3968 self.failUnlessReallyEqual(convert(1), "1 second")
3969 self.failUnlessReallyEqual(convert(2), "2 seconds")
3971 return "has share%s: %s" % (status.plural(s), ",".join(s))
3972 self.failUnlessReallyEqual(convert2([]), "has shares: ")
3973 self.failUnlessReallyEqual(convert2(["1"]), "has share: 1")
3974 self.failUnlessReallyEqual(convert2(["1","2"]), "has shares: 1,2")
3977 class Grid(GridTestMixin, WebErrorMixin, ShouldFailMixin, testutil.ReallyEqualMixin, unittest.TestCase):
3979 def CHECK(self, ign, which, args, clientnum=0):
3980 fileurl = self.fileurls[which]
3981 url = fileurl + "?" + args
3982 return self.GET(url, method="POST", clientnum=clientnum)
3984 def test_filecheck(self):
3985 self.basedir = "web/Grid/filecheck"
3987 c0 = self.g.clients[0]
3990 d = c0.upload(upload.Data(DATA, convergence=""))
3991 def _stash_uri(ur, which):
3992 self.uris[which] = ur.uri
3993 d.addCallback(_stash_uri, "good")
3994 d.addCallback(lambda ign:
3995 c0.upload(upload.Data(DATA+"1", convergence="")))
3996 d.addCallback(_stash_uri, "sick")
3997 d.addCallback(lambda ign:
3998 c0.upload(upload.Data(DATA+"2", convergence="")))
3999 d.addCallback(_stash_uri, "dead")
4000 def _stash_mutable_uri(n, which):
4001 self.uris[which] = n.get_uri()
4002 assert isinstance(self.uris[which], str)
4003 d.addCallback(lambda ign:
4004 c0.create_mutable_file(publish.MutableData(DATA+"3")))
4005 d.addCallback(_stash_mutable_uri, "corrupt")
4006 d.addCallback(lambda ign:
4007 c0.upload(upload.Data("literal", convergence="")))
4008 d.addCallback(_stash_uri, "small")
4009 d.addCallback(lambda ign: c0.create_immutable_dirnode({}))
4010 d.addCallback(_stash_mutable_uri, "smalldir")
4012 def _compute_fileurls(ignored):
4014 for which in self.uris:
4015 self.fileurls[which] = "uri/" + urllib.quote(self.uris[which])
4016 d.addCallback(_compute_fileurls)
4018 def _clobber_shares(ignored):
4019 good_shares = self.find_uri_shares(self.uris["good"])
4020 self.failUnlessReallyEqual(len(good_shares), 10)
4021 sick_shares = self.find_uri_shares(self.uris["sick"])
4022 os.unlink(sick_shares[0][2])
4023 dead_shares = self.find_uri_shares(self.uris["dead"])
4024 for i in range(1, 10):
4025 os.unlink(dead_shares[i][2])
4026 c_shares = self.find_uri_shares(self.uris["corrupt"])
4027 cso = CorruptShareOptions()
4028 cso.stdout = StringIO()
4029 cso.parseOptions([c_shares[0][2]])
4031 d.addCallback(_clobber_shares)
4033 d.addCallback(self.CHECK, "good", "t=check")
4034 def _got_html_good(res):
4035 self.failUnless("Healthy" in res, res)
4036 self.failIf("Not Healthy" in res, res)
4037 d.addCallback(_got_html_good)
4038 d.addCallback(self.CHECK, "good", "t=check&return_to=somewhere")
4039 def _got_html_good_return_to(res):
4040 self.failUnless("Healthy" in res, res)
4041 self.failIf("Not Healthy" in res, res)
4042 self.failUnless('<a href="somewhere">Return to file'
4044 d.addCallback(_got_html_good_return_to)
4045 d.addCallback(self.CHECK, "good", "t=check&output=json")
4046 def _got_json_good(res):
4047 r = simplejson.loads(res)
4048 self.failUnlessEqual(r["summary"], "Healthy")
4049 self.failUnless(r["results"]["healthy"])
4050 self.failIf(r["results"]["needs-rebalancing"])
4051 self.failUnless(r["results"]["recoverable"])
4052 d.addCallback(_got_json_good)
4054 d.addCallback(self.CHECK, "small", "t=check")
4055 def _got_html_small(res):
4056 self.failUnless("Literal files are always healthy" in res, res)
4057 self.failIf("Not Healthy" in res, res)
4058 d.addCallback(_got_html_small)
4059 d.addCallback(self.CHECK, "small", "t=check&return_to=somewhere")
4060 def _got_html_small_return_to(res):
4061 self.failUnless("Literal files are always healthy" in res, res)
4062 self.failIf("Not Healthy" in res, res)
4063 self.failUnless('<a href="somewhere">Return to file'
4065 d.addCallback(_got_html_small_return_to)
4066 d.addCallback(self.CHECK, "small", "t=check&output=json")
4067 def _got_json_small(res):
4068 r = simplejson.loads(res)
4069 self.failUnlessEqual(r["storage-index"], "")
4070 self.failUnless(r["results"]["healthy"])
4071 d.addCallback(_got_json_small)
4073 d.addCallback(self.CHECK, "smalldir", "t=check")
4074 def _got_html_smalldir(res):
4075 self.failUnless("Literal files are always healthy" in res, res)
4076 self.failIf("Not Healthy" in res, res)
4077 d.addCallback(_got_html_smalldir)
4078 d.addCallback(self.CHECK, "smalldir", "t=check&output=json")
4079 def _got_json_smalldir(res):
4080 r = simplejson.loads(res)
4081 self.failUnlessEqual(r["storage-index"], "")
4082 self.failUnless(r["results"]["healthy"])
4083 d.addCallback(_got_json_smalldir)
4085 d.addCallback(self.CHECK, "sick", "t=check")
4086 def _got_html_sick(res):
4087 self.failUnless("Not Healthy" in res, res)
4088 d.addCallback(_got_html_sick)
4089 d.addCallback(self.CHECK, "sick", "t=check&output=json")
4090 def _got_json_sick(res):
4091 r = simplejson.loads(res)
4092 self.failUnlessEqual(r["summary"],
4093 "Not Healthy: 9 shares (enc 3-of-10)")
4094 self.failIf(r["results"]["healthy"])
4095 self.failIf(r["results"]["needs-rebalancing"])
4096 self.failUnless(r["results"]["recoverable"])
4097 d.addCallback(_got_json_sick)
4099 d.addCallback(self.CHECK, "dead", "t=check")
4100 def _got_html_dead(res):
4101 self.failUnless("Not Healthy" in res, res)
4102 d.addCallback(_got_html_dead)
4103 d.addCallback(self.CHECK, "dead", "t=check&output=json")
4104 def _got_json_dead(res):
4105 r = simplejson.loads(res)
4106 self.failUnlessEqual(r["summary"],
4107 "Not Healthy: 1 shares (enc 3-of-10)")
4108 self.failIf(r["results"]["healthy"])
4109 self.failIf(r["results"]["needs-rebalancing"])
4110 self.failIf(r["results"]["recoverable"])
4111 d.addCallback(_got_json_dead)
4113 d.addCallback(self.CHECK, "corrupt", "t=check&verify=true")
4114 def _got_html_corrupt(res):
4115 self.failUnless("Not Healthy! : Unhealthy" in res, res)
4116 d.addCallback(_got_html_corrupt)
4117 d.addCallback(self.CHECK, "corrupt", "t=check&verify=true&output=json")
4118 def _got_json_corrupt(res):
4119 r = simplejson.loads(res)
4120 self.failUnless("Unhealthy: 9 shares (enc 3-of-10)" in r["summary"],
4122 self.failIf(r["results"]["healthy"])
4123 self.failUnless(r["results"]["recoverable"])
4124 self.failUnlessReallyEqual(r["results"]["count-shares-good"], 9)
4125 self.failUnlessReallyEqual(r["results"]["count-corrupt-shares"], 1)
4126 d.addCallback(_got_json_corrupt)
4128 d.addErrback(self.explain_web_error)
4131 def test_repair_html(self):
4132 self.basedir = "web/Grid/repair_html"
4134 c0 = self.g.clients[0]
4137 d = c0.upload(upload.Data(DATA, convergence=""))
4138 def _stash_uri(ur, which):
4139 self.uris[which] = ur.uri
4140 d.addCallback(_stash_uri, "good")
4141 d.addCallback(lambda ign:
4142 c0.upload(upload.Data(DATA+"1", convergence="")))
4143 d.addCallback(_stash_uri, "sick")
4144 d.addCallback(lambda ign:
4145 c0.upload(upload.Data(DATA+"2", convergence="")))
4146 d.addCallback(_stash_uri, "dead")
4147 def _stash_mutable_uri(n, which):
4148 self.uris[which] = n.get_uri()
4149 assert isinstance(self.uris[which], str)
4150 d.addCallback(lambda ign:
4151 c0.create_mutable_file(publish.MutableData(DATA+"3")))
4152 d.addCallback(_stash_mutable_uri, "corrupt")
4154 def _compute_fileurls(ignored):
4156 for which in self.uris:
4157 self.fileurls[which] = "uri/" + urllib.quote(self.uris[which])
4158 d.addCallback(_compute_fileurls)
4160 def _clobber_shares(ignored):
4161 good_shares = self.find_uri_shares(self.uris["good"])
4162 self.failUnlessReallyEqual(len(good_shares), 10)
4163 sick_shares = self.find_uri_shares(self.uris["sick"])
4164 os.unlink(sick_shares[0][2])
4165 dead_shares = self.find_uri_shares(self.uris["dead"])
4166 for i in range(1, 10):
4167 os.unlink(dead_shares[i][2])
4168 c_shares = self.find_uri_shares(self.uris["corrupt"])
4169 cso = CorruptShareOptions()
4170 cso.stdout = StringIO()
4171 cso.parseOptions([c_shares[0][2]])
4173 d.addCallback(_clobber_shares)
4175 d.addCallback(self.CHECK, "good", "t=check&repair=true")
4176 def _got_html_good(res):
4177 self.failUnless("Healthy" in res, res)
4178 self.failIf("Not Healthy" in res, res)
4179 self.failUnless("No repair necessary" in res, res)
4180 d.addCallback(_got_html_good)
4182 d.addCallback(self.CHECK, "sick", "t=check&repair=true")
4183 def _got_html_sick(res):
4184 self.failUnless("Healthy : healthy" in res, res)
4185 self.failIf("Not Healthy" in res, res)
4186 self.failUnless("Repair successful" in res, res)
4187 d.addCallback(_got_html_sick)
4189 # repair of a dead file will fail, of course, but it isn't yet
4190 # clear how this should be reported. Right now it shows up as
4193 #d.addCallback(self.CHECK, "dead", "t=check&repair=true")
4194 #def _got_html_dead(res):
4196 # self.failUnless("Healthy : healthy" in res, res)
4197 # self.failIf("Not Healthy" in res, res)
4198 # self.failUnless("No repair necessary" in res, res)
4199 #d.addCallback(_got_html_dead)
4201 d.addCallback(self.CHECK, "corrupt", "t=check&verify=true&repair=true")
4202 def _got_html_corrupt(res):
4203 self.failUnless("Healthy : Healthy" in res, res)
4204 self.failIf("Not Healthy" in res, res)
4205 self.failUnless("Repair successful" in res, res)
4206 d.addCallback(_got_html_corrupt)
4208 d.addErrback(self.explain_web_error)
4211 def test_repair_json(self):
4212 self.basedir = "web/Grid/repair_json"
4214 c0 = self.g.clients[0]
4217 d = c0.upload(upload.Data(DATA+"1", convergence=""))
4218 def _stash_uri(ur, which):
4219 self.uris[which] = ur.uri
4220 d.addCallback(_stash_uri, "sick")
4222 def _compute_fileurls(ignored):
4224 for which in self.uris:
4225 self.fileurls[which] = "uri/" + urllib.quote(self.uris[which])
4226 d.addCallback(_compute_fileurls)
4228 def _clobber_shares(ignored):
4229 sick_shares = self.find_uri_shares(self.uris["sick"])
4230 os.unlink(sick_shares[0][2])
4231 d.addCallback(_clobber_shares)
4233 d.addCallback(self.CHECK, "sick", "t=check&repair=true&output=json")
4234 def _got_json_sick(res):
4235 r = simplejson.loads(res)
4236 self.failUnlessReallyEqual(r["repair-attempted"], True)
4237 self.failUnlessReallyEqual(r["repair-successful"], True)
4238 self.failUnlessEqual(r["pre-repair-results"]["summary"],
4239 "Not Healthy: 9 shares (enc 3-of-10)")
4240 self.failIf(r["pre-repair-results"]["results"]["healthy"])
4241 self.failUnlessEqual(r["post-repair-results"]["summary"], "healthy")
4242 self.failUnless(r["post-repair-results"]["results"]["healthy"])
4243 d.addCallback(_got_json_sick)
4245 d.addErrback(self.explain_web_error)
4248 def test_unknown(self, immutable=False):
4249 self.basedir = "web/Grid/unknown"
4251 self.basedir = "web/Grid/unknown-immutable"
4254 c0 = self.g.clients[0]
4258 # the future cap format may contain slashes, which must be tolerated
4259 expected_info_url = "uri/%s?t=info" % urllib.quote(unknown_rwcap,
4263 name = u"future-imm"
4264 future_node = UnknownNode(None, unknown_immcap, deep_immutable=True)
4265 d = c0.create_immutable_dirnode({name: (future_node, {})})
4268 future_node = UnknownNode(unknown_rwcap, unknown_rocap)
4269 d = c0.create_dirnode()
4271 def _stash_root_and_create_file(n):
4273 self.rooturl = "uri/" + urllib.quote(n.get_uri()) + "/"
4274 self.rourl = "uri/" + urllib.quote(n.get_readonly_uri()) + "/"
4276 return self.rootnode.set_node(name, future_node)
4277 d.addCallback(_stash_root_and_create_file)
4279 # make sure directory listing tolerates unknown nodes
4280 d.addCallback(lambda ign: self.GET(self.rooturl))
4281 def _check_directory_html(res, expected_type_suffix):
4282 pattern = re.compile(r'<td>\?%s</td>[ \t\n\r]*'
4283 '<td>%s</td>' % (expected_type_suffix, str(name)),
4285 self.failUnless(re.search(pattern, res), res)
4286 # find the More Info link for name, should be relative
4287 mo = re.search(r'<a href="([^"]+)">More Info</a>', res)
4288 info_url = mo.group(1)
4289 self.failUnlessReallyEqual(info_url, "%s?t=info" % (str(name),))
4291 d.addCallback(_check_directory_html, "-IMM")
4293 d.addCallback(_check_directory_html, "")
4295 d.addCallback(lambda ign: self.GET(self.rooturl+"?t=json"))
4296 def _check_directory_json(res, expect_rw_uri):
4297 data = simplejson.loads(res)
4298 self.failUnlessEqual(data[0], "dirnode")
4299 f = data[1]["children"][name]
4300 self.failUnlessEqual(f[0], "unknown")
4302 self.failUnlessReallyEqual(to_str(f[1]["rw_uri"]), unknown_rwcap, data)
4304 self.failIfIn("rw_uri", f[1])
4306 self.failUnlessReallyEqual(to_str(f[1]["ro_uri"]), unknown_immcap, data)
4308 self.failUnlessReallyEqual(to_str(f[1]["ro_uri"]), unknown_rocap, data)
4309 self.failUnless("metadata" in f[1])
4310 d.addCallback(_check_directory_json, expect_rw_uri=not immutable)
4312 def _check_info(res, expect_rw_uri, expect_ro_uri):
4313 self.failUnlessIn("Object Type: <span>unknown</span>", res)
4315 self.failUnlessIn(unknown_rwcap, res)
4318 self.failUnlessIn(unknown_immcap, res)
4320 self.failUnlessIn(unknown_rocap, res)
4322 self.failIfIn(unknown_rocap, res)
4323 self.failIfIn("Raw data as", res)
4324 self.failIfIn("Directory writecap", res)
4325 self.failIfIn("Checker Operations", res)
4326 self.failIfIn("Mutable File Operations", res)
4327 self.failIfIn("Directory Operations", res)
4329 # FIXME: these should have expect_rw_uri=not immutable; I don't know
4330 # why they fail. Possibly related to ticket #922.
4332 d.addCallback(lambda ign: self.GET(expected_info_url))
4333 d.addCallback(_check_info, expect_rw_uri=False, expect_ro_uri=False)
4334 d.addCallback(lambda ign: self.GET("%s%s?t=info" % (self.rooturl, str(name))))
4335 d.addCallback(_check_info, expect_rw_uri=False, expect_ro_uri=True)
4337 def _check_json(res, expect_rw_uri):
4338 data = simplejson.loads(res)
4339 self.failUnlessEqual(data[0], "unknown")
4341 self.failUnlessReallyEqual(to_str(data[1]["rw_uri"]), unknown_rwcap, data)
4343 self.failIfIn("rw_uri", data[1])
4346 self.failUnlessReallyEqual(to_str(data[1]["ro_uri"]), unknown_immcap, data)
4347 self.failUnlessReallyEqual(data[1]["mutable"], False)
4349 self.failUnlessReallyEqual(to_str(data[1]["ro_uri"]), unknown_rocap, data)
4350 self.failUnlessReallyEqual(data[1]["mutable"], True)
4352 self.failUnlessReallyEqual(to_str(data[1]["ro_uri"]), unknown_rocap, data)
4353 self.failIf("mutable" in data[1], data[1])
4355 # TODO: check metadata contents
4356 self.failUnless("metadata" in data[1])
4358 d.addCallback(lambda ign: self.GET("%s%s?t=json" % (self.rooturl, str(name))))
4359 d.addCallback(_check_json, expect_rw_uri=not immutable)
4361 # and make sure that a read-only version of the directory can be
4362 # rendered too. This version will not have unknown_rwcap, whether
4363 # or not future_node was immutable.
4364 d.addCallback(lambda ign: self.GET(self.rourl))
4366 d.addCallback(_check_directory_html, "-IMM")
4368 d.addCallback(_check_directory_html, "-RO")
4370 d.addCallback(lambda ign: self.GET(self.rourl+"?t=json"))
4371 d.addCallback(_check_directory_json, expect_rw_uri=False)
4373 d.addCallback(lambda ign: self.GET("%s%s?t=json" % (self.rourl, str(name))))
4374 d.addCallback(_check_json, expect_rw_uri=False)
4376 # TODO: check that getting t=info from the Info link in the ro directory
4377 # works, and does not include the writecap URI.
4380 def test_immutable_unknown(self):
4381 return self.test_unknown(immutable=True)
4383 def test_mutant_dirnodes_are_omitted(self):
4384 self.basedir = "web/Grid/mutant_dirnodes_are_omitted"
4387 c = self.g.clients[0]
4392 lonely_uri = "URI:LIT:n5xgk" # LIT for "one"
4393 mut_write_uri = "URI:SSK:vfvcbdfbszyrsaxchgevhmmlii:euw4iw7bbnkrrwpzuburbhppuxhc3gwxv26f6imekhz7zyw2ojnq"
4394 mut_read_uri = "URI:SSK-RO:e3mdrzfwhoq42hy5ubcz6rp3o4:ybyibhnp3vvwuq2vaw2ckjmesgkklfs6ghxleztqidihjyofgw7q"
4396 # This method tests mainly dirnode, but we'd have to duplicate code in order to
4397 # test the dirnode and web layers separately.
4399 # 'lonely' is a valid LIT child, 'ro' is a mutant child with an SSK-RO readcap,
4400 # and 'write-in-ro' is a mutant child with an SSK writecap in the ro_uri field.
4401 # When the directory is read, the mutants should be silently disposed of, leaving
4402 # their lonely sibling.
4403 # We don't test the case of a retrieving a cap from the encrypted rw_uri field,
4404 # because immutable directories don't have a writecap and therefore that field
4405 # isn't (and can't be) decrypted.
4406 # TODO: The field still exists in the netstring. Technically we should check what
4407 # happens if something is put there (_unpack_contents should raise ValueError),
4408 # but that can wait.
4410 lonely_child = nm.create_from_cap(lonely_uri)
4411 mutant_ro_child = nm.create_from_cap(mut_read_uri)
4412 mutant_write_in_ro_child = nm.create_from_cap(mut_write_uri)
4414 def _by_hook_or_by_crook():
4416 for n in [mutant_ro_child, mutant_write_in_ro_child]:
4417 n.is_allowed_in_immutable_directory = _by_hook_or_by_crook
4419 mutant_write_in_ro_child.get_write_uri = lambda: None
4420 mutant_write_in_ro_child.get_readonly_uri = lambda: mut_write_uri
4422 kids = {u"lonely": (lonely_child, {}),
4423 u"ro": (mutant_ro_child, {}),
4424 u"write-in-ro": (mutant_write_in_ro_child, {}),
4426 d = c.create_immutable_dirnode(kids)
4429 self.failUnless(isinstance(dn, dirnode.DirectoryNode))
4430 self.failIf(dn.is_mutable())
4431 self.failUnless(dn.is_readonly())
4432 # This checks that if we somehow ended up calling dn._decrypt_rwcapdata, it would fail.
4433 self.failIf(hasattr(dn._node, 'get_writekey'))
4435 self.failUnless("RO-IMM" in rep)
4437 self.failUnlessIn("CHK", cap.to_string())
4440 self.rooturl = "uri/" + urllib.quote(dn.get_uri()) + "/"
4441 return download_to_data(dn._node)
4442 d.addCallback(_created)
4444 def _check_data(data):
4445 # Decode the netstring representation of the directory to check that all children
4446 # are present. This is a bit of an abstraction violation, but there's not really
4447 # any other way to do it given that the real DirectoryNode._unpack_contents would
4448 # strip the mutant children out (which is what we're trying to test, later).
4451 while position < len(data):
4452 entries, position = split_netstring(data, 1, position)
4454 (name_utf8, ro_uri, rwcapdata, metadata_s), subpos = split_netstring(entry, 4)
4455 name = name_utf8.decode("utf-8")
4456 self.failUnless(rwcapdata == "")
4457 self.failUnless(name in kids)
4458 (expected_child, ign) = kids[name]
4459 self.failUnlessReallyEqual(ro_uri, expected_child.get_readonly_uri())
4462 self.failUnlessReallyEqual(numkids, 3)
4463 return self.rootnode.list()
4464 d.addCallback(_check_data)
4466 # Now when we use the real directory listing code, the mutants should be absent.
4467 def _check_kids(children):
4468 self.failUnlessReallyEqual(sorted(children.keys()), [u"lonely"])
4469 lonely_node, lonely_metadata = children[u"lonely"]
4471 self.failUnlessReallyEqual(lonely_node.get_write_uri(), None)
4472 self.failUnlessReallyEqual(lonely_node.get_readonly_uri(), lonely_uri)
4473 d.addCallback(_check_kids)
4475 d.addCallback(lambda ign: nm.create_from_cap(self.cap.to_string()))
4476 d.addCallback(lambda n: n.list())
4477 d.addCallback(_check_kids) # again with dirnode recreated from cap
4479 # Make sure the lonely child can be listed in HTML...
4480 d.addCallback(lambda ign: self.GET(self.rooturl))
4481 def _check_html(res):
4482 self.failIfIn("URI:SSK", res)
4483 get_lonely = "".join([r'<td>FILE</td>',
4485 r'<a href="[^"]+%s[^"]+">lonely</a>' % (urllib.quote(lonely_uri),),
4487 r'\s+<td align="right">%d</td>' % len("one"),
4489 self.failUnless(re.search(get_lonely, res), res)
4491 # find the More Info link for name, should be relative
4492 mo = re.search(r'<a href="([^"]+)">More Info</a>', res)
4493 info_url = mo.group(1)
4494 self.failUnless(info_url.endswith(urllib.quote(lonely_uri) + "?t=info"), info_url)
4495 d.addCallback(_check_html)
4498 d.addCallback(lambda ign: self.GET(self.rooturl+"?t=json"))
4499 def _check_json(res):
4500 data = simplejson.loads(res)
4501 self.failUnlessEqual(data[0], "dirnode")
4502 listed_children = data[1]["children"]
4503 self.failUnlessReallyEqual(sorted(listed_children.keys()), [u"lonely"])
4504 ll_type, ll_data = listed_children[u"lonely"]
4505 self.failUnlessEqual(ll_type, "filenode")
4506 self.failIf("rw_uri" in ll_data)
4507 self.failUnlessReallyEqual(to_str(ll_data["ro_uri"]), lonely_uri)
4508 d.addCallback(_check_json)
4511 def test_deep_check(self):
4512 self.basedir = "web/Grid/deep_check"
4514 c0 = self.g.clients[0]
4518 d = c0.create_dirnode()
4519 def _stash_root_and_create_file(n):
4521 self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri()) + "/"
4522 return n.add_file(u"good", upload.Data(DATA, convergence=""))
4523 d.addCallback(_stash_root_and_create_file)
4524 def _stash_uri(fn, which):
4525 self.uris[which] = fn.get_uri()
4527 d.addCallback(_stash_uri, "good")
4528 d.addCallback(lambda ign:
4529 self.rootnode.add_file(u"small",
4530 upload.Data("literal",
4532 d.addCallback(_stash_uri, "small")
4533 d.addCallback(lambda ign:
4534 self.rootnode.add_file(u"sick",
4535 upload.Data(DATA+"1",
4537 d.addCallback(_stash_uri, "sick")
4539 # this tests that deep-check and stream-manifest will ignore
4540 # UnknownNode instances. Hopefully this will also cover deep-stats.
4541 future_node = UnknownNode(unknown_rwcap, unknown_rocap)
4542 d.addCallback(lambda ign: self.rootnode.set_node(u"future", future_node))
4544 def _clobber_shares(ignored):
4545 self.delete_shares_numbered(self.uris["sick"], [0,1])
4546 d.addCallback(_clobber_shares)
4554 d.addCallback(self.CHECK, "root", "t=stream-deep-check")
4557 units = [simplejson.loads(line)
4558 for line in res.splitlines()
4561 print "response is:", res
4562 print "undecodeable line was '%s'" % line
4564 self.failUnlessReallyEqual(len(units), 5+1)
4565 # should be parent-first
4567 self.failUnlessEqual(u0["path"], [])
4568 self.failUnlessEqual(u0["type"], "directory")
4569 self.failUnlessReallyEqual(to_str(u0["cap"]), self.rootnode.get_uri())
4570 u0cr = u0["check-results"]
4571 self.failUnlessReallyEqual(u0cr["results"]["count-shares-good"], 10)
4573 ugood = [u for u in units
4574 if u["type"] == "file" and u["path"] == [u"good"]][0]
4575 self.failUnlessReallyEqual(to_str(ugood["cap"]), self.uris["good"])
4576 ugoodcr = ugood["check-results"]
4577 self.failUnlessReallyEqual(ugoodcr["results"]["count-shares-good"], 10)
4580 self.failUnlessEqual(stats["type"], "stats")
4582 self.failUnlessReallyEqual(s["count-immutable-files"], 2)
4583 self.failUnlessReallyEqual(s["count-literal-files"], 1)
4584 self.failUnlessReallyEqual(s["count-directories"], 1)
4585 self.failUnlessReallyEqual(s["count-unknown"], 1)
4586 d.addCallback(_done)
4588 d.addCallback(self.CHECK, "root", "t=stream-manifest")
4589 def _check_manifest(res):
4590 self.failUnless(res.endswith("\n"))
4591 units = [simplejson.loads(t) for t in res[:-1].split("\n")]
4592 self.failUnlessReallyEqual(len(units), 5+1)
4593 self.failUnlessEqual(units[-1]["type"], "stats")
4595 self.failUnlessEqual(first["path"], [])
4596 self.failUnlessEqual(to_str(first["cap"]), self.rootnode.get_uri())
4597 self.failUnlessEqual(first["type"], "directory")
4598 stats = units[-1]["stats"]
4599 self.failUnlessReallyEqual(stats["count-immutable-files"], 2)
4600 self.failUnlessReallyEqual(stats["count-literal-files"], 1)
4601 self.failUnlessReallyEqual(stats["count-mutable-files"], 0)
4602 self.failUnlessReallyEqual(stats["count-immutable-files"], 2)
4603 self.failUnlessReallyEqual(stats["count-unknown"], 1)
4604 d.addCallback(_check_manifest)
4606 # now add root/subdir and root/subdir/grandchild, then make subdir
4607 # unrecoverable, then see what happens
4609 d.addCallback(lambda ign:
4610 self.rootnode.create_subdirectory(u"subdir"))
4611 d.addCallback(_stash_uri, "subdir")
4612 d.addCallback(lambda subdir_node:
4613 subdir_node.add_file(u"grandchild",
4614 upload.Data(DATA+"2",
4616 d.addCallback(_stash_uri, "grandchild")
4618 d.addCallback(lambda ign:
4619 self.delete_shares_numbered(self.uris["subdir"],
4627 # root/subdir [unrecoverable]
4628 # root/subdir/grandchild
4630 # how should a streaming-JSON API indicate fatal error?
4631 # answer: emit ERROR: instead of a JSON string
4633 d.addCallback(self.CHECK, "root", "t=stream-manifest")
4634 def _check_broken_manifest(res):
4635 lines = res.splitlines()
4637 for (i,line) in enumerate(lines)
4638 if line.startswith("ERROR:")]
4640 self.fail("no ERROR: in output: %s" % (res,))
4641 first_error = error_lines[0]
4642 error_line = lines[first_error]
4643 error_msg = lines[first_error+1:]
4644 error_msg_s = "\n".join(error_msg) + "\n"
4645 self.failUnlessIn("ERROR: UnrecoverableFileError(no recoverable versions)",
4647 self.failUnless(len(error_msg) > 2, error_msg_s) # some traceback
4648 units = [simplejson.loads(line) for line in lines[:first_error]]
4649 self.failUnlessReallyEqual(len(units), 6) # includes subdir
4650 last_unit = units[-1]
4651 self.failUnlessEqual(last_unit["path"], ["subdir"])
4652 d.addCallback(_check_broken_manifest)
4654 d.addCallback(self.CHECK, "root", "t=stream-deep-check")
4655 def _check_broken_deepcheck(res):
4656 lines = res.splitlines()
4658 for (i,line) in enumerate(lines)
4659 if line.startswith("ERROR:")]
4661 self.fail("no ERROR: in output: %s" % (res,))
4662 first_error = error_lines[0]
4663 error_line = lines[first_error]
4664 error_msg = lines[first_error+1:]
4665 error_msg_s = "\n".join(error_msg) + "\n"
4666 self.failUnlessIn("ERROR: UnrecoverableFileError(no recoverable versions)",
4668 self.failUnless(len(error_msg) > 2, error_msg_s) # some traceback
4669 units = [simplejson.loads(line) for line in lines[:first_error]]
4670 self.failUnlessReallyEqual(len(units), 6) # includes subdir
4671 last_unit = units[-1]
4672 self.failUnlessEqual(last_unit["path"], ["subdir"])
4673 r = last_unit["check-results"]["results"]
4674 self.failUnlessReallyEqual(r["count-recoverable-versions"], 0)
4675 self.failUnlessReallyEqual(r["count-shares-good"], 1)
4676 self.failUnlessReallyEqual(r["recoverable"], False)
4677 d.addCallback(_check_broken_deepcheck)
4679 d.addErrback(self.explain_web_error)
4682 def test_deep_check_and_repair(self):
4683 self.basedir = "web/Grid/deep_check_and_repair"
4685 c0 = self.g.clients[0]
4689 d = c0.create_dirnode()
4690 def _stash_root_and_create_file(n):
4692 self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri()) + "/"
4693 return n.add_file(u"good", upload.Data(DATA, convergence=""))
4694 d.addCallback(_stash_root_and_create_file)
4695 def _stash_uri(fn, which):
4696 self.uris[which] = fn.get_uri()
4697 d.addCallback(_stash_uri, "good")
4698 d.addCallback(lambda ign:
4699 self.rootnode.add_file(u"small",
4700 upload.Data("literal",
4702 d.addCallback(_stash_uri, "small")
4703 d.addCallback(lambda ign:
4704 self.rootnode.add_file(u"sick",
4705 upload.Data(DATA+"1",
4707 d.addCallback(_stash_uri, "sick")
4708 #d.addCallback(lambda ign:
4709 # self.rootnode.add_file(u"dead",
4710 # upload.Data(DATA+"2",
4712 #d.addCallback(_stash_uri, "dead")
4714 #d.addCallback(lambda ign: c0.create_mutable_file("mutable"))
4715 #d.addCallback(lambda fn: self.rootnode.set_node(u"corrupt", fn))
4716 #d.addCallback(_stash_uri, "corrupt")
4718 def _clobber_shares(ignored):
4719 good_shares = self.find_uri_shares(self.uris["good"])
4720 self.failUnlessReallyEqual(len(good_shares), 10)
4721 sick_shares = self.find_uri_shares(self.uris["sick"])
4722 os.unlink(sick_shares[0][2])
4723 #dead_shares = self.find_uri_shares(self.uris["dead"])
4724 #for i in range(1, 10):
4725 # os.unlink(dead_shares[i][2])
4727 #c_shares = self.find_uri_shares(self.uris["corrupt"])
4728 #cso = CorruptShareOptions()
4729 #cso.stdout = StringIO()
4730 #cso.parseOptions([c_shares[0][2]])
4732 d.addCallback(_clobber_shares)
4735 # root/good CHK, 10 shares
4737 # root/sick CHK, 9 shares
4739 d.addCallback(self.CHECK, "root", "t=stream-deep-check&repair=true")
4741 units = [simplejson.loads(line)
4742 for line in res.splitlines()
4744 self.failUnlessReallyEqual(len(units), 4+1)
4745 # should be parent-first
4747 self.failUnlessEqual(u0["path"], [])
4748 self.failUnlessEqual(u0["type"], "directory")
4749 self.failUnlessReallyEqual(to_str(u0["cap"]), self.rootnode.get_uri())
4750 u0crr = u0["check-and-repair-results"]
4751 self.failUnlessReallyEqual(u0crr["repair-attempted"], False)
4752 self.failUnlessReallyEqual(u0crr["pre-repair-results"]["results"]["count-shares-good"], 10)
4754 ugood = [u for u in units
4755 if u["type"] == "file" and u["path"] == [u"good"]][0]
4756 self.failUnlessEqual(to_str(ugood["cap"]), self.uris["good"])
4757 ugoodcrr = ugood["check-and-repair-results"]
4758 self.failUnlessReallyEqual(ugoodcrr["repair-attempted"], False)
4759 self.failUnlessReallyEqual(ugoodcrr["pre-repair-results"]["results"]["count-shares-good"], 10)
4761 usick = [u for u in units
4762 if u["type"] == "file" and u["path"] == [u"sick"]][0]
4763 self.failUnlessReallyEqual(to_str(usick["cap"]), self.uris["sick"])
4764 usickcrr = usick["check-and-repair-results"]
4765 self.failUnlessReallyEqual(usickcrr["repair-attempted"], True)
4766 self.failUnlessReallyEqual(usickcrr["repair-successful"], True)
4767 self.failUnlessReallyEqual(usickcrr["pre-repair-results"]["results"]["count-shares-good"], 9)
4768 self.failUnlessReallyEqual(usickcrr["post-repair-results"]["results"]["count-shares-good"], 10)
4771 self.failUnlessEqual(stats["type"], "stats")
4773 self.failUnlessReallyEqual(s["count-immutable-files"], 2)
4774 self.failUnlessReallyEqual(s["count-literal-files"], 1)
4775 self.failUnlessReallyEqual(s["count-directories"], 1)
4776 d.addCallback(_done)
4778 d.addErrback(self.explain_web_error)
4781 def _count_leases(self, ignored, which):
4782 u = self.uris[which]
4783 shares = self.find_uri_shares(u)
4785 for shnum, serverid, fn in shares:
4786 sf = get_share_file(fn)
4787 num_leases = len(list(sf.get_leases()))
4788 lease_counts.append( (fn, num_leases) )
4791 def _assert_leasecount(self, lease_counts, expected):
4792 for (fn, num_leases) in lease_counts:
4793 if num_leases != expected:
4794 self.fail("expected %d leases, have %d, on %s" %
4795 (expected, num_leases, fn))
4797 def test_add_lease(self):
4798 self.basedir = "web/Grid/add_lease"
4799 self.set_up_grid(num_clients=2)
4800 c0 = self.g.clients[0]
4803 d = c0.upload(upload.Data(DATA, convergence=""))
4804 def _stash_uri(ur, which):
4805 self.uris[which] = ur.uri
4806 d.addCallback(_stash_uri, "one")
4807 d.addCallback(lambda ign:
4808 c0.upload(upload.Data(DATA+"1", convergence="")))
4809 d.addCallback(_stash_uri, "two")
4810 def _stash_mutable_uri(n, which):
4811 self.uris[which] = n.get_uri()
4812 assert isinstance(self.uris[which], str)
4813 d.addCallback(lambda ign:
4814 c0.create_mutable_file(publish.MutableData(DATA+"2")))
4815 d.addCallback(_stash_mutable_uri, "mutable")
4817 def _compute_fileurls(ignored):
4819 for which in self.uris:
4820 self.fileurls[which] = "uri/" + urllib.quote(self.uris[which])
4821 d.addCallback(_compute_fileurls)
4823 d.addCallback(self._count_leases, "one")
4824 d.addCallback(self._assert_leasecount, 1)
4825 d.addCallback(self._count_leases, "two")
4826 d.addCallback(self._assert_leasecount, 1)
4827 d.addCallback(self._count_leases, "mutable")
4828 d.addCallback(self._assert_leasecount, 1)
4830 d.addCallback(self.CHECK, "one", "t=check") # no add-lease
4831 def _got_html_good(res):
4832 self.failUnless("Healthy" in res, res)
4833 self.failIf("Not Healthy" in res, res)
4834 d.addCallback(_got_html_good)
4836 d.addCallback(self._count_leases, "one")
4837 d.addCallback(self._assert_leasecount, 1)
4838 d.addCallback(self._count_leases, "two")
4839 d.addCallback(self._assert_leasecount, 1)
4840 d.addCallback(self._count_leases, "mutable")
4841 d.addCallback(self._assert_leasecount, 1)
4843 # this CHECK uses the original client, which uses the same
4844 # lease-secrets, so it will just renew the original lease
4845 d.addCallback(self.CHECK, "one", "t=check&add-lease=true")
4846 d.addCallback(_got_html_good)
4848 d.addCallback(self._count_leases, "one")
4849 d.addCallback(self._assert_leasecount, 1)
4850 d.addCallback(self._count_leases, "two")
4851 d.addCallback(self._assert_leasecount, 1)
4852 d.addCallback(self._count_leases, "mutable")
4853 d.addCallback(self._assert_leasecount, 1)
4855 # this CHECK uses an alternate client, which adds a second lease
4856 d.addCallback(self.CHECK, "one", "t=check&add-lease=true", clientnum=1)
4857 d.addCallback(_got_html_good)
4859 d.addCallback(self._count_leases, "one")
4860 d.addCallback(self._assert_leasecount, 2)
4861 d.addCallback(self._count_leases, "two")
4862 d.addCallback(self._assert_leasecount, 1)
4863 d.addCallback(self._count_leases, "mutable")
4864 d.addCallback(self._assert_leasecount, 1)
4866 d.addCallback(self.CHECK, "mutable", "t=check&add-lease=true")
4867 d.addCallback(_got_html_good)
4869 d.addCallback(self._count_leases, "one")
4870 d.addCallback(self._assert_leasecount, 2)
4871 d.addCallback(self._count_leases, "two")
4872 d.addCallback(self._assert_leasecount, 1)
4873 d.addCallback(self._count_leases, "mutable")
4874 d.addCallback(self._assert_leasecount, 1)
4876 d.addCallback(self.CHECK, "mutable", "t=check&add-lease=true",
4878 d.addCallback(_got_html_good)
4880 d.addCallback(self._count_leases, "one")
4881 d.addCallback(self._assert_leasecount, 2)
4882 d.addCallback(self._count_leases, "two")
4883 d.addCallback(self._assert_leasecount, 1)
4884 d.addCallback(self._count_leases, "mutable")
4885 d.addCallback(self._assert_leasecount, 2)
4887 d.addErrback(self.explain_web_error)
4890 def test_deep_add_lease(self):
4891 self.basedir = "web/Grid/deep_add_lease"
4892 self.set_up_grid(num_clients=2)
4893 c0 = self.g.clients[0]
4897 d = c0.create_dirnode()
4898 def _stash_root_and_create_file(n):
4900 self.uris["root"] = n.get_uri()
4901 self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri()) + "/"
4902 return n.add_file(u"one", upload.Data(DATA, convergence=""))
4903 d.addCallback(_stash_root_and_create_file)
4904 def _stash_uri(fn, which):
4905 self.uris[which] = fn.get_uri()
4906 d.addCallback(_stash_uri, "one")
4907 d.addCallback(lambda ign:
4908 self.rootnode.add_file(u"small",
4909 upload.Data("literal",
4911 d.addCallback(_stash_uri, "small")
4913 d.addCallback(lambda ign:
4914 c0.create_mutable_file(publish.MutableData("mutable")))
4915 d.addCallback(lambda fn: self.rootnode.set_node(u"mutable", fn))
4916 d.addCallback(_stash_uri, "mutable")
4918 d.addCallback(self.CHECK, "root", "t=stream-deep-check") # no add-lease
4920 units = [simplejson.loads(line)
4921 for line in res.splitlines()
4923 # root, one, small, mutable, stats
4924 self.failUnlessReallyEqual(len(units), 4+1)
4925 d.addCallback(_done)
4927 d.addCallback(self._count_leases, "root")
4928 d.addCallback(self._assert_leasecount, 1)
4929 d.addCallback(self._count_leases, "one")
4930 d.addCallback(self._assert_leasecount, 1)
4931 d.addCallback(self._count_leases, "mutable")
4932 d.addCallback(self._assert_leasecount, 1)
4934 d.addCallback(self.CHECK, "root", "t=stream-deep-check&add-lease=true")
4935 d.addCallback(_done)
4937 d.addCallback(self._count_leases, "root")
4938 d.addCallback(self._assert_leasecount, 1)
4939 d.addCallback(self._count_leases, "one")
4940 d.addCallback(self._assert_leasecount, 1)
4941 d.addCallback(self._count_leases, "mutable")
4942 d.addCallback(self._assert_leasecount, 1)
4944 d.addCallback(self.CHECK, "root", "t=stream-deep-check&add-lease=true",
4946 d.addCallback(_done)
4948 d.addCallback(self._count_leases, "root")
4949 d.addCallback(self._assert_leasecount, 2)
4950 d.addCallback(self._count_leases, "one")
4951 d.addCallback(self._assert_leasecount, 2)
4952 d.addCallback(self._count_leases, "mutable")
4953 d.addCallback(self._assert_leasecount, 2)
4955 d.addErrback(self.explain_web_error)
4959 def test_exceptions(self):
4960 self.basedir = "web/Grid/exceptions"
4961 self.set_up_grid(num_clients=1, num_servers=2)
4962 c0 = self.g.clients[0]
4963 c0.DEFAULT_ENCODING_PARAMETERS['happy'] = 2
4966 d = c0.create_dirnode()
4968 self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri()) + "/"
4969 self.fileurls["imaginary"] = self.fileurls["root"] + "imaginary"
4971 d.addCallback(_stash_root)
4972 d.addCallback(lambda ign: c0.upload(upload.Data(DATA, convergence="")))
4974 self.fileurls["1share"] = "uri/" + urllib.quote(ur.uri)
4975 self.delete_shares_numbered(ur.uri, range(1,10))
4977 u = uri.from_string(ur.uri)
4978 u.key = testutil.flip_bit(u.key, 0)
4979 baduri = u.to_string()
4980 self.fileurls["0shares"] = "uri/" + urllib.quote(baduri)
4981 d.addCallback(_stash_bad)
4982 d.addCallback(lambda ign: c0.create_dirnode())
4983 def _mangle_dirnode_1share(n):
4985 url = self.fileurls["dir-1share"] = "uri/" + urllib.quote(u) + "/"
4986 self.fileurls["dir-1share-json"] = url + "?t=json"
4987 self.delete_shares_numbered(u, range(1,10))
4988 d.addCallback(_mangle_dirnode_1share)
4989 d.addCallback(lambda ign: c0.create_dirnode())
4990 def _mangle_dirnode_0share(n):
4992 url = self.fileurls["dir-0share"] = "uri/" + urllib.quote(u) + "/"
4993 self.fileurls["dir-0share-json"] = url + "?t=json"
4994 self.delete_shares_numbered(u, range(0,10))
4995 d.addCallback(_mangle_dirnode_0share)
4997 # NotEnoughSharesError should be reported sensibly, with a
4998 # text/plain explanation of the problem, and perhaps some
4999 # information on which shares *could* be found.
5001 d.addCallback(lambda ignored:
5002 self.shouldHTTPError("GET unrecoverable",
5003 410, "Gone", "NoSharesError",
5004 self.GET, self.fileurls["0shares"]))
5005 def _check_zero_shares(body):
5006 self.failIf("<html>" in body, body)
5007 body = " ".join(body.strip().split())
5008 exp = ("NoSharesError: no shares could be found. "
5009 "Zero shares usually indicates a corrupt URI, or that "
5010 "no servers were connected, but it might also indicate "
5011 "severe corruption. You should perform a filecheck on "
5012 "this object to learn more. The full error message is: "
5013 "no shares (need 3). Last failure: None")
5014 self.failUnlessReallyEqual(exp, body)
5015 d.addCallback(_check_zero_shares)
5018 d.addCallback(lambda ignored:
5019 self.shouldHTTPError("GET 1share",
5020 410, "Gone", "NotEnoughSharesError",
5021 self.GET, self.fileurls["1share"]))
5022 def _check_one_share(body):
5023 self.failIf("<html>" in body, body)
5024 body = " ".join(body.strip().split())
5025 msgbase = ("NotEnoughSharesError: This indicates that some "
5026 "servers were unavailable, or that shares have been "
5027 "lost to server departure, hard drive failure, or disk "
5028 "corruption. You should perform a filecheck on "
5029 "this object to learn more. The full error message is:"
5031 msg1 = msgbase + (" ran out of shares:"
5034 " overdue= unused= need 3. Last failure: None")
5035 msg2 = msgbase + (" ran out of shares:"
5037 " pending=Share(sh0-on-xgru5)"
5038 " overdue= unused= need 3. Last failure: None")
5039 self.failUnless(body == msg1 or body == msg2, body)
5040 d.addCallback(_check_one_share)
5042 d.addCallback(lambda ignored:
5043 self.shouldHTTPError("GET imaginary",
5044 404, "Not Found", None,
5045 self.GET, self.fileurls["imaginary"]))
5046 def _missing_child(body):
5047 self.failUnless("No such child: imaginary" in body, body)
5048 d.addCallback(_missing_child)
5050 d.addCallback(lambda ignored: self.GET(self.fileurls["dir-0share"]))
5051 def _check_0shares_dir_html(body):
5052 self.failUnless("<html>" in body, body)
5053 # we should see the regular page, but without the child table or
5055 body = " ".join(body.strip().split())
5056 self.failUnlessIn('href="?t=info">More info on this directory',
5058 exp = ("UnrecoverableFileError: the directory (or mutable file) "
5059 "could not be retrieved, because there were insufficient "
5060 "good shares. This might indicate that no servers were "
5061 "connected, insufficient servers were connected, the URI "
5062 "was corrupt, or that shares have been lost due to server "
5063 "departure, hard drive failure, or disk corruption. You "
5064 "should perform a filecheck on this object to learn more.")
5065 self.failUnlessIn(exp, body)
5066 self.failUnlessIn("No upload forms: directory is unreadable", body)
5067 d.addCallback(_check_0shares_dir_html)
5069 d.addCallback(lambda ignored: self.GET(self.fileurls["dir-1share"]))
5070 def _check_1shares_dir_html(body):
5071 # at some point, we'll split UnrecoverableFileError into 0-shares
5072 # and some-shares like we did for immutable files (since there
5073 # are different sorts of advice to offer in each case). For now,
5074 # they present the same way.
5075 self.failUnless("<html>" in body, body)
5076 body = " ".join(body.strip().split())
5077 self.failUnlessIn('href="?t=info">More info on this directory',
5079 exp = ("UnrecoverableFileError: the directory (or mutable file) "
5080 "could not be retrieved, because there were insufficient "
5081 "good shares. This might indicate that no servers were "
5082 "connected, insufficient servers were connected, the URI "
5083 "was corrupt, or that shares have been lost due to server "
5084 "departure, hard drive failure, or disk corruption. You "
5085 "should perform a filecheck on this object to learn more.")
5086 self.failUnlessIn(exp, body)
5087 self.failUnlessIn("No upload forms: directory is unreadable", body)
5088 d.addCallback(_check_1shares_dir_html)
5090 d.addCallback(lambda ignored:
5091 self.shouldHTTPError("GET dir-0share-json",
5092 410, "Gone", "UnrecoverableFileError",
5094 self.fileurls["dir-0share-json"]))
5095 def _check_unrecoverable_file(body):
5096 self.failIf("<html>" in body, body)
5097 body = " ".join(body.strip().split())
5098 exp = ("UnrecoverableFileError: the directory (or mutable file) "
5099 "could not be retrieved, because there were insufficient "
5100 "good shares. This might indicate that no servers were "
5101 "connected, insufficient servers were connected, the URI "
5102 "was corrupt, or that shares have been lost due to server "
5103 "departure, hard drive failure, or disk corruption. You "
5104 "should perform a filecheck on this object to learn more.")
5105 self.failUnlessReallyEqual(exp, body)
5106 d.addCallback(_check_unrecoverable_file)
5108 d.addCallback(lambda ignored:
5109 self.shouldHTTPError("GET dir-1share-json",
5110 410, "Gone", "UnrecoverableFileError",
5112 self.fileurls["dir-1share-json"]))
5113 d.addCallback(_check_unrecoverable_file)
5115 d.addCallback(lambda ignored:
5116 self.shouldHTTPError("GET imaginary",
5117 404, "Not Found", None,
5118 self.GET, self.fileurls["imaginary"]))
5120 # attach a webapi child that throws a random error, to test how it
5122 w = c0.getServiceNamed("webish")
5123 w.root.putChild("ERRORBOOM", ErrorBoom())
5125 # "Accept: */*" : should get a text/html stack trace
5126 # "Accept: text/plain" : should get a text/plain stack trace
5127 # "Accept: text/plain, application/octet-stream" : text/plain (CLI)
5128 # no Accept header: should get a text/html stack trace
5130 d.addCallback(lambda ignored:
5131 self.shouldHTTPError("GET errorboom_html",
5132 500, "Internal Server Error", None,
5133 self.GET, "ERRORBOOM",
5134 headers={"accept": ["*/*"]}))
5135 def _internal_error_html1(body):
5136 self.failUnless("<html>" in body, "expected HTML, not '%s'" % body)
5137 d.addCallback(_internal_error_html1)
5139 d.addCallback(lambda ignored:
5140 self.shouldHTTPError("GET errorboom_text",
5141 500, "Internal Server Error", None,
5142 self.GET, "ERRORBOOM",
5143 headers={"accept": ["text/plain"]}))
5144 def _internal_error_text2(body):
5145 self.failIf("<html>" in body, body)
5146 self.failUnless(body.startswith("Traceback "), body)
5147 d.addCallback(_internal_error_text2)
5149 CLI_accepts = "text/plain, application/octet-stream"
5150 d.addCallback(lambda ignored:
5151 self.shouldHTTPError("GET errorboom_text",
5152 500, "Internal Server Error", None,
5153 self.GET, "ERRORBOOM",
5154 headers={"accept": [CLI_accepts]}))
5155 def _internal_error_text3(body):
5156 self.failIf("<html>" in body, body)
5157 self.failUnless(body.startswith("Traceback "), body)
5158 d.addCallback(_internal_error_text3)
5160 d.addCallback(lambda ignored:
5161 self.shouldHTTPError("GET errorboom_text",
5162 500, "Internal Server Error", None,
5163 self.GET, "ERRORBOOM"))
5164 def _internal_error_html4(body):
5165 self.failUnless("<html>" in body, "expected HTML, not '%s'" % body)
5166 d.addCallback(_internal_error_html4)
5168 def _flush_errors(res):
5169 # Trial: please ignore the CompletelyUnhandledError in the logs
5170 self.flushLoggedErrors(CompletelyUnhandledError)
5172 d.addBoth(_flush_errors)
5176 def test_blacklist(self):
5177 # download from a blacklisted URI, get an error
5178 self.basedir = "web/Grid/blacklist"
5180 c0 = self.g.clients[0]
5181 c0_basedir = c0.basedir
5182 fn = os.path.join(c0_basedir, "access.blacklist")
5184 DATA = "off-limits " * 50
5186 d = c0.upload(upload.Data(DATA, convergence=""))
5187 def _stash_uri_and_create_dir(ur):
5189 self.url = "uri/"+self.uri
5190 u = uri.from_string_filenode(self.uri)
5191 self.si = u.get_storage_index()
5192 childnode = c0.create_node_from_uri(self.uri, None)
5193 return c0.create_dirnode({u"blacklisted.txt": (childnode,{}) })
5194 d.addCallback(_stash_uri_and_create_dir)
5195 def _stash_dir(node):
5196 self.dir_node = node
5197 self.dir_uri = node.get_uri()
5198 self.dir_url = "uri/"+self.dir_uri
5199 d.addCallback(_stash_dir)
5200 d.addCallback(lambda ign: self.GET(self.dir_url, followRedirect=True))
5201 def _check_dir_html(body):
5202 self.failUnlessIn("<html>", body)
5203 self.failUnlessIn("blacklisted.txt</a>", body)
5204 d.addCallback(_check_dir_html)
5205 d.addCallback(lambda ign: self.GET(self.url))
5206 d.addCallback(lambda body: self.failUnlessEqual(DATA, body))
5208 def _blacklist(ign):
5210 f.write(" # this is a comment\n")
5212 f.write("\n") # also exercise blank lines
5213 f.write("%s %s\n" % (base32.b2a(self.si), "off-limits to you"))
5215 # clients should be checking the blacklist each time, so we don't
5216 # need to restart the client
5217 d.addCallback(_blacklist)
5218 d.addCallback(lambda ign: self.shouldHTTPError("get_from_blacklisted_uri",
5220 "Access Prohibited: off-limits",
5221 self.GET, self.url))
5223 # We should still be able to list the parent directory, in HTML...
5224 d.addCallback(lambda ign: self.GET(self.dir_url, followRedirect=True))
5225 def _check_dir_html2(body):
5226 self.failUnlessIn("<html>", body)
5227 self.failUnlessIn("blacklisted.txt</strike>", body)
5228 d.addCallback(_check_dir_html2)
5230 # ... and in JSON (used by CLI).
5231 d.addCallback(lambda ign: self.GET(self.dir_url+"?t=json", followRedirect=True))
5232 def _check_dir_json(res):
5233 data = simplejson.loads(res)
5234 self.failUnless(isinstance(data, list), data)
5235 self.failUnlessEqual(data[0], "dirnode")
5236 self.failUnless(isinstance(data[1], dict), data)
5237 self.failUnlessIn("children", data[1])
5238 self.failUnlessIn("blacklisted.txt", data[1]["children"])
5239 childdata = data[1]["children"]["blacklisted.txt"]
5240 self.failUnless(isinstance(childdata, list), data)
5241 self.failUnlessEqual(childdata[0], "filenode")
5242 self.failUnless(isinstance(childdata[1], dict), data)
5243 d.addCallback(_check_dir_json)
5245 def _unblacklist(ign):
5246 open(fn, "w").close()
5247 # the Blacklist object watches mtime to tell when the file has
5248 # changed, but on windows this test will run faster than the
5249 # filesystem's mtime resolution. So we edit Blacklist.last_mtime
5250 # to force a reload.
5251 self.g.clients[0].blacklist.last_mtime -= 2.0
5252 d.addCallback(_unblacklist)
5254 # now a read should work
5255 d.addCallback(lambda ign: self.GET(self.url))
5256 d.addCallback(lambda body: self.failUnlessEqual(DATA, body))
5258 # read again to exercise the blacklist-is-unchanged logic
5259 d.addCallback(lambda ign: self.GET(self.url))
5260 d.addCallback(lambda body: self.failUnlessEqual(DATA, body))
5262 # now add a blacklisted directory, and make sure files under it are
5265 childnode = c0.create_node_from_uri(self.uri, None)
5266 return c0.create_dirnode({u"child": (childnode,{}) })
5267 d.addCallback(_add_dir)
5268 def _get_dircap(dn):
5269 self.dir_si_b32 = base32.b2a(dn.get_storage_index())
5270 self.dir_url_base = "uri/"+dn.get_write_uri()
5271 self.dir_url_json1 = "uri/"+dn.get_write_uri()+"?t=json"
5272 self.dir_url_json2 = "uri/"+dn.get_write_uri()+"/?t=json"
5273 self.dir_url_json_ro = "uri/"+dn.get_readonly_uri()+"/?t=json"
5274 self.child_url = "uri/"+dn.get_readonly_uri()+"/child"
5275 d.addCallback(_get_dircap)
5276 d.addCallback(lambda ign: self.GET(self.dir_url_base, followRedirect=True))
5277 d.addCallback(lambda body: self.failUnlessIn("<html>", body))
5278 d.addCallback(lambda ign: self.GET(self.dir_url_json1))
5279 d.addCallback(lambda res: simplejson.loads(res)) # just check it decodes
5280 d.addCallback(lambda ign: self.GET(self.dir_url_json2))
5281 d.addCallback(lambda res: simplejson.loads(res)) # just check it decodes
5282 d.addCallback(lambda ign: self.GET(self.dir_url_json_ro))
5283 d.addCallback(lambda res: simplejson.loads(res)) # just check it decodes
5284 d.addCallback(lambda ign: self.GET(self.child_url))
5285 d.addCallback(lambda body: self.failUnlessEqual(DATA, body))
5287 def _block_dir(ign):
5289 f.write("%s %s\n" % (self.dir_si_b32, "dir-off-limits to you"))
5291 self.g.clients[0].blacklist.last_mtime -= 2.0
5292 d.addCallback(_block_dir)
5293 d.addCallback(lambda ign: self.shouldHTTPError("get_from_blacklisted_dir base",
5295 "Access Prohibited: dir-off-limits",
5296 self.GET, self.dir_url_base))
5297 d.addCallback(lambda ign: self.shouldHTTPError("get_from_blacklisted_dir json1",
5299 "Access Prohibited: dir-off-limits",
5300 self.GET, self.dir_url_json1))
5301 d.addCallback(lambda ign: self.shouldHTTPError("get_from_blacklisted_dir json2",
5303 "Access Prohibited: dir-off-limits",
5304 self.GET, self.dir_url_json2))
5305 d.addCallback(lambda ign: self.shouldHTTPError("get_from_blacklisted_dir json_ro",
5307 "Access Prohibited: dir-off-limits",
5308 self.GET, self.dir_url_json_ro))
5309 d.addCallback(lambda ign: self.shouldHTTPError("get_from_blacklisted_dir child",
5311 "Access Prohibited: dir-off-limits",
5312 self.GET, self.child_url))
5316 class CompletelyUnhandledError(Exception):
5318 class ErrorBoom(rend.Page):
5319 def beforeRender(self, ctx):
5320 raise CompletelyUnhandledError("whoops")