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?mutable=true&mutable-type=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?mutable=true&mutable-type=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_mutable_type(self):
1087 contents = self.NEWFILE_CONTENTS * 300000
1088 return self.shouldHTTPError("test bad mutable type",
1089 400, "Bad Request", "Unknown type: foo",
1090 self.PUT, "/uri?mutable=true&mutable-type=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 mutable-type, which says whether the
1225 # file is SDMF or MDMF
1226 d = self.PUT("/uri?mutable=true&mutable-type=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("mutable-type", data)
1236 self.failUnlessEqual(data['mutable-type'], 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?mutable=true&mutable-type=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_mutable_type(self):
1447 d = self.PUT(self.public_url + \
1448 "/foo/sdmf.txt?mutable=true&mutable-type=sdmf",
1449 self.NEWFILE_CONTENTS * 300000)
1450 d.addCallback(lambda ignored:
1451 self.PUT(self.public_url + \
1452 "/foo/mdmf.txt?mutable=true&mutable-type=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("mutable-type", mdmf_data)
1467 self.failUnlessEqual(mdmf_data['mutable-type'], "mdmf")
1469 sdmf_data = kids['sdmf.txt'][1]
1470 self.failUnlessIn("mutable-type", sdmf_data)
1471 self.failUnlessEqual(sdmf_data['mutable-type'], "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&mutable-type=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&mutable-type=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_mutable_type(self):
1653 return self.shouldHTTPError("test bad mutable type",
1654 400, "Bad Request", "Unknown type: foo",
1655 self.PUT, self.public_url + \
1656 "/foo/newdir=?t=mkdir&mutable-type=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&mutable-type=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&mutable-type=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_mutable_type(self):
1685 return self.shouldHTTPError("test bad mutable type",
1686 400, "Bad Request", "Unknown type: foo",
1687 self.POST2, self.public_url + \
1688 "/foo/newdir?t=mkdir&mutable-type=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 += "&mutable-type=mdmf"
1703 elif version == SDMF_VERSION:
1704 query += "&mutable-type=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_mutable_type(self):
1760 (newkids, caps) = self._create_initial_children()
1761 return self.shouldHTTPError("test bad mutable type",
1762 400, "Bad Request", "Unknown type: foo",
1763 self.POST2, self.public_url + \
1764 "/foo/newdir?t=mkdir-with-children&mutable-type=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&mutable-type=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&mutable-type=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_mutable_type(self):
1904 return self.shouldHTTPError("test bad mutable type",
1905 400, "Bad Request", "Unknown type: foo",
1906 self.PUT, self.public_url + \
1907 "/foo/subdir/newdir?t=mkdir&mutable-type=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_mutable_type_unlinked(self):
2156 d = self.POST("/uri?t=upload&mutable=true&mutable-type=sdmf",
2157 file=("sdmf.txt", self.NEWFILE_CONTENTS * 300000))
2158 d.addCallback(lambda filecap: self.GET("/uri/%s?t=json" % filecap))
2159 def _got_json(json, version):
2160 data = simplejson.loads(json)
2163 self.failUnlessIn("mutable-type", data)
2164 self.failUnlessEqual(data['mutable-type'], version)
2165 d.addCallback(_got_json, "sdmf")
2166 d.addCallback(lambda ignored:
2167 self.POST("/uri?t=upload&mutable=true&mutable-type=mdmf",
2168 file=('mdmf.txt', self.NEWFILE_CONTENTS * 300000)))
2169 def _got_filecap(filecap):
2170 self.failUnless(filecap.startswith("URI:MDMF"))
2172 d.addCallback(_got_filecap)
2173 d.addCallback(lambda filecap: self.GET("/uri/%s?t=json" % filecap))
2174 d.addCallback(_got_json, "mdmf")
2177 def test_POST_upload_mutable_type_unlinked_bad_mutable_type(self):
2178 return self.shouldHTTPError("test bad mutable type",
2179 400, "Bad Request", "Unknown type: foo",
2181 "/uri?5=upload&mutable=true&mutable-type=foo",
2182 file=("foo.txt", self.NEWFILE_CONTENTS * 300000))
2184 def test_POST_upload_mutable_type(self):
2185 d = self.POST(self.public_url + \
2186 "/foo?t=upload&mutable=true&mutable-type=sdmf",
2187 file=("sdmf.txt", self.NEWFILE_CONTENTS * 300000))
2189 def _got_cap(filecap, filename):
2190 filenameu = unicode(filename)
2191 self.failUnlessURIMatchesRWChild(filecap, fn, filenameu)
2192 return self.GET(self.public_url + "/foo/%s?t=json" % filename)
2193 def _got_mdmf_cap(filecap):
2194 self.failUnless(filecap.startswith("URI:MDMF"))
2196 d.addCallback(_got_cap, "sdmf.txt")
2197 def _got_json(json, version):
2198 data = simplejson.loads(json)
2201 self.failUnlessIn("mutable-type", data)
2202 self.failUnlessEqual(data['mutable-type'], version)
2203 d.addCallback(_got_json, "sdmf")
2204 d.addCallback(lambda ignored:
2205 self.POST(self.public_url + \
2206 "/foo?t=upload&mutable=true&mutable-type=mdmf",
2207 file=("mdmf.txt", self.NEWFILE_CONTENTS * 300000)))
2208 d.addCallback(_got_mdmf_cap)
2209 d.addCallback(_got_cap, "mdmf.txt")
2210 d.addCallback(_got_json, "mdmf")
2213 def test_POST_upload_bad_mutable_type(self):
2214 return self.shouldHTTPError("test bad mutable type",
2215 400, "Bad Request", "Unknown type: foo",
2216 self.POST, self.public_url + \
2217 "/foo?t=upload&mutable=true&mutable-type=foo",
2218 file=("foo.txt", self.NEWFILE_CONTENTS * 300000))
2220 def test_POST_upload_mutable(self):
2221 # this creates a mutable file
2222 d = self.POST(self.public_url + "/foo", t="upload", mutable="true",
2223 file=("new.txt", self.NEWFILE_CONTENTS))
2225 d.addCallback(self.failUnlessURIMatchesRWChild, fn, u"new.txt")
2226 d.addCallback(lambda res:
2227 self.failUnlessMutableChildContentsAre(fn, u"new.txt",
2228 self.NEWFILE_CONTENTS))
2229 d.addCallback(lambda res: self._foo_node.get(u"new.txt"))
2231 self.failUnless(IMutableFileNode.providedBy(newnode))
2232 self.failUnless(newnode.is_mutable())
2233 self.failIf(newnode.is_readonly())
2234 self._mutable_node = newnode
2235 self._mutable_uri = newnode.get_uri()
2238 # now upload it again and make sure that the URI doesn't change
2239 NEWER_CONTENTS = self.NEWFILE_CONTENTS + "newer\n"
2240 d.addCallback(lambda res:
2241 self.POST(self.public_url + "/foo", t="upload",
2243 file=("new.txt", NEWER_CONTENTS)))
2244 d.addCallback(self.failUnlessURIMatchesRWChild, fn, u"new.txt")
2245 d.addCallback(lambda res:
2246 self.failUnlessMutableChildContentsAre(fn, u"new.txt",
2248 d.addCallback(lambda res: self._foo_node.get(u"new.txt"))
2250 self.failUnless(IMutableFileNode.providedBy(newnode))
2251 self.failUnless(newnode.is_mutable())
2252 self.failIf(newnode.is_readonly())
2253 self.failUnlessReallyEqual(self._mutable_uri, newnode.get_uri())
2254 d.addCallback(_got2)
2256 # upload a second time, using PUT instead of POST
2257 NEW2_CONTENTS = NEWER_CONTENTS + "overwrite with PUT\n"
2258 d.addCallback(lambda res:
2259 self.PUT(self.public_url + "/foo/new.txt", NEW2_CONTENTS))
2260 d.addCallback(self.failUnlessURIMatchesRWChild, fn, u"new.txt")
2261 d.addCallback(lambda res:
2262 self.failUnlessMutableChildContentsAre(fn, u"new.txt",
2265 # finally list the directory, since mutable files are displayed
2266 # slightly differently
2268 d.addCallback(lambda res:
2269 self.GET(self.public_url + "/foo/",
2270 followRedirect=True))
2271 def _check_page(res):
2272 # TODO: assert more about the contents
2273 self.failUnless("SSK" in res)
2275 d.addCallback(_check_page)
2277 d.addCallback(lambda res: self._foo_node.get(u"new.txt"))
2279 self.failUnless(IMutableFileNode.providedBy(newnode))
2280 self.failUnless(newnode.is_mutable())
2281 self.failIf(newnode.is_readonly())
2282 self.failUnlessReallyEqual(self._mutable_uri, newnode.get_uri())
2283 d.addCallback(_got3)
2285 # look at the JSON form of the enclosing directory
2286 d.addCallback(lambda res:
2287 self.GET(self.public_url + "/foo/?t=json",
2288 followRedirect=True))
2289 def _check_page_json(res):
2290 parsed = simplejson.loads(res)
2291 self.failUnlessEqual(parsed[0], "dirnode")
2292 children = dict( [(unicode(name),value)
2294 in parsed[1]["children"].iteritems()] )
2295 self.failUnless(u"new.txt" in children)
2296 new_json = children[u"new.txt"]
2297 self.failUnlessEqual(new_json[0], "filenode")
2298 self.failUnless(new_json[1]["mutable"])
2299 self.failUnlessReallyEqual(to_str(new_json[1]["rw_uri"]), self._mutable_uri)
2300 ro_uri = self._mutable_node.get_readonly().to_string()
2301 self.failUnlessReallyEqual(to_str(new_json[1]["ro_uri"]), ro_uri)
2302 d.addCallback(_check_page_json)
2304 # and the JSON form of the file
2305 d.addCallback(lambda res:
2306 self.GET(self.public_url + "/foo/new.txt?t=json"))
2307 def _check_file_json(res):
2308 parsed = simplejson.loads(res)
2309 self.failUnlessEqual(parsed[0], "filenode")
2310 self.failUnless(parsed[1]["mutable"])
2311 self.failUnlessReallyEqual(to_str(parsed[1]["rw_uri"]), self._mutable_uri)
2312 ro_uri = self._mutable_node.get_readonly().to_string()
2313 self.failUnlessReallyEqual(to_str(parsed[1]["ro_uri"]), ro_uri)
2314 d.addCallback(_check_file_json)
2316 # and look at t=uri and t=readonly-uri
2317 d.addCallback(lambda res:
2318 self.GET(self.public_url + "/foo/new.txt?t=uri"))
2319 d.addCallback(lambda res: self.failUnlessReallyEqual(res, self._mutable_uri))
2320 d.addCallback(lambda res:
2321 self.GET(self.public_url + "/foo/new.txt?t=readonly-uri"))
2322 def _check_ro_uri(res):
2323 ro_uri = self._mutable_node.get_readonly().to_string()
2324 self.failUnlessReallyEqual(res, ro_uri)
2325 d.addCallback(_check_ro_uri)
2327 # make sure we can get to it from /uri/URI
2328 d.addCallback(lambda res:
2329 self.GET("/uri/%s" % urllib.quote(self._mutable_uri)))
2330 d.addCallback(lambda res:
2331 self.failUnlessReallyEqual(res, NEW2_CONTENTS))
2333 # and that HEAD computes the size correctly
2334 d.addCallback(lambda res:
2335 self.HEAD(self.public_url + "/foo/new.txt",
2336 return_response=True))
2337 def _got_headers((res, status, headers)):
2338 self.failUnlessReallyEqual(res, "")
2339 self.failUnlessReallyEqual(headers["content-length"][0],
2340 str(len(NEW2_CONTENTS)))
2341 self.failUnlessReallyEqual(headers["content-type"], ["text/plain"])
2342 d.addCallback(_got_headers)
2344 # make sure that outdated size limits aren't enforced anymore.
2345 d.addCallback(lambda ignored:
2346 self.POST(self.public_url + "/foo", t="upload",
2349 "b" * (self.s.MUTABLE_SIZELIMIT+1))))
2350 d.addErrback(self.dump_error)
2353 def test_POST_upload_mutable_toobig(self):
2354 # SDMF had a size limti that was removed a while ago. MDMF has
2355 # never had a size limit. Test to make sure that we do not
2356 # encounter errors when trying to upload large mutable files,
2357 # since there should be no coded prohibitions regarding large
2359 d = self.POST(self.public_url + "/foo",
2360 t="upload", mutable="true",
2361 file=("new.txt", "b" * (self.s.MUTABLE_SIZELIMIT + 1)))
2364 def dump_error(self, f):
2365 # if the web server returns an error code (like 400 Bad Request),
2366 # web.client.getPage puts the HTTP response body into the .response
2367 # attribute of the exception object that it gives back. It does not
2368 # appear in the Failure's repr(), so the ERROR that trial displays
2369 # will be rather terse and unhelpful. addErrback this method to the
2370 # end of your chain to get more information out of these errors.
2371 if f.check(error.Error):
2372 print "web.error.Error:"
2374 print f.value.response
2377 def test_POST_upload_replace(self):
2378 d = self.POST(self.public_url + "/foo", t="upload",
2379 file=("bar.txt", self.NEWFILE_CONTENTS))
2381 d.addCallback(self.failUnlessURIMatchesROChild, fn, u"bar.txt")
2382 d.addCallback(lambda res:
2383 self.failUnlessChildContentsAre(fn, u"bar.txt",
2384 self.NEWFILE_CONTENTS))
2387 def test_POST_upload_no_replace_ok(self):
2388 d = self.POST(self.public_url + "/foo?replace=false", t="upload",
2389 file=("new.txt", self.NEWFILE_CONTENTS))
2390 d.addCallback(lambda res: self.GET(self.public_url + "/foo/new.txt"))
2391 d.addCallback(lambda res: self.failUnlessReallyEqual(res,
2392 self.NEWFILE_CONTENTS))
2395 def test_POST_upload_no_replace_queryarg(self):
2396 d = self.POST(self.public_url + "/foo?replace=false", t="upload",
2397 file=("bar.txt", self.NEWFILE_CONTENTS))
2398 d.addBoth(self.shouldFail, error.Error,
2399 "POST_upload_no_replace_queryarg",
2401 "There was already a child by that name, and you asked me "
2402 "to not replace it")
2403 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
2404 d.addCallback(self.failUnlessIsBarDotTxt)
2407 def test_POST_upload_no_replace_field(self):
2408 d = self.POST(self.public_url + "/foo", t="upload", replace="false",
2409 file=("bar.txt", self.NEWFILE_CONTENTS))
2410 d.addBoth(self.shouldFail, error.Error, "POST_upload_no_replace_field",
2412 "There was already a child by that name, and you asked me "
2413 "to not replace it")
2414 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
2415 d.addCallback(self.failUnlessIsBarDotTxt)
2418 def test_POST_upload_whendone(self):
2419 d = self.POST(self.public_url + "/foo", t="upload", when_done="/THERE",
2420 file=("new.txt", self.NEWFILE_CONTENTS))
2421 d.addBoth(self.shouldRedirect, "/THERE")
2423 d.addCallback(lambda res:
2424 self.failUnlessChildContentsAre(fn, u"new.txt",
2425 self.NEWFILE_CONTENTS))
2428 def test_POST_upload_named(self):
2430 d = self.POST(self.public_url + "/foo", t="upload",
2431 name="new.txt", file=self.NEWFILE_CONTENTS)
2432 d.addCallback(self.failUnlessURIMatchesROChild, fn, u"new.txt")
2433 d.addCallback(lambda res:
2434 self.failUnlessChildContentsAre(fn, u"new.txt",
2435 self.NEWFILE_CONTENTS))
2438 def test_POST_upload_named_badfilename(self):
2439 d = self.POST(self.public_url + "/foo", t="upload",
2440 name="slashes/are/bad.txt", file=self.NEWFILE_CONTENTS)
2441 d.addBoth(self.shouldFail, error.Error,
2442 "test_POST_upload_named_badfilename",
2444 "name= may not contain a slash",
2446 # make sure that nothing was added
2447 d.addCallback(lambda res:
2448 self.failUnlessNodeKeysAre(self._foo_node,
2449 [u"bar.txt", u"baz.txt", u"blockingfile",
2450 u"empty", u"n\u00fc.txt", u"quux.txt",
2454 def test_POST_FILEURL_check(self):
2455 bar_url = self.public_url + "/foo/bar.txt"
2456 d = self.POST(bar_url, t="check")
2458 self.failUnless("Healthy :" in res)
2459 d.addCallback(_check)
2460 redir_url = "http://allmydata.org/TARGET"
2461 def _check2(statuscode, target):
2462 self.failUnlessReallyEqual(statuscode, str(http.FOUND))
2463 self.failUnlessReallyEqual(target, redir_url)
2464 d.addCallback(lambda res:
2465 self.shouldRedirect2("test_POST_FILEURL_check",
2469 when_done=redir_url))
2470 d.addCallback(lambda res:
2471 self.POST(bar_url, t="check", return_to=redir_url))
2473 self.failUnless("Healthy :" in res)
2474 self.failUnless("Return to file" in res)
2475 self.failUnless(redir_url in res)
2476 d.addCallback(_check3)
2478 d.addCallback(lambda res:
2479 self.POST(bar_url, t="check", output="JSON"))
2480 def _check_json(res):
2481 data = simplejson.loads(res)
2482 self.failUnless("storage-index" in data)
2483 self.failUnless(data["results"]["healthy"])
2484 d.addCallback(_check_json)
2488 def test_POST_FILEURL_check_and_repair(self):
2489 bar_url = self.public_url + "/foo/bar.txt"
2490 d = self.POST(bar_url, t="check", repair="true")
2492 self.failUnless("Healthy :" in res)
2493 d.addCallback(_check)
2494 redir_url = "http://allmydata.org/TARGET"
2495 def _check2(statuscode, target):
2496 self.failUnlessReallyEqual(statuscode, str(http.FOUND))
2497 self.failUnlessReallyEqual(target, redir_url)
2498 d.addCallback(lambda res:
2499 self.shouldRedirect2("test_POST_FILEURL_check_and_repair",
2502 t="check", repair="true",
2503 when_done=redir_url))
2504 d.addCallback(lambda res:
2505 self.POST(bar_url, t="check", return_to=redir_url))
2507 self.failUnless("Healthy :" in res)
2508 self.failUnless("Return to file" in res)
2509 self.failUnless(redir_url in res)
2510 d.addCallback(_check3)
2513 def test_POST_DIRURL_check(self):
2514 foo_url = self.public_url + "/foo/"
2515 d = self.POST(foo_url, t="check")
2517 self.failUnless("Healthy :" in res, res)
2518 d.addCallback(_check)
2519 redir_url = "http://allmydata.org/TARGET"
2520 def _check2(statuscode, target):
2521 self.failUnlessReallyEqual(statuscode, str(http.FOUND))
2522 self.failUnlessReallyEqual(target, redir_url)
2523 d.addCallback(lambda res:
2524 self.shouldRedirect2("test_POST_DIRURL_check",
2528 when_done=redir_url))
2529 d.addCallback(lambda res:
2530 self.POST(foo_url, t="check", return_to=redir_url))
2532 self.failUnless("Healthy :" in res, res)
2533 self.failUnless("Return to file/directory" in res)
2534 self.failUnless(redir_url in res)
2535 d.addCallback(_check3)
2537 d.addCallback(lambda res:
2538 self.POST(foo_url, t="check", output="JSON"))
2539 def _check_json(res):
2540 data = simplejson.loads(res)
2541 self.failUnless("storage-index" in data)
2542 self.failUnless(data["results"]["healthy"])
2543 d.addCallback(_check_json)
2547 def test_POST_DIRURL_check_and_repair(self):
2548 foo_url = self.public_url + "/foo/"
2549 d = self.POST(foo_url, t="check", repair="true")
2551 self.failUnless("Healthy :" in res, res)
2552 d.addCallback(_check)
2553 redir_url = "http://allmydata.org/TARGET"
2554 def _check2(statuscode, target):
2555 self.failUnlessReallyEqual(statuscode, str(http.FOUND))
2556 self.failUnlessReallyEqual(target, redir_url)
2557 d.addCallback(lambda res:
2558 self.shouldRedirect2("test_POST_DIRURL_check_and_repair",
2561 t="check", repair="true",
2562 when_done=redir_url))
2563 d.addCallback(lambda res:
2564 self.POST(foo_url, t="check", return_to=redir_url))
2566 self.failUnless("Healthy :" in res)
2567 self.failUnless("Return to file/directory" in res)
2568 self.failUnless(redir_url in res)
2569 d.addCallback(_check3)
2572 def test_POST_FILEURL_mdmf_check(self):
2573 quux_url = "/uri/%s" % urllib.quote(self._quux_txt_uri)
2574 d = self.POST(quux_url, t="check")
2576 self.failUnlessIn("Healthy", res)
2577 d.addCallback(_check)
2578 quux_extension_url = "/uri/%s" % urllib.quote("%s:3:131073" % self._quux_txt_uri)
2579 d.addCallback(lambda ignored:
2580 self.POST(quux_extension_url, t="check"))
2581 d.addCallback(_check)
2584 def test_POST_FILEURL_mdmf_check_and_repair(self):
2585 quux_url = "/uri/%s" % urllib.quote(self._quux_txt_uri)
2586 d = self.POST(quux_url, t="check", repair="true")
2588 self.failUnlessIn("Healthy", res)
2589 d.addCallback(_check)
2590 quux_extension_url = "/uri/%s" %\
2591 urllib.quote("%s:3:131073" % self._quux_txt_uri)
2592 d.addCallback(lambda ignored:
2593 self.POST(quux_extension_url, t="check", repair="true"))
2594 d.addCallback(_check)
2597 def wait_for_operation(self, ignored, ophandle):
2598 url = "/operations/" + ophandle
2599 url += "?t=status&output=JSON"
2602 data = simplejson.loads(res)
2603 if not data["finished"]:
2604 d = self.stall(delay=1.0)
2605 d.addCallback(self.wait_for_operation, ophandle)
2611 def get_operation_results(self, ignored, ophandle, output=None):
2612 url = "/operations/" + ophandle
2615 url += "&output=" + output
2618 if output and output.lower() == "json":
2619 return simplejson.loads(res)
2624 def test_POST_DIRURL_deepcheck_no_ophandle(self):
2625 d = self.shouldFail2(error.Error,
2626 "test_POST_DIRURL_deepcheck_no_ophandle",
2628 "slow operation requires ophandle=",
2629 self.POST, self.public_url, t="start-deep-check")
2632 def test_POST_DIRURL_deepcheck(self):
2633 def _check_redirect(statuscode, target):
2634 self.failUnlessReallyEqual(statuscode, str(http.FOUND))
2635 self.failUnless(target.endswith("/operations/123"))
2636 d = self.shouldRedirect2("test_POST_DIRURL_deepcheck", _check_redirect,
2637 self.POST, self.public_url,
2638 t="start-deep-check", ophandle="123")
2639 d.addCallback(self.wait_for_operation, "123")
2640 def _check_json(data):
2641 self.failUnlessReallyEqual(data["finished"], True)
2642 self.failUnlessReallyEqual(data["count-objects-checked"], 10)
2643 self.failUnlessReallyEqual(data["count-objects-healthy"], 10)
2644 d.addCallback(_check_json)
2645 d.addCallback(self.get_operation_results, "123", "html")
2646 def _check_html(res):
2647 self.failUnless("Objects Checked: <span>10</span>" in res)
2648 self.failUnless("Objects Healthy: <span>10</span>" in res)
2649 d.addCallback(_check_html)
2651 d.addCallback(lambda res:
2652 self.GET("/operations/123/"))
2653 d.addCallback(_check_html) # should be the same as without the slash
2655 d.addCallback(lambda res:
2656 self.shouldFail2(error.Error, "one", "404 Not Found",
2657 "No detailed results for SI bogus",
2658 self.GET, "/operations/123/bogus"))
2660 foo_si = self._foo_node.get_storage_index()
2661 foo_si_s = base32.b2a(foo_si)
2662 d.addCallback(lambda res:
2663 self.GET("/operations/123/%s?output=JSON" % foo_si_s))
2664 def _check_foo_json(res):
2665 data = simplejson.loads(res)
2666 self.failUnlessEqual(data["storage-index"], foo_si_s)
2667 self.failUnless(data["results"]["healthy"])
2668 d.addCallback(_check_foo_json)
2671 def test_POST_DIRURL_deepcheck_and_repair(self):
2672 d = self.POST(self.public_url, t="start-deep-check", repair="true",
2673 ophandle="124", output="json", followRedirect=True)
2674 d.addCallback(self.wait_for_operation, "124")
2675 def _check_json(data):
2676 self.failUnlessReallyEqual(data["finished"], True)
2677 self.failUnlessReallyEqual(data["count-objects-checked"], 10)
2678 self.failUnlessReallyEqual(data["count-objects-healthy-pre-repair"], 10)
2679 self.failUnlessReallyEqual(data["count-objects-unhealthy-pre-repair"], 0)
2680 self.failUnlessReallyEqual(data["count-corrupt-shares-pre-repair"], 0)
2681 self.failUnlessReallyEqual(data["count-repairs-attempted"], 0)
2682 self.failUnlessReallyEqual(data["count-repairs-successful"], 0)
2683 self.failUnlessReallyEqual(data["count-repairs-unsuccessful"], 0)
2684 self.failUnlessReallyEqual(data["count-objects-healthy-post-repair"], 10)
2685 self.failUnlessReallyEqual(data["count-objects-unhealthy-post-repair"], 0)
2686 self.failUnlessReallyEqual(data["count-corrupt-shares-post-repair"], 0)
2687 d.addCallback(_check_json)
2688 d.addCallback(self.get_operation_results, "124", "html")
2689 def _check_html(res):
2690 self.failUnless("Objects Checked: <span>10</span>" in res)
2692 self.failUnless("Objects Healthy (before repair): <span>10</span>" in res)
2693 self.failUnless("Objects Unhealthy (before repair): <span>0</span>" in res)
2694 self.failUnless("Corrupt Shares (before repair): <span>0</span>" in res)
2696 self.failUnless("Repairs Attempted: <span>0</span>" in res)
2697 self.failUnless("Repairs Successful: <span>0</span>" in res)
2698 self.failUnless("Repairs Unsuccessful: <span>0</span>" in res)
2700 self.failUnless("Objects Healthy (after repair): <span>10</span>" in res)
2701 self.failUnless("Objects Unhealthy (after repair): <span>0</span>" in res)
2702 self.failUnless("Corrupt Shares (after repair): <span>0</span>" in res)
2703 d.addCallback(_check_html)
2706 def test_POST_FILEURL_bad_t(self):
2707 d = self.shouldFail2(error.Error, "POST_bad_t", "400 Bad Request",
2708 "POST to file: bad t=bogus",
2709 self.POST, self.public_url + "/foo/bar.txt",
2713 def test_POST_mkdir(self): # return value?
2714 d = self.POST(self.public_url + "/foo", t="mkdir", name="newdir")
2715 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2716 d.addCallback(self.failUnlessNodeKeysAre, [])
2719 def test_POST_mkdir_mdmf(self):
2720 d = self.POST(self.public_url + "/foo?t=mkdir&name=newdir&mutable-type=mdmf")
2721 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2722 d.addCallback(lambda node:
2723 self.failUnlessEqual(node._node.get_version(), MDMF_VERSION))
2726 def test_POST_mkdir_sdmf(self):
2727 d = self.POST(self.public_url + "/foo?t=mkdir&name=newdir&mutable-type=sdmf")
2728 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2729 d.addCallback(lambda node:
2730 self.failUnlessEqual(node._node.get_version(), SDMF_VERSION))
2733 def test_POST_mkdir_bad_mutable_type(self):
2734 return self.shouldHTTPError("test bad mutable type",
2735 400, "Bad Request", "Unknown type: foo",
2736 self.POST, self.public_url + \
2737 "/foo?t=mkdir&name=newdir&mutable-type=foo")
2739 def test_POST_mkdir_initial_children(self):
2740 (newkids, caps) = self._create_initial_children()
2741 d = self.POST2(self.public_url +
2742 "/foo?t=mkdir-with-children&name=newdir",
2743 simplejson.dumps(newkids))
2744 d.addCallback(lambda res:
2745 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
2746 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2747 d.addCallback(self.failUnlessNodeKeysAre, newkids.keys())
2748 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2749 d.addCallback(self.failUnlessROChildURIIs, u"child-imm", caps['filecap1'])
2752 def test_POST_mkdir_initial_children_mdmf(self):
2753 (newkids, caps) = self._create_initial_children()
2754 d = self.POST2(self.public_url +
2755 "/foo?t=mkdir-with-children&name=newdir&mutable-type=mdmf",
2756 simplejson.dumps(newkids))
2757 d.addCallback(lambda res:
2758 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
2759 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2760 d.addCallback(lambda node:
2761 self.failUnlessEqual(node._node.get_version(), MDMF_VERSION))
2762 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2763 d.addCallback(self.failUnlessROChildURIIs, u"child-imm",
2768 def test_POST_mkdir_initial_children_sdmf(self):
2769 (newkids, caps) = self._create_initial_children()
2770 d = self.POST2(self.public_url +
2771 "/foo?t=mkdir-with-children&name=newdir&mutable-type=sdmf",
2772 simplejson.dumps(newkids))
2773 d.addCallback(lambda res:
2774 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
2775 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2776 d.addCallback(lambda node:
2777 self.failUnlessEqual(node._node.get_version(), SDMF_VERSION))
2778 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2779 d.addCallback(self.failUnlessROChildURIIs, u"child-imm",
2783 def test_POST_mkdir_initial_children_bad_mutable_type(self):
2784 (newkids, caps) = self._create_initial_children()
2785 return self.shouldHTTPError("test bad mutable type",
2786 400, "Bad Request", "Unknown type: foo",
2787 self.POST, self.public_url + \
2788 "/foo?t=mkdir-with-children&name=newdir&mutable-type=foo",
2789 simplejson.dumps(newkids))
2791 def test_POST_mkdir_immutable(self):
2792 (newkids, caps) = self._create_immutable_children()
2793 d = self.POST2(self.public_url +
2794 "/foo?t=mkdir-immutable&name=newdir",
2795 simplejson.dumps(newkids))
2796 d.addCallback(lambda res:
2797 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
2798 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2799 d.addCallback(self.failUnlessNodeKeysAre, newkids.keys())
2800 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2801 d.addCallback(self.failUnlessROChildURIIs, u"child-imm", caps['filecap1'])
2802 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2803 d.addCallback(self.failUnlessROChildURIIs, u"unknownchild-imm", caps['unknown_immcap'])
2804 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2805 d.addCallback(self.failUnlessROChildURIIs, u"dirchild-imm", caps['immdircap'])
2806 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2807 d.addCallback(self.failUnlessROChildURIIs, u"dirchild-lit", caps['litdircap'])
2808 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2809 d.addCallback(self.failUnlessROChildURIIs, u"dirchild-empty", caps['emptydircap'])
2812 def test_POST_mkdir_immutable_bad(self):
2813 (newkids, caps) = self._create_initial_children()
2814 d = self.shouldFail2(error.Error, "test_POST_mkdir_immutable_bad",
2816 "needed to be immutable but was not",
2819 "/foo?t=mkdir-immutable&name=newdir",
2820 simplejson.dumps(newkids))
2823 def test_POST_mkdir_2(self):
2824 d = self.POST(self.public_url + "/foo/newdir?t=mkdir", "")
2825 d.addCallback(lambda res:
2826 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
2827 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2828 d.addCallback(self.failUnlessNodeKeysAre, [])
2831 def test_POST_mkdirs_2(self):
2832 d = self.POST(self.public_url + "/foo/bardir/newdir?t=mkdir", "")
2833 d.addCallback(lambda res:
2834 self.failUnlessNodeHasChild(self._foo_node, u"bardir"))
2835 d.addCallback(lambda res: self._foo_node.get(u"bardir"))
2836 d.addCallback(lambda bardirnode: bardirnode.get(u"newdir"))
2837 d.addCallback(self.failUnlessNodeKeysAre, [])
2840 def test_POST_mkdir_no_parentdir_noredirect(self):
2841 d = self.POST("/uri?t=mkdir")
2842 def _after_mkdir(res):
2843 uri.DirectoryURI.init_from_string(res)
2844 d.addCallback(_after_mkdir)
2847 def test_POST_mkdir_no_parentdir_noredirect_mdmf(self):
2848 d = self.POST("/uri?t=mkdir&mutable-type=mdmf")
2849 def _after_mkdir(res):
2850 u = uri.from_string(res)
2851 # Check that this is an MDMF writecap
2852 self.failUnlessIsInstance(u, uri.MDMFDirectoryURI)
2853 d.addCallback(_after_mkdir)
2856 def test_POST_mkdir_no_parentdir_noredirect_sdmf(self):
2857 d = self.POST("/uri?t=mkdir&mutable-type=sdmf")
2858 def _after_mkdir(res):
2859 u = uri.from_string(res)
2860 self.failUnlessIsInstance(u, uri.DirectoryURI)
2861 d.addCallback(_after_mkdir)
2864 def test_POST_mkdir_no_parentdir_noredirect_bad_mutable_type(self):
2865 return self.shouldHTTPError("test bad mutable type",
2866 400, "Bad Request", "Unknown type: foo",
2867 self.POST, self.public_url + \
2868 "/uri?t=mkdir&mutable-type=foo")
2870 def test_POST_mkdir_no_parentdir_noredirect2(self):
2871 # make sure form-based arguments (as on the welcome page) still work
2872 d = self.POST("/uri", t="mkdir")
2873 def _after_mkdir(res):
2874 uri.DirectoryURI.init_from_string(res)
2875 d.addCallback(_after_mkdir)
2876 d.addErrback(self.explain_web_error)
2879 def test_POST_mkdir_no_parentdir_redirect(self):
2880 d = self.POST("/uri?t=mkdir&redirect_to_result=true")
2881 d.addBoth(self.shouldRedirect, None, statuscode='303')
2882 def _check_target(target):
2883 target = urllib.unquote(target)
2884 self.failUnless(target.startswith("uri/URI:DIR2:"), target)
2885 d.addCallback(_check_target)
2888 def test_POST_mkdir_no_parentdir_redirect2(self):
2889 d = self.POST("/uri", t="mkdir", redirect_to_result="true")
2890 d.addBoth(self.shouldRedirect, None, statuscode='303')
2891 def _check_target(target):
2892 target = urllib.unquote(target)
2893 self.failUnless(target.startswith("uri/URI:DIR2:"), target)
2894 d.addCallback(_check_target)
2895 d.addErrback(self.explain_web_error)
2898 def _make_readonly(self, u):
2899 ro_uri = uri.from_string(u).get_readonly()
2902 return ro_uri.to_string()
2904 def _create_initial_children(self):
2905 contents, n, filecap1 = self.makefile(12)
2906 md1 = {"metakey1": "metavalue1"}
2907 filecap2 = make_mutable_file_uri()
2908 node3 = self.s.create_node_from_uri(make_mutable_file_uri())
2909 filecap3 = node3.get_readonly_uri()
2910 node4 = self.s.create_node_from_uri(make_mutable_file_uri())
2911 dircap = DirectoryNode(node4, None, None).get_uri()
2912 mdmfcap = make_mutable_file_uri(mdmf=True)
2913 litdircap = "URI:DIR2-LIT:ge3dumj2mewdcotyfqydulbshj5x2lbm"
2914 emptydircap = "URI:DIR2-LIT:"
2915 newkids = {u"child-imm": ["filenode", {"rw_uri": filecap1,
2916 "ro_uri": self._make_readonly(filecap1),
2917 "metadata": md1, }],
2918 u"child-mutable": ["filenode", {"rw_uri": filecap2,
2919 "ro_uri": self._make_readonly(filecap2)}],
2920 u"child-mutable-ro": ["filenode", {"ro_uri": filecap3}],
2921 u"unknownchild-rw": ["unknown", {"rw_uri": unknown_rwcap,
2922 "ro_uri": unknown_rocap}],
2923 u"unknownchild-ro": ["unknown", {"ro_uri": unknown_rocap}],
2924 u"unknownchild-imm": ["unknown", {"ro_uri": unknown_immcap}],
2925 u"dirchild": ["dirnode", {"rw_uri": dircap,
2926 "ro_uri": self._make_readonly(dircap)}],
2927 u"dirchild-lit": ["dirnode", {"ro_uri": litdircap}],
2928 u"dirchild-empty": ["dirnode", {"ro_uri": emptydircap}],
2929 u"child-mutable-mdmf": ["filenode", {"rw_uri": mdmfcap,
2930 "ro_uri": self._make_readonly(mdmfcap)}],
2932 return newkids, {'filecap1': filecap1,
2933 'filecap2': filecap2,
2934 'filecap3': filecap3,
2935 'unknown_rwcap': unknown_rwcap,
2936 'unknown_rocap': unknown_rocap,
2937 'unknown_immcap': unknown_immcap,
2939 'litdircap': litdircap,
2940 'emptydircap': emptydircap,
2943 def _create_immutable_children(self):
2944 contents, n, filecap1 = self.makefile(12)
2945 md1 = {"metakey1": "metavalue1"}
2946 tnode = create_chk_filenode("immutable directory contents\n"*10)
2947 dnode = DirectoryNode(tnode, None, None)
2948 assert not dnode.is_mutable()
2949 immdircap = dnode.get_uri()
2950 litdircap = "URI:DIR2-LIT:ge3dumj2mewdcotyfqydulbshj5x2lbm"
2951 emptydircap = "URI:DIR2-LIT:"
2952 newkids = {u"child-imm": ["filenode", {"ro_uri": filecap1,
2953 "metadata": md1, }],
2954 u"unknownchild-imm": ["unknown", {"ro_uri": unknown_immcap}],
2955 u"dirchild-imm": ["dirnode", {"ro_uri": immdircap}],
2956 u"dirchild-lit": ["dirnode", {"ro_uri": litdircap}],
2957 u"dirchild-empty": ["dirnode", {"ro_uri": emptydircap}],
2959 return newkids, {'filecap1': filecap1,
2960 'unknown_immcap': unknown_immcap,
2961 'immdircap': immdircap,
2962 'litdircap': litdircap,
2963 'emptydircap': emptydircap}
2965 def test_POST_mkdir_no_parentdir_initial_children(self):
2966 (newkids, caps) = self._create_initial_children()
2967 d = self.POST2("/uri?t=mkdir-with-children", simplejson.dumps(newkids))
2968 def _after_mkdir(res):
2969 self.failUnless(res.startswith("URI:DIR"), res)
2970 n = self.s.create_node_from_uri(res)
2971 d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
2972 d2.addCallback(lambda ign:
2973 self.failUnlessROChildURIIs(n, u"child-imm",
2975 d2.addCallback(lambda ign:
2976 self.failUnlessRWChildURIIs(n, u"child-mutable",
2978 d2.addCallback(lambda ign:
2979 self.failUnlessROChildURIIs(n, u"child-mutable-ro",
2981 d2.addCallback(lambda ign:
2982 self.failUnlessRWChildURIIs(n, u"unknownchild-rw",
2983 caps['unknown_rwcap']))
2984 d2.addCallback(lambda ign:
2985 self.failUnlessROChildURIIs(n, u"unknownchild-ro",
2986 caps['unknown_rocap']))
2987 d2.addCallback(lambda ign:
2988 self.failUnlessROChildURIIs(n, u"unknownchild-imm",
2989 caps['unknown_immcap']))
2990 d2.addCallback(lambda ign:
2991 self.failUnlessRWChildURIIs(n, u"dirchild",
2994 d.addCallback(_after_mkdir)
2997 def test_POST_mkdir_no_parentdir_unexpected_children(self):
2998 # the regular /uri?t=mkdir operation is specified to ignore its body.
2999 # Only t=mkdir-with-children pays attention to it.
3000 (newkids, caps) = self._create_initial_children()
3001 d = self.shouldHTTPError("POST t=mkdir unexpected children",
3003 "t=mkdir does not accept children=, "
3004 "try t=mkdir-with-children instead",
3005 self.POST2, "/uri?t=mkdir", # without children
3006 simplejson.dumps(newkids))
3009 def test_POST_noparent_bad(self):
3010 d = self.shouldHTTPError("POST /uri?t=bogus", 400, "Bad Request",
3011 "/uri accepts only PUT, PUT?t=mkdir, "
3012 "POST?t=upload, and POST?t=mkdir",
3013 self.POST, "/uri?t=bogus")
3016 def test_POST_mkdir_no_parentdir_immutable(self):
3017 (newkids, caps) = self._create_immutable_children()
3018 d = self.POST2("/uri?t=mkdir-immutable", simplejson.dumps(newkids))
3019 def _after_mkdir(res):
3020 self.failUnless(res.startswith("URI:DIR"), res)
3021 n = self.s.create_node_from_uri(res)
3022 d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
3023 d2.addCallback(lambda ign:
3024 self.failUnlessROChildURIIs(n, u"child-imm",
3026 d2.addCallback(lambda ign:
3027 self.failUnlessROChildURIIs(n, u"unknownchild-imm",
3028 caps['unknown_immcap']))
3029 d2.addCallback(lambda ign:
3030 self.failUnlessROChildURIIs(n, u"dirchild-imm",
3032 d2.addCallback(lambda ign:
3033 self.failUnlessROChildURIIs(n, u"dirchild-lit",
3035 d2.addCallback(lambda ign:
3036 self.failUnlessROChildURIIs(n, u"dirchild-empty",
3037 caps['emptydircap']))
3039 d.addCallback(_after_mkdir)
3042 def test_POST_mkdir_no_parentdir_immutable_bad(self):
3043 (newkids, caps) = self._create_initial_children()
3044 d = self.shouldFail2(error.Error,
3045 "test_POST_mkdir_no_parentdir_immutable_bad",
3047 "needed to be immutable but was not",
3049 "/uri?t=mkdir-immutable",
3050 simplejson.dumps(newkids))
3053 def test_welcome_page_mkdir_button(self):
3054 # Fetch the welcome page.
3056 def _after_get_welcome_page(res):
3057 MKDIR_BUTTON_RE = re.compile(
3058 '<form action="([^"]*)" method="post".*?'
3059 '<input type="hidden" name="t" value="([^"]*)" />'
3060 '<input type="hidden" name="([^"]*)" value="([^"]*)" />'
3061 '<input type="submit" value="Create a directory" />',
3063 mo = MKDIR_BUTTON_RE.search(res)
3064 formaction = mo.group(1)
3066 formaname = mo.group(3)
3067 formavalue = mo.group(4)
3068 return (formaction, formt, formaname, formavalue)
3069 d.addCallback(_after_get_welcome_page)
3070 def _after_parse_form(res):
3071 (formaction, formt, formaname, formavalue) = res
3072 return self.POST("/%s?t=%s&%s=%s" % (formaction, formt, formaname, formavalue))
3073 d.addCallback(_after_parse_form)
3074 d.addBoth(self.shouldRedirect, None, statuscode='303')
3077 def test_POST_mkdir_replace(self): # return value?
3078 d = self.POST(self.public_url + "/foo", t="mkdir", name="sub")
3079 d.addCallback(lambda res: self._foo_node.get(u"sub"))
3080 d.addCallback(self.failUnlessNodeKeysAre, [])
3083 def test_POST_mkdir_no_replace_queryarg(self): # return value?
3084 d = self.POST(self.public_url + "/foo?replace=false", t="mkdir", name="sub")
3085 d.addBoth(self.shouldFail, error.Error,
3086 "POST_mkdir_no_replace_queryarg",
3088 "There was already a child by that name, and you asked me "
3089 "to not replace it")
3090 d.addCallback(lambda res: self._foo_node.get(u"sub"))
3091 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
3094 def test_POST_mkdir_no_replace_field(self): # return value?
3095 d = self.POST(self.public_url + "/foo", t="mkdir", name="sub",
3097 d.addBoth(self.shouldFail, error.Error, "POST_mkdir_no_replace_field",
3099 "There was already a child by that name, and you asked me "
3100 "to not replace it")
3101 d.addCallback(lambda res: self._foo_node.get(u"sub"))
3102 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
3105 def test_POST_mkdir_whendone_field(self):
3106 d = self.POST(self.public_url + "/foo",
3107 t="mkdir", name="newdir", when_done="/THERE")
3108 d.addBoth(self.shouldRedirect, "/THERE")
3109 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
3110 d.addCallback(self.failUnlessNodeKeysAre, [])
3113 def test_POST_mkdir_whendone_queryarg(self):
3114 d = self.POST(self.public_url + "/foo?when_done=/THERE",
3115 t="mkdir", name="newdir")
3116 d.addBoth(self.shouldRedirect, "/THERE")
3117 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
3118 d.addCallback(self.failUnlessNodeKeysAre, [])
3121 def test_POST_bad_t(self):
3122 d = self.shouldFail2(error.Error, "POST_bad_t", "400 Bad Request",
3123 "POST to a directory with bad t=BOGUS",
3124 self.POST, self.public_url + "/foo", t="BOGUS")
3127 def test_POST_set_children(self, command_name="set_children"):
3128 contents9, n9, newuri9 = self.makefile(9)
3129 contents10, n10, newuri10 = self.makefile(10)
3130 contents11, n11, newuri11 = self.makefile(11)
3133 "atomic_added_1": [ "filenode", { "rw_uri": "%s",
3136 "ctime": 1002777696.7564139,
3137 "mtime": 1002777696.7564139
3140 "atomic_added_2": [ "filenode", { "rw_uri": "%s",
3143 "ctime": 1002777696.7564139,
3144 "mtime": 1002777696.7564139
3147 "atomic_added_3": [ "filenode", { "rw_uri": "%s",
3150 "ctime": 1002777696.7564139,
3151 "mtime": 1002777696.7564139
3154 }""" % (newuri9, newuri10, newuri11)
3156 url = self.webish_url + self.public_url + "/foo" + "?t=" + command_name
3158 d = client.getPage(url, method="POST", postdata=reqbody)
3160 self.failUnlessURIMatchesROChild(newuri9, self._foo_node, u"atomic_added_1")
3161 self.failUnlessURIMatchesROChild(newuri10, self._foo_node, u"atomic_added_2")
3162 self.failUnlessURIMatchesROChild(newuri11, self._foo_node, u"atomic_added_3")
3164 d.addCallback(_then)
3165 d.addErrback(self.dump_error)
3168 def test_POST_set_children_with_hyphen(self):
3169 return self.test_POST_set_children(command_name="set-children")
3171 def test_POST_link_uri(self):
3172 contents, n, newuri = self.makefile(8)
3173 d = self.POST(self.public_url + "/foo", t="uri", name="new.txt", uri=newuri)
3174 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"new.txt")
3175 d.addCallback(lambda res:
3176 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
3180 def test_POST_link_uri_replace(self):
3181 contents, n, newuri = self.makefile(8)
3182 d = self.POST(self.public_url + "/foo", t="uri", name="bar.txt", uri=newuri)
3183 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"bar.txt")
3184 d.addCallback(lambda res:
3185 self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
3189 def test_POST_link_uri_unknown_bad(self):
3190 d = self.POST(self.public_url + "/foo", t="uri", name="future.txt", uri=unknown_rwcap)
3191 d.addBoth(self.shouldFail, error.Error,
3192 "POST_link_uri_unknown_bad",
3194 "unknown cap in a write slot")
3197 def test_POST_link_uri_unknown_ro_good(self):
3198 d = self.POST(self.public_url + "/foo", t="uri", name="future-ro.txt", uri=unknown_rocap)
3199 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"future-ro.txt")
3202 def test_POST_link_uri_unknown_imm_good(self):
3203 d = self.POST(self.public_url + "/foo", t="uri", name="future-imm.txt", uri=unknown_immcap)
3204 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"future-imm.txt")
3207 def test_POST_link_uri_no_replace_queryarg(self):
3208 contents, n, newuri = self.makefile(8)
3209 d = self.POST(self.public_url + "/foo?replace=false", t="uri",
3210 name="bar.txt", uri=newuri)
3211 d.addBoth(self.shouldFail, error.Error,
3212 "POST_link_uri_no_replace_queryarg",
3214 "There was already a child by that name, and you asked me "
3215 "to not replace it")
3216 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
3217 d.addCallback(self.failUnlessIsBarDotTxt)
3220 def test_POST_link_uri_no_replace_field(self):
3221 contents, n, newuri = self.makefile(8)
3222 d = self.POST(self.public_url + "/foo", t="uri", replace="false",
3223 name="bar.txt", uri=newuri)
3224 d.addBoth(self.shouldFail, error.Error,
3225 "POST_link_uri_no_replace_field",
3227 "There was already a child by that name, and you asked me "
3228 "to not replace it")
3229 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
3230 d.addCallback(self.failUnlessIsBarDotTxt)
3233 def test_POST_delete(self, command_name='delete'):
3234 d = self._foo_node.list()
3235 def _check_before(children):
3236 self.failUnless(u"bar.txt" in children)
3237 d.addCallback(_check_before)
3238 d.addCallback(lambda res: self.POST(self.public_url + "/foo", t=command_name, name="bar.txt"))
3239 d.addCallback(lambda res: self._foo_node.list())
3240 def _check_after(children):
3241 self.failIf(u"bar.txt" in children)
3242 d.addCallback(_check_after)
3245 def test_POST_unlink(self):
3246 return self.test_POST_delete(command_name='unlink')
3248 def test_POST_rename_file(self):
3249 d = self.POST(self.public_url + "/foo", t="rename",
3250 from_name="bar.txt", to_name='wibble.txt')
3251 d.addCallback(lambda res:
3252 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
3253 d.addCallback(lambda res:
3254 self.failUnlessNodeHasChild(self._foo_node, u"wibble.txt"))
3255 d.addCallback(lambda res: self.GET(self.public_url + "/foo/wibble.txt"))
3256 d.addCallback(self.failUnlessIsBarDotTxt)
3257 d.addCallback(lambda res: self.GET(self.public_url + "/foo/wibble.txt?t=json"))
3258 d.addCallback(self.failUnlessIsBarJSON)
3261 def test_POST_rename_file_redundant(self):
3262 d = self.POST(self.public_url + "/foo", t="rename",
3263 from_name="bar.txt", to_name='bar.txt')
3264 d.addCallback(lambda res:
3265 self.failUnlessNodeHasChild(self._foo_node, u"bar.txt"))
3266 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
3267 d.addCallback(self.failUnlessIsBarDotTxt)
3268 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt?t=json"))
3269 d.addCallback(self.failUnlessIsBarJSON)
3272 def test_POST_rename_file_replace(self):
3273 # rename a file and replace a directory with it
3274 d = self.POST(self.public_url + "/foo", t="rename",
3275 from_name="bar.txt", to_name='empty')
3276 d.addCallback(lambda res:
3277 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
3278 d.addCallback(lambda res:
3279 self.failUnlessNodeHasChild(self._foo_node, u"empty"))
3280 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty"))
3281 d.addCallback(self.failUnlessIsBarDotTxt)
3282 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
3283 d.addCallback(self.failUnlessIsBarJSON)
3286 def test_POST_rename_file_no_replace_queryarg(self):
3287 # rename a file and replace a directory with it
3288 d = self.POST(self.public_url + "/foo?replace=false", t="rename",
3289 from_name="bar.txt", to_name='empty')
3290 d.addBoth(self.shouldFail, error.Error,
3291 "POST_rename_file_no_replace_queryarg",
3293 "There was already a child by that name, and you asked me "
3294 "to not replace it")
3295 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
3296 d.addCallback(self.failUnlessIsEmptyJSON)
3299 def test_POST_rename_file_no_replace_field(self):
3300 # rename a file and replace a directory with it
3301 d = self.POST(self.public_url + "/foo", t="rename", replace="false",
3302 from_name="bar.txt", to_name='empty')
3303 d.addBoth(self.shouldFail, error.Error,
3304 "POST_rename_file_no_replace_field",
3306 "There was already a child by that name, and you asked me "
3307 "to not replace it")
3308 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
3309 d.addCallback(self.failUnlessIsEmptyJSON)
3312 def failUnlessIsEmptyJSON(self, res):
3313 data = simplejson.loads(res)
3314 self.failUnlessEqual(data[0], "dirnode", data)
3315 self.failUnlessReallyEqual(len(data[1]["children"]), 0)
3317 def test_POST_rename_file_slash_fail(self):
3318 d = self.POST(self.public_url + "/foo", t="rename",
3319 from_name="bar.txt", to_name='kirk/spock.txt')
3320 d.addBoth(self.shouldFail, error.Error,
3321 "test_POST_rename_file_slash_fail",
3323 "to_name= may not contain a slash",
3325 d.addCallback(lambda res:
3326 self.failUnlessNodeHasChild(self._foo_node, u"bar.txt"))
3329 def test_POST_rename_dir(self):
3330 d = self.POST(self.public_url, t="rename",
3331 from_name="foo", to_name='plunk')
3332 d.addCallback(lambda res:
3333 self.failIfNodeHasChild(self.public_root, u"foo"))
3334 d.addCallback(lambda res:
3335 self.failUnlessNodeHasChild(self.public_root, u"plunk"))
3336 d.addCallback(lambda res: self.GET(self.public_url + "/plunk?t=json"))
3337 d.addCallback(self.failUnlessIsFooJSON)
3340 def shouldRedirect(self, res, target=None, statuscode=None, which=""):
3341 """ If target is not None then the redirection has to go to target. If
3342 statuscode is not None then the redirection has to be accomplished with
3343 that HTTP status code."""
3344 if not isinstance(res, failure.Failure):
3345 to_where = (target is None) and "somewhere" or ("to " + target)
3346 self.fail("%s: we were expecting to get redirected %s, not get an"
3347 " actual page: %s" % (which, to_where, res))
3348 res.trap(error.PageRedirect)
3349 if statuscode is not None:
3350 self.failUnlessReallyEqual(res.value.status, statuscode,
3351 "%s: not a redirect" % which)
3352 if target is not None:
3353 # the PageRedirect does not seem to capture the uri= query arg
3354 # properly, so we can't check for it.
3355 realtarget = self.webish_url + target
3356 self.failUnlessReallyEqual(res.value.location, realtarget,
3357 "%s: wrong target" % which)
3358 return res.value.location
3360 def test_GET_URI_form(self):
3361 base = "/uri?uri=%s" % self._bar_txt_uri
3362 # this is supposed to give us a redirect to /uri/$URI, plus arguments
3363 targetbase = "/uri/%s" % urllib.quote(self._bar_txt_uri)
3365 d.addBoth(self.shouldRedirect, targetbase)
3366 d.addCallback(lambda res: self.GET(base+"&filename=bar.txt"))
3367 d.addBoth(self.shouldRedirect, targetbase+"?filename=bar.txt")
3368 d.addCallback(lambda res: self.GET(base+"&t=json"))
3369 d.addBoth(self.shouldRedirect, targetbase+"?t=json")
3370 d.addCallback(self.log, "about to get file by uri")
3371 d.addCallback(lambda res: self.GET(base, followRedirect=True))
3372 d.addCallback(self.failUnlessIsBarDotTxt)
3373 d.addCallback(self.log, "got file by uri, about to get dir by uri")
3374 d.addCallback(lambda res: self.GET("/uri?uri=%s&t=json" % self._foo_uri,
3375 followRedirect=True))
3376 d.addCallback(self.failUnlessIsFooJSON)
3377 d.addCallback(self.log, "got dir by uri")
3381 def test_GET_URI_form_bad(self):
3382 d = self.shouldFail2(error.Error, "test_GET_URI_form_bad",
3383 "400 Bad Request", "GET /uri requires uri=",
3387 def test_GET_rename_form(self):
3388 d = self.GET(self.public_url + "/foo?t=rename-form&name=bar.txt",
3389 followRedirect=True)
3391 self.failUnless('name="when_done" value="."' in res, res)
3392 self.failUnless(re.search(r'name="from_name" value="bar\.txt"', res))
3393 d.addCallback(_check)
3396 def log(self, res, msg):
3397 #print "MSG: %s RES: %s" % (msg, res)
3401 def test_GET_URI_URL(self):
3402 base = "/uri/%s" % self._bar_txt_uri
3404 d.addCallback(self.failUnlessIsBarDotTxt)
3405 d.addCallback(lambda res: self.GET(base+"?filename=bar.txt"))
3406 d.addCallback(self.failUnlessIsBarDotTxt)
3407 d.addCallback(lambda res: self.GET(base+"?filename=bar.txt&save=true"))
3408 d.addCallback(self.failUnlessIsBarDotTxt)
3411 def test_GET_URI_URL_dir(self):
3412 base = "/uri/%s?t=json" % self._foo_uri
3414 d.addCallback(self.failUnlessIsFooJSON)
3417 def test_GET_URI_URL_missing(self):
3418 base = "/uri/%s" % self._bad_file_uri
3419 d = self.shouldHTTPError("test_GET_URI_URL_missing",
3420 http.GONE, None, "NotEnoughSharesError",
3422 # TODO: how can we exercise both sides of WebDownloadTarget.fail
3423 # here? we must arrange for a download to fail after target.open()
3424 # has been called, and then inspect the response to see that it is
3425 # shorter than we expected.
3428 def test_PUT_DIRURL_uri(self):
3429 d = self.s.create_dirnode()
3431 new_uri = dn.get_uri()
3432 # replace /foo with a new (empty) directory
3433 d = self.PUT(self.public_url + "/foo?t=uri", new_uri)
3434 d.addCallback(lambda res:
3435 self.failUnlessReallyEqual(res.strip(), new_uri))
3436 d.addCallback(lambda res:
3437 self.failUnlessRWChildURIIs(self.public_root,
3441 d.addCallback(_made_dir)
3444 def test_PUT_DIRURL_uri_noreplace(self):
3445 d = self.s.create_dirnode()
3447 new_uri = dn.get_uri()
3448 # replace /foo with a new (empty) directory, but ask that
3449 # replace=false, so it should fail
3450 d = self.shouldFail2(error.Error, "test_PUT_DIRURL_uri_noreplace",
3451 "409 Conflict", "There was already a child by that name, and you asked me to not replace it",
3453 self.public_url + "/foo?t=uri&replace=false",
3455 d.addCallback(lambda res:
3456 self.failUnlessRWChildURIIs(self.public_root,
3460 d.addCallback(_made_dir)
3463 def test_PUT_DIRURL_bad_t(self):
3464 d = self.shouldFail2(error.Error, "test_PUT_DIRURL_bad_t",
3465 "400 Bad Request", "PUT to a directory",
3466 self.PUT, self.public_url + "/foo?t=BOGUS", "")
3467 d.addCallback(lambda res:
3468 self.failUnlessRWChildURIIs(self.public_root,
3473 def test_PUT_NEWFILEURL_uri(self):
3474 contents, n, new_uri = self.makefile(8)
3475 d = self.PUT(self.public_url + "/foo/new.txt?t=uri", new_uri)
3476 d.addCallback(lambda res: self.failUnlessReallyEqual(res.strip(), new_uri))
3477 d.addCallback(lambda res:
3478 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
3482 def test_PUT_NEWFILEURL_mdmf(self):
3483 new_contents = self.NEWFILE_CONTENTS * 300000
3484 d = self.PUT(self.public_url + \
3485 "/foo/mdmf.txt?mutable=true&mutable-type=mdmf",
3487 d.addCallback(lambda ignored:
3488 self.GET(self.public_url + "/foo/mdmf.txt?t=json"))
3489 def _got_json(json):
3490 data = simplejson.loads(json)
3492 self.failUnlessIn("mutable-type", data)
3493 self.failUnlessEqual(data['mutable-type'], "mdmf")
3494 self.failUnless(data['rw_uri'].startswith("URI:MDMF"))
3495 self.failUnless(data['ro_uri'].startswith("URI:MDMF"))
3496 d.addCallback(_got_json)
3499 def test_PUT_NEWFILEURL_sdmf(self):
3500 new_contents = self.NEWFILE_CONTENTS * 300000
3501 d = self.PUT(self.public_url + \
3502 "/foo/sdmf.txt?mutable=true&mutable-type=sdmf",
3504 d.addCallback(lambda ignored:
3505 self.GET(self.public_url + "/foo/sdmf.txt?t=json"))
3506 def _got_json(json):
3507 data = simplejson.loads(json)
3509 self.failUnlessIn("mutable-type", data)
3510 self.failUnlessEqual(data['mutable-type'], "sdmf")
3511 d.addCallback(_got_json)
3514 def test_PUT_NEWFILEURL_bad_mutable_type(self):
3515 new_contents = self.NEWFILE_CONTENTS * 300000
3516 return self.shouldHTTPError("test bad mutable type",
3517 400, "Bad Request", "Unknown type: foo",
3518 self.PUT, self.public_url + \
3519 "/foo/foo.txt?mutable=true&mutable-type=foo",
3522 def test_PUT_NEWFILEURL_uri_replace(self):
3523 contents, n, new_uri = self.makefile(8)
3524 d = self.PUT(self.public_url + "/foo/bar.txt?t=uri", new_uri)
3525 d.addCallback(lambda res: self.failUnlessReallyEqual(res.strip(), new_uri))
3526 d.addCallback(lambda res:
3527 self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
3531 def test_PUT_NEWFILEURL_uri_no_replace(self):
3532 contents, n, new_uri = self.makefile(8)
3533 d = self.PUT(self.public_url + "/foo/bar.txt?t=uri&replace=false", new_uri)
3534 d.addBoth(self.shouldFail, error.Error, "PUT_NEWFILEURL_uri_no_replace",
3536 "There was already a child by that name, and you asked me "
3537 "to not replace it")
3540 def test_PUT_NEWFILEURL_uri_unknown_bad(self):
3541 d = self.PUT(self.public_url + "/foo/put-future.txt?t=uri", unknown_rwcap)
3542 d.addBoth(self.shouldFail, error.Error,
3543 "POST_put_uri_unknown_bad",
3545 "unknown cap in a write slot")
3548 def test_PUT_NEWFILEURL_uri_unknown_ro_good(self):
3549 d = self.PUT(self.public_url + "/foo/put-future-ro.txt?t=uri", unknown_rocap)
3550 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node,
3551 u"put-future-ro.txt")
3554 def test_PUT_NEWFILEURL_uri_unknown_imm_good(self):
3555 d = self.PUT(self.public_url + "/foo/put-future-imm.txt?t=uri", unknown_immcap)
3556 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node,
3557 u"put-future-imm.txt")
3560 def test_PUT_NEWFILE_URI(self):
3561 file_contents = "New file contents here\n"
3562 d = self.PUT("/uri", file_contents)
3564 assert isinstance(uri, str), uri
3565 self.failUnless(uri in FakeCHKFileNode.all_contents)
3566 self.failUnlessReallyEqual(FakeCHKFileNode.all_contents[uri],
3568 return self.GET("/uri/%s" % uri)
3569 d.addCallback(_check)
3571 self.failUnlessReallyEqual(res, file_contents)
3572 d.addCallback(_check2)
3575 def test_PUT_NEWFILE_URI_not_mutable(self):
3576 file_contents = "New file contents here\n"
3577 d = self.PUT("/uri?mutable=false", file_contents)
3579 assert isinstance(uri, str), uri
3580 self.failUnless(uri in FakeCHKFileNode.all_contents)
3581 self.failUnlessReallyEqual(FakeCHKFileNode.all_contents[uri],
3583 return self.GET("/uri/%s" % uri)
3584 d.addCallback(_check)
3586 self.failUnlessReallyEqual(res, file_contents)
3587 d.addCallback(_check2)
3590 def test_PUT_NEWFILE_URI_only_PUT(self):
3591 d = self.PUT("/uri?t=bogus", "")
3592 d.addBoth(self.shouldFail, error.Error,
3593 "PUT_NEWFILE_URI_only_PUT",
3595 "/uri accepts only PUT, PUT?t=mkdir, POST?t=upload, and POST?t=mkdir")
3598 def test_PUT_NEWFILE_URI_mutable(self):
3599 file_contents = "New file contents here\n"
3600 d = self.PUT("/uri?mutable=true", file_contents)
3601 def _check1(filecap):
3602 filecap = filecap.strip()
3603 self.failUnless(filecap.startswith("URI:SSK:"), filecap)
3604 self.filecap = filecap
3605 u = uri.WriteableSSKFileURI.init_from_string(filecap)
3606 self.failUnless(u.get_storage_index() in FakeMutableFileNode.all_contents)
3607 n = self.s.create_node_from_uri(filecap)
3608 return n.download_best_version()
3609 d.addCallback(_check1)
3611 self.failUnlessReallyEqual(data, file_contents)
3612 return self.GET("/uri/%s" % urllib.quote(self.filecap))
3613 d.addCallback(_check2)
3615 self.failUnlessReallyEqual(res, file_contents)
3616 d.addCallback(_check3)
3619 def test_PUT_mkdir(self):
3620 d = self.PUT("/uri?t=mkdir", "")
3622 n = self.s.create_node_from_uri(uri.strip())
3623 d2 = self.failUnlessNodeKeysAre(n, [])
3624 d2.addCallback(lambda res:
3625 self.GET("/uri/%s?t=json" % uri))
3627 d.addCallback(_check)
3628 d.addCallback(self.failUnlessIsEmptyJSON)
3631 def test_PUT_mkdir_mdmf(self):
3632 d = self.PUT("/uri?t=mkdir&mutable-type=mdmf", "")
3634 u = uri.from_string(res)
3635 # Check that this is an MDMF writecap
3636 self.failUnlessIsInstance(u, uri.MDMFDirectoryURI)
3640 def test_PUT_mkdir_sdmf(self):
3641 d = self.PUT("/uri?t=mkdir&mutable-type=sdmf", "")
3643 u = uri.from_string(res)
3644 self.failUnlessIsInstance(u, uri.DirectoryURI)
3648 def test_PUT_mkdir_bad_mutable_type(self):
3649 return self.shouldHTTPError("bad mutable type",
3650 400, "Bad Request", "Unknown type: foo",
3651 self.PUT, "/uri?t=mkdir&mutable-type=foo",
3654 def test_POST_check(self):
3655 d = self.POST(self.public_url + "/foo", t="check", name="bar.txt")
3657 # this returns a string form of the results, which are probably
3658 # None since we're using fake filenodes.
3659 # TODO: verify that the check actually happened, by changing
3660 # FakeCHKFileNode to count how many times .check() has been
3663 d.addCallback(_done)
3667 def test_PUT_update_at_offset(self):
3668 file_contents = "test file" * 100000 # about 900 KiB
3669 d = self.PUT("/uri?mutable=true", file_contents)
3671 self.filecap = filecap
3672 new_data = file_contents[:100]
3673 new = "replaced and so on"
3675 new_data += file_contents[len(new_data):]
3676 assert len(new_data) == len(file_contents)
3677 self.new_data = new_data
3678 d.addCallback(_then)
3679 d.addCallback(lambda ignored:
3680 self.PUT("/uri/%s?replace=True&offset=100" % self.filecap,
3681 "replaced and so on"))
3682 def _get_data(filecap):
3683 n = self.s.create_node_from_uri(filecap)
3684 return n.download_best_version()
3685 d.addCallback(_get_data)
3686 d.addCallback(lambda results:
3687 self.failUnlessEqual(results, self.new_data))
3688 # Now try appending things to the file
3689 d.addCallback(lambda ignored:
3690 self.PUT("/uri/%s?offset=%d" % (self.filecap, len(self.new_data)),
3692 d.addCallback(_get_data)
3693 d.addCallback(lambda results:
3694 self.failUnlessEqual(results, self.new_data + ("puppies" * 100)))
3695 # and try replacing the beginning of the file
3696 d.addCallback(lambda ignored:
3697 self.PUT("/uri/%s?offset=0" % self.filecap, "begin"))
3698 d.addCallback(_get_data)
3699 d.addCallback(lambda results:
3700 self.failUnlessEqual(results, "begin"+self.new_data[len("begin"):]+("puppies"*100)))
3703 def test_PUT_update_at_invalid_offset(self):
3704 file_contents = "test file" * 100000 # about 900 KiB
3705 d = self.PUT("/uri?mutable=true", file_contents)
3707 self.filecap = filecap
3708 d.addCallback(_then)
3709 # Negative offsets should cause an error.
3710 d.addCallback(lambda ignored:
3711 self.shouldHTTPError("test mutable invalid offset negative",
3715 "/uri/%s?offset=-1" % self.filecap,
3719 def test_PUT_update_at_offset_immutable(self):
3720 file_contents = "Test file" * 100000
3721 d = self.PUT("/uri", file_contents)
3723 self.filecap = filecap
3724 d.addCallback(_then)
3725 d.addCallback(lambda ignored:
3726 self.shouldHTTPError("test immutable update",
3730 "/uri/%s?offset=50" % self.filecap,
3735 def test_bad_method(self):
3736 url = self.webish_url + self.public_url + "/foo/bar.txt"
3737 d = self.shouldHTTPError("test_bad_method",
3738 501, "Not Implemented",
3739 "I don't know how to treat a BOGUS request.",
3740 client.getPage, url, method="BOGUS")
3743 def test_short_url(self):
3744 url = self.webish_url + "/uri"
3745 d = self.shouldHTTPError("test_short_url", 501, "Not Implemented",
3746 "I don't know how to treat a DELETE request.",
3747 client.getPage, url, method="DELETE")
3750 def test_ophandle_bad(self):
3751 url = self.webish_url + "/operations/bogus?t=status"
3752 d = self.shouldHTTPError("test_ophandle_bad", 404, "404 Not Found",
3753 "unknown/expired handle 'bogus'",
3754 client.getPage, url)
3757 def test_ophandle_cancel(self):
3758 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=128",
3759 followRedirect=True)
3760 d.addCallback(lambda ignored:
3761 self.GET("/operations/128?t=status&output=JSON"))
3763 data = simplejson.loads(res)
3764 self.failUnless("finished" in data, res)
3765 monitor = self.ws.root.child_operations.handles["128"][0]
3766 d = self.POST("/operations/128?t=cancel&output=JSON")
3768 data = simplejson.loads(res)
3769 self.failUnless("finished" in data, res)
3770 # t=cancel causes the handle to be forgotten
3771 self.failUnless(monitor.is_cancelled())
3772 d.addCallback(_check2)
3774 d.addCallback(_check1)
3775 d.addCallback(lambda ignored:
3776 self.shouldHTTPError("test_ophandle_cancel",
3777 404, "404 Not Found",
3778 "unknown/expired handle '128'",
3780 "/operations/128?t=status&output=JSON"))
3783 def test_ophandle_retainfor(self):
3784 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=129&retain-for=60",
3785 followRedirect=True)
3786 d.addCallback(lambda ignored:
3787 self.GET("/operations/129?t=status&output=JSON&retain-for=0"))
3789 data = simplejson.loads(res)
3790 self.failUnless("finished" in data, res)
3791 d.addCallback(_check1)
3792 # the retain-for=0 will cause the handle to be expired very soon
3793 d.addCallback(lambda ign:
3794 self.clock.advance(2.0))
3795 d.addCallback(lambda ignored:
3796 self.shouldHTTPError("test_ophandle_retainfor",
3797 404, "404 Not Found",
3798 "unknown/expired handle '129'",
3800 "/operations/129?t=status&output=JSON"))
3803 def test_ophandle_release_after_complete(self):
3804 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=130",
3805 followRedirect=True)
3806 d.addCallback(self.wait_for_operation, "130")
3807 d.addCallback(lambda ignored:
3808 self.GET("/operations/130?t=status&output=JSON&release-after-complete=true"))
3809 # the release-after-complete=true will cause the handle to be expired
3810 d.addCallback(lambda ignored:
3811 self.shouldHTTPError("test_ophandle_release_after_complete",
3812 404, "404 Not Found",
3813 "unknown/expired handle '130'",
3815 "/operations/130?t=status&output=JSON"))
3818 def test_uncollected_ophandle_expiration(self):
3819 # uncollected ophandles should expire after 4 days
3820 def _make_uncollected_ophandle(ophandle):
3821 d = self.POST(self.public_url +
3822 "/foo/?t=start-manifest&ophandle=%d" % ophandle,
3823 followRedirect=False)
3824 # When we start the operation, the webapi server will want
3825 # to redirect us to the page for the ophandle, so we get
3826 # confirmation that the operation has started. If the
3827 # manifest operation has finished by the time we get there,
3828 # following that redirect (by setting followRedirect=True
3829 # above) has the side effect of collecting the ophandle that
3830 # we've just created, which means that we can't use the
3831 # ophandle to test the uncollected timeout anymore. So,
3832 # instead, catch the 302 here and don't follow it.
3833 d.addBoth(self.should302, "uncollected_ophandle_creation")
3835 # Create an ophandle, don't collect it, then advance the clock by
3836 # 4 days - 1 second and make sure that the ophandle is still there.
3837 d = _make_uncollected_ophandle(131)
3838 d.addCallback(lambda ign:
3839 self.clock.advance((96*60*60) - 1)) # 96 hours = 4 days
3840 d.addCallback(lambda ign:
3841 self.GET("/operations/131?t=status&output=JSON"))
3843 data = simplejson.loads(res)
3844 self.failUnless("finished" in data, res)
3845 d.addCallback(_check1)
3846 # Create an ophandle, don't collect it, then try to collect it
3847 # after 4 days. It should be gone.
3848 d.addCallback(lambda ign:
3849 _make_uncollected_ophandle(132))
3850 d.addCallback(lambda ign:
3851 self.clock.advance(96*60*60))
3852 d.addCallback(lambda ign:
3853 self.shouldHTTPError("test_uncollected_ophandle_expired_after_100_hours",
3854 404, "404 Not Found",
3855 "unknown/expired handle '132'",
3857 "/operations/132?t=status&output=JSON"))
3860 def test_collected_ophandle_expiration(self):
3861 # collected ophandles should expire after 1 day
3862 def _make_collected_ophandle(ophandle):
3863 d = self.POST(self.public_url +
3864 "/foo/?t=start-manifest&ophandle=%d" % ophandle,
3865 followRedirect=True)
3866 # By following the initial redirect, we collect the ophandle
3867 # we've just created.
3869 # Create a collected ophandle, then collect it after 23 hours
3870 # and 59 seconds to make sure that it is still there.
3871 d = _make_collected_ophandle(133)
3872 d.addCallback(lambda ign:
3873 self.clock.advance((24*60*60) - 1))
3874 d.addCallback(lambda ign:
3875 self.GET("/operations/133?t=status&output=JSON"))
3877 data = simplejson.loads(res)
3878 self.failUnless("finished" in data, res)
3879 d.addCallback(_check1)
3880 # Create another uncollected ophandle, then try to collect it
3881 # after 24 hours to make sure that it is gone.
3882 d.addCallback(lambda ign:
3883 _make_collected_ophandle(134))
3884 d.addCallback(lambda ign:
3885 self.clock.advance(24*60*60))
3886 d.addCallback(lambda ign:
3887 self.shouldHTTPError("test_collected_ophandle_expired_after_1000_minutes",
3888 404, "404 Not Found",
3889 "unknown/expired handle '134'",
3891 "/operations/134?t=status&output=JSON"))
3894 def test_incident(self):
3895 d = self.POST("/report_incident", details="eek")
3897 self.failUnless("Thank you for your report!" in res, res)
3898 d.addCallback(_done)
3901 def test_static(self):
3902 webdir = os.path.join(self.staticdir, "subdir")
3903 fileutil.make_dirs(webdir)
3904 f = open(os.path.join(webdir, "hello.txt"), "wb")
3908 d = self.GET("/static/subdir/hello.txt")
3910 self.failUnlessReallyEqual(res, "hello")
3911 d.addCallback(_check)
3915 class Util(ShouldFailMixin, testutil.ReallyEqualMixin, unittest.TestCase):
3916 def test_load_file(self):
3917 # This will raise an exception unless a well-formed XML file is found under that name.
3918 common.getxmlfile('directory.xhtml').load()
3920 def test_parse_replace_arg(self):
3921 self.failUnlessReallyEqual(common.parse_replace_arg("true"), True)
3922 self.failUnlessReallyEqual(common.parse_replace_arg("false"), False)
3923 self.failUnlessReallyEqual(common.parse_replace_arg("only-files"),
3925 self.shouldFail(AssertionError, "test_parse_replace_arg", "",
3926 common.parse_replace_arg, "only_fles")
3928 def test_abbreviate_time(self):
3929 self.failUnlessReallyEqual(common.abbreviate_time(None), "")
3930 self.failUnlessReallyEqual(common.abbreviate_time(1.234), "1.23s")
3931 self.failUnlessReallyEqual(common.abbreviate_time(0.123), "123ms")
3932 self.failUnlessReallyEqual(common.abbreviate_time(0.00123), "1.2ms")
3933 self.failUnlessReallyEqual(common.abbreviate_time(0.000123), "123us")
3934 self.failUnlessReallyEqual(common.abbreviate_time(-123000), "-123000000000us")
3936 def test_compute_rate(self):
3937 self.failUnlessReallyEqual(common.compute_rate(None, None), None)
3938 self.failUnlessReallyEqual(common.compute_rate(None, 1), None)
3939 self.failUnlessReallyEqual(common.compute_rate(250000, None), None)
3940 self.failUnlessReallyEqual(common.compute_rate(250000, 0), None)
3941 self.failUnlessReallyEqual(common.compute_rate(250000, 10), 25000.0)
3942 self.failUnlessReallyEqual(common.compute_rate(0, 10), 0.0)
3943 self.shouldFail(AssertionError, "test_compute_rate", "",
3944 common.compute_rate, -100, 10)
3945 self.shouldFail(AssertionError, "test_compute_rate", "",
3946 common.compute_rate, 100, -10)
3949 rate = common.compute_rate(10*1000*1000, 1)
3950 self.failUnlessReallyEqual(common.abbreviate_rate(rate), "10.00MBps")
3952 def test_abbreviate_rate(self):
3953 self.failUnlessReallyEqual(common.abbreviate_rate(None), "")
3954 self.failUnlessReallyEqual(common.abbreviate_rate(1234000), "1.23MBps")
3955 self.failUnlessReallyEqual(common.abbreviate_rate(12340), "12.3kBps")
3956 self.failUnlessReallyEqual(common.abbreviate_rate(123), "123Bps")
3958 def test_abbreviate_size(self):
3959 self.failUnlessReallyEqual(common.abbreviate_size(None), "")
3960 self.failUnlessReallyEqual(common.abbreviate_size(1.23*1000*1000*1000), "1.23GB")
3961 self.failUnlessReallyEqual(common.abbreviate_size(1.23*1000*1000), "1.23MB")
3962 self.failUnlessReallyEqual(common.abbreviate_size(1230), "1.2kB")
3963 self.failUnlessReallyEqual(common.abbreviate_size(123), "123B")
3965 def test_plural(self):
3967 return "%d second%s" % (s, status.plural(s))
3968 self.failUnlessReallyEqual(convert(0), "0 seconds")
3969 self.failUnlessReallyEqual(convert(1), "1 second")
3970 self.failUnlessReallyEqual(convert(2), "2 seconds")
3972 return "has share%s: %s" % (status.plural(s), ",".join(s))
3973 self.failUnlessReallyEqual(convert2([]), "has shares: ")
3974 self.failUnlessReallyEqual(convert2(["1"]), "has share: 1")
3975 self.failUnlessReallyEqual(convert2(["1","2"]), "has shares: 1,2")
3978 class Grid(GridTestMixin, WebErrorMixin, ShouldFailMixin, testutil.ReallyEqualMixin, unittest.TestCase):
3980 def CHECK(self, ign, which, args, clientnum=0):
3981 fileurl = self.fileurls[which]
3982 url = fileurl + "?" + args
3983 return self.GET(url, method="POST", clientnum=clientnum)
3985 def test_filecheck(self):
3986 self.basedir = "web/Grid/filecheck"
3988 c0 = self.g.clients[0]
3991 d = c0.upload(upload.Data(DATA, convergence=""))
3992 def _stash_uri(ur, which):
3993 self.uris[which] = ur.uri
3994 d.addCallback(_stash_uri, "good")
3995 d.addCallback(lambda ign:
3996 c0.upload(upload.Data(DATA+"1", convergence="")))
3997 d.addCallback(_stash_uri, "sick")
3998 d.addCallback(lambda ign:
3999 c0.upload(upload.Data(DATA+"2", convergence="")))
4000 d.addCallback(_stash_uri, "dead")
4001 def _stash_mutable_uri(n, which):
4002 self.uris[which] = n.get_uri()
4003 assert isinstance(self.uris[which], str)
4004 d.addCallback(lambda ign:
4005 c0.create_mutable_file(publish.MutableData(DATA+"3")))
4006 d.addCallback(_stash_mutable_uri, "corrupt")
4007 d.addCallback(lambda ign:
4008 c0.upload(upload.Data("literal", convergence="")))
4009 d.addCallback(_stash_uri, "small")
4010 d.addCallback(lambda ign: c0.create_immutable_dirnode({}))
4011 d.addCallback(_stash_mutable_uri, "smalldir")
4013 def _compute_fileurls(ignored):
4015 for which in self.uris:
4016 self.fileurls[which] = "uri/" + urllib.quote(self.uris[which])
4017 d.addCallback(_compute_fileurls)
4019 def _clobber_shares(ignored):
4020 good_shares = self.find_uri_shares(self.uris["good"])
4021 self.failUnlessReallyEqual(len(good_shares), 10)
4022 sick_shares = self.find_uri_shares(self.uris["sick"])
4023 os.unlink(sick_shares[0][2])
4024 dead_shares = self.find_uri_shares(self.uris["dead"])
4025 for i in range(1, 10):
4026 os.unlink(dead_shares[i][2])
4027 c_shares = self.find_uri_shares(self.uris["corrupt"])
4028 cso = CorruptShareOptions()
4029 cso.stdout = StringIO()
4030 cso.parseOptions([c_shares[0][2]])
4032 d.addCallback(_clobber_shares)
4034 d.addCallback(self.CHECK, "good", "t=check")
4035 def _got_html_good(res):
4036 self.failUnless("Healthy" in res, res)
4037 self.failIf("Not Healthy" in res, res)
4038 d.addCallback(_got_html_good)
4039 d.addCallback(self.CHECK, "good", "t=check&return_to=somewhere")
4040 def _got_html_good_return_to(res):
4041 self.failUnless("Healthy" in res, res)
4042 self.failIf("Not Healthy" in res, res)
4043 self.failUnless('<a href="somewhere">Return to file'
4045 d.addCallback(_got_html_good_return_to)
4046 d.addCallback(self.CHECK, "good", "t=check&output=json")
4047 def _got_json_good(res):
4048 r = simplejson.loads(res)
4049 self.failUnlessEqual(r["summary"], "Healthy")
4050 self.failUnless(r["results"]["healthy"])
4051 self.failIf(r["results"]["needs-rebalancing"])
4052 self.failUnless(r["results"]["recoverable"])
4053 d.addCallback(_got_json_good)
4055 d.addCallback(self.CHECK, "small", "t=check")
4056 def _got_html_small(res):
4057 self.failUnless("Literal files are always healthy" in res, res)
4058 self.failIf("Not Healthy" in res, res)
4059 d.addCallback(_got_html_small)
4060 d.addCallback(self.CHECK, "small", "t=check&return_to=somewhere")
4061 def _got_html_small_return_to(res):
4062 self.failUnless("Literal files are always healthy" in res, res)
4063 self.failIf("Not Healthy" in res, res)
4064 self.failUnless('<a href="somewhere">Return to file'
4066 d.addCallback(_got_html_small_return_to)
4067 d.addCallback(self.CHECK, "small", "t=check&output=json")
4068 def _got_json_small(res):
4069 r = simplejson.loads(res)
4070 self.failUnlessEqual(r["storage-index"], "")
4071 self.failUnless(r["results"]["healthy"])
4072 d.addCallback(_got_json_small)
4074 d.addCallback(self.CHECK, "smalldir", "t=check")
4075 def _got_html_smalldir(res):
4076 self.failUnless("Literal files are always healthy" in res, res)
4077 self.failIf("Not Healthy" in res, res)
4078 d.addCallback(_got_html_smalldir)
4079 d.addCallback(self.CHECK, "smalldir", "t=check&output=json")
4080 def _got_json_smalldir(res):
4081 r = simplejson.loads(res)
4082 self.failUnlessEqual(r["storage-index"], "")
4083 self.failUnless(r["results"]["healthy"])
4084 d.addCallback(_got_json_smalldir)
4086 d.addCallback(self.CHECK, "sick", "t=check")
4087 def _got_html_sick(res):
4088 self.failUnless("Not Healthy" in res, res)
4089 d.addCallback(_got_html_sick)
4090 d.addCallback(self.CHECK, "sick", "t=check&output=json")
4091 def _got_json_sick(res):
4092 r = simplejson.loads(res)
4093 self.failUnlessEqual(r["summary"],
4094 "Not Healthy: 9 shares (enc 3-of-10)")
4095 self.failIf(r["results"]["healthy"])
4096 self.failIf(r["results"]["needs-rebalancing"])
4097 self.failUnless(r["results"]["recoverable"])
4098 d.addCallback(_got_json_sick)
4100 d.addCallback(self.CHECK, "dead", "t=check")
4101 def _got_html_dead(res):
4102 self.failUnless("Not Healthy" in res, res)
4103 d.addCallback(_got_html_dead)
4104 d.addCallback(self.CHECK, "dead", "t=check&output=json")
4105 def _got_json_dead(res):
4106 r = simplejson.loads(res)
4107 self.failUnlessEqual(r["summary"],
4108 "Not Healthy: 1 shares (enc 3-of-10)")
4109 self.failIf(r["results"]["healthy"])
4110 self.failIf(r["results"]["needs-rebalancing"])
4111 self.failIf(r["results"]["recoverable"])
4112 d.addCallback(_got_json_dead)
4114 d.addCallback(self.CHECK, "corrupt", "t=check&verify=true")
4115 def _got_html_corrupt(res):
4116 self.failUnless("Not Healthy! : Unhealthy" in res, res)
4117 d.addCallback(_got_html_corrupt)
4118 d.addCallback(self.CHECK, "corrupt", "t=check&verify=true&output=json")
4119 def _got_json_corrupt(res):
4120 r = simplejson.loads(res)
4121 self.failUnless("Unhealthy: 9 shares (enc 3-of-10)" in r["summary"],
4123 self.failIf(r["results"]["healthy"])
4124 self.failUnless(r["results"]["recoverable"])
4125 self.failUnlessReallyEqual(r["results"]["count-shares-good"], 9)
4126 self.failUnlessReallyEqual(r["results"]["count-corrupt-shares"], 1)
4127 d.addCallback(_got_json_corrupt)
4129 d.addErrback(self.explain_web_error)
4132 def test_repair_html(self):
4133 self.basedir = "web/Grid/repair_html"
4135 c0 = self.g.clients[0]
4138 d = c0.upload(upload.Data(DATA, convergence=""))
4139 def _stash_uri(ur, which):
4140 self.uris[which] = ur.uri
4141 d.addCallback(_stash_uri, "good")
4142 d.addCallback(lambda ign:
4143 c0.upload(upload.Data(DATA+"1", convergence="")))
4144 d.addCallback(_stash_uri, "sick")
4145 d.addCallback(lambda ign:
4146 c0.upload(upload.Data(DATA+"2", convergence="")))
4147 d.addCallback(_stash_uri, "dead")
4148 def _stash_mutable_uri(n, which):
4149 self.uris[which] = n.get_uri()
4150 assert isinstance(self.uris[which], str)
4151 d.addCallback(lambda ign:
4152 c0.create_mutable_file(publish.MutableData(DATA+"3")))
4153 d.addCallback(_stash_mutable_uri, "corrupt")
4155 def _compute_fileurls(ignored):
4157 for which in self.uris:
4158 self.fileurls[which] = "uri/" + urllib.quote(self.uris[which])
4159 d.addCallback(_compute_fileurls)
4161 def _clobber_shares(ignored):
4162 good_shares = self.find_uri_shares(self.uris["good"])
4163 self.failUnlessReallyEqual(len(good_shares), 10)
4164 sick_shares = self.find_uri_shares(self.uris["sick"])
4165 os.unlink(sick_shares[0][2])
4166 dead_shares = self.find_uri_shares(self.uris["dead"])
4167 for i in range(1, 10):
4168 os.unlink(dead_shares[i][2])
4169 c_shares = self.find_uri_shares(self.uris["corrupt"])
4170 cso = CorruptShareOptions()
4171 cso.stdout = StringIO()
4172 cso.parseOptions([c_shares[0][2]])
4174 d.addCallback(_clobber_shares)
4176 d.addCallback(self.CHECK, "good", "t=check&repair=true")
4177 def _got_html_good(res):
4178 self.failUnless("Healthy" in res, res)
4179 self.failIf("Not Healthy" in res, res)
4180 self.failUnless("No repair necessary" in res, res)
4181 d.addCallback(_got_html_good)
4183 d.addCallback(self.CHECK, "sick", "t=check&repair=true")
4184 def _got_html_sick(res):
4185 self.failUnless("Healthy : healthy" in res, res)
4186 self.failIf("Not Healthy" in res, res)
4187 self.failUnless("Repair successful" in res, res)
4188 d.addCallback(_got_html_sick)
4190 # repair of a dead file will fail, of course, but it isn't yet
4191 # clear how this should be reported. Right now it shows up as
4194 #d.addCallback(self.CHECK, "dead", "t=check&repair=true")
4195 #def _got_html_dead(res):
4197 # self.failUnless("Healthy : healthy" in res, res)
4198 # self.failIf("Not Healthy" in res, res)
4199 # self.failUnless("No repair necessary" in res, res)
4200 #d.addCallback(_got_html_dead)
4202 d.addCallback(self.CHECK, "corrupt", "t=check&verify=true&repair=true")
4203 def _got_html_corrupt(res):
4204 self.failUnless("Healthy : Healthy" in res, res)
4205 self.failIf("Not Healthy" in res, res)
4206 self.failUnless("Repair successful" in res, res)
4207 d.addCallback(_got_html_corrupt)
4209 d.addErrback(self.explain_web_error)
4212 def test_repair_json(self):
4213 self.basedir = "web/Grid/repair_json"
4215 c0 = self.g.clients[0]
4218 d = c0.upload(upload.Data(DATA+"1", convergence=""))
4219 def _stash_uri(ur, which):
4220 self.uris[which] = ur.uri
4221 d.addCallback(_stash_uri, "sick")
4223 def _compute_fileurls(ignored):
4225 for which in self.uris:
4226 self.fileurls[which] = "uri/" + urllib.quote(self.uris[which])
4227 d.addCallback(_compute_fileurls)
4229 def _clobber_shares(ignored):
4230 sick_shares = self.find_uri_shares(self.uris["sick"])
4231 os.unlink(sick_shares[0][2])
4232 d.addCallback(_clobber_shares)
4234 d.addCallback(self.CHECK, "sick", "t=check&repair=true&output=json")
4235 def _got_json_sick(res):
4236 r = simplejson.loads(res)
4237 self.failUnlessReallyEqual(r["repair-attempted"], True)
4238 self.failUnlessReallyEqual(r["repair-successful"], True)
4239 self.failUnlessEqual(r["pre-repair-results"]["summary"],
4240 "Not Healthy: 9 shares (enc 3-of-10)")
4241 self.failIf(r["pre-repair-results"]["results"]["healthy"])
4242 self.failUnlessEqual(r["post-repair-results"]["summary"], "healthy")
4243 self.failUnless(r["post-repair-results"]["results"]["healthy"])
4244 d.addCallback(_got_json_sick)
4246 d.addErrback(self.explain_web_error)
4249 def test_unknown(self, immutable=False):
4250 self.basedir = "web/Grid/unknown"
4252 self.basedir = "web/Grid/unknown-immutable"
4255 c0 = self.g.clients[0]
4259 # the future cap format may contain slashes, which must be tolerated
4260 expected_info_url = "uri/%s?t=info" % urllib.quote(unknown_rwcap,
4264 name = u"future-imm"
4265 future_node = UnknownNode(None, unknown_immcap, deep_immutable=True)
4266 d = c0.create_immutable_dirnode({name: (future_node, {})})
4269 future_node = UnknownNode(unknown_rwcap, unknown_rocap)
4270 d = c0.create_dirnode()
4272 def _stash_root_and_create_file(n):
4274 self.rooturl = "uri/" + urllib.quote(n.get_uri()) + "/"
4275 self.rourl = "uri/" + urllib.quote(n.get_readonly_uri()) + "/"
4277 return self.rootnode.set_node(name, future_node)
4278 d.addCallback(_stash_root_and_create_file)
4280 # make sure directory listing tolerates unknown nodes
4281 d.addCallback(lambda ign: self.GET(self.rooturl))
4282 def _check_directory_html(res, expected_type_suffix):
4283 pattern = re.compile(r'<td>\?%s</td>[ \t\n\r]*'
4284 '<td>%s</td>' % (expected_type_suffix, str(name)),
4286 self.failUnless(re.search(pattern, res), res)
4287 # find the More Info link for name, should be relative
4288 mo = re.search(r'<a href="([^"]+)">More Info</a>', res)
4289 info_url = mo.group(1)
4290 self.failUnlessReallyEqual(info_url, "%s?t=info" % (str(name),))
4292 d.addCallback(_check_directory_html, "-IMM")
4294 d.addCallback(_check_directory_html, "")
4296 d.addCallback(lambda ign: self.GET(self.rooturl+"?t=json"))
4297 def _check_directory_json(res, expect_rw_uri):
4298 data = simplejson.loads(res)
4299 self.failUnlessEqual(data[0], "dirnode")
4300 f = data[1]["children"][name]
4301 self.failUnlessEqual(f[0], "unknown")
4303 self.failUnlessReallyEqual(to_str(f[1]["rw_uri"]), unknown_rwcap, data)
4305 self.failIfIn("rw_uri", f[1])
4307 self.failUnlessReallyEqual(to_str(f[1]["ro_uri"]), unknown_immcap, data)
4309 self.failUnlessReallyEqual(to_str(f[1]["ro_uri"]), unknown_rocap, data)
4310 self.failUnless("metadata" in f[1])
4311 d.addCallback(_check_directory_json, expect_rw_uri=not immutable)
4313 def _check_info(res, expect_rw_uri, expect_ro_uri):
4314 self.failUnlessIn("Object Type: <span>unknown</span>", res)
4316 self.failUnlessIn(unknown_rwcap, res)
4319 self.failUnlessIn(unknown_immcap, res)
4321 self.failUnlessIn(unknown_rocap, res)
4323 self.failIfIn(unknown_rocap, res)
4324 self.failIfIn("Raw data as", res)
4325 self.failIfIn("Directory writecap", res)
4326 self.failIfIn("Checker Operations", res)
4327 self.failIfIn("Mutable File Operations", res)
4328 self.failIfIn("Directory Operations", res)
4330 # FIXME: these should have expect_rw_uri=not immutable; I don't know
4331 # why they fail. Possibly related to ticket #922.
4333 d.addCallback(lambda ign: self.GET(expected_info_url))
4334 d.addCallback(_check_info, expect_rw_uri=False, expect_ro_uri=False)
4335 d.addCallback(lambda ign: self.GET("%s%s?t=info" % (self.rooturl, str(name))))
4336 d.addCallback(_check_info, expect_rw_uri=False, expect_ro_uri=True)
4338 def _check_json(res, expect_rw_uri):
4339 data = simplejson.loads(res)
4340 self.failUnlessEqual(data[0], "unknown")
4342 self.failUnlessReallyEqual(to_str(data[1]["rw_uri"]), unknown_rwcap, data)
4344 self.failIfIn("rw_uri", data[1])
4347 self.failUnlessReallyEqual(to_str(data[1]["ro_uri"]), unknown_immcap, data)
4348 self.failUnlessReallyEqual(data[1]["mutable"], False)
4350 self.failUnlessReallyEqual(to_str(data[1]["ro_uri"]), unknown_rocap, data)
4351 self.failUnlessReallyEqual(data[1]["mutable"], True)
4353 self.failUnlessReallyEqual(to_str(data[1]["ro_uri"]), unknown_rocap, data)
4354 self.failIf("mutable" in data[1], data[1])
4356 # TODO: check metadata contents
4357 self.failUnless("metadata" in data[1])
4359 d.addCallback(lambda ign: self.GET("%s%s?t=json" % (self.rooturl, str(name))))
4360 d.addCallback(_check_json, expect_rw_uri=not immutable)
4362 # and make sure that a read-only version of the directory can be
4363 # rendered too. This version will not have unknown_rwcap, whether
4364 # or not future_node was immutable.
4365 d.addCallback(lambda ign: self.GET(self.rourl))
4367 d.addCallback(_check_directory_html, "-IMM")
4369 d.addCallback(_check_directory_html, "-RO")
4371 d.addCallback(lambda ign: self.GET(self.rourl+"?t=json"))
4372 d.addCallback(_check_directory_json, expect_rw_uri=False)
4374 d.addCallback(lambda ign: self.GET("%s%s?t=json" % (self.rourl, str(name))))
4375 d.addCallback(_check_json, expect_rw_uri=False)
4377 # TODO: check that getting t=info from the Info link in the ro directory
4378 # works, and does not include the writecap URI.
4381 def test_immutable_unknown(self):
4382 return self.test_unknown(immutable=True)
4384 def test_mutant_dirnodes_are_omitted(self):
4385 self.basedir = "web/Grid/mutant_dirnodes_are_omitted"
4388 c = self.g.clients[0]
4393 lonely_uri = "URI:LIT:n5xgk" # LIT for "one"
4394 mut_write_uri = "URI:SSK:vfvcbdfbszyrsaxchgevhmmlii:euw4iw7bbnkrrwpzuburbhppuxhc3gwxv26f6imekhz7zyw2ojnq"
4395 mut_read_uri = "URI:SSK-RO:e3mdrzfwhoq42hy5ubcz6rp3o4:ybyibhnp3vvwuq2vaw2ckjmesgkklfs6ghxleztqidihjyofgw7q"
4397 # This method tests mainly dirnode, but we'd have to duplicate code in order to
4398 # test the dirnode and web layers separately.
4400 # 'lonely' is a valid LIT child, 'ro' is a mutant child with an SSK-RO readcap,
4401 # and 'write-in-ro' is a mutant child with an SSK writecap in the ro_uri field.
4402 # When the directory is read, the mutants should be silently disposed of, leaving
4403 # their lonely sibling.
4404 # We don't test the case of a retrieving a cap from the encrypted rw_uri field,
4405 # because immutable directories don't have a writecap and therefore that field
4406 # isn't (and can't be) decrypted.
4407 # TODO: The field still exists in the netstring. Technically we should check what
4408 # happens if something is put there (_unpack_contents should raise ValueError),
4409 # but that can wait.
4411 lonely_child = nm.create_from_cap(lonely_uri)
4412 mutant_ro_child = nm.create_from_cap(mut_read_uri)
4413 mutant_write_in_ro_child = nm.create_from_cap(mut_write_uri)
4415 def _by_hook_or_by_crook():
4417 for n in [mutant_ro_child, mutant_write_in_ro_child]:
4418 n.is_allowed_in_immutable_directory = _by_hook_or_by_crook
4420 mutant_write_in_ro_child.get_write_uri = lambda: None
4421 mutant_write_in_ro_child.get_readonly_uri = lambda: mut_write_uri
4423 kids = {u"lonely": (lonely_child, {}),
4424 u"ro": (mutant_ro_child, {}),
4425 u"write-in-ro": (mutant_write_in_ro_child, {}),
4427 d = c.create_immutable_dirnode(kids)
4430 self.failUnless(isinstance(dn, dirnode.DirectoryNode))
4431 self.failIf(dn.is_mutable())
4432 self.failUnless(dn.is_readonly())
4433 # This checks that if we somehow ended up calling dn._decrypt_rwcapdata, it would fail.
4434 self.failIf(hasattr(dn._node, 'get_writekey'))
4436 self.failUnless("RO-IMM" in rep)
4438 self.failUnlessIn("CHK", cap.to_string())
4441 self.rooturl = "uri/" + urllib.quote(dn.get_uri()) + "/"
4442 return download_to_data(dn._node)
4443 d.addCallback(_created)
4445 def _check_data(data):
4446 # Decode the netstring representation of the directory to check that all children
4447 # are present. This is a bit of an abstraction violation, but there's not really
4448 # any other way to do it given that the real DirectoryNode._unpack_contents would
4449 # strip the mutant children out (which is what we're trying to test, later).
4452 while position < len(data):
4453 entries, position = split_netstring(data, 1, position)
4455 (name_utf8, ro_uri, rwcapdata, metadata_s), subpos = split_netstring(entry, 4)
4456 name = name_utf8.decode("utf-8")
4457 self.failUnless(rwcapdata == "")
4458 self.failUnless(name in kids)
4459 (expected_child, ign) = kids[name]
4460 self.failUnlessReallyEqual(ro_uri, expected_child.get_readonly_uri())
4463 self.failUnlessReallyEqual(numkids, 3)
4464 return self.rootnode.list()
4465 d.addCallback(_check_data)
4467 # Now when we use the real directory listing code, the mutants should be absent.
4468 def _check_kids(children):
4469 self.failUnlessReallyEqual(sorted(children.keys()), [u"lonely"])
4470 lonely_node, lonely_metadata = children[u"lonely"]
4472 self.failUnlessReallyEqual(lonely_node.get_write_uri(), None)
4473 self.failUnlessReallyEqual(lonely_node.get_readonly_uri(), lonely_uri)
4474 d.addCallback(_check_kids)
4476 d.addCallback(lambda ign: nm.create_from_cap(self.cap.to_string()))
4477 d.addCallback(lambda n: n.list())
4478 d.addCallback(_check_kids) # again with dirnode recreated from cap
4480 # Make sure the lonely child can be listed in HTML...
4481 d.addCallback(lambda ign: self.GET(self.rooturl))
4482 def _check_html(res):
4483 self.failIfIn("URI:SSK", res)
4484 get_lonely = "".join([r'<td>FILE</td>',
4486 r'<a href="[^"]+%s[^"]+">lonely</a>' % (urllib.quote(lonely_uri),),
4488 r'\s+<td align="right">%d</td>' % len("one"),
4490 self.failUnless(re.search(get_lonely, res), res)
4492 # find the More Info link for name, should be relative
4493 mo = re.search(r'<a href="([^"]+)">More Info</a>', res)
4494 info_url = mo.group(1)
4495 self.failUnless(info_url.endswith(urllib.quote(lonely_uri) + "?t=info"), info_url)
4496 d.addCallback(_check_html)
4499 d.addCallback(lambda ign: self.GET(self.rooturl+"?t=json"))
4500 def _check_json(res):
4501 data = simplejson.loads(res)
4502 self.failUnlessEqual(data[0], "dirnode")
4503 listed_children = data[1]["children"]
4504 self.failUnlessReallyEqual(sorted(listed_children.keys()), [u"lonely"])
4505 ll_type, ll_data = listed_children[u"lonely"]
4506 self.failUnlessEqual(ll_type, "filenode")
4507 self.failIf("rw_uri" in ll_data)
4508 self.failUnlessReallyEqual(to_str(ll_data["ro_uri"]), lonely_uri)
4509 d.addCallback(_check_json)
4512 def test_deep_check(self):
4513 self.basedir = "web/Grid/deep_check"
4515 c0 = self.g.clients[0]
4519 d = c0.create_dirnode()
4520 def _stash_root_and_create_file(n):
4522 self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri()) + "/"
4523 return n.add_file(u"good", upload.Data(DATA, convergence=""))
4524 d.addCallback(_stash_root_and_create_file)
4525 def _stash_uri(fn, which):
4526 self.uris[which] = fn.get_uri()
4528 d.addCallback(_stash_uri, "good")
4529 d.addCallback(lambda ign:
4530 self.rootnode.add_file(u"small",
4531 upload.Data("literal",
4533 d.addCallback(_stash_uri, "small")
4534 d.addCallback(lambda ign:
4535 self.rootnode.add_file(u"sick",
4536 upload.Data(DATA+"1",
4538 d.addCallback(_stash_uri, "sick")
4540 # this tests that deep-check and stream-manifest will ignore
4541 # UnknownNode instances. Hopefully this will also cover deep-stats.
4542 future_node = UnknownNode(unknown_rwcap, unknown_rocap)
4543 d.addCallback(lambda ign: self.rootnode.set_node(u"future", future_node))
4545 def _clobber_shares(ignored):
4546 self.delete_shares_numbered(self.uris["sick"], [0,1])
4547 d.addCallback(_clobber_shares)
4555 d.addCallback(self.CHECK, "root", "t=stream-deep-check")
4558 units = [simplejson.loads(line)
4559 for line in res.splitlines()
4562 print "response is:", res
4563 print "undecodeable line was '%s'" % line
4565 self.failUnlessReallyEqual(len(units), 5+1)
4566 # should be parent-first
4568 self.failUnlessEqual(u0["path"], [])
4569 self.failUnlessEqual(u0["type"], "directory")
4570 self.failUnlessReallyEqual(to_str(u0["cap"]), self.rootnode.get_uri())
4571 u0cr = u0["check-results"]
4572 self.failUnlessReallyEqual(u0cr["results"]["count-shares-good"], 10)
4574 ugood = [u for u in units
4575 if u["type"] == "file" and u["path"] == [u"good"]][0]
4576 self.failUnlessReallyEqual(to_str(ugood["cap"]), self.uris["good"])
4577 ugoodcr = ugood["check-results"]
4578 self.failUnlessReallyEqual(ugoodcr["results"]["count-shares-good"], 10)
4581 self.failUnlessEqual(stats["type"], "stats")
4583 self.failUnlessReallyEqual(s["count-immutable-files"], 2)
4584 self.failUnlessReallyEqual(s["count-literal-files"], 1)
4585 self.failUnlessReallyEqual(s["count-directories"], 1)
4586 self.failUnlessReallyEqual(s["count-unknown"], 1)
4587 d.addCallback(_done)
4589 d.addCallback(self.CHECK, "root", "t=stream-manifest")
4590 def _check_manifest(res):
4591 self.failUnless(res.endswith("\n"))
4592 units = [simplejson.loads(t) for t in res[:-1].split("\n")]
4593 self.failUnlessReallyEqual(len(units), 5+1)
4594 self.failUnlessEqual(units[-1]["type"], "stats")
4596 self.failUnlessEqual(first["path"], [])
4597 self.failUnlessEqual(to_str(first["cap"]), self.rootnode.get_uri())
4598 self.failUnlessEqual(first["type"], "directory")
4599 stats = units[-1]["stats"]
4600 self.failUnlessReallyEqual(stats["count-immutable-files"], 2)
4601 self.failUnlessReallyEqual(stats["count-literal-files"], 1)
4602 self.failUnlessReallyEqual(stats["count-mutable-files"], 0)
4603 self.failUnlessReallyEqual(stats["count-immutable-files"], 2)
4604 self.failUnlessReallyEqual(stats["count-unknown"], 1)
4605 d.addCallback(_check_manifest)
4607 # now add root/subdir and root/subdir/grandchild, then make subdir
4608 # unrecoverable, then see what happens
4610 d.addCallback(lambda ign:
4611 self.rootnode.create_subdirectory(u"subdir"))
4612 d.addCallback(_stash_uri, "subdir")
4613 d.addCallback(lambda subdir_node:
4614 subdir_node.add_file(u"grandchild",
4615 upload.Data(DATA+"2",
4617 d.addCallback(_stash_uri, "grandchild")
4619 d.addCallback(lambda ign:
4620 self.delete_shares_numbered(self.uris["subdir"],
4628 # root/subdir [unrecoverable]
4629 # root/subdir/grandchild
4631 # how should a streaming-JSON API indicate fatal error?
4632 # answer: emit ERROR: instead of a JSON string
4634 d.addCallback(self.CHECK, "root", "t=stream-manifest")
4635 def _check_broken_manifest(res):
4636 lines = res.splitlines()
4638 for (i,line) in enumerate(lines)
4639 if line.startswith("ERROR:")]
4641 self.fail("no ERROR: in output: %s" % (res,))
4642 first_error = error_lines[0]
4643 error_line = lines[first_error]
4644 error_msg = lines[first_error+1:]
4645 error_msg_s = "\n".join(error_msg) + "\n"
4646 self.failUnlessIn("ERROR: UnrecoverableFileError(no recoverable versions)",
4648 self.failUnless(len(error_msg) > 2, error_msg_s) # some traceback
4649 units = [simplejson.loads(line) for line in lines[:first_error]]
4650 self.failUnlessReallyEqual(len(units), 6) # includes subdir
4651 last_unit = units[-1]
4652 self.failUnlessEqual(last_unit["path"], ["subdir"])
4653 d.addCallback(_check_broken_manifest)
4655 d.addCallback(self.CHECK, "root", "t=stream-deep-check")
4656 def _check_broken_deepcheck(res):
4657 lines = res.splitlines()
4659 for (i,line) in enumerate(lines)
4660 if line.startswith("ERROR:")]
4662 self.fail("no ERROR: in output: %s" % (res,))
4663 first_error = error_lines[0]
4664 error_line = lines[first_error]
4665 error_msg = lines[first_error+1:]
4666 error_msg_s = "\n".join(error_msg) + "\n"
4667 self.failUnlessIn("ERROR: UnrecoverableFileError(no recoverable versions)",
4669 self.failUnless(len(error_msg) > 2, error_msg_s) # some traceback
4670 units = [simplejson.loads(line) for line in lines[:first_error]]
4671 self.failUnlessReallyEqual(len(units), 6) # includes subdir
4672 last_unit = units[-1]
4673 self.failUnlessEqual(last_unit["path"], ["subdir"])
4674 r = last_unit["check-results"]["results"]
4675 self.failUnlessReallyEqual(r["count-recoverable-versions"], 0)
4676 self.failUnlessReallyEqual(r["count-shares-good"], 1)
4677 self.failUnlessReallyEqual(r["recoverable"], False)
4678 d.addCallback(_check_broken_deepcheck)
4680 d.addErrback(self.explain_web_error)
4683 def test_deep_check_and_repair(self):
4684 self.basedir = "web/Grid/deep_check_and_repair"
4686 c0 = self.g.clients[0]
4690 d = c0.create_dirnode()
4691 def _stash_root_and_create_file(n):
4693 self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri()) + "/"
4694 return n.add_file(u"good", upload.Data(DATA, convergence=""))
4695 d.addCallback(_stash_root_and_create_file)
4696 def _stash_uri(fn, which):
4697 self.uris[which] = fn.get_uri()
4698 d.addCallback(_stash_uri, "good")
4699 d.addCallback(lambda ign:
4700 self.rootnode.add_file(u"small",
4701 upload.Data("literal",
4703 d.addCallback(_stash_uri, "small")
4704 d.addCallback(lambda ign:
4705 self.rootnode.add_file(u"sick",
4706 upload.Data(DATA+"1",
4708 d.addCallback(_stash_uri, "sick")
4709 #d.addCallback(lambda ign:
4710 # self.rootnode.add_file(u"dead",
4711 # upload.Data(DATA+"2",
4713 #d.addCallback(_stash_uri, "dead")
4715 #d.addCallback(lambda ign: c0.create_mutable_file("mutable"))
4716 #d.addCallback(lambda fn: self.rootnode.set_node(u"corrupt", fn))
4717 #d.addCallback(_stash_uri, "corrupt")
4719 def _clobber_shares(ignored):
4720 good_shares = self.find_uri_shares(self.uris["good"])
4721 self.failUnlessReallyEqual(len(good_shares), 10)
4722 sick_shares = self.find_uri_shares(self.uris["sick"])
4723 os.unlink(sick_shares[0][2])
4724 #dead_shares = self.find_uri_shares(self.uris["dead"])
4725 #for i in range(1, 10):
4726 # os.unlink(dead_shares[i][2])
4728 #c_shares = self.find_uri_shares(self.uris["corrupt"])
4729 #cso = CorruptShareOptions()
4730 #cso.stdout = StringIO()
4731 #cso.parseOptions([c_shares[0][2]])
4733 d.addCallback(_clobber_shares)
4736 # root/good CHK, 10 shares
4738 # root/sick CHK, 9 shares
4740 d.addCallback(self.CHECK, "root", "t=stream-deep-check&repair=true")
4742 units = [simplejson.loads(line)
4743 for line in res.splitlines()
4745 self.failUnlessReallyEqual(len(units), 4+1)
4746 # should be parent-first
4748 self.failUnlessEqual(u0["path"], [])
4749 self.failUnlessEqual(u0["type"], "directory")
4750 self.failUnlessReallyEqual(to_str(u0["cap"]), self.rootnode.get_uri())
4751 u0crr = u0["check-and-repair-results"]
4752 self.failUnlessReallyEqual(u0crr["repair-attempted"], False)
4753 self.failUnlessReallyEqual(u0crr["pre-repair-results"]["results"]["count-shares-good"], 10)
4755 ugood = [u for u in units
4756 if u["type"] == "file" and u["path"] == [u"good"]][0]
4757 self.failUnlessEqual(to_str(ugood["cap"]), self.uris["good"])
4758 ugoodcrr = ugood["check-and-repair-results"]
4759 self.failUnlessReallyEqual(ugoodcrr["repair-attempted"], False)
4760 self.failUnlessReallyEqual(ugoodcrr["pre-repair-results"]["results"]["count-shares-good"], 10)
4762 usick = [u for u in units
4763 if u["type"] == "file" and u["path"] == [u"sick"]][0]
4764 self.failUnlessReallyEqual(to_str(usick["cap"]), self.uris["sick"])
4765 usickcrr = usick["check-and-repair-results"]
4766 self.failUnlessReallyEqual(usickcrr["repair-attempted"], True)
4767 self.failUnlessReallyEqual(usickcrr["repair-successful"], True)
4768 self.failUnlessReallyEqual(usickcrr["pre-repair-results"]["results"]["count-shares-good"], 9)
4769 self.failUnlessReallyEqual(usickcrr["post-repair-results"]["results"]["count-shares-good"], 10)
4772 self.failUnlessEqual(stats["type"], "stats")
4774 self.failUnlessReallyEqual(s["count-immutable-files"], 2)
4775 self.failUnlessReallyEqual(s["count-literal-files"], 1)
4776 self.failUnlessReallyEqual(s["count-directories"], 1)
4777 d.addCallback(_done)
4779 d.addErrback(self.explain_web_error)
4782 def _count_leases(self, ignored, which):
4783 u = self.uris[which]
4784 shares = self.find_uri_shares(u)
4786 for shnum, serverid, fn in shares:
4787 sf = get_share_file(fn)
4788 num_leases = len(list(sf.get_leases()))
4789 lease_counts.append( (fn, num_leases) )
4792 def _assert_leasecount(self, lease_counts, expected):
4793 for (fn, num_leases) in lease_counts:
4794 if num_leases != expected:
4795 self.fail("expected %d leases, have %d, on %s" %
4796 (expected, num_leases, fn))
4798 def test_add_lease(self):
4799 self.basedir = "web/Grid/add_lease"
4800 self.set_up_grid(num_clients=2)
4801 c0 = self.g.clients[0]
4804 d = c0.upload(upload.Data(DATA, convergence=""))
4805 def _stash_uri(ur, which):
4806 self.uris[which] = ur.uri
4807 d.addCallback(_stash_uri, "one")
4808 d.addCallback(lambda ign:
4809 c0.upload(upload.Data(DATA+"1", convergence="")))
4810 d.addCallback(_stash_uri, "two")
4811 def _stash_mutable_uri(n, which):
4812 self.uris[which] = n.get_uri()
4813 assert isinstance(self.uris[which], str)
4814 d.addCallback(lambda ign:
4815 c0.create_mutable_file(publish.MutableData(DATA+"2")))
4816 d.addCallback(_stash_mutable_uri, "mutable")
4818 def _compute_fileurls(ignored):
4820 for which in self.uris:
4821 self.fileurls[which] = "uri/" + urllib.quote(self.uris[which])
4822 d.addCallback(_compute_fileurls)
4824 d.addCallback(self._count_leases, "one")
4825 d.addCallback(self._assert_leasecount, 1)
4826 d.addCallback(self._count_leases, "two")
4827 d.addCallback(self._assert_leasecount, 1)
4828 d.addCallback(self._count_leases, "mutable")
4829 d.addCallback(self._assert_leasecount, 1)
4831 d.addCallback(self.CHECK, "one", "t=check") # no add-lease
4832 def _got_html_good(res):
4833 self.failUnless("Healthy" in res, res)
4834 self.failIf("Not Healthy" in res, res)
4835 d.addCallback(_got_html_good)
4837 d.addCallback(self._count_leases, "one")
4838 d.addCallback(self._assert_leasecount, 1)
4839 d.addCallback(self._count_leases, "two")
4840 d.addCallback(self._assert_leasecount, 1)
4841 d.addCallback(self._count_leases, "mutable")
4842 d.addCallback(self._assert_leasecount, 1)
4844 # this CHECK uses the original client, which uses the same
4845 # lease-secrets, so it will just renew the original lease
4846 d.addCallback(self.CHECK, "one", "t=check&add-lease=true")
4847 d.addCallback(_got_html_good)
4849 d.addCallback(self._count_leases, "one")
4850 d.addCallback(self._assert_leasecount, 1)
4851 d.addCallback(self._count_leases, "two")
4852 d.addCallback(self._assert_leasecount, 1)
4853 d.addCallback(self._count_leases, "mutable")
4854 d.addCallback(self._assert_leasecount, 1)
4856 # this CHECK uses an alternate client, which adds a second lease
4857 d.addCallback(self.CHECK, "one", "t=check&add-lease=true", clientnum=1)
4858 d.addCallback(_got_html_good)
4860 d.addCallback(self._count_leases, "one")
4861 d.addCallback(self._assert_leasecount, 2)
4862 d.addCallback(self._count_leases, "two")
4863 d.addCallback(self._assert_leasecount, 1)
4864 d.addCallback(self._count_leases, "mutable")
4865 d.addCallback(self._assert_leasecount, 1)
4867 d.addCallback(self.CHECK, "mutable", "t=check&add-lease=true")
4868 d.addCallback(_got_html_good)
4870 d.addCallback(self._count_leases, "one")
4871 d.addCallback(self._assert_leasecount, 2)
4872 d.addCallback(self._count_leases, "two")
4873 d.addCallback(self._assert_leasecount, 1)
4874 d.addCallback(self._count_leases, "mutable")
4875 d.addCallback(self._assert_leasecount, 1)
4877 d.addCallback(self.CHECK, "mutable", "t=check&add-lease=true",
4879 d.addCallback(_got_html_good)
4881 d.addCallback(self._count_leases, "one")
4882 d.addCallback(self._assert_leasecount, 2)
4883 d.addCallback(self._count_leases, "two")
4884 d.addCallback(self._assert_leasecount, 1)
4885 d.addCallback(self._count_leases, "mutable")
4886 d.addCallback(self._assert_leasecount, 2)
4888 d.addErrback(self.explain_web_error)
4891 def test_deep_add_lease(self):
4892 self.basedir = "web/Grid/deep_add_lease"
4893 self.set_up_grid(num_clients=2)
4894 c0 = self.g.clients[0]
4898 d = c0.create_dirnode()
4899 def _stash_root_and_create_file(n):
4901 self.uris["root"] = n.get_uri()
4902 self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri()) + "/"
4903 return n.add_file(u"one", upload.Data(DATA, convergence=""))
4904 d.addCallback(_stash_root_and_create_file)
4905 def _stash_uri(fn, which):
4906 self.uris[which] = fn.get_uri()
4907 d.addCallback(_stash_uri, "one")
4908 d.addCallback(lambda ign:
4909 self.rootnode.add_file(u"small",
4910 upload.Data("literal",
4912 d.addCallback(_stash_uri, "small")
4914 d.addCallback(lambda ign:
4915 c0.create_mutable_file(publish.MutableData("mutable")))
4916 d.addCallback(lambda fn: self.rootnode.set_node(u"mutable", fn))
4917 d.addCallback(_stash_uri, "mutable")
4919 d.addCallback(self.CHECK, "root", "t=stream-deep-check") # no add-lease
4921 units = [simplejson.loads(line)
4922 for line in res.splitlines()
4924 # root, one, small, mutable, stats
4925 self.failUnlessReallyEqual(len(units), 4+1)
4926 d.addCallback(_done)
4928 d.addCallback(self._count_leases, "root")
4929 d.addCallback(self._assert_leasecount, 1)
4930 d.addCallback(self._count_leases, "one")
4931 d.addCallback(self._assert_leasecount, 1)
4932 d.addCallback(self._count_leases, "mutable")
4933 d.addCallback(self._assert_leasecount, 1)
4935 d.addCallback(self.CHECK, "root", "t=stream-deep-check&add-lease=true")
4936 d.addCallback(_done)
4938 d.addCallback(self._count_leases, "root")
4939 d.addCallback(self._assert_leasecount, 1)
4940 d.addCallback(self._count_leases, "one")
4941 d.addCallback(self._assert_leasecount, 1)
4942 d.addCallback(self._count_leases, "mutable")
4943 d.addCallback(self._assert_leasecount, 1)
4945 d.addCallback(self.CHECK, "root", "t=stream-deep-check&add-lease=true",
4947 d.addCallback(_done)
4949 d.addCallback(self._count_leases, "root")
4950 d.addCallback(self._assert_leasecount, 2)
4951 d.addCallback(self._count_leases, "one")
4952 d.addCallback(self._assert_leasecount, 2)
4953 d.addCallback(self._count_leases, "mutable")
4954 d.addCallback(self._assert_leasecount, 2)
4956 d.addErrback(self.explain_web_error)
4960 def test_exceptions(self):
4961 self.basedir = "web/Grid/exceptions"
4962 self.set_up_grid(num_clients=1, num_servers=2)
4963 c0 = self.g.clients[0]
4964 c0.DEFAULT_ENCODING_PARAMETERS['happy'] = 2
4967 d = c0.create_dirnode()
4969 self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri()) + "/"
4970 self.fileurls["imaginary"] = self.fileurls["root"] + "imaginary"
4972 d.addCallback(_stash_root)
4973 d.addCallback(lambda ign: c0.upload(upload.Data(DATA, convergence="")))
4975 self.fileurls["1share"] = "uri/" + urllib.quote(ur.uri)
4976 self.delete_shares_numbered(ur.uri, range(1,10))
4978 u = uri.from_string(ur.uri)
4979 u.key = testutil.flip_bit(u.key, 0)
4980 baduri = u.to_string()
4981 self.fileurls["0shares"] = "uri/" + urllib.quote(baduri)
4982 d.addCallback(_stash_bad)
4983 d.addCallback(lambda ign: c0.create_dirnode())
4984 def _mangle_dirnode_1share(n):
4986 url = self.fileurls["dir-1share"] = "uri/" + urllib.quote(u) + "/"
4987 self.fileurls["dir-1share-json"] = url + "?t=json"
4988 self.delete_shares_numbered(u, range(1,10))
4989 d.addCallback(_mangle_dirnode_1share)
4990 d.addCallback(lambda ign: c0.create_dirnode())
4991 def _mangle_dirnode_0share(n):
4993 url = self.fileurls["dir-0share"] = "uri/" + urllib.quote(u) + "/"
4994 self.fileurls["dir-0share-json"] = url + "?t=json"
4995 self.delete_shares_numbered(u, range(0,10))
4996 d.addCallback(_mangle_dirnode_0share)
4998 # NotEnoughSharesError should be reported sensibly, with a
4999 # text/plain explanation of the problem, and perhaps some
5000 # information on which shares *could* be found.
5002 d.addCallback(lambda ignored:
5003 self.shouldHTTPError("GET unrecoverable",
5004 410, "Gone", "NoSharesError",
5005 self.GET, self.fileurls["0shares"]))
5006 def _check_zero_shares(body):
5007 self.failIf("<html>" in body, body)
5008 body = " ".join(body.strip().split())
5009 exp = ("NoSharesError: no shares could be found. "
5010 "Zero shares usually indicates a corrupt URI, or that "
5011 "no servers were connected, but it might also indicate "
5012 "severe corruption. You should perform a filecheck on "
5013 "this object to learn more. The full error message is: "
5014 "no shares (need 3). Last failure: None")
5015 self.failUnlessReallyEqual(exp, body)
5016 d.addCallback(_check_zero_shares)
5019 d.addCallback(lambda ignored:
5020 self.shouldHTTPError("GET 1share",
5021 410, "Gone", "NotEnoughSharesError",
5022 self.GET, self.fileurls["1share"]))
5023 def _check_one_share(body):
5024 self.failIf("<html>" in body, body)
5025 body = " ".join(body.strip().split())
5026 msgbase = ("NotEnoughSharesError: This indicates that some "
5027 "servers were unavailable, or that shares have been "
5028 "lost to server departure, hard drive failure, or disk "
5029 "corruption. You should perform a filecheck on "
5030 "this object to learn more. The full error message is:"
5032 msg1 = msgbase + (" ran out of shares:"
5035 " overdue= unused= need 3. Last failure: None")
5036 msg2 = msgbase + (" ran out of shares:"
5038 " pending=Share(sh0-on-xgru5)"
5039 " overdue= unused= need 3. Last failure: None")
5040 self.failUnless(body == msg1 or body == msg2, body)
5041 d.addCallback(_check_one_share)
5043 d.addCallback(lambda ignored:
5044 self.shouldHTTPError("GET imaginary",
5045 404, "Not Found", None,
5046 self.GET, self.fileurls["imaginary"]))
5047 def _missing_child(body):
5048 self.failUnless("No such child: imaginary" in body, body)
5049 d.addCallback(_missing_child)
5051 d.addCallback(lambda ignored: self.GET(self.fileurls["dir-0share"]))
5052 def _check_0shares_dir_html(body):
5053 self.failUnless("<html>" in body, body)
5054 # we should see the regular page, but without the child table or
5056 body = " ".join(body.strip().split())
5057 self.failUnlessIn('href="?t=info">More info on this directory',
5059 exp = ("UnrecoverableFileError: the directory (or mutable file) "
5060 "could not be retrieved, because there were insufficient "
5061 "good shares. This might indicate that no servers were "
5062 "connected, insufficient servers were connected, the URI "
5063 "was corrupt, or that shares have been lost due to server "
5064 "departure, hard drive failure, or disk corruption. You "
5065 "should perform a filecheck on this object to learn more.")
5066 self.failUnlessIn(exp, body)
5067 self.failUnlessIn("No upload forms: directory is unreadable", body)
5068 d.addCallback(_check_0shares_dir_html)
5070 d.addCallback(lambda ignored: self.GET(self.fileurls["dir-1share"]))
5071 def _check_1shares_dir_html(body):
5072 # at some point, we'll split UnrecoverableFileError into 0-shares
5073 # and some-shares like we did for immutable files (since there
5074 # are different sorts of advice to offer in each case). For now,
5075 # they present the same way.
5076 self.failUnless("<html>" in body, body)
5077 body = " ".join(body.strip().split())
5078 self.failUnlessIn('href="?t=info">More info on this directory',
5080 exp = ("UnrecoverableFileError: the directory (or mutable file) "
5081 "could not be retrieved, because there were insufficient "
5082 "good shares. This might indicate that no servers were "
5083 "connected, insufficient servers were connected, the URI "
5084 "was corrupt, or that shares have been lost due to server "
5085 "departure, hard drive failure, or disk corruption. You "
5086 "should perform a filecheck on this object to learn more.")
5087 self.failUnlessIn(exp, body)
5088 self.failUnlessIn("No upload forms: directory is unreadable", body)
5089 d.addCallback(_check_1shares_dir_html)
5091 d.addCallback(lambda ignored:
5092 self.shouldHTTPError("GET dir-0share-json",
5093 410, "Gone", "UnrecoverableFileError",
5095 self.fileurls["dir-0share-json"]))
5096 def _check_unrecoverable_file(body):
5097 self.failIf("<html>" in body, body)
5098 body = " ".join(body.strip().split())
5099 exp = ("UnrecoverableFileError: the directory (or mutable file) "
5100 "could not be retrieved, because there were insufficient "
5101 "good shares. This might indicate that no servers were "
5102 "connected, insufficient servers were connected, the URI "
5103 "was corrupt, or that shares have been lost due to server "
5104 "departure, hard drive failure, or disk corruption. You "
5105 "should perform a filecheck on this object to learn more.")
5106 self.failUnlessReallyEqual(exp, body)
5107 d.addCallback(_check_unrecoverable_file)
5109 d.addCallback(lambda ignored:
5110 self.shouldHTTPError("GET dir-1share-json",
5111 410, "Gone", "UnrecoverableFileError",
5113 self.fileurls["dir-1share-json"]))
5114 d.addCallback(_check_unrecoverable_file)
5116 d.addCallback(lambda ignored:
5117 self.shouldHTTPError("GET imaginary",
5118 404, "Not Found", None,
5119 self.GET, self.fileurls["imaginary"]))
5121 # attach a webapi child that throws a random error, to test how it
5123 w = c0.getServiceNamed("webish")
5124 w.root.putChild("ERRORBOOM", ErrorBoom())
5126 # "Accept: */*" : should get a text/html stack trace
5127 # "Accept: text/plain" : should get a text/plain stack trace
5128 # "Accept: text/plain, application/octet-stream" : text/plain (CLI)
5129 # no Accept header: should get a text/html stack trace
5131 d.addCallback(lambda ignored:
5132 self.shouldHTTPError("GET errorboom_html",
5133 500, "Internal Server Error", None,
5134 self.GET, "ERRORBOOM",
5135 headers={"accept": ["*/*"]}))
5136 def _internal_error_html1(body):
5137 self.failUnless("<html>" in body, "expected HTML, not '%s'" % body)
5138 d.addCallback(_internal_error_html1)
5140 d.addCallback(lambda ignored:
5141 self.shouldHTTPError("GET errorboom_text",
5142 500, "Internal Server Error", None,
5143 self.GET, "ERRORBOOM",
5144 headers={"accept": ["text/plain"]}))
5145 def _internal_error_text2(body):
5146 self.failIf("<html>" in body, body)
5147 self.failUnless(body.startswith("Traceback "), body)
5148 d.addCallback(_internal_error_text2)
5150 CLI_accepts = "text/plain, application/octet-stream"
5151 d.addCallback(lambda ignored:
5152 self.shouldHTTPError("GET errorboom_text",
5153 500, "Internal Server Error", None,
5154 self.GET, "ERRORBOOM",
5155 headers={"accept": [CLI_accepts]}))
5156 def _internal_error_text3(body):
5157 self.failIf("<html>" in body, body)
5158 self.failUnless(body.startswith("Traceback "), body)
5159 d.addCallback(_internal_error_text3)
5161 d.addCallback(lambda ignored:
5162 self.shouldHTTPError("GET errorboom_text",
5163 500, "Internal Server Error", None,
5164 self.GET, "ERRORBOOM"))
5165 def _internal_error_html4(body):
5166 self.failUnless("<html>" in body, "expected HTML, not '%s'" % body)
5167 d.addCallback(_internal_error_html4)
5169 def _flush_errors(res):
5170 # Trial: please ignore the CompletelyUnhandledError in the logs
5171 self.flushLoggedErrors(CompletelyUnhandledError)
5173 d.addBoth(_flush_errors)
5177 def test_blacklist(self):
5178 # download from a blacklisted URI, get an error
5179 self.basedir = "web/Grid/blacklist"
5181 c0 = self.g.clients[0]
5182 c0_basedir = c0.basedir
5183 fn = os.path.join(c0_basedir, "access.blacklist")
5185 DATA = "off-limits " * 50
5187 d = c0.upload(upload.Data(DATA, convergence=""))
5188 def _stash_uri_and_create_dir(ur):
5190 self.url = "uri/"+self.uri
5191 u = uri.from_string_filenode(self.uri)
5192 self.si = u.get_storage_index()
5193 childnode = c0.create_node_from_uri(self.uri, None)
5194 return c0.create_dirnode({u"blacklisted.txt": (childnode,{}) })
5195 d.addCallback(_stash_uri_and_create_dir)
5196 def _stash_dir(node):
5197 self.dir_node = node
5198 self.dir_uri = node.get_uri()
5199 self.dir_url = "uri/"+self.dir_uri
5200 d.addCallback(_stash_dir)
5201 d.addCallback(lambda ign: self.GET(self.dir_url, followRedirect=True))
5202 def _check_dir_html(body):
5203 self.failUnlessIn("<html>", body)
5204 self.failUnlessIn("blacklisted.txt</a>", body)
5205 d.addCallback(_check_dir_html)
5206 d.addCallback(lambda ign: self.GET(self.url))
5207 d.addCallback(lambda body: self.failUnlessEqual(DATA, body))
5209 def _blacklist(ign):
5211 f.write(" # this is a comment\n")
5213 f.write("\n") # also exercise blank lines
5214 f.write("%s %s\n" % (base32.b2a(self.si), "off-limits to you"))
5216 # clients should be checking the blacklist each time, so we don't
5217 # need to restart the client
5218 d.addCallback(_blacklist)
5219 d.addCallback(lambda ign: self.shouldHTTPError("get_from_blacklisted_uri",
5221 "Access Prohibited: off-limits",
5222 self.GET, self.url))
5224 # We should still be able to list the parent directory, in HTML...
5225 d.addCallback(lambda ign: self.GET(self.dir_url, followRedirect=True))
5226 def _check_dir_html2(body):
5227 self.failUnlessIn("<html>", body)
5228 self.failUnlessIn("blacklisted.txt</strike>", body)
5229 d.addCallback(_check_dir_html2)
5231 # ... and in JSON (used by CLI).
5232 d.addCallback(lambda ign: self.GET(self.dir_url+"?t=json", followRedirect=True))
5233 def _check_dir_json(res):
5234 data = simplejson.loads(res)
5235 self.failUnless(isinstance(data, list), data)
5236 self.failUnlessEqual(data[0], "dirnode")
5237 self.failUnless(isinstance(data[1], dict), data)
5238 self.failUnlessIn("children", data[1])
5239 self.failUnlessIn("blacklisted.txt", data[1]["children"])
5240 childdata = data[1]["children"]["blacklisted.txt"]
5241 self.failUnless(isinstance(childdata, list), data)
5242 self.failUnlessEqual(childdata[0], "filenode")
5243 self.failUnless(isinstance(childdata[1], dict), data)
5244 d.addCallback(_check_dir_json)
5246 def _unblacklist(ign):
5247 open(fn, "w").close()
5248 # the Blacklist object watches mtime to tell when the file has
5249 # changed, but on windows this test will run faster than the
5250 # filesystem's mtime resolution. So we edit Blacklist.last_mtime
5251 # to force a reload.
5252 self.g.clients[0].blacklist.last_mtime -= 2.0
5253 d.addCallback(_unblacklist)
5255 # now a read should work
5256 d.addCallback(lambda ign: self.GET(self.url))
5257 d.addCallback(lambda body: self.failUnlessEqual(DATA, body))
5259 # read again to exercise the blacklist-is-unchanged logic
5260 d.addCallback(lambda ign: self.GET(self.url))
5261 d.addCallback(lambda body: self.failUnlessEqual(DATA, body))
5263 # now add a blacklisted directory, and make sure files under it are
5266 childnode = c0.create_node_from_uri(self.uri, None)
5267 return c0.create_dirnode({u"child": (childnode,{}) })
5268 d.addCallback(_add_dir)
5269 def _get_dircap(dn):
5270 self.dir_si_b32 = base32.b2a(dn.get_storage_index())
5271 self.dir_url_base = "uri/"+dn.get_write_uri()
5272 self.dir_url_json1 = "uri/"+dn.get_write_uri()+"?t=json"
5273 self.dir_url_json2 = "uri/"+dn.get_write_uri()+"/?t=json"
5274 self.dir_url_json_ro = "uri/"+dn.get_readonly_uri()+"/?t=json"
5275 self.child_url = "uri/"+dn.get_readonly_uri()+"/child"
5276 d.addCallback(_get_dircap)
5277 d.addCallback(lambda ign: self.GET(self.dir_url_base, followRedirect=True))
5278 d.addCallback(lambda body: self.failUnlessIn("<html>", body))
5279 d.addCallback(lambda ign: self.GET(self.dir_url_json1))
5280 d.addCallback(lambda res: simplejson.loads(res)) # just check it decodes
5281 d.addCallback(lambda ign: self.GET(self.dir_url_json2))
5282 d.addCallback(lambda res: simplejson.loads(res)) # just check it decodes
5283 d.addCallback(lambda ign: self.GET(self.dir_url_json_ro))
5284 d.addCallback(lambda res: simplejson.loads(res)) # just check it decodes
5285 d.addCallback(lambda ign: self.GET(self.child_url))
5286 d.addCallback(lambda body: self.failUnlessEqual(DATA, body))
5288 def _block_dir(ign):
5290 f.write("%s %s\n" % (self.dir_si_b32, "dir-off-limits to you"))
5292 self.g.clients[0].blacklist.last_mtime -= 2.0
5293 d.addCallback(_block_dir)
5294 d.addCallback(lambda ign: self.shouldHTTPError("get_from_blacklisted_dir base",
5296 "Access Prohibited: dir-off-limits",
5297 self.GET, self.dir_url_base))
5298 d.addCallback(lambda ign: self.shouldHTTPError("get_from_blacklisted_dir json1",
5300 "Access Prohibited: dir-off-limits",
5301 self.GET, self.dir_url_json1))
5302 d.addCallback(lambda ign: self.shouldHTTPError("get_from_blacklisted_dir json2",
5304 "Access Prohibited: dir-off-limits",
5305 self.GET, self.dir_url_json2))
5306 d.addCallback(lambda ign: self.shouldHTTPError("get_from_blacklisted_dir json_ro",
5308 "Access Prohibited: dir-off-limits",
5309 self.GET, self.dir_url_json_ro))
5310 d.addCallback(lambda ign: self.shouldHTTPError("get_from_blacklisted_dir child",
5312 "Access Prohibited: dir-off-limits",
5313 self.GET, self.child_url))
5317 class CompletelyUnhandledError(Exception):
5319 class ErrorBoom(rend.Page):
5320 def beforeRender(self, ctx):
5321 raise CompletelyUnhandledError("whoops")