2 import os.path, re, urllib, time
4 from StringIO import StringIO
5 from twisted.application import service
6 from twisted.trial import unittest
7 from twisted.internet import defer, reactor
8 from twisted.internet.task import Clock
9 from twisted.web import client, error, http
10 from twisted.python import failure, log
11 from nevow import rend
12 from allmydata import interfaces, uri, webish, dirnode
13 from allmydata.storage.shares import get_share_file
14 from allmydata.storage_client import StorageFarmBroker
15 from allmydata.immutable import upload
16 from allmydata.immutable.downloader.status import DownloadStatus
17 from allmydata.dirnode import DirectoryNode
18 from allmydata.nodemaker import NodeMaker
19 from allmydata.unknown import UnknownNode
20 from allmydata.web import status, common
21 from allmydata.scripts.debug import CorruptShareOptions, corrupt_share
22 from allmydata.util import fileutil, base32
23 from allmydata.util.consumer import download_to_data
24 from allmydata.util.netstring import split_netstring
25 from allmydata.util.encodingutil import to_str
26 from allmydata.test.common import FakeCHKFileNode, FakeMutableFileNode, \
27 create_chk_filenode, WebErrorMixin, ShouldFailMixin, make_mutable_file_uri
28 from allmydata.interfaces import IMutableFileNode
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):
51 def _create_lit(self, cap):
52 return FakeCHKFileNode(cap)
53 def _create_immutable(self, cap):
54 return FakeCHKFileNode(cap)
55 def _create_mutable(self, cap):
56 return FakeMutableFileNode(None, None, None, None).init_from_cap(cap)
57 def create_mutable_file(self, contents="", keysize=None):
58 n = FakeMutableFileNode(None, None, None, None)
59 return n.create(contents)
61 class FakeUploader(service.Service):
63 def upload(self, uploadable, history=None):
64 d = uploadable.get_size()
65 d.addCallback(lambda size: uploadable.read(size))
68 n = create_chk_filenode(data)
69 results = upload.UploadResults()
70 results.uri = n.get_uri()
72 d.addCallback(_got_data)
74 def get_helper_info(self):
78 ds = DownloadStatus("storage_index", 1234)
81 ds.add_segment_request(0, now)
82 # segnum, when, start,len, decodetime
83 ds.add_segment_delivery(0, now+1, 0, 100, 0.5)
84 ds.add_segment_request(1, now+2)
85 ds.add_segment_error(1, now+3)
87 e = ds.add_dyhb_sent("serverid_a", now)
88 e.finished([1,2], now+1)
89 e = ds.add_dyhb_sent("serverid_b", now+2) # left unfinished
91 e = ds.add_read_event(0, 120, now)
92 e.update(60, 0.5, 0.1) # bytes, decrypttime, pausetime
94 e = ds.add_read_event(120, 30, now+2) # left unfinished
96 e = ds.add_request_sent("serverid_a", 1, 100, 20, now)
98 e = ds.add_request_sent("serverid_a", 1, 120, 30, now+1) # left unfinished
100 # make sure that add_read_event() can come first too
101 ds1 = DownloadStatus("storage_index", 1234)
102 e = ds1.add_read_event(0, 120, now)
103 e.update(60, 0.5, 0.1) # bytes, decrypttime, pausetime
109 _all_upload_status = [upload.UploadStatus()]
110 _all_download_status = [build_one_ds()]
111 _all_mapupdate_statuses = [servermap.UpdateStatus()]
112 _all_publish_statuses = [publish.PublishStatus()]
113 _all_retrieve_statuses = [retrieve.RetrieveStatus()]
115 def list_all_upload_statuses(self):
116 return self._all_upload_status
117 def list_all_download_statuses(self):
118 return self._all_download_status
119 def list_all_mapupdate_statuses(self):
120 return self._all_mapupdate_statuses
121 def list_all_publish_statuses(self):
122 return self._all_publish_statuses
123 def list_all_retrieve_statuses(self):
124 return self._all_retrieve_statuses
125 def list_all_helper_statuses(self):
128 class FakeClient(Client):
130 # don't upcall to Client.__init__, since we only want to initialize a
132 service.MultiService.__init__(self)
133 self.nodeid = "fake_nodeid"
134 self.nickname = "fake_nickname"
135 self.introducer_furl = "None"
136 self.stats_provider = FakeStatsProvider()
137 self._secret_holder = SecretHolder("lease secret", "convergence secret")
139 self.convergence = "some random string"
140 self.storage_broker = StorageFarmBroker(None, permute_peers=True)
141 self.introducer_client = None
142 self.history = FakeHistory()
143 self.uploader = FakeUploader()
144 self.uploader.setServiceParent(self)
145 self.nodemaker = FakeNodeMaker(None, self._secret_holder, None,
149 def startService(self):
150 return service.MultiService.startService(self)
151 def stopService(self):
152 return service.MultiService.stopService(self)
154 MUTABLE_SIZELIMIT = FakeMutableFileNode.MUTABLE_SIZELIMIT
156 class WebMixin(object):
158 self.s = FakeClient()
159 self.s.startService()
160 self.staticdir = self.mktemp()
162 self.ws = webish.WebishServer(self.s, "0", staticdir=self.staticdir,
164 self.ws.setServiceParent(self.s)
165 self.webish_port = port = self.ws.listener._port.getHost().port
166 self.webish_url = "http://localhost:%d" % port
168 l = [ self.s.create_dirnode() for x in range(6) ]
169 d = defer.DeferredList(l)
171 self.public_root = res[0][1]
172 assert interfaces.IDirectoryNode.providedBy(self.public_root), res
173 self.public_url = "/uri/" + self.public_root.get_uri()
174 self.private_root = res[1][1]
178 self._foo_uri = foo.get_uri()
179 self._foo_readonly_uri = foo.get_readonly_uri()
180 self._foo_verifycap = foo.get_verify_cap().to_string()
181 # NOTE: we ignore the deferred on all set_uri() calls, because we
182 # know the fake nodes do these synchronously
183 self.public_root.set_uri(u"foo", foo.get_uri(),
184 foo.get_readonly_uri())
186 self.BAR_CONTENTS, n, self._bar_txt_uri = self.makefile(0)
187 foo.set_uri(u"bar.txt", self._bar_txt_uri, self._bar_txt_uri)
188 self._bar_txt_verifycap = n.get_verify_cap().to_string()
190 foo.set_uri(u"empty", res[3][1].get_uri(),
191 res[3][1].get_readonly_uri())
192 sub_uri = res[4][1].get_uri()
193 self._sub_uri = sub_uri
194 foo.set_uri(u"sub", sub_uri, sub_uri)
195 sub = self.s.create_node_from_uri(sub_uri)
197 _ign, n, blocking_uri = self.makefile(1)
198 foo.set_uri(u"blockingfile", blocking_uri, blocking_uri)
200 unicode_filename = u"n\u00fc.txt" # n u-umlaut . t x t
201 # ok, unicode calls it LATIN SMALL LETTER U WITH DIAERESIS but I
202 # still think of it as an umlaut
203 foo.set_uri(unicode_filename, self._bar_txt_uri, self._bar_txt_uri)
205 _ign, n, baz_file = self.makefile(2)
206 self._baz_file_uri = baz_file
207 sub.set_uri(u"baz.txt", baz_file, baz_file)
209 _ign, n, self._bad_file_uri = self.makefile(3)
210 # this uri should not be downloadable
211 del FakeCHKFileNode.all_contents[self._bad_file_uri]
214 self.public_root.set_uri(u"reedownlee", rodir.get_readonly_uri(),
215 rodir.get_readonly_uri())
216 rodir.set_uri(u"nor", baz_file, baz_file)
221 # public/foo/blockingfile
224 # public/foo/sub/baz.txt
226 # public/reedownlee/nor
227 self.NEWFILE_CONTENTS = "newfile contents\n"
229 return foo.get_metadata_for(u"bar.txt")
231 def _got_metadata(metadata):
232 self._bar_txt_metadata = metadata
233 d.addCallback(_got_metadata)
236 def makefile(self, number):
237 contents = "contents of file %s\n" % number
238 n = create_chk_filenode(contents)
239 return contents, n, n.get_uri()
242 return self.s.stopService()
244 def failUnlessIsBarDotTxt(self, res):
245 self.failUnlessReallyEqual(res, self.BAR_CONTENTS, res)
247 def failUnlessIsBarJSON(self, res):
248 data = simplejson.loads(res)
249 self.failUnless(isinstance(data, list))
250 self.failUnlessEqual(data[0], "filenode")
251 self.failUnless(isinstance(data[1], dict))
252 self.failIf(data[1]["mutable"])
253 self.failIf("rw_uri" in data[1]) # immutable
254 self.failUnlessReallyEqual(to_str(data[1]["ro_uri"]), self._bar_txt_uri)
255 self.failUnlessReallyEqual(to_str(data[1]["verify_uri"]), self._bar_txt_verifycap)
256 self.failUnlessReallyEqual(data[1]["size"], len(self.BAR_CONTENTS))
258 def failUnlessIsFooJSON(self, res):
259 data = simplejson.loads(res)
260 self.failUnless(isinstance(data, list))
261 self.failUnlessEqual(data[0], "dirnode", res)
262 self.failUnless(isinstance(data[1], dict))
263 self.failUnless(data[1]["mutable"])
264 self.failUnless("rw_uri" in data[1]) # mutable
265 self.failUnlessReallyEqual(to_str(data[1]["rw_uri"]), self._foo_uri)
266 self.failUnlessReallyEqual(to_str(data[1]["ro_uri"]), self._foo_readonly_uri)
267 self.failUnlessReallyEqual(to_str(data[1]["verify_uri"]), self._foo_verifycap)
269 kidnames = sorted([unicode(n) for n in data[1]["children"]])
270 self.failUnlessEqual(kidnames,
271 [u"bar.txt", u"blockingfile", u"empty",
272 u"n\u00fc.txt", u"sub"])
273 kids = dict( [(unicode(name),value)
275 in data[1]["children"].iteritems()] )
276 self.failUnlessEqual(kids[u"sub"][0], "dirnode")
277 self.failUnlessIn("metadata", kids[u"sub"][1])
278 self.failUnlessIn("tahoe", kids[u"sub"][1]["metadata"])
279 tahoe_md = kids[u"sub"][1]["metadata"]["tahoe"]
280 self.failUnlessIn("linkcrtime", tahoe_md)
281 self.failUnlessIn("linkmotime", tahoe_md)
282 self.failUnlessEqual(kids[u"bar.txt"][0], "filenode")
283 self.failUnlessReallyEqual(kids[u"bar.txt"][1]["size"], len(self.BAR_CONTENTS))
284 self.failUnlessReallyEqual(to_str(kids[u"bar.txt"][1]["ro_uri"]), self._bar_txt_uri)
285 self.failUnlessReallyEqual(to_str(kids[u"bar.txt"][1]["verify_uri"]),
286 self._bar_txt_verifycap)
287 self.failUnlessIn("metadata", kids[u"bar.txt"][1])
288 self.failUnlessIn("tahoe", kids[u"bar.txt"][1]["metadata"])
289 self.failUnlessReallyEqual(kids[u"bar.txt"][1]["metadata"]["tahoe"]["linkcrtime"],
290 self._bar_txt_metadata["tahoe"]["linkcrtime"])
291 self.failUnlessReallyEqual(to_str(kids[u"n\u00fc.txt"][1]["ro_uri"]),
294 def GET(self, urlpath, followRedirect=False, return_response=False,
296 # if return_response=True, this fires with (data, statuscode,
297 # respheaders) instead of just data.
298 assert not isinstance(urlpath, unicode)
299 url = self.webish_url + urlpath
300 factory = HTTPClientGETFactory(url, method="GET",
301 followRedirect=followRedirect, **kwargs)
302 reactor.connectTCP("localhost", self.webish_port, factory)
305 return (data, factory.status, factory.response_headers)
307 d.addCallback(_got_data)
308 return factory.deferred
310 def HEAD(self, urlpath, return_response=False, **kwargs):
311 # this requires some surgery, because twisted.web.client doesn't want
312 # to give us back the response headers.
313 factory = HTTPClientHEADFactory(urlpath, method="HEAD", **kwargs)
314 reactor.connectTCP("localhost", self.webish_port, factory)
317 return (data, factory.status, factory.response_headers)
319 d.addCallback(_got_data)
320 return factory.deferred
322 def PUT(self, urlpath, data, **kwargs):
323 url = self.webish_url + urlpath
324 return client.getPage(url, method="PUT", postdata=data, **kwargs)
326 def DELETE(self, urlpath):
327 url = self.webish_url + urlpath
328 return client.getPage(url, method="DELETE")
330 def POST(self, urlpath, followRedirect=False, **fields):
331 sepbase = "boogabooga"
335 form.append('Content-Disposition: form-data; name="_charset"')
339 for name, value in fields.iteritems():
340 if isinstance(value, tuple):
341 filename, value = value
342 form.append('Content-Disposition: form-data; name="%s"; '
343 'filename="%s"' % (name, filename.encode("utf-8")))
345 form.append('Content-Disposition: form-data; name="%s"' % name)
347 if isinstance(value, unicode):
348 value = value.encode("utf-8")
351 assert isinstance(value, str)
358 body = "\r\n".join(form) + "\r\n"
359 headers["content-type"] = "multipart/form-data; boundary=%s" % sepbase
360 return self.POST2(urlpath, body, headers, followRedirect)
362 def POST2(self, urlpath, body="", headers={}, followRedirect=False):
363 url = self.webish_url + urlpath
364 return client.getPage(url, method="POST", postdata=body,
365 headers=headers, followRedirect=followRedirect)
367 def shouldFail(self, res, expected_failure, which,
368 substring=None, response_substring=None):
369 if isinstance(res, failure.Failure):
370 res.trap(expected_failure)
372 self.failUnless(substring in str(res),
373 "substring '%s' not in '%s'"
374 % (substring, str(res)))
375 if response_substring:
376 self.failUnless(response_substring in res.value.response,
377 "response substring '%s' not in '%s'"
378 % (response_substring, res.value.response))
380 self.fail("%s was supposed to raise %s, not get '%s'" %
381 (which, expected_failure, res))
383 def shouldFail2(self, expected_failure, which, substring,
385 callable, *args, **kwargs):
386 assert substring is None or isinstance(substring, str)
387 assert response_substring is None or isinstance(response_substring, str)
388 d = defer.maybeDeferred(callable, *args, **kwargs)
390 if isinstance(res, failure.Failure):
391 res.trap(expected_failure)
393 self.failUnless(substring in str(res),
394 "%s: substring '%s' not in '%s'"
395 % (which, substring, str(res)))
396 if response_substring:
397 self.failUnless(response_substring in res.value.response,
398 "%s: response substring '%s' not in '%s'"
400 response_substring, res.value.response))
402 self.fail("%s was supposed to raise %s, not get '%s'" %
403 (which, expected_failure, res))
407 def should404(self, res, which):
408 if isinstance(res, failure.Failure):
409 res.trap(error.Error)
410 self.failUnlessReallyEqual(res.value.status, "404")
412 self.fail("%s was supposed to Error(404), not get '%s'" %
415 def should302(self, res, which):
416 if isinstance(res, failure.Failure):
417 res.trap(error.Error)
418 self.failUnlessReallyEqual(res.value.status, "302")
420 self.fail("%s was supposed to Error(302), not get '%s'" %
424 class Web(WebMixin, WebErrorMixin, testutil.StallMixin, testutil.ReallyEqualMixin, unittest.TestCase):
425 def test_create(self):
428 def test_welcome(self):
431 self.failUnless('Welcome To Tahoe-LAFS' in res, res)
433 self.s.basedir = 'web/test_welcome'
434 fileutil.make_dirs("web/test_welcome")
435 fileutil.make_dirs("web/test_welcome/private")
437 d.addCallback(_check)
440 def test_provisioning(self):
441 d = self.GET("/provisioning/")
443 self.failUnless('Provisioning Tool' in res)
444 fields = {'filled': True,
445 "num_users": int(50e3),
446 "files_per_user": 1000,
447 "space_per_user": int(1e9),
448 "sharing_ratio": 1.0,
449 "encoding_parameters": "3-of-10-5",
451 "ownership_mode": "A",
452 "download_rate": 100,
457 return self.POST("/provisioning/", **fields)
459 d.addCallback(_check)
461 self.failUnless('Provisioning Tool' in res)
462 self.failUnless("Share space consumed: 167.01TB" in res)
464 fields = {'filled': True,
465 "num_users": int(50e6),
466 "files_per_user": 1000,
467 "space_per_user": int(5e9),
468 "sharing_ratio": 1.0,
469 "encoding_parameters": "25-of-100-50",
470 "num_servers": 30000,
471 "ownership_mode": "E",
472 "drive_failure_model": "U",
474 "download_rate": 1000,
479 return self.POST("/provisioning/", **fields)
480 d.addCallback(_check2)
482 self.failUnless("Share space consumed: huge!" in res)
483 fields = {'filled': True}
484 return self.POST("/provisioning/", **fields)
485 d.addCallback(_check3)
487 self.failUnless("Share space consumed:" in res)
488 d.addCallback(_check4)
491 def test_reliability_tool(self):
493 from allmydata import reliability
494 _hush_pyflakes = reliability
497 raise unittest.SkipTest("reliability tool requires NumPy")
499 d = self.GET("/reliability/")
501 self.failUnless('Reliability Tool' in res)
502 fields = {'drive_lifetime': "8Y",
507 "check_period": "1M",
508 "report_period": "3M",
511 return self.POST("/reliability/", **fields)
513 d.addCallback(_check)
515 self.failUnless('Reliability Tool' in res)
516 r = r'Probability of loss \(no maintenance\):\s+<span>0.033591'
517 self.failUnless(re.search(r, res), res)
518 d.addCallback(_check2)
521 def test_status(self):
522 h = self.s.get_history()
523 dl_num = h.list_all_download_statuses()[0].get_counter()
524 ul_num = h.list_all_upload_statuses()[0].get_counter()
525 mu_num = h.list_all_mapupdate_statuses()[0].get_counter()
526 pub_num = h.list_all_publish_statuses()[0].get_counter()
527 ret_num = h.list_all_retrieve_statuses()[0].get_counter()
528 d = self.GET("/status", followRedirect=True)
530 self.failUnless('Upload and Download Status' in res, res)
531 self.failUnless('"down-%d"' % dl_num in res, res)
532 self.failUnless('"up-%d"' % ul_num in res, res)
533 self.failUnless('"mapupdate-%d"' % mu_num in res, res)
534 self.failUnless('"publish-%d"' % pub_num in res, res)
535 self.failUnless('"retrieve-%d"' % ret_num in res, res)
536 d.addCallback(_check)
537 d.addCallback(lambda res: self.GET("/status/?t=json"))
538 def _check_json(res):
539 data = simplejson.loads(res)
540 self.failUnless(isinstance(data, dict))
541 #active = data["active"]
542 # TODO: test more. We need a way to fake an active operation
544 d.addCallback(_check_json)
546 d.addCallback(lambda res: self.GET("/status/down-%d" % dl_num))
548 self.failUnless("File Download Status" in res, res)
549 d.addCallback(_check_dl)
550 d.addCallback(lambda res: self.GET("/status/down-%d?t=json" % dl_num))
551 def _check_dl_json(res):
552 data = simplejson.loads(res)
553 self.failUnless(isinstance(data, dict))
554 d.addCallback(_check_dl_json)
555 d.addCallback(lambda res: self.GET("/status/up-%d" % ul_num))
557 self.failUnless("File Upload Status" in res, res)
558 d.addCallback(_check_ul)
559 d.addCallback(lambda res: self.GET("/status/mapupdate-%d" % mu_num))
560 def _check_mapupdate(res):
561 self.failUnless("Mutable File Servermap Update Status" in res, res)
562 d.addCallback(_check_mapupdate)
563 d.addCallback(lambda res: self.GET("/status/publish-%d" % pub_num))
564 def _check_publish(res):
565 self.failUnless("Mutable File Publish Status" in res, res)
566 d.addCallback(_check_publish)
567 d.addCallback(lambda res: self.GET("/status/retrieve-%d" % ret_num))
568 def _check_retrieve(res):
569 self.failUnless("Mutable File Retrieve Status" in res, res)
570 d.addCallback(_check_retrieve)
574 def test_status_numbers(self):
575 drrm = status.DownloadResultsRendererMixin()
576 self.failUnlessReallyEqual(drrm.render_time(None, None), "")
577 self.failUnlessReallyEqual(drrm.render_time(None, 2.5), "2.50s")
578 self.failUnlessReallyEqual(drrm.render_time(None, 0.25), "250ms")
579 self.failUnlessReallyEqual(drrm.render_time(None, 0.0021), "2.1ms")
580 self.failUnlessReallyEqual(drrm.render_time(None, 0.000123), "123us")
581 self.failUnlessReallyEqual(drrm.render_rate(None, None), "")
582 self.failUnlessReallyEqual(drrm.render_rate(None, 2500000), "2.50MBps")
583 self.failUnlessReallyEqual(drrm.render_rate(None, 30100), "30.1kBps")
584 self.failUnlessReallyEqual(drrm.render_rate(None, 123), "123Bps")
586 urrm = status.UploadResultsRendererMixin()
587 self.failUnlessReallyEqual(urrm.render_time(None, None), "")
588 self.failUnlessReallyEqual(urrm.render_time(None, 2.5), "2.50s")
589 self.failUnlessReallyEqual(urrm.render_time(None, 0.25), "250ms")
590 self.failUnlessReallyEqual(urrm.render_time(None, 0.0021), "2.1ms")
591 self.failUnlessReallyEqual(urrm.render_time(None, 0.000123), "123us")
592 self.failUnlessReallyEqual(urrm.render_rate(None, None), "")
593 self.failUnlessReallyEqual(urrm.render_rate(None, 2500000), "2.50MBps")
594 self.failUnlessReallyEqual(urrm.render_rate(None, 30100), "30.1kBps")
595 self.failUnlessReallyEqual(urrm.render_rate(None, 123), "123Bps")
597 def test_GET_FILEURL(self):
598 d = self.GET(self.public_url + "/foo/bar.txt")
599 d.addCallback(self.failUnlessIsBarDotTxt)
602 def test_GET_FILEURL_range(self):
603 headers = {"range": "bytes=1-10"}
604 d = self.GET(self.public_url + "/foo/bar.txt", headers=headers,
605 return_response=True)
606 def _got((res, status, headers)):
607 self.failUnlessReallyEqual(int(status), 206)
608 self.failUnless(headers.has_key("content-range"))
609 self.failUnlessReallyEqual(headers["content-range"][0],
610 "bytes 1-10/%d" % len(self.BAR_CONTENTS))
611 self.failUnlessReallyEqual(res, self.BAR_CONTENTS[1:11])
615 def test_GET_FILEURL_partial_range(self):
616 headers = {"range": "bytes=5-"}
617 length = len(self.BAR_CONTENTS)
618 d = self.GET(self.public_url + "/foo/bar.txt", headers=headers,
619 return_response=True)
620 def _got((res, status, headers)):
621 self.failUnlessReallyEqual(int(status), 206)
622 self.failUnless(headers.has_key("content-range"))
623 self.failUnlessReallyEqual(headers["content-range"][0],
624 "bytes 5-%d/%d" % (length-1, length))
625 self.failUnlessReallyEqual(res, self.BAR_CONTENTS[5:])
629 def test_GET_FILEURL_partial_end_range(self):
630 headers = {"range": "bytes=-5"}
631 length = len(self.BAR_CONTENTS)
632 d = self.GET(self.public_url + "/foo/bar.txt", headers=headers,
633 return_response=True)
634 def _got((res, status, headers)):
635 self.failUnlessReallyEqual(int(status), 206)
636 self.failUnless(headers.has_key("content-range"))
637 self.failUnlessReallyEqual(headers["content-range"][0],
638 "bytes %d-%d/%d" % (length-5, length-1, length))
639 self.failUnlessReallyEqual(res, self.BAR_CONTENTS[-5:])
643 def test_GET_FILEURL_partial_range_overrun(self):
644 headers = {"range": "bytes=100-200"}
645 d = self.shouldFail2(error.Error, "test_GET_FILEURL_range_overrun",
646 "416 Requested Range not satisfiable",
647 "First beyond end of file",
648 self.GET, self.public_url + "/foo/bar.txt",
652 def test_HEAD_FILEURL_range(self):
653 headers = {"range": "bytes=1-10"}
654 d = self.HEAD(self.public_url + "/foo/bar.txt", headers=headers,
655 return_response=True)
656 def _got((res, status, headers)):
657 self.failUnlessReallyEqual(res, "")
658 self.failUnlessReallyEqual(int(status), 206)
659 self.failUnless(headers.has_key("content-range"))
660 self.failUnlessReallyEqual(headers["content-range"][0],
661 "bytes 1-10/%d" % len(self.BAR_CONTENTS))
665 def test_HEAD_FILEURL_partial_range(self):
666 headers = {"range": "bytes=5-"}
667 length = len(self.BAR_CONTENTS)
668 d = self.HEAD(self.public_url + "/foo/bar.txt", headers=headers,
669 return_response=True)
670 def _got((res, status, headers)):
671 self.failUnlessReallyEqual(int(status), 206)
672 self.failUnless(headers.has_key("content-range"))
673 self.failUnlessReallyEqual(headers["content-range"][0],
674 "bytes 5-%d/%d" % (length-1, length))
678 def test_HEAD_FILEURL_partial_end_range(self):
679 headers = {"range": "bytes=-5"}
680 length = len(self.BAR_CONTENTS)
681 d = self.HEAD(self.public_url + "/foo/bar.txt", headers=headers,
682 return_response=True)
683 def _got((res, status, headers)):
684 self.failUnlessReallyEqual(int(status), 206)
685 self.failUnless(headers.has_key("content-range"))
686 self.failUnlessReallyEqual(headers["content-range"][0],
687 "bytes %d-%d/%d" % (length-5, length-1, length))
691 def test_HEAD_FILEURL_partial_range_overrun(self):
692 headers = {"range": "bytes=100-200"}
693 d = self.shouldFail2(error.Error, "test_HEAD_FILEURL_range_overrun",
694 "416 Requested Range not satisfiable",
696 self.HEAD, self.public_url + "/foo/bar.txt",
700 def test_GET_FILEURL_range_bad(self):
701 headers = {"range": "BOGUS=fizbop-quarnak"}
702 d = self.GET(self.public_url + "/foo/bar.txt", headers=headers,
703 return_response=True)
704 def _got((res, status, headers)):
705 self.failUnlessReallyEqual(int(status), 200)
706 self.failUnless(not headers.has_key("content-range"))
707 self.failUnlessReallyEqual(res, self.BAR_CONTENTS)
711 def test_HEAD_FILEURL(self):
712 d = self.HEAD(self.public_url + "/foo/bar.txt", return_response=True)
713 def _got((res, status, headers)):
714 self.failUnlessReallyEqual(res, "")
715 self.failUnlessReallyEqual(headers["content-length"][0],
716 str(len(self.BAR_CONTENTS)))
717 self.failUnlessReallyEqual(headers["content-type"], ["text/plain"])
721 def test_GET_FILEURL_named(self):
722 base = "/file/%s" % urllib.quote(self._bar_txt_uri)
723 base2 = "/named/%s" % urllib.quote(self._bar_txt_uri)
724 d = self.GET(base + "/@@name=/blah.txt")
725 d.addCallback(self.failUnlessIsBarDotTxt)
726 d.addCallback(lambda res: self.GET(base + "/blah.txt"))
727 d.addCallback(self.failUnlessIsBarDotTxt)
728 d.addCallback(lambda res: self.GET(base + "/ignore/lots/blah.txt"))
729 d.addCallback(self.failUnlessIsBarDotTxt)
730 d.addCallback(lambda res: self.GET(base2 + "/@@name=/blah.txt"))
731 d.addCallback(self.failUnlessIsBarDotTxt)
732 save_url = base + "?save=true&filename=blah.txt"
733 d.addCallback(lambda res: self.GET(save_url))
734 d.addCallback(self.failUnlessIsBarDotTxt) # TODO: check headers
735 u_filename = u"n\u00e9wer.txt" # n e-acute w e r . t x t
736 u_fn_e = urllib.quote(u_filename.encode("utf-8"))
737 u_url = base + "?save=true&filename=" + u_fn_e
738 d.addCallback(lambda res: self.GET(u_url))
739 d.addCallback(self.failUnlessIsBarDotTxt) # TODO: check headers
742 def test_PUT_FILEURL_named_bad(self):
743 base = "/file/%s" % urllib.quote(self._bar_txt_uri)
744 d = self.shouldFail2(error.Error, "test_PUT_FILEURL_named_bad",
746 "/file can only be used with GET or HEAD",
747 self.PUT, base + "/@@name=/blah.txt", "")
750 def test_GET_DIRURL_named_bad(self):
751 base = "/file/%s" % urllib.quote(self._foo_uri)
752 d = self.shouldFail2(error.Error, "test_PUT_DIRURL_named_bad",
755 self.GET, base + "/@@name=/blah.txt")
758 def test_GET_slash_file_bad(self):
759 d = self.shouldFail2(error.Error, "test_GET_slash_file_bad",
761 "/file must be followed by a file-cap and a name",
765 def test_GET_unhandled_URI_named(self):
766 contents, n, newuri = self.makefile(12)
767 verifier_cap = n.get_verify_cap().to_string()
768 base = "/file/%s" % urllib.quote(verifier_cap)
769 # client.create_node_from_uri() can't handle verify-caps
770 d = self.shouldFail2(error.Error, "GET_unhandled_URI_named",
771 "400 Bad Request", "is not a file-cap",
775 def test_GET_unhandled_URI(self):
776 contents, n, newuri = self.makefile(12)
777 verifier_cap = n.get_verify_cap().to_string()
778 base = "/uri/%s" % urllib.quote(verifier_cap)
779 # client.create_node_from_uri() can't handle verify-caps
780 d = self.shouldFail2(error.Error, "test_GET_unhandled_URI",
782 "GET unknown URI type: can only do t=info",
786 def test_GET_FILE_URI(self):
787 base = "/uri/%s" % urllib.quote(self._bar_txt_uri)
789 d.addCallback(self.failUnlessIsBarDotTxt)
792 def test_GET_FILE_URI_badchild(self):
793 base = "/uri/%s/boguschild" % urllib.quote(self._bar_txt_uri)
794 errmsg = "Files have no children, certainly not named 'boguschild'"
795 d = self.shouldFail2(error.Error, "test_GET_FILE_URI_badchild",
796 "400 Bad Request", errmsg,
800 def test_PUT_FILE_URI_badchild(self):
801 base = "/uri/%s/boguschild" % urllib.quote(self._bar_txt_uri)
802 errmsg = "Cannot create directory 'boguschild', because its parent is a file, not a directory"
803 d = self.shouldFail2(error.Error, "test_GET_FILE_URI_badchild",
804 "400 Bad Request", errmsg,
808 # TODO: version of this with a Unicode filename
809 def test_GET_FILEURL_save(self):
810 d = self.GET(self.public_url + "/foo/bar.txt?filename=bar.txt&save=true",
811 return_response=True)
812 def _got((res, statuscode, headers)):
813 content_disposition = headers["content-disposition"][0]
814 self.failUnless(content_disposition == 'attachment; filename="bar.txt"', content_disposition)
815 self.failUnlessIsBarDotTxt(res)
819 def test_GET_FILEURL_missing(self):
820 d = self.GET(self.public_url + "/foo/missing")
821 d.addBoth(self.should404, "test_GET_FILEURL_missing")
824 def test_PUT_overwrite_only_files(self):
825 # create a directory, put a file in that directory.
826 contents, n, filecap = self.makefile(8)
827 d = self.PUT(self.public_url + "/foo/dir?t=mkdir", "")
828 d.addCallback(lambda res:
829 self.PUT(self.public_url + "/foo/dir/file1.txt",
830 self.NEWFILE_CONTENTS))
831 # try to overwrite the file with replace=only-files
833 d.addCallback(lambda res:
834 self.PUT(self.public_url + "/foo/dir/file1.txt?t=uri&replace=only-files",
836 d.addCallback(lambda res:
837 self.shouldFail2(error.Error, "PUT_bad_t", "409 Conflict",
838 "There was already a child by that name, and you asked me "
840 self.PUT, self.public_url + "/foo/dir?t=uri&replace=only-files",
844 def test_PUT_NEWFILEURL(self):
845 d = self.PUT(self.public_url + "/foo/new.txt", self.NEWFILE_CONTENTS)
846 # TODO: we lose the response code, so we can't check this
847 #self.failUnlessReallyEqual(responsecode, 201)
848 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"new.txt")
849 d.addCallback(lambda res:
850 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
851 self.NEWFILE_CONTENTS))
854 def test_PUT_NEWFILEURL_not_mutable(self):
855 d = self.PUT(self.public_url + "/foo/new.txt?mutable=false",
856 self.NEWFILE_CONTENTS)
857 # TODO: we lose the response code, so we can't check this
858 #self.failUnlessReallyEqual(responsecode, 201)
859 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"new.txt")
860 d.addCallback(lambda res:
861 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
862 self.NEWFILE_CONTENTS))
865 def test_PUT_NEWFILEURL_range_bad(self):
866 headers = {"content-range": "bytes 1-10/%d" % len(self.NEWFILE_CONTENTS)}
867 target = self.public_url + "/foo/new.txt"
868 d = self.shouldFail2(error.Error, "test_PUT_NEWFILEURL_range_bad",
869 "501 Not Implemented",
870 "Content-Range in PUT not yet supported",
871 # (and certainly not for immutable files)
872 self.PUT, target, self.NEWFILE_CONTENTS[1:11],
874 d.addCallback(lambda res:
875 self.failIfNodeHasChild(self._foo_node, u"new.txt"))
878 def test_PUT_NEWFILEURL_mutable(self):
879 d = self.PUT(self.public_url + "/foo/new.txt?mutable=true",
880 self.NEWFILE_CONTENTS)
881 # TODO: we lose the response code, so we can't check this
882 #self.failUnlessReallyEqual(responsecode, 201)
884 u = uri.from_string_mutable_filenode(res)
885 self.failUnless(u.is_mutable())
886 self.failIf(u.is_readonly())
888 d.addCallback(_check_uri)
889 d.addCallback(self.failUnlessURIMatchesRWChild, self._foo_node, u"new.txt")
890 d.addCallback(lambda res:
891 self.failUnlessMutableChildContentsAre(self._foo_node,
893 self.NEWFILE_CONTENTS))
896 def test_PUT_NEWFILEURL_mutable_toobig(self):
897 d = self.shouldFail2(error.Error, "test_PUT_NEWFILEURL_mutable_toobig",
898 "413 Request Entity Too Large",
899 "SDMF is limited to one segment, and 10001 > 10000",
901 self.public_url + "/foo/new.txt?mutable=true",
902 "b" * (self.s.MUTABLE_SIZELIMIT+1))
905 def test_PUT_NEWFILEURL_replace(self):
906 d = self.PUT(self.public_url + "/foo/bar.txt", self.NEWFILE_CONTENTS)
907 # TODO: we lose the response code, so we can't check this
908 #self.failUnlessReallyEqual(responsecode, 200)
909 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"bar.txt")
910 d.addCallback(lambda res:
911 self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
912 self.NEWFILE_CONTENTS))
915 def test_PUT_NEWFILEURL_bad_t(self):
916 d = self.shouldFail2(error.Error, "PUT_bad_t", "400 Bad Request",
917 "PUT to a file: bad t=bogus",
918 self.PUT, self.public_url + "/foo/bar.txt?t=bogus",
922 def test_PUT_NEWFILEURL_no_replace(self):
923 d = self.PUT(self.public_url + "/foo/bar.txt?replace=false",
924 self.NEWFILE_CONTENTS)
925 d.addBoth(self.shouldFail, error.Error, "PUT_NEWFILEURL_no_replace",
927 "There was already a child by that name, and you asked me "
931 def test_PUT_NEWFILEURL_mkdirs(self):
932 d = self.PUT(self.public_url + "/foo/newdir/new.txt", self.NEWFILE_CONTENTS)
934 d.addCallback(self.failUnlessURIMatchesROChild, fn, u"newdir/new.txt")
935 d.addCallback(lambda res: self.failIfNodeHasChild(fn, u"new.txt"))
936 d.addCallback(lambda res: self.failUnlessNodeHasChild(fn, u"newdir"))
937 d.addCallback(lambda res:
938 self.failUnlessChildContentsAre(fn, u"newdir/new.txt",
939 self.NEWFILE_CONTENTS))
942 def test_PUT_NEWFILEURL_blocked(self):
943 d = self.PUT(self.public_url + "/foo/blockingfile/new.txt",
944 self.NEWFILE_CONTENTS)
945 d.addBoth(self.shouldFail, error.Error, "PUT_NEWFILEURL_blocked",
947 "Unable to create directory 'blockingfile': a file was in the way")
950 def test_PUT_NEWFILEURL_emptyname(self):
951 # an empty pathname component (i.e. a double-slash) is disallowed
952 d = self.shouldFail2(error.Error, "test_PUT_NEWFILEURL_emptyname",
954 "The webapi does not allow empty pathname components",
955 self.PUT, self.public_url + "/foo//new.txt", "")
958 def test_DELETE_FILEURL(self):
959 d = self.DELETE(self.public_url + "/foo/bar.txt")
960 d.addCallback(lambda res:
961 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
964 def test_DELETE_FILEURL_missing(self):
965 d = self.DELETE(self.public_url + "/foo/missing")
966 d.addBoth(self.should404, "test_DELETE_FILEURL_missing")
969 def test_DELETE_FILEURL_missing2(self):
970 d = self.DELETE(self.public_url + "/missing/missing")
971 d.addBoth(self.should404, "test_DELETE_FILEURL_missing2")
974 def failUnlessHasBarDotTxtMetadata(self, res):
975 data = simplejson.loads(res)
976 self.failUnless(isinstance(data, list))
977 self.failUnlessIn("metadata", data[1])
978 self.failUnlessIn("tahoe", data[1]["metadata"])
979 self.failUnlessIn("linkcrtime", data[1]["metadata"]["tahoe"])
980 self.failUnlessIn("linkmotime", data[1]["metadata"]["tahoe"])
981 self.failUnlessReallyEqual(data[1]["metadata"]["tahoe"]["linkcrtime"],
982 self._bar_txt_metadata["tahoe"]["linkcrtime"])
984 def test_GET_FILEURL_json(self):
985 # twisted.web.http.parse_qs ignores any query args without an '=', so
986 # I can't do "GET /path?json", I have to do "GET /path/t=json"
987 # instead. This may make it tricky to emulate the S3 interface
989 d = self.GET(self.public_url + "/foo/bar.txt?t=json")
991 self.failUnlessIsBarJSON(data)
992 self.failUnlessHasBarDotTxtMetadata(data)
994 d.addCallback(_check1)
997 def test_GET_FILEURL_json_missing(self):
998 d = self.GET(self.public_url + "/foo/missing?json")
999 d.addBoth(self.should404, "test_GET_FILEURL_json_missing")
1002 def test_GET_FILEURL_uri(self):
1003 d = self.GET(self.public_url + "/foo/bar.txt?t=uri")
1005 self.failUnlessReallyEqual(res, self._bar_txt_uri)
1006 d.addCallback(_check)
1007 d.addCallback(lambda res:
1008 self.GET(self.public_url + "/foo/bar.txt?t=readonly-uri"))
1010 # for now, for files, uris and readonly-uris are the same
1011 self.failUnlessReallyEqual(res, self._bar_txt_uri)
1012 d.addCallback(_check2)
1015 def test_GET_FILEURL_badtype(self):
1016 d = self.shouldHTTPError("GET t=bogus", 400, "Bad Request",
1019 self.public_url + "/foo/bar.txt?t=bogus")
1022 def test_CSS_FILE(self):
1023 d = self.GET("/tahoe_css", followRedirect=True)
1025 CSS_STYLE=re.compile('toolbar\s{.+text-align:\scenter.+toolbar-item.+display:\sinline',re.DOTALL)
1026 self.failUnless(CSS_STYLE.search(res), res)
1027 d.addCallback(_check)
1030 def test_GET_FILEURL_uri_missing(self):
1031 d = self.GET(self.public_url + "/foo/missing?t=uri")
1032 d.addBoth(self.should404, "test_GET_FILEURL_uri_missing")
1035 def test_GET_DIRECTORY_html_banner(self):
1036 d = self.GET(self.public_url + "/foo", followRedirect=True)
1038 self.failUnlessIn('<div class="toolbar-item"><a href="../../..">Return to Welcome page</a></div>',res)
1039 d.addCallback(_check)
1042 def test_GET_DIRURL(self):
1043 # the addSlash means we get a redirect here
1044 # from /uri/$URI/foo/ , we need ../../../ to get back to the root
1046 d = self.GET(self.public_url + "/foo", followRedirect=True)
1048 self.failUnless(('<a href="%s">Return to Welcome page' % ROOT)
1050 # the FILE reference points to a URI, but it should end in bar.txt
1051 bar_url = ("%s/file/%s/@@named=/bar.txt" %
1052 (ROOT, urllib.quote(self._bar_txt_uri)))
1053 get_bar = "".join([r'<td>FILE</td>',
1055 r'<a href="%s">bar.txt</a>' % bar_url,
1057 r'\s+<td>%d</td>' % len(self.BAR_CONTENTS),
1059 self.failUnless(re.search(get_bar, res), res)
1060 for line in res.split("\n"):
1061 # find the line that contains the delete button for bar.txt
1062 if ("form action" in line and
1063 'value="delete"' in line and
1064 'value="bar.txt"' in line):
1065 # the form target should use a relative URL
1066 foo_url = urllib.quote("%s/uri/%s/" % (ROOT, self._foo_uri))
1067 self.failUnless(('action="%s"' % foo_url) in line, line)
1068 # and the when_done= should too
1069 #done_url = urllib.quote(???)
1070 #self.failUnless(('name="when_done" value="%s"' % done_url)
1074 self.fail("unable to find delete-bar.txt line", res)
1076 # the DIR reference just points to a URI
1077 sub_url = ("%s/uri/%s/" % (ROOT, urllib.quote(self._sub_uri)))
1078 get_sub = ((r'<td>DIR</td>')
1079 +r'\s+<td><a href="%s">sub</a></td>' % sub_url)
1080 self.failUnless(re.search(get_sub, res), res)
1081 d.addCallback(_check)
1083 # look at a readonly directory
1084 d.addCallback(lambda res:
1085 self.GET(self.public_url + "/reedownlee", followRedirect=True))
1087 self.failUnless("(read-only)" in res, res)
1088 self.failIf("Upload a file" in res, res)
1089 d.addCallback(_check2)
1091 # and at a directory that contains a readonly directory
1092 d.addCallback(lambda res:
1093 self.GET(self.public_url, followRedirect=True))
1095 self.failUnless(re.search('<td>DIR-RO</td>'
1096 r'\s+<td><a href="[\.\/]+/uri/URI%3ADIR2-RO%3A[^"]+">reedownlee</a></td>', res), res)
1097 d.addCallback(_check3)
1099 # and an empty directory
1100 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty/"))
1102 self.failUnless("directory is empty" in res, res)
1103 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)
1104 self.failUnless(MKDIR_BUTTON_RE.search(res), res)
1105 d.addCallback(_check4)
1107 # and at a literal directory
1108 tiny_litdir_uri = "URI:DIR2-LIT:gqytunj2onug64tufqzdcosvkjetutcjkq5gw4tvm5vwszdgnz5hgyzufqydulbshj5x2lbm" # contains one child which is itself also LIT
1109 d.addCallback(lambda res:
1110 self.GET("/uri/" + tiny_litdir_uri + "/", followRedirect=True))
1112 self.failUnless('(immutable)' in res, res)
1113 self.failUnless(re.search('<td>FILE</td>'
1114 r'\s+<td><a href="[\.\/]+/file/URI%3ALIT%3Akrugkidfnzsc4/@@named=/short">short</a></td>', res), res)
1115 d.addCallback(_check5)
1118 def test_GET_DIRURL_badtype(self):
1119 d = self.shouldHTTPError("test_GET_DIRURL_badtype",
1123 self.public_url + "/foo?t=bogus")
1126 def test_GET_DIRURL_json(self):
1127 d = self.GET(self.public_url + "/foo?t=json")
1128 d.addCallback(self.failUnlessIsFooJSON)
1132 def test_POST_DIRURL_manifest_no_ophandle(self):
1133 d = self.shouldFail2(error.Error,
1134 "test_POST_DIRURL_manifest_no_ophandle",
1136 "slow operation requires ophandle=",
1137 self.POST, self.public_url, t="start-manifest")
1140 def test_POST_DIRURL_manifest(self):
1141 d = defer.succeed(None)
1142 def getman(ignored, output):
1143 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=125",
1144 followRedirect=True)
1145 d.addCallback(self.wait_for_operation, "125")
1146 d.addCallback(self.get_operation_results, "125", output)
1148 d.addCallback(getman, None)
1149 def _got_html(manifest):
1150 self.failUnless("Manifest of SI=" in manifest)
1151 self.failUnless("<td>sub</td>" in manifest)
1152 self.failUnless(self._sub_uri in manifest)
1153 self.failUnless("<td>sub/baz.txt</td>" in manifest)
1154 d.addCallback(_got_html)
1156 # both t=status and unadorned GET should be identical
1157 d.addCallback(lambda res: self.GET("/operations/125"))
1158 d.addCallback(_got_html)
1160 d.addCallback(getman, "html")
1161 d.addCallback(_got_html)
1162 d.addCallback(getman, "text")
1163 def _got_text(manifest):
1164 self.failUnless("\nsub " + self._sub_uri + "\n" in manifest)
1165 self.failUnless("\nsub/baz.txt URI:CHK:" in manifest)
1166 d.addCallback(_got_text)
1167 d.addCallback(getman, "JSON")
1169 data = res["manifest"]
1171 for (path_list, cap) in data:
1172 got[tuple(path_list)] = cap
1173 self.failUnlessReallyEqual(to_str(got[(u"sub",)]), self._sub_uri)
1174 self.failUnless((u"sub",u"baz.txt") in got)
1175 self.failUnless("finished" in res)
1176 self.failUnless("origin" in res)
1177 self.failUnless("storage-index" in res)
1178 self.failUnless("verifycaps" in res)
1179 self.failUnless("stats" in res)
1180 d.addCallback(_got_json)
1183 def test_POST_DIRURL_deepsize_no_ophandle(self):
1184 d = self.shouldFail2(error.Error,
1185 "test_POST_DIRURL_deepsize_no_ophandle",
1187 "slow operation requires ophandle=",
1188 self.POST, self.public_url, t="start-deep-size")
1191 def test_POST_DIRURL_deepsize(self):
1192 d = self.POST(self.public_url + "/foo/?t=start-deep-size&ophandle=126",
1193 followRedirect=True)
1194 d.addCallback(self.wait_for_operation, "126")
1195 d.addCallback(self.get_operation_results, "126", "json")
1196 def _got_json(data):
1197 self.failUnlessReallyEqual(data["finished"], True)
1199 self.failUnless(size > 1000)
1200 d.addCallback(_got_json)
1201 d.addCallback(self.get_operation_results, "126", "text")
1203 mo = re.search(r'^size: (\d+)$', res, re.M)
1204 self.failUnless(mo, res)
1205 size = int(mo.group(1))
1206 # with directories, the size varies.
1207 self.failUnless(size > 1000)
1208 d.addCallback(_got_text)
1211 def test_POST_DIRURL_deepstats_no_ophandle(self):
1212 d = self.shouldFail2(error.Error,
1213 "test_POST_DIRURL_deepstats_no_ophandle",
1215 "slow operation requires ophandle=",
1216 self.POST, self.public_url, t="start-deep-stats")
1219 def test_POST_DIRURL_deepstats(self):
1220 d = self.POST(self.public_url + "/foo/?t=start-deep-stats&ophandle=127",
1221 followRedirect=True)
1222 d.addCallback(self.wait_for_operation, "127")
1223 d.addCallback(self.get_operation_results, "127", "json")
1224 def _got_json(stats):
1225 expected = {"count-immutable-files": 3,
1226 "count-mutable-files": 0,
1227 "count-literal-files": 0,
1229 "count-directories": 3,
1230 "size-immutable-files": 57,
1231 "size-literal-files": 0,
1232 #"size-directories": 1912, # varies
1233 #"largest-directory": 1590,
1234 "largest-directory-children": 5,
1235 "largest-immutable-file": 19,
1237 for k,v in expected.iteritems():
1238 self.failUnlessReallyEqual(stats[k], v,
1239 "stats[%s] was %s, not %s" %
1241 self.failUnlessReallyEqual(stats["size-files-histogram"],
1243 d.addCallback(_got_json)
1246 def test_POST_DIRURL_stream_manifest(self):
1247 d = self.POST(self.public_url + "/foo/?t=stream-manifest")
1249 self.failUnless(res.endswith("\n"))
1250 units = [simplejson.loads(t) for t in res[:-1].split("\n")]
1251 self.failUnlessReallyEqual(len(units), 7)
1252 self.failUnlessEqual(units[-1]["type"], "stats")
1254 self.failUnlessEqual(first["path"], [])
1255 self.failUnlessReallyEqual(to_str(first["cap"]), self._foo_uri)
1256 self.failUnlessEqual(first["type"], "directory")
1257 baz = [u for u in units[:-1] if to_str(u["cap"]) == self._baz_file_uri][0]
1258 self.failUnlessEqual(baz["path"], ["sub", "baz.txt"])
1259 self.failIfEqual(baz["storage-index"], None)
1260 self.failIfEqual(baz["verifycap"], None)
1261 self.failIfEqual(baz["repaircap"], None)
1263 d.addCallback(_check)
1266 def test_GET_DIRURL_uri(self):
1267 d = self.GET(self.public_url + "/foo?t=uri")
1269 self.failUnlessReallyEqual(to_str(res), self._foo_uri)
1270 d.addCallback(_check)
1273 def test_GET_DIRURL_readonly_uri(self):
1274 d = self.GET(self.public_url + "/foo?t=readonly-uri")
1276 self.failUnlessReallyEqual(to_str(res), self._foo_readonly_uri)
1277 d.addCallback(_check)
1280 def test_PUT_NEWDIRURL(self):
1281 d = self.PUT(self.public_url + "/foo/newdir?t=mkdir", "")
1282 d.addCallback(lambda res:
1283 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1284 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1285 d.addCallback(self.failUnlessNodeKeysAre, [])
1288 def test_POST_NEWDIRURL(self):
1289 d = self.POST2(self.public_url + "/foo/newdir?t=mkdir", "")
1290 d.addCallback(lambda res:
1291 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1292 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1293 d.addCallback(self.failUnlessNodeKeysAre, [])
1296 def test_POST_NEWDIRURL_emptyname(self):
1297 # an empty pathname component (i.e. a double-slash) is disallowed
1298 d = self.shouldFail2(error.Error, "test_POST_NEWDIRURL_emptyname",
1300 "The webapi does not allow empty pathname components, i.e. a double slash",
1301 self.POST, self.public_url + "//?t=mkdir")
1304 def test_POST_NEWDIRURL_initial_children(self):
1305 (newkids, caps) = self._create_initial_children()
1306 d = self.POST2(self.public_url + "/foo/newdir?t=mkdir-with-children",
1307 simplejson.dumps(newkids))
1309 n = self.s.create_node_from_uri(uri.strip())
1310 d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
1311 d2.addCallback(lambda ign:
1312 self.failUnlessROChildURIIs(n, u"child-imm",
1314 d2.addCallback(lambda ign:
1315 self.failUnlessRWChildURIIs(n, u"child-mutable",
1317 d2.addCallback(lambda ign:
1318 self.failUnlessROChildURIIs(n, u"child-mutable-ro",
1320 d2.addCallback(lambda ign:
1321 self.failUnlessROChildURIIs(n, u"unknownchild-ro",
1322 caps['unknown_rocap']))
1323 d2.addCallback(lambda ign:
1324 self.failUnlessRWChildURIIs(n, u"unknownchild-rw",
1325 caps['unknown_rwcap']))
1326 d2.addCallback(lambda ign:
1327 self.failUnlessROChildURIIs(n, u"unknownchild-imm",
1328 caps['unknown_immcap']))
1329 d2.addCallback(lambda ign:
1330 self.failUnlessRWChildURIIs(n, u"dirchild",
1332 d2.addCallback(lambda ign:
1333 self.failUnlessROChildURIIs(n, u"dirchild-lit",
1335 d2.addCallback(lambda ign:
1336 self.failUnlessROChildURIIs(n, u"dirchild-empty",
1337 caps['emptydircap']))
1339 d.addCallback(_check)
1340 d.addCallback(lambda res:
1341 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1342 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1343 d.addCallback(self.failUnlessNodeKeysAre, newkids.keys())
1344 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1345 d.addCallback(self.failUnlessROChildURIIs, u"child-imm", caps['filecap1'])
1348 def test_POST_NEWDIRURL_immutable(self):
1349 (newkids, caps) = self._create_immutable_children()
1350 d = self.POST2(self.public_url + "/foo/newdir?t=mkdir-immutable",
1351 simplejson.dumps(newkids))
1353 n = self.s.create_node_from_uri(uri.strip())
1354 d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
1355 d2.addCallback(lambda ign:
1356 self.failUnlessROChildURIIs(n, u"child-imm",
1358 d2.addCallback(lambda ign:
1359 self.failUnlessROChildURIIs(n, u"unknownchild-imm",
1360 caps['unknown_immcap']))
1361 d2.addCallback(lambda ign:
1362 self.failUnlessROChildURIIs(n, u"dirchild-imm",
1364 d2.addCallback(lambda ign:
1365 self.failUnlessROChildURIIs(n, u"dirchild-lit",
1367 d2.addCallback(lambda ign:
1368 self.failUnlessROChildURIIs(n, u"dirchild-empty",
1369 caps['emptydircap']))
1371 d.addCallback(_check)
1372 d.addCallback(lambda res:
1373 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1374 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1375 d.addCallback(self.failUnlessNodeKeysAre, newkids.keys())
1376 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1377 d.addCallback(self.failUnlessROChildURIIs, u"child-imm", caps['filecap1'])
1378 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1379 d.addCallback(self.failUnlessROChildURIIs, u"unknownchild-imm", caps['unknown_immcap'])
1380 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1381 d.addCallback(self.failUnlessROChildURIIs, u"dirchild-imm", caps['immdircap'])
1382 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1383 d.addCallback(self.failUnlessROChildURIIs, u"dirchild-lit", caps['litdircap'])
1384 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1385 d.addCallback(self.failUnlessROChildURIIs, u"dirchild-empty", caps['emptydircap'])
1386 d.addErrback(self.explain_web_error)
1389 def test_POST_NEWDIRURL_immutable_bad(self):
1390 (newkids, caps) = self._create_initial_children()
1391 d = self.shouldFail2(error.Error, "test_POST_NEWDIRURL_immutable_bad",
1393 "needed to be immutable but was not",
1395 self.public_url + "/foo/newdir?t=mkdir-immutable",
1396 simplejson.dumps(newkids))
1399 def test_PUT_NEWDIRURL_exists(self):
1400 d = self.PUT(self.public_url + "/foo/sub?t=mkdir", "")
1401 d.addCallback(lambda res:
1402 self.failUnlessNodeHasChild(self._foo_node, u"sub"))
1403 d.addCallback(lambda res: self._foo_node.get(u"sub"))
1404 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
1407 def test_PUT_NEWDIRURL_blocked(self):
1408 d = self.shouldFail2(error.Error, "PUT_NEWDIRURL_blocked",
1409 "409 Conflict", "Unable to create directory 'bar.txt': a file was in the way",
1411 self.public_url + "/foo/bar.txt/sub?t=mkdir", "")
1412 d.addCallback(lambda res:
1413 self.failUnlessNodeHasChild(self._foo_node, u"sub"))
1414 d.addCallback(lambda res: self._foo_node.get(u"sub"))
1415 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
1418 def test_PUT_NEWDIRURL_mkdir_p(self):
1419 d = defer.succeed(None)
1420 d.addCallback(lambda res: self.POST(self.public_url + "/foo", t='mkdir', name='mkp'))
1421 d.addCallback(lambda res: self.failUnlessNodeHasChild(self._foo_node, u"mkp"))
1422 d.addCallback(lambda res: self._foo_node.get(u"mkp"))
1423 def mkdir_p(mkpnode):
1424 url = '/uri/%s?t=mkdir-p&path=/sub1/sub2' % urllib.quote(mkpnode.get_uri())
1426 def made_subsub(ssuri):
1427 d = self._foo_node.get_child_at_path(u"mkp/sub1/sub2")
1428 d.addCallback(lambda ssnode: self.failUnlessReallyEqual(ssnode.get_uri(), ssuri))
1430 d.addCallback(lambda uri2: self.failUnlessReallyEqual(uri2, ssuri))
1432 d.addCallback(made_subsub)
1434 d.addCallback(mkdir_p)
1437 def test_PUT_NEWDIRURL_mkdirs(self):
1438 d = self.PUT(self.public_url + "/foo/subdir/newdir?t=mkdir", "")
1439 d.addCallback(lambda res:
1440 self.failIfNodeHasChild(self._foo_node, u"newdir"))
1441 d.addCallback(lambda res:
1442 self.failUnlessNodeHasChild(self._foo_node, u"subdir"))
1443 d.addCallback(lambda res:
1444 self._foo_node.get_child_at_path(u"subdir/newdir"))
1445 d.addCallback(self.failUnlessNodeKeysAre, [])
1448 def test_DELETE_DIRURL(self):
1449 d = self.DELETE(self.public_url + "/foo")
1450 d.addCallback(lambda res:
1451 self.failIfNodeHasChild(self.public_root, u"foo"))
1454 def test_DELETE_DIRURL_missing(self):
1455 d = self.DELETE(self.public_url + "/foo/missing")
1456 d.addBoth(self.should404, "test_DELETE_DIRURL_missing")
1457 d.addCallback(lambda res:
1458 self.failUnlessNodeHasChild(self.public_root, u"foo"))
1461 def test_DELETE_DIRURL_missing2(self):
1462 d = self.DELETE(self.public_url + "/missing")
1463 d.addBoth(self.should404, "test_DELETE_DIRURL_missing2")
1466 def dump_root(self):
1468 w = webish.DirnodeWalkerMixin()
1469 def visitor(childpath, childnode, metadata):
1471 d = w.walk(self.public_root, visitor)
1474 def failUnlessNodeKeysAre(self, node, expected_keys):
1475 for k in expected_keys:
1476 assert isinstance(k, unicode)
1478 def _check(children):
1479 self.failUnlessReallyEqual(sorted(children.keys()), sorted(expected_keys))
1480 d.addCallback(_check)
1482 def failUnlessNodeHasChild(self, node, name):
1483 assert isinstance(name, unicode)
1485 def _check(children):
1486 self.failUnless(name in children)
1487 d.addCallback(_check)
1489 def failIfNodeHasChild(self, node, name):
1490 assert isinstance(name, unicode)
1492 def _check(children):
1493 self.failIf(name in children)
1494 d.addCallback(_check)
1497 def failUnlessChildContentsAre(self, node, name, expected_contents):
1498 assert isinstance(name, unicode)
1499 d = node.get_child_at_path(name)
1500 d.addCallback(lambda node: download_to_data(node))
1501 def _check(contents):
1502 self.failUnlessReallyEqual(contents, expected_contents)
1503 d.addCallback(_check)
1506 def failUnlessMutableChildContentsAre(self, node, name, expected_contents):
1507 assert isinstance(name, unicode)
1508 d = node.get_child_at_path(name)
1509 d.addCallback(lambda node: node.download_best_version())
1510 def _check(contents):
1511 self.failUnlessReallyEqual(contents, expected_contents)
1512 d.addCallback(_check)
1515 def failUnlessRWChildURIIs(self, node, name, expected_uri):
1516 assert isinstance(name, unicode)
1517 d = node.get_child_at_path(name)
1519 self.failUnless(child.is_unknown() or not child.is_readonly())
1520 self.failUnlessReallyEqual(child.get_uri(), expected_uri.strip())
1521 self.failUnlessReallyEqual(child.get_write_uri(), expected_uri.strip())
1522 expected_ro_uri = self._make_readonly(expected_uri)
1524 self.failUnlessReallyEqual(child.get_readonly_uri(), expected_ro_uri.strip())
1525 d.addCallback(_check)
1528 def failUnlessROChildURIIs(self, node, name, expected_uri):
1529 assert isinstance(name, unicode)
1530 d = node.get_child_at_path(name)
1532 self.failUnless(child.is_unknown() or child.is_readonly())
1533 self.failUnlessReallyEqual(child.get_write_uri(), None)
1534 self.failUnlessReallyEqual(child.get_uri(), expected_uri.strip())
1535 self.failUnlessReallyEqual(child.get_readonly_uri(), expected_uri.strip())
1536 d.addCallback(_check)
1539 def failUnlessURIMatchesRWChild(self, got_uri, node, name):
1540 assert isinstance(name, unicode)
1541 d = node.get_child_at_path(name)
1543 self.failUnless(child.is_unknown() or not child.is_readonly())
1544 self.failUnlessReallyEqual(child.get_uri(), got_uri.strip())
1545 self.failUnlessReallyEqual(child.get_write_uri(), got_uri.strip())
1546 expected_ro_uri = self._make_readonly(got_uri)
1548 self.failUnlessReallyEqual(child.get_readonly_uri(), expected_ro_uri.strip())
1549 d.addCallback(_check)
1552 def failUnlessURIMatchesROChild(self, got_uri, node, name):
1553 assert isinstance(name, unicode)
1554 d = node.get_child_at_path(name)
1556 self.failUnless(child.is_unknown() or child.is_readonly())
1557 self.failUnlessReallyEqual(child.get_write_uri(), None)
1558 self.failUnlessReallyEqual(got_uri.strip(), child.get_uri())
1559 self.failUnlessReallyEqual(got_uri.strip(), child.get_readonly_uri())
1560 d.addCallback(_check)
1563 def failUnlessCHKURIHasContents(self, got_uri, contents):
1564 self.failUnless(FakeCHKFileNode.all_contents[got_uri] == contents)
1566 def test_POST_upload(self):
1567 d = self.POST(self.public_url + "/foo", t="upload",
1568 file=("new.txt", self.NEWFILE_CONTENTS))
1570 d.addCallback(self.failUnlessURIMatchesROChild, fn, u"new.txt")
1571 d.addCallback(lambda res:
1572 self.failUnlessChildContentsAre(fn, u"new.txt",
1573 self.NEWFILE_CONTENTS))
1576 def test_POST_upload_unicode(self):
1577 filename = u"n\u00e9wer.txt" # n e-acute w e r . t x t
1578 d = self.POST(self.public_url + "/foo", t="upload",
1579 file=(filename, self.NEWFILE_CONTENTS))
1581 d.addCallback(self.failUnlessURIMatchesROChild, fn, filename)
1582 d.addCallback(lambda res:
1583 self.failUnlessChildContentsAre(fn, filename,
1584 self.NEWFILE_CONTENTS))
1585 target_url = self.public_url + "/foo/" + filename.encode("utf-8")
1586 d.addCallback(lambda res: self.GET(target_url))
1587 d.addCallback(lambda contents: self.failUnlessReallyEqual(contents,
1588 self.NEWFILE_CONTENTS,
1592 def test_POST_upload_unicode_named(self):
1593 filename = u"n\u00e9wer.txt" # n e-acute w e r . t x t
1594 d = self.POST(self.public_url + "/foo", t="upload",
1596 file=("overridden", self.NEWFILE_CONTENTS))
1598 d.addCallback(self.failUnlessURIMatchesROChild, fn, filename)
1599 d.addCallback(lambda res:
1600 self.failUnlessChildContentsAre(fn, filename,
1601 self.NEWFILE_CONTENTS))
1602 target_url = self.public_url + "/foo/" + filename.encode("utf-8")
1603 d.addCallback(lambda res: self.GET(target_url))
1604 d.addCallback(lambda contents: self.failUnlessReallyEqual(contents,
1605 self.NEWFILE_CONTENTS,
1609 def test_POST_upload_no_link(self):
1610 d = self.POST("/uri", t="upload",
1611 file=("new.txt", self.NEWFILE_CONTENTS))
1612 def _check_upload_results(page):
1613 # this should be a page which describes the results of the upload
1614 # that just finished.
1615 self.failUnless("Upload Results:" in page)
1616 self.failUnless("URI:" in page)
1617 uri_re = re.compile("URI: <tt><span>(.*)</span>")
1618 mo = uri_re.search(page)
1619 self.failUnless(mo, page)
1620 new_uri = mo.group(1)
1622 d.addCallback(_check_upload_results)
1623 d.addCallback(self.failUnlessCHKURIHasContents, self.NEWFILE_CONTENTS)
1626 def test_POST_upload_no_link_whendone(self):
1627 d = self.POST("/uri", t="upload", when_done="/",
1628 file=("new.txt", self.NEWFILE_CONTENTS))
1629 d.addBoth(self.shouldRedirect, "/")
1632 def shouldRedirect2(self, which, checker, callable, *args, **kwargs):
1633 d = defer.maybeDeferred(callable, *args, **kwargs)
1635 if isinstance(res, failure.Failure):
1636 res.trap(error.PageRedirect)
1637 statuscode = res.value.status
1638 target = res.value.location
1639 return checker(statuscode, target)
1640 self.fail("%s: callable was supposed to redirect, not return '%s'"
1645 def test_POST_upload_no_link_whendone_results(self):
1646 def check(statuscode, target):
1647 self.failUnlessReallyEqual(statuscode, str(http.FOUND))
1648 self.failUnless(target.startswith(self.webish_url), target)
1649 return client.getPage(target, method="GET")
1650 d = self.shouldRedirect2("test_POST_upload_no_link_whendone_results",
1652 self.POST, "/uri", t="upload",
1653 when_done="/uri/%(uri)s",
1654 file=("new.txt", self.NEWFILE_CONTENTS))
1655 d.addCallback(lambda res:
1656 self.failUnlessReallyEqual(res, self.NEWFILE_CONTENTS))
1659 def test_POST_upload_no_link_mutable(self):
1660 d = self.POST("/uri", t="upload", mutable="true",
1661 file=("new.txt", self.NEWFILE_CONTENTS))
1662 def _check(filecap):
1663 filecap = filecap.strip()
1664 self.failUnless(filecap.startswith("URI:SSK:"), filecap)
1665 self.filecap = filecap
1666 u = uri.WriteableSSKFileURI.init_from_string(filecap)
1667 self.failUnless(u.get_storage_index() in FakeMutableFileNode.all_contents)
1668 n = self.s.create_node_from_uri(filecap)
1669 return n.download_best_version()
1670 d.addCallback(_check)
1672 self.failUnlessReallyEqual(data, self.NEWFILE_CONTENTS)
1673 return self.GET("/uri/%s" % urllib.quote(self.filecap))
1674 d.addCallback(_check2)
1676 self.failUnlessReallyEqual(data, self.NEWFILE_CONTENTS)
1677 return self.GET("/file/%s" % urllib.quote(self.filecap))
1678 d.addCallback(_check3)
1680 self.failUnlessReallyEqual(data, self.NEWFILE_CONTENTS)
1681 d.addCallback(_check4)
1684 def test_POST_upload_no_link_mutable_toobig(self):
1685 d = self.shouldFail2(error.Error,
1686 "test_POST_upload_no_link_mutable_toobig",
1687 "413 Request Entity Too Large",
1688 "SDMF is limited to one segment, and 10001 > 10000",
1690 "/uri", t="upload", mutable="true",
1692 "b" * (self.s.MUTABLE_SIZELIMIT+1)) )
1695 def test_POST_upload_mutable(self):
1696 # this creates a mutable file
1697 d = self.POST(self.public_url + "/foo", t="upload", mutable="true",
1698 file=("new.txt", self.NEWFILE_CONTENTS))
1700 d.addCallback(self.failUnlessURIMatchesRWChild, fn, u"new.txt")
1701 d.addCallback(lambda res:
1702 self.failUnlessMutableChildContentsAre(fn, u"new.txt",
1703 self.NEWFILE_CONTENTS))
1704 d.addCallback(lambda res: self._foo_node.get(u"new.txt"))
1706 self.failUnless(IMutableFileNode.providedBy(newnode))
1707 self.failUnless(newnode.is_mutable())
1708 self.failIf(newnode.is_readonly())
1709 self._mutable_node = newnode
1710 self._mutable_uri = newnode.get_uri()
1713 # now upload it again and make sure that the URI doesn't change
1714 NEWER_CONTENTS = self.NEWFILE_CONTENTS + "newer\n"
1715 d.addCallback(lambda res:
1716 self.POST(self.public_url + "/foo", t="upload",
1718 file=("new.txt", NEWER_CONTENTS)))
1719 d.addCallback(self.failUnlessURIMatchesRWChild, fn, u"new.txt")
1720 d.addCallback(lambda res:
1721 self.failUnlessMutableChildContentsAre(fn, u"new.txt",
1723 d.addCallback(lambda res: self._foo_node.get(u"new.txt"))
1725 self.failUnless(IMutableFileNode.providedBy(newnode))
1726 self.failUnless(newnode.is_mutable())
1727 self.failIf(newnode.is_readonly())
1728 self.failUnlessReallyEqual(self._mutable_uri, newnode.get_uri())
1729 d.addCallback(_got2)
1731 # upload a second time, using PUT instead of POST
1732 NEW2_CONTENTS = NEWER_CONTENTS + "overwrite with PUT\n"
1733 d.addCallback(lambda res:
1734 self.PUT(self.public_url + "/foo/new.txt", NEW2_CONTENTS))
1735 d.addCallback(self.failUnlessURIMatchesRWChild, fn, u"new.txt")
1736 d.addCallback(lambda res:
1737 self.failUnlessMutableChildContentsAre(fn, u"new.txt",
1740 # finally list the directory, since mutable files are displayed
1741 # slightly differently
1743 d.addCallback(lambda res:
1744 self.GET(self.public_url + "/foo/",
1745 followRedirect=True))
1746 def _check_page(res):
1747 # TODO: assert more about the contents
1748 self.failUnless("SSK" in res)
1750 d.addCallback(_check_page)
1752 d.addCallback(lambda res: self._foo_node.get(u"new.txt"))
1754 self.failUnless(IMutableFileNode.providedBy(newnode))
1755 self.failUnless(newnode.is_mutable())
1756 self.failIf(newnode.is_readonly())
1757 self.failUnlessReallyEqual(self._mutable_uri, newnode.get_uri())
1758 d.addCallback(_got3)
1760 # look at the JSON form of the enclosing directory
1761 d.addCallback(lambda res:
1762 self.GET(self.public_url + "/foo/?t=json",
1763 followRedirect=True))
1764 def _check_page_json(res):
1765 parsed = simplejson.loads(res)
1766 self.failUnlessEqual(parsed[0], "dirnode")
1767 children = dict( [(unicode(name),value)
1769 in parsed[1]["children"].iteritems()] )
1770 self.failUnless(u"new.txt" in children)
1771 new_json = children[u"new.txt"]
1772 self.failUnlessEqual(new_json[0], "filenode")
1773 self.failUnless(new_json[1]["mutable"])
1774 self.failUnlessReallyEqual(to_str(new_json[1]["rw_uri"]), self._mutable_uri)
1775 ro_uri = self._mutable_node.get_readonly().to_string()
1776 self.failUnlessReallyEqual(to_str(new_json[1]["ro_uri"]), ro_uri)
1777 d.addCallback(_check_page_json)
1779 # and the JSON form of the file
1780 d.addCallback(lambda res:
1781 self.GET(self.public_url + "/foo/new.txt?t=json"))
1782 def _check_file_json(res):
1783 parsed = simplejson.loads(res)
1784 self.failUnlessEqual(parsed[0], "filenode")
1785 self.failUnless(parsed[1]["mutable"])
1786 self.failUnlessReallyEqual(to_str(parsed[1]["rw_uri"]), self._mutable_uri)
1787 ro_uri = self._mutable_node.get_readonly().to_string()
1788 self.failUnlessReallyEqual(to_str(parsed[1]["ro_uri"]), ro_uri)
1789 d.addCallback(_check_file_json)
1791 # and look at t=uri and t=readonly-uri
1792 d.addCallback(lambda res:
1793 self.GET(self.public_url + "/foo/new.txt?t=uri"))
1794 d.addCallback(lambda res: self.failUnlessReallyEqual(res, self._mutable_uri))
1795 d.addCallback(lambda res:
1796 self.GET(self.public_url + "/foo/new.txt?t=readonly-uri"))
1797 def _check_ro_uri(res):
1798 ro_uri = self._mutable_node.get_readonly().to_string()
1799 self.failUnlessReallyEqual(res, ro_uri)
1800 d.addCallback(_check_ro_uri)
1802 # make sure we can get to it from /uri/URI
1803 d.addCallback(lambda res:
1804 self.GET("/uri/%s" % urllib.quote(self._mutable_uri)))
1805 d.addCallback(lambda res:
1806 self.failUnlessReallyEqual(res, NEW2_CONTENTS))
1808 # and that HEAD computes the size correctly
1809 d.addCallback(lambda res:
1810 self.HEAD(self.public_url + "/foo/new.txt",
1811 return_response=True))
1812 def _got_headers((res, status, headers)):
1813 self.failUnlessReallyEqual(res, "")
1814 self.failUnlessReallyEqual(headers["content-length"][0],
1815 str(len(NEW2_CONTENTS)))
1816 self.failUnlessReallyEqual(headers["content-type"], ["text/plain"])
1817 d.addCallback(_got_headers)
1819 # make sure that size errors are displayed correctly for overwrite
1820 d.addCallback(lambda res:
1821 self.shouldFail2(error.Error,
1822 "test_POST_upload_mutable-toobig",
1823 "413 Request Entity Too Large",
1824 "SDMF is limited to one segment, and 10001 > 10000",
1826 self.public_url + "/foo", t="upload",
1829 "b" * (self.s.MUTABLE_SIZELIMIT+1)),
1832 d.addErrback(self.dump_error)
1835 def test_POST_upload_mutable_toobig(self):
1836 d = self.shouldFail2(error.Error,
1837 "test_POST_upload_mutable_toobig",
1838 "413 Request Entity Too Large",
1839 "SDMF is limited to one segment, and 10001 > 10000",
1841 self.public_url + "/foo",
1842 t="upload", mutable="true",
1844 "b" * (self.s.MUTABLE_SIZELIMIT+1)) )
1847 def dump_error(self, f):
1848 # if the web server returns an error code (like 400 Bad Request),
1849 # web.client.getPage puts the HTTP response body into the .response
1850 # attribute of the exception object that it gives back. It does not
1851 # appear in the Failure's repr(), so the ERROR that trial displays
1852 # will be rather terse and unhelpful. addErrback this method to the
1853 # end of your chain to get more information out of these errors.
1854 if f.check(error.Error):
1855 print "web.error.Error:"
1857 print f.value.response
1860 def test_POST_upload_replace(self):
1861 d = self.POST(self.public_url + "/foo", t="upload",
1862 file=("bar.txt", self.NEWFILE_CONTENTS))
1864 d.addCallback(self.failUnlessURIMatchesROChild, fn, u"bar.txt")
1865 d.addCallback(lambda res:
1866 self.failUnlessChildContentsAre(fn, u"bar.txt",
1867 self.NEWFILE_CONTENTS))
1870 def test_POST_upload_no_replace_ok(self):
1871 d = self.POST(self.public_url + "/foo?replace=false", t="upload",
1872 file=("new.txt", self.NEWFILE_CONTENTS))
1873 d.addCallback(lambda res: self.GET(self.public_url + "/foo/new.txt"))
1874 d.addCallback(lambda res: self.failUnlessReallyEqual(res,
1875 self.NEWFILE_CONTENTS))
1878 def test_POST_upload_no_replace_queryarg(self):
1879 d = self.POST(self.public_url + "/foo?replace=false", t="upload",
1880 file=("bar.txt", self.NEWFILE_CONTENTS))
1881 d.addBoth(self.shouldFail, error.Error,
1882 "POST_upload_no_replace_queryarg",
1884 "There was already a child by that name, and you asked me "
1885 "to not replace it")
1886 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
1887 d.addCallback(self.failUnlessIsBarDotTxt)
1890 def test_POST_upload_no_replace_field(self):
1891 d = self.POST(self.public_url + "/foo", t="upload", replace="false",
1892 file=("bar.txt", self.NEWFILE_CONTENTS))
1893 d.addBoth(self.shouldFail, error.Error, "POST_upload_no_replace_field",
1895 "There was already a child by that name, and you asked me "
1896 "to not replace it")
1897 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
1898 d.addCallback(self.failUnlessIsBarDotTxt)
1901 def test_POST_upload_whendone(self):
1902 d = self.POST(self.public_url + "/foo", t="upload", when_done="/THERE",
1903 file=("new.txt", self.NEWFILE_CONTENTS))
1904 d.addBoth(self.shouldRedirect, "/THERE")
1906 d.addCallback(lambda res:
1907 self.failUnlessChildContentsAre(fn, u"new.txt",
1908 self.NEWFILE_CONTENTS))
1911 def test_POST_upload_named(self):
1913 d = self.POST(self.public_url + "/foo", t="upload",
1914 name="new.txt", file=self.NEWFILE_CONTENTS)
1915 d.addCallback(self.failUnlessURIMatchesROChild, fn, u"new.txt")
1916 d.addCallback(lambda res:
1917 self.failUnlessChildContentsAre(fn, u"new.txt",
1918 self.NEWFILE_CONTENTS))
1921 def test_POST_upload_named_badfilename(self):
1922 d = self.POST(self.public_url + "/foo", t="upload",
1923 name="slashes/are/bad.txt", file=self.NEWFILE_CONTENTS)
1924 d.addBoth(self.shouldFail, error.Error,
1925 "test_POST_upload_named_badfilename",
1927 "name= may not contain a slash",
1929 # make sure that nothing was added
1930 d.addCallback(lambda res:
1931 self.failUnlessNodeKeysAre(self._foo_node,
1932 [u"bar.txt", u"blockingfile",
1933 u"empty", u"n\u00fc.txt",
1937 def test_POST_FILEURL_check(self):
1938 bar_url = self.public_url + "/foo/bar.txt"
1939 d = self.POST(bar_url, t="check")
1941 self.failUnless("Healthy :" in res)
1942 d.addCallback(_check)
1943 redir_url = "http://allmydata.org/TARGET"
1944 def _check2(statuscode, target):
1945 self.failUnlessReallyEqual(statuscode, str(http.FOUND))
1946 self.failUnlessReallyEqual(target, redir_url)
1947 d.addCallback(lambda res:
1948 self.shouldRedirect2("test_POST_FILEURL_check",
1952 when_done=redir_url))
1953 d.addCallback(lambda res:
1954 self.POST(bar_url, t="check", return_to=redir_url))
1956 self.failUnless("Healthy :" in res)
1957 self.failUnless("Return to file" in res)
1958 self.failUnless(redir_url in res)
1959 d.addCallback(_check3)
1961 d.addCallback(lambda res:
1962 self.POST(bar_url, t="check", output="JSON"))
1963 def _check_json(res):
1964 data = simplejson.loads(res)
1965 self.failUnless("storage-index" in data)
1966 self.failUnless(data["results"]["healthy"])
1967 d.addCallback(_check_json)
1971 def test_POST_FILEURL_check_and_repair(self):
1972 bar_url = self.public_url + "/foo/bar.txt"
1973 d = self.POST(bar_url, t="check", repair="true")
1975 self.failUnless("Healthy :" in res)
1976 d.addCallback(_check)
1977 redir_url = "http://allmydata.org/TARGET"
1978 def _check2(statuscode, target):
1979 self.failUnlessReallyEqual(statuscode, str(http.FOUND))
1980 self.failUnlessReallyEqual(target, redir_url)
1981 d.addCallback(lambda res:
1982 self.shouldRedirect2("test_POST_FILEURL_check_and_repair",
1985 t="check", repair="true",
1986 when_done=redir_url))
1987 d.addCallback(lambda res:
1988 self.POST(bar_url, t="check", return_to=redir_url))
1990 self.failUnless("Healthy :" in res)
1991 self.failUnless("Return to file" in res)
1992 self.failUnless(redir_url in res)
1993 d.addCallback(_check3)
1996 def test_POST_DIRURL_check(self):
1997 foo_url = self.public_url + "/foo/"
1998 d = self.POST(foo_url, t="check")
2000 self.failUnless("Healthy :" in res, res)
2001 d.addCallback(_check)
2002 redir_url = "http://allmydata.org/TARGET"
2003 def _check2(statuscode, target):
2004 self.failUnlessReallyEqual(statuscode, str(http.FOUND))
2005 self.failUnlessReallyEqual(target, redir_url)
2006 d.addCallback(lambda res:
2007 self.shouldRedirect2("test_POST_DIRURL_check",
2011 when_done=redir_url))
2012 d.addCallback(lambda res:
2013 self.POST(foo_url, t="check", return_to=redir_url))
2015 self.failUnless("Healthy :" in res, res)
2016 self.failUnless("Return to file/directory" in res)
2017 self.failUnless(redir_url in res)
2018 d.addCallback(_check3)
2020 d.addCallback(lambda res:
2021 self.POST(foo_url, t="check", output="JSON"))
2022 def _check_json(res):
2023 data = simplejson.loads(res)
2024 self.failUnless("storage-index" in data)
2025 self.failUnless(data["results"]["healthy"])
2026 d.addCallback(_check_json)
2030 def test_POST_DIRURL_check_and_repair(self):
2031 foo_url = self.public_url + "/foo/"
2032 d = self.POST(foo_url, t="check", repair="true")
2034 self.failUnless("Healthy :" in res, res)
2035 d.addCallback(_check)
2036 redir_url = "http://allmydata.org/TARGET"
2037 def _check2(statuscode, target):
2038 self.failUnlessReallyEqual(statuscode, str(http.FOUND))
2039 self.failUnlessReallyEqual(target, redir_url)
2040 d.addCallback(lambda res:
2041 self.shouldRedirect2("test_POST_DIRURL_check_and_repair",
2044 t="check", repair="true",
2045 when_done=redir_url))
2046 d.addCallback(lambda res:
2047 self.POST(foo_url, t="check", return_to=redir_url))
2049 self.failUnless("Healthy :" in res)
2050 self.failUnless("Return to file/directory" in res)
2051 self.failUnless(redir_url in res)
2052 d.addCallback(_check3)
2055 def wait_for_operation(self, ignored, ophandle):
2056 url = "/operations/" + ophandle
2057 url += "?t=status&output=JSON"
2060 data = simplejson.loads(res)
2061 if not data["finished"]:
2062 d = self.stall(delay=1.0)
2063 d.addCallback(self.wait_for_operation, ophandle)
2069 def get_operation_results(self, ignored, ophandle, output=None):
2070 url = "/operations/" + ophandle
2073 url += "&output=" + output
2076 if output and output.lower() == "json":
2077 return simplejson.loads(res)
2082 def test_POST_DIRURL_deepcheck_no_ophandle(self):
2083 d = self.shouldFail2(error.Error,
2084 "test_POST_DIRURL_deepcheck_no_ophandle",
2086 "slow operation requires ophandle=",
2087 self.POST, self.public_url, t="start-deep-check")
2090 def test_POST_DIRURL_deepcheck(self):
2091 def _check_redirect(statuscode, target):
2092 self.failUnlessReallyEqual(statuscode, str(http.FOUND))
2093 self.failUnless(target.endswith("/operations/123"))
2094 d = self.shouldRedirect2("test_POST_DIRURL_deepcheck", _check_redirect,
2095 self.POST, self.public_url,
2096 t="start-deep-check", ophandle="123")
2097 d.addCallback(self.wait_for_operation, "123")
2098 def _check_json(data):
2099 self.failUnlessReallyEqual(data["finished"], True)
2100 self.failUnlessReallyEqual(data["count-objects-checked"], 8)
2101 self.failUnlessReallyEqual(data["count-objects-healthy"], 8)
2102 d.addCallback(_check_json)
2103 d.addCallback(self.get_operation_results, "123", "html")
2104 def _check_html(res):
2105 self.failUnless("Objects Checked: <span>8</span>" in res)
2106 self.failUnless("Objects Healthy: <span>8</span>" in res)
2107 d.addCallback(_check_html)
2109 d.addCallback(lambda res:
2110 self.GET("/operations/123/"))
2111 d.addCallback(_check_html) # should be the same as without the slash
2113 d.addCallback(lambda res:
2114 self.shouldFail2(error.Error, "one", "404 Not Found",
2115 "No detailed results for SI bogus",
2116 self.GET, "/operations/123/bogus"))
2118 foo_si = self._foo_node.get_storage_index()
2119 foo_si_s = base32.b2a(foo_si)
2120 d.addCallback(lambda res:
2121 self.GET("/operations/123/%s?output=JSON" % foo_si_s))
2122 def _check_foo_json(res):
2123 data = simplejson.loads(res)
2124 self.failUnlessEqual(data["storage-index"], foo_si_s)
2125 self.failUnless(data["results"]["healthy"])
2126 d.addCallback(_check_foo_json)
2129 def test_POST_DIRURL_deepcheck_and_repair(self):
2130 d = self.POST(self.public_url, t="start-deep-check", repair="true",
2131 ophandle="124", output="json", followRedirect=True)
2132 d.addCallback(self.wait_for_operation, "124")
2133 def _check_json(data):
2134 self.failUnlessReallyEqual(data["finished"], True)
2135 self.failUnlessReallyEqual(data["count-objects-checked"], 8)
2136 self.failUnlessReallyEqual(data["count-objects-healthy-pre-repair"], 8)
2137 self.failUnlessReallyEqual(data["count-objects-unhealthy-pre-repair"], 0)
2138 self.failUnlessReallyEqual(data["count-corrupt-shares-pre-repair"], 0)
2139 self.failUnlessReallyEqual(data["count-repairs-attempted"], 0)
2140 self.failUnlessReallyEqual(data["count-repairs-successful"], 0)
2141 self.failUnlessReallyEqual(data["count-repairs-unsuccessful"], 0)
2142 self.failUnlessReallyEqual(data["count-objects-healthy-post-repair"], 8)
2143 self.failUnlessReallyEqual(data["count-objects-unhealthy-post-repair"], 0)
2144 self.failUnlessReallyEqual(data["count-corrupt-shares-post-repair"], 0)
2145 d.addCallback(_check_json)
2146 d.addCallback(self.get_operation_results, "124", "html")
2147 def _check_html(res):
2148 self.failUnless("Objects Checked: <span>8</span>" in res)
2150 self.failUnless("Objects Healthy (before repair): <span>8</span>" in res)
2151 self.failUnless("Objects Unhealthy (before repair): <span>0</span>" in res)
2152 self.failUnless("Corrupt Shares (before repair): <span>0</span>" in res)
2154 self.failUnless("Repairs Attempted: <span>0</span>" in res)
2155 self.failUnless("Repairs Successful: <span>0</span>" in res)
2156 self.failUnless("Repairs Unsuccessful: <span>0</span>" in res)
2158 self.failUnless("Objects Healthy (after repair): <span>8</span>" in res)
2159 self.failUnless("Objects Unhealthy (after repair): <span>0</span>" in res)
2160 self.failUnless("Corrupt Shares (after repair): <span>0</span>" in res)
2161 d.addCallback(_check_html)
2164 def test_POST_FILEURL_bad_t(self):
2165 d = self.shouldFail2(error.Error, "POST_bad_t", "400 Bad Request",
2166 "POST to file: bad t=bogus",
2167 self.POST, self.public_url + "/foo/bar.txt",
2171 def test_POST_mkdir(self): # return value?
2172 d = self.POST(self.public_url + "/foo", t="mkdir", name="newdir")
2173 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2174 d.addCallback(self.failUnlessNodeKeysAre, [])
2177 def test_POST_mkdir_initial_children(self):
2178 (newkids, caps) = self._create_initial_children()
2179 d = self.POST2(self.public_url +
2180 "/foo?t=mkdir-with-children&name=newdir",
2181 simplejson.dumps(newkids))
2182 d.addCallback(lambda res:
2183 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
2184 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2185 d.addCallback(self.failUnlessNodeKeysAre, newkids.keys())
2186 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2187 d.addCallback(self.failUnlessROChildURIIs, u"child-imm", caps['filecap1'])
2190 def test_POST_mkdir_immutable(self):
2191 (newkids, caps) = self._create_immutable_children()
2192 d = self.POST2(self.public_url +
2193 "/foo?t=mkdir-immutable&name=newdir",
2194 simplejson.dumps(newkids))
2195 d.addCallback(lambda res:
2196 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
2197 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2198 d.addCallback(self.failUnlessNodeKeysAre, newkids.keys())
2199 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2200 d.addCallback(self.failUnlessROChildURIIs, u"child-imm", caps['filecap1'])
2201 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2202 d.addCallback(self.failUnlessROChildURIIs, u"unknownchild-imm", caps['unknown_immcap'])
2203 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2204 d.addCallback(self.failUnlessROChildURIIs, u"dirchild-imm", caps['immdircap'])
2205 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2206 d.addCallback(self.failUnlessROChildURIIs, u"dirchild-lit", caps['litdircap'])
2207 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2208 d.addCallback(self.failUnlessROChildURIIs, u"dirchild-empty", caps['emptydircap'])
2211 def test_POST_mkdir_immutable_bad(self):
2212 (newkids, caps) = self._create_initial_children()
2213 d = self.shouldFail2(error.Error, "test_POST_mkdir_immutable_bad",
2215 "needed to be immutable but was not",
2218 "/foo?t=mkdir-immutable&name=newdir",
2219 simplejson.dumps(newkids))
2222 def test_POST_mkdir_2(self):
2223 d = self.POST(self.public_url + "/foo/newdir?t=mkdir", "")
2224 d.addCallback(lambda res:
2225 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
2226 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2227 d.addCallback(self.failUnlessNodeKeysAre, [])
2230 def test_POST_mkdirs_2(self):
2231 d = self.POST(self.public_url + "/foo/bardir/newdir?t=mkdir", "")
2232 d.addCallback(lambda res:
2233 self.failUnlessNodeHasChild(self._foo_node, u"bardir"))
2234 d.addCallback(lambda res: self._foo_node.get(u"bardir"))
2235 d.addCallback(lambda bardirnode: bardirnode.get(u"newdir"))
2236 d.addCallback(self.failUnlessNodeKeysAre, [])
2239 def test_POST_mkdir_no_parentdir_noredirect(self):
2240 d = self.POST("/uri?t=mkdir")
2241 def _after_mkdir(res):
2242 uri.DirectoryURI.init_from_string(res)
2243 d.addCallback(_after_mkdir)
2246 def test_POST_mkdir_no_parentdir_noredirect2(self):
2247 # make sure form-based arguments (as on the welcome page) still work
2248 d = self.POST("/uri", t="mkdir")
2249 def _after_mkdir(res):
2250 uri.DirectoryURI.init_from_string(res)
2251 d.addCallback(_after_mkdir)
2252 d.addErrback(self.explain_web_error)
2255 def test_POST_mkdir_no_parentdir_redirect(self):
2256 d = self.POST("/uri?t=mkdir&redirect_to_result=true")
2257 d.addBoth(self.shouldRedirect, None, statuscode='303')
2258 def _check_target(target):
2259 target = urllib.unquote(target)
2260 self.failUnless(target.startswith("uri/URI:DIR2:"), target)
2261 d.addCallback(_check_target)
2264 def test_POST_mkdir_no_parentdir_redirect2(self):
2265 d = self.POST("/uri", t="mkdir", redirect_to_result="true")
2266 d.addBoth(self.shouldRedirect, None, statuscode='303')
2267 def _check_target(target):
2268 target = urllib.unquote(target)
2269 self.failUnless(target.startswith("uri/URI:DIR2:"), target)
2270 d.addCallback(_check_target)
2271 d.addErrback(self.explain_web_error)
2274 def _make_readonly(self, u):
2275 ro_uri = uri.from_string(u).get_readonly()
2278 return ro_uri.to_string()
2280 def _create_initial_children(self):
2281 contents, n, filecap1 = self.makefile(12)
2282 md1 = {"metakey1": "metavalue1"}
2283 filecap2 = make_mutable_file_uri()
2284 node3 = self.s.create_node_from_uri(make_mutable_file_uri())
2285 filecap3 = node3.get_readonly_uri()
2286 node4 = self.s.create_node_from_uri(make_mutable_file_uri())
2287 dircap = DirectoryNode(node4, None, None).get_uri()
2288 litdircap = "URI:DIR2-LIT:ge3dumj2mewdcotyfqydulbshj5x2lbm"
2289 emptydircap = "URI:DIR2-LIT:"
2290 newkids = {u"child-imm": ["filenode", {"rw_uri": filecap1,
2291 "ro_uri": self._make_readonly(filecap1),
2292 "metadata": md1, }],
2293 u"child-mutable": ["filenode", {"rw_uri": filecap2,
2294 "ro_uri": self._make_readonly(filecap2)}],
2295 u"child-mutable-ro": ["filenode", {"ro_uri": filecap3}],
2296 u"unknownchild-rw": ["unknown", {"rw_uri": unknown_rwcap,
2297 "ro_uri": unknown_rocap}],
2298 u"unknownchild-ro": ["unknown", {"ro_uri": unknown_rocap}],
2299 u"unknownchild-imm": ["unknown", {"ro_uri": unknown_immcap}],
2300 u"dirchild": ["dirnode", {"rw_uri": dircap,
2301 "ro_uri": self._make_readonly(dircap)}],
2302 u"dirchild-lit": ["dirnode", {"ro_uri": litdircap}],
2303 u"dirchild-empty": ["dirnode", {"ro_uri": emptydircap}],
2305 return newkids, {'filecap1': filecap1,
2306 'filecap2': filecap2,
2307 'filecap3': filecap3,
2308 'unknown_rwcap': unknown_rwcap,
2309 'unknown_rocap': unknown_rocap,
2310 'unknown_immcap': unknown_immcap,
2312 'litdircap': litdircap,
2313 'emptydircap': emptydircap}
2315 def _create_immutable_children(self):
2316 contents, n, filecap1 = self.makefile(12)
2317 md1 = {"metakey1": "metavalue1"}
2318 tnode = create_chk_filenode("immutable directory contents\n"*10)
2319 dnode = DirectoryNode(tnode, None, None)
2320 assert not dnode.is_mutable()
2321 immdircap = dnode.get_uri()
2322 litdircap = "URI:DIR2-LIT:ge3dumj2mewdcotyfqydulbshj5x2lbm"
2323 emptydircap = "URI:DIR2-LIT:"
2324 newkids = {u"child-imm": ["filenode", {"ro_uri": filecap1,
2325 "metadata": md1, }],
2326 u"unknownchild-imm": ["unknown", {"ro_uri": unknown_immcap}],
2327 u"dirchild-imm": ["dirnode", {"ro_uri": immdircap}],
2328 u"dirchild-lit": ["dirnode", {"ro_uri": litdircap}],
2329 u"dirchild-empty": ["dirnode", {"ro_uri": emptydircap}],
2331 return newkids, {'filecap1': filecap1,
2332 'unknown_immcap': unknown_immcap,
2333 'immdircap': immdircap,
2334 'litdircap': litdircap,
2335 'emptydircap': emptydircap}
2337 def test_POST_mkdir_no_parentdir_initial_children(self):
2338 (newkids, caps) = self._create_initial_children()
2339 d = self.POST2("/uri?t=mkdir-with-children", simplejson.dumps(newkids))
2340 def _after_mkdir(res):
2341 self.failUnless(res.startswith("URI:DIR"), res)
2342 n = self.s.create_node_from_uri(res)
2343 d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
2344 d2.addCallback(lambda ign:
2345 self.failUnlessROChildURIIs(n, u"child-imm",
2347 d2.addCallback(lambda ign:
2348 self.failUnlessRWChildURIIs(n, u"child-mutable",
2350 d2.addCallback(lambda ign:
2351 self.failUnlessROChildURIIs(n, u"child-mutable-ro",
2353 d2.addCallback(lambda ign:
2354 self.failUnlessRWChildURIIs(n, u"unknownchild-rw",
2355 caps['unknown_rwcap']))
2356 d2.addCallback(lambda ign:
2357 self.failUnlessROChildURIIs(n, u"unknownchild-ro",
2358 caps['unknown_rocap']))
2359 d2.addCallback(lambda ign:
2360 self.failUnlessROChildURIIs(n, u"unknownchild-imm",
2361 caps['unknown_immcap']))
2362 d2.addCallback(lambda ign:
2363 self.failUnlessRWChildURIIs(n, u"dirchild",
2366 d.addCallback(_after_mkdir)
2369 def test_POST_mkdir_no_parentdir_unexpected_children(self):
2370 # the regular /uri?t=mkdir operation is specified to ignore its body.
2371 # Only t=mkdir-with-children pays attention to it.
2372 (newkids, caps) = self._create_initial_children()
2373 d = self.shouldHTTPError("POST t=mkdir unexpected children",
2375 "t=mkdir does not accept children=, "
2376 "try t=mkdir-with-children instead",
2377 self.POST2, "/uri?t=mkdir", # without children
2378 simplejson.dumps(newkids))
2381 def test_POST_noparent_bad(self):
2382 d = self.shouldHTTPError("POST /uri?t=bogus", 400, "Bad Request",
2383 "/uri accepts only PUT, PUT?t=mkdir, "
2384 "POST?t=upload, and POST?t=mkdir",
2385 self.POST, "/uri?t=bogus")
2388 def test_POST_mkdir_no_parentdir_immutable(self):
2389 (newkids, caps) = self._create_immutable_children()
2390 d = self.POST2("/uri?t=mkdir-immutable", simplejson.dumps(newkids))
2391 def _after_mkdir(res):
2392 self.failUnless(res.startswith("URI:DIR"), res)
2393 n = self.s.create_node_from_uri(res)
2394 d2 = self.failUnlessNodeKeysAre(n, newkids.keys())
2395 d2.addCallback(lambda ign:
2396 self.failUnlessROChildURIIs(n, u"child-imm",
2398 d2.addCallback(lambda ign:
2399 self.failUnlessROChildURIIs(n, u"unknownchild-imm",
2400 caps['unknown_immcap']))
2401 d2.addCallback(lambda ign:
2402 self.failUnlessROChildURIIs(n, u"dirchild-imm",
2404 d2.addCallback(lambda ign:
2405 self.failUnlessROChildURIIs(n, u"dirchild-lit",
2407 d2.addCallback(lambda ign:
2408 self.failUnlessROChildURIIs(n, u"dirchild-empty",
2409 caps['emptydircap']))
2411 d.addCallback(_after_mkdir)
2414 def test_POST_mkdir_no_parentdir_immutable_bad(self):
2415 (newkids, caps) = self._create_initial_children()
2416 d = self.shouldFail2(error.Error,
2417 "test_POST_mkdir_no_parentdir_immutable_bad",
2419 "needed to be immutable but was not",
2421 "/uri?t=mkdir-immutable",
2422 simplejson.dumps(newkids))
2425 def test_welcome_page_mkdir_button(self):
2426 # Fetch the welcome page.
2428 def _after_get_welcome_page(res):
2429 MKDIR_BUTTON_RE = re.compile(
2430 '<form action="([^"]*)" method="post".*?'
2431 '<input type="hidden" name="t" value="([^"]*)" />'
2432 '<input type="hidden" name="([^"]*)" value="([^"]*)" />'
2433 '<input type="submit" value="Create a directory" />',
2435 mo = MKDIR_BUTTON_RE.search(res)
2436 formaction = mo.group(1)
2438 formaname = mo.group(3)
2439 formavalue = mo.group(4)
2440 return (formaction, formt, formaname, formavalue)
2441 d.addCallback(_after_get_welcome_page)
2442 def _after_parse_form(res):
2443 (formaction, formt, formaname, formavalue) = res
2444 return self.POST("/%s?t=%s&%s=%s" % (formaction, formt, formaname, formavalue))
2445 d.addCallback(_after_parse_form)
2446 d.addBoth(self.shouldRedirect, None, statuscode='303')
2449 def test_POST_mkdir_replace(self): # return value?
2450 d = self.POST(self.public_url + "/foo", t="mkdir", name="sub")
2451 d.addCallback(lambda res: self._foo_node.get(u"sub"))
2452 d.addCallback(self.failUnlessNodeKeysAre, [])
2455 def test_POST_mkdir_no_replace_queryarg(self): # return value?
2456 d = self.POST(self.public_url + "/foo?replace=false", t="mkdir", name="sub")
2457 d.addBoth(self.shouldFail, error.Error,
2458 "POST_mkdir_no_replace_queryarg",
2460 "There was already a child by that name, and you asked me "
2461 "to not replace it")
2462 d.addCallback(lambda res: self._foo_node.get(u"sub"))
2463 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
2466 def test_POST_mkdir_no_replace_field(self): # return value?
2467 d = self.POST(self.public_url + "/foo", t="mkdir", name="sub",
2469 d.addBoth(self.shouldFail, error.Error, "POST_mkdir_no_replace_field",
2471 "There was already a child by that name, and you asked me "
2472 "to not replace it")
2473 d.addCallback(lambda res: self._foo_node.get(u"sub"))
2474 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
2477 def test_POST_mkdir_whendone_field(self):
2478 d = self.POST(self.public_url + "/foo",
2479 t="mkdir", name="newdir", when_done="/THERE")
2480 d.addBoth(self.shouldRedirect, "/THERE")
2481 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2482 d.addCallback(self.failUnlessNodeKeysAre, [])
2485 def test_POST_mkdir_whendone_queryarg(self):
2486 d = self.POST(self.public_url + "/foo?when_done=/THERE",
2487 t="mkdir", name="newdir")
2488 d.addBoth(self.shouldRedirect, "/THERE")
2489 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
2490 d.addCallback(self.failUnlessNodeKeysAre, [])
2493 def test_POST_bad_t(self):
2494 d = self.shouldFail2(error.Error, "POST_bad_t", "400 Bad Request",
2495 "POST to a directory with bad t=BOGUS",
2496 self.POST, self.public_url + "/foo", t="BOGUS")
2499 def test_POST_set_children(self, command_name="set_children"):
2500 contents9, n9, newuri9 = self.makefile(9)
2501 contents10, n10, newuri10 = self.makefile(10)
2502 contents11, n11, newuri11 = self.makefile(11)
2505 "atomic_added_1": [ "filenode", { "rw_uri": "%s",
2508 "ctime": 1002777696.7564139,
2509 "mtime": 1002777696.7564139
2512 "atomic_added_2": [ "filenode", { "rw_uri": "%s",
2515 "ctime": 1002777696.7564139,
2516 "mtime": 1002777696.7564139
2519 "atomic_added_3": [ "filenode", { "rw_uri": "%s",
2522 "ctime": 1002777696.7564139,
2523 "mtime": 1002777696.7564139
2526 }""" % (newuri9, newuri10, newuri11)
2528 url = self.webish_url + self.public_url + "/foo" + "?t=" + command_name
2530 d = client.getPage(url, method="POST", postdata=reqbody)
2532 self.failUnlessURIMatchesROChild(newuri9, self._foo_node, u"atomic_added_1")
2533 self.failUnlessURIMatchesROChild(newuri10, self._foo_node, u"atomic_added_2")
2534 self.failUnlessURIMatchesROChild(newuri11, self._foo_node, u"atomic_added_3")
2536 d.addCallback(_then)
2537 d.addErrback(self.dump_error)
2540 def test_POST_set_children_with_hyphen(self):
2541 return self.test_POST_set_children(command_name="set-children")
2543 def test_POST_link_uri(self):
2544 contents, n, newuri = self.makefile(8)
2545 d = self.POST(self.public_url + "/foo", t="uri", name="new.txt", uri=newuri)
2546 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"new.txt")
2547 d.addCallback(lambda res:
2548 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
2552 def test_POST_link_uri_replace(self):
2553 contents, n, newuri = self.makefile(8)
2554 d = self.POST(self.public_url + "/foo", t="uri", name="bar.txt", uri=newuri)
2555 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"bar.txt")
2556 d.addCallback(lambda res:
2557 self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
2561 def test_POST_link_uri_unknown_bad(self):
2562 d = self.POST(self.public_url + "/foo", t="uri", name="future.txt", uri=unknown_rwcap)
2563 d.addBoth(self.shouldFail, error.Error,
2564 "POST_link_uri_unknown_bad",
2566 "unknown cap in a write slot")
2569 def test_POST_link_uri_unknown_ro_good(self):
2570 d = self.POST(self.public_url + "/foo", t="uri", name="future-ro.txt", uri=unknown_rocap)
2571 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"future-ro.txt")
2574 def test_POST_link_uri_unknown_imm_good(self):
2575 d = self.POST(self.public_url + "/foo", t="uri", name="future-imm.txt", uri=unknown_immcap)
2576 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node, u"future-imm.txt")
2579 def test_POST_link_uri_no_replace_queryarg(self):
2580 contents, n, newuri = self.makefile(8)
2581 d = self.POST(self.public_url + "/foo?replace=false", t="uri",
2582 name="bar.txt", uri=newuri)
2583 d.addBoth(self.shouldFail, error.Error,
2584 "POST_link_uri_no_replace_queryarg",
2586 "There was already a child by that name, and you asked me "
2587 "to not replace it")
2588 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
2589 d.addCallback(self.failUnlessIsBarDotTxt)
2592 def test_POST_link_uri_no_replace_field(self):
2593 contents, n, newuri = self.makefile(8)
2594 d = self.POST(self.public_url + "/foo", t="uri", replace="false",
2595 name="bar.txt", uri=newuri)
2596 d.addBoth(self.shouldFail, error.Error,
2597 "POST_link_uri_no_replace_field",
2599 "There was already a child by that name, and you asked me "
2600 "to not replace it")
2601 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
2602 d.addCallback(self.failUnlessIsBarDotTxt)
2605 def test_POST_delete(self):
2606 d = self.POST(self.public_url + "/foo", t="delete", name="bar.txt")
2607 d.addCallback(lambda res: self._foo_node.list())
2608 def _check(children):
2609 self.failIf(u"bar.txt" in children)
2610 d.addCallback(_check)
2613 def test_POST_rename_file(self):
2614 d = self.POST(self.public_url + "/foo", t="rename",
2615 from_name="bar.txt", to_name='wibble.txt')
2616 d.addCallback(lambda res:
2617 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
2618 d.addCallback(lambda res:
2619 self.failUnlessNodeHasChild(self._foo_node, u"wibble.txt"))
2620 d.addCallback(lambda res: self.GET(self.public_url + "/foo/wibble.txt"))
2621 d.addCallback(self.failUnlessIsBarDotTxt)
2622 d.addCallback(lambda res: self.GET(self.public_url + "/foo/wibble.txt?t=json"))
2623 d.addCallback(self.failUnlessIsBarJSON)
2626 def test_POST_rename_file_redundant(self):
2627 d = self.POST(self.public_url + "/foo", t="rename",
2628 from_name="bar.txt", to_name='bar.txt')
2629 d.addCallback(lambda res:
2630 self.failUnlessNodeHasChild(self._foo_node, u"bar.txt"))
2631 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
2632 d.addCallback(self.failUnlessIsBarDotTxt)
2633 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt?t=json"))
2634 d.addCallback(self.failUnlessIsBarJSON)
2637 def test_POST_rename_file_replace(self):
2638 # rename a file and replace a directory with it
2639 d = self.POST(self.public_url + "/foo", t="rename",
2640 from_name="bar.txt", to_name='empty')
2641 d.addCallback(lambda res:
2642 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
2643 d.addCallback(lambda res:
2644 self.failUnlessNodeHasChild(self._foo_node, u"empty"))
2645 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty"))
2646 d.addCallback(self.failUnlessIsBarDotTxt)
2647 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
2648 d.addCallback(self.failUnlessIsBarJSON)
2651 def test_POST_rename_file_no_replace_queryarg(self):
2652 # rename a file and replace a directory with it
2653 d = self.POST(self.public_url + "/foo?replace=false", t="rename",
2654 from_name="bar.txt", to_name='empty')
2655 d.addBoth(self.shouldFail, error.Error,
2656 "POST_rename_file_no_replace_queryarg",
2658 "There was already a child by that name, and you asked me "
2659 "to not replace it")
2660 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
2661 d.addCallback(self.failUnlessIsEmptyJSON)
2664 def test_POST_rename_file_no_replace_field(self):
2665 # rename a file and replace a directory with it
2666 d = self.POST(self.public_url + "/foo", t="rename", replace="false",
2667 from_name="bar.txt", to_name='empty')
2668 d.addBoth(self.shouldFail, error.Error,
2669 "POST_rename_file_no_replace_field",
2671 "There was already a child by that name, and you asked me "
2672 "to not replace it")
2673 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
2674 d.addCallback(self.failUnlessIsEmptyJSON)
2677 def failUnlessIsEmptyJSON(self, res):
2678 data = simplejson.loads(res)
2679 self.failUnlessEqual(data[0], "dirnode", data)
2680 self.failUnlessReallyEqual(len(data[1]["children"]), 0)
2682 def test_POST_rename_file_slash_fail(self):
2683 d = self.POST(self.public_url + "/foo", t="rename",
2684 from_name="bar.txt", to_name='kirk/spock.txt')
2685 d.addBoth(self.shouldFail, error.Error,
2686 "test_POST_rename_file_slash_fail",
2688 "to_name= may not contain a slash",
2690 d.addCallback(lambda res:
2691 self.failUnlessNodeHasChild(self._foo_node, u"bar.txt"))
2694 def test_POST_rename_dir(self):
2695 d = self.POST(self.public_url, t="rename",
2696 from_name="foo", to_name='plunk')
2697 d.addCallback(lambda res:
2698 self.failIfNodeHasChild(self.public_root, u"foo"))
2699 d.addCallback(lambda res:
2700 self.failUnlessNodeHasChild(self.public_root, u"plunk"))
2701 d.addCallback(lambda res: self.GET(self.public_url + "/plunk?t=json"))
2702 d.addCallback(self.failUnlessIsFooJSON)
2705 def shouldRedirect(self, res, target=None, statuscode=None, which=""):
2706 """ If target is not None then the redirection has to go to target. If
2707 statuscode is not None then the redirection has to be accomplished with
2708 that HTTP status code."""
2709 if not isinstance(res, failure.Failure):
2710 to_where = (target is None) and "somewhere" or ("to " + target)
2711 self.fail("%s: we were expecting to get redirected %s, not get an"
2712 " actual page: %s" % (which, to_where, res))
2713 res.trap(error.PageRedirect)
2714 if statuscode is not None:
2715 self.failUnlessReallyEqual(res.value.status, statuscode,
2716 "%s: not a redirect" % which)
2717 if target is not None:
2718 # the PageRedirect does not seem to capture the uri= query arg
2719 # properly, so we can't check for it.
2720 realtarget = self.webish_url + target
2721 self.failUnlessReallyEqual(res.value.location, realtarget,
2722 "%s: wrong target" % which)
2723 return res.value.location
2725 def test_GET_URI_form(self):
2726 base = "/uri?uri=%s" % self._bar_txt_uri
2727 # this is supposed to give us a redirect to /uri/$URI, plus arguments
2728 targetbase = "/uri/%s" % urllib.quote(self._bar_txt_uri)
2730 d.addBoth(self.shouldRedirect, targetbase)
2731 d.addCallback(lambda res: self.GET(base+"&filename=bar.txt"))
2732 d.addBoth(self.shouldRedirect, targetbase+"?filename=bar.txt")
2733 d.addCallback(lambda res: self.GET(base+"&t=json"))
2734 d.addBoth(self.shouldRedirect, targetbase+"?t=json")
2735 d.addCallback(self.log, "about to get file by uri")
2736 d.addCallback(lambda res: self.GET(base, followRedirect=True))
2737 d.addCallback(self.failUnlessIsBarDotTxt)
2738 d.addCallback(self.log, "got file by uri, about to get dir by uri")
2739 d.addCallback(lambda res: self.GET("/uri?uri=%s&t=json" % self._foo_uri,
2740 followRedirect=True))
2741 d.addCallback(self.failUnlessIsFooJSON)
2742 d.addCallback(self.log, "got dir by uri")
2746 def test_GET_URI_form_bad(self):
2747 d = self.shouldFail2(error.Error, "test_GET_URI_form_bad",
2748 "400 Bad Request", "GET /uri requires uri=",
2752 def test_GET_rename_form(self):
2753 d = self.GET(self.public_url + "/foo?t=rename-form&name=bar.txt",
2754 followRedirect=True)
2756 self.failUnless('name="when_done" value="."' in res, res)
2757 self.failUnless(re.search(r'name="from_name" value="bar\.txt"', res))
2758 d.addCallback(_check)
2761 def log(self, res, msg):
2762 #print "MSG: %s RES: %s" % (msg, res)
2766 def test_GET_URI_URL(self):
2767 base = "/uri/%s" % self._bar_txt_uri
2769 d.addCallback(self.failUnlessIsBarDotTxt)
2770 d.addCallback(lambda res: self.GET(base+"?filename=bar.txt"))
2771 d.addCallback(self.failUnlessIsBarDotTxt)
2772 d.addCallback(lambda res: self.GET(base+"?filename=bar.txt&save=true"))
2773 d.addCallback(self.failUnlessIsBarDotTxt)
2776 def test_GET_URI_URL_dir(self):
2777 base = "/uri/%s?t=json" % self._foo_uri
2779 d.addCallback(self.failUnlessIsFooJSON)
2782 def test_GET_URI_URL_missing(self):
2783 base = "/uri/%s" % self._bad_file_uri
2784 d = self.shouldHTTPError("test_GET_URI_URL_missing",
2785 http.GONE, None, "NotEnoughSharesError",
2787 # TODO: how can we exercise both sides of WebDownloadTarget.fail
2788 # here? we must arrange for a download to fail after target.open()
2789 # has been called, and then inspect the response to see that it is
2790 # shorter than we expected.
2793 def test_PUT_DIRURL_uri(self):
2794 d = self.s.create_dirnode()
2796 new_uri = dn.get_uri()
2797 # replace /foo with a new (empty) directory
2798 d = self.PUT(self.public_url + "/foo?t=uri", new_uri)
2799 d.addCallback(lambda res:
2800 self.failUnlessReallyEqual(res.strip(), new_uri))
2801 d.addCallback(lambda res:
2802 self.failUnlessRWChildURIIs(self.public_root,
2806 d.addCallback(_made_dir)
2809 def test_PUT_DIRURL_uri_noreplace(self):
2810 d = self.s.create_dirnode()
2812 new_uri = dn.get_uri()
2813 # replace /foo with a new (empty) directory, but ask that
2814 # replace=false, so it should fail
2815 d = self.shouldFail2(error.Error, "test_PUT_DIRURL_uri_noreplace",
2816 "409 Conflict", "There was already a child by that name, and you asked me to not replace it",
2818 self.public_url + "/foo?t=uri&replace=false",
2820 d.addCallback(lambda res:
2821 self.failUnlessRWChildURIIs(self.public_root,
2825 d.addCallback(_made_dir)
2828 def test_PUT_DIRURL_bad_t(self):
2829 d = self.shouldFail2(error.Error, "test_PUT_DIRURL_bad_t",
2830 "400 Bad Request", "PUT to a directory",
2831 self.PUT, self.public_url + "/foo?t=BOGUS", "")
2832 d.addCallback(lambda res:
2833 self.failUnlessRWChildURIIs(self.public_root,
2838 def test_PUT_NEWFILEURL_uri(self):
2839 contents, n, new_uri = self.makefile(8)
2840 d = self.PUT(self.public_url + "/foo/new.txt?t=uri", new_uri)
2841 d.addCallback(lambda res: self.failUnlessReallyEqual(res.strip(), new_uri))
2842 d.addCallback(lambda res:
2843 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
2847 def test_PUT_NEWFILEURL_uri_replace(self):
2848 contents, n, new_uri = self.makefile(8)
2849 d = self.PUT(self.public_url + "/foo/bar.txt?t=uri", new_uri)
2850 d.addCallback(lambda res: self.failUnlessReallyEqual(res.strip(), new_uri))
2851 d.addCallback(lambda res:
2852 self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
2856 def test_PUT_NEWFILEURL_uri_no_replace(self):
2857 contents, n, new_uri = self.makefile(8)
2858 d = self.PUT(self.public_url + "/foo/bar.txt?t=uri&replace=false", new_uri)
2859 d.addBoth(self.shouldFail, error.Error, "PUT_NEWFILEURL_uri_no_replace",
2861 "There was already a child by that name, and you asked me "
2862 "to not replace it")
2865 def test_PUT_NEWFILEURL_uri_unknown_bad(self):
2866 d = self.PUT(self.public_url + "/foo/put-future.txt?t=uri", unknown_rwcap)
2867 d.addBoth(self.shouldFail, error.Error,
2868 "POST_put_uri_unknown_bad",
2870 "unknown cap in a write slot")
2873 def test_PUT_NEWFILEURL_uri_unknown_ro_good(self):
2874 d = self.PUT(self.public_url + "/foo/put-future-ro.txt?t=uri", unknown_rocap)
2875 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node,
2876 u"put-future-ro.txt")
2879 def test_PUT_NEWFILEURL_uri_unknown_imm_good(self):
2880 d = self.PUT(self.public_url + "/foo/put-future-imm.txt?t=uri", unknown_immcap)
2881 d.addCallback(self.failUnlessURIMatchesROChild, self._foo_node,
2882 u"put-future-imm.txt")
2885 def test_PUT_NEWFILE_URI(self):
2886 file_contents = "New file contents here\n"
2887 d = self.PUT("/uri", file_contents)
2889 assert isinstance(uri, str), uri
2890 self.failUnless(uri in FakeCHKFileNode.all_contents)
2891 self.failUnlessReallyEqual(FakeCHKFileNode.all_contents[uri],
2893 return self.GET("/uri/%s" % uri)
2894 d.addCallback(_check)
2896 self.failUnlessReallyEqual(res, file_contents)
2897 d.addCallback(_check2)
2900 def test_PUT_NEWFILE_URI_not_mutable(self):
2901 file_contents = "New file contents here\n"
2902 d = self.PUT("/uri?mutable=false", file_contents)
2904 assert isinstance(uri, str), uri
2905 self.failUnless(uri in FakeCHKFileNode.all_contents)
2906 self.failUnlessReallyEqual(FakeCHKFileNode.all_contents[uri],
2908 return self.GET("/uri/%s" % uri)
2909 d.addCallback(_check)
2911 self.failUnlessReallyEqual(res, file_contents)
2912 d.addCallback(_check2)
2915 def test_PUT_NEWFILE_URI_only_PUT(self):
2916 d = self.PUT("/uri?t=bogus", "")
2917 d.addBoth(self.shouldFail, error.Error,
2918 "PUT_NEWFILE_URI_only_PUT",
2920 "/uri accepts only PUT, PUT?t=mkdir, POST?t=upload, and POST?t=mkdir")
2923 def test_PUT_NEWFILE_URI_mutable(self):
2924 file_contents = "New file contents here\n"
2925 d = self.PUT("/uri?mutable=true", file_contents)
2926 def _check1(filecap):
2927 filecap = filecap.strip()
2928 self.failUnless(filecap.startswith("URI:SSK:"), filecap)
2929 self.filecap = filecap
2930 u = uri.WriteableSSKFileURI.init_from_string(filecap)
2931 self.failUnless(u.get_storage_index() in FakeMutableFileNode.all_contents)
2932 n = self.s.create_node_from_uri(filecap)
2933 return n.download_best_version()
2934 d.addCallback(_check1)
2936 self.failUnlessReallyEqual(data, file_contents)
2937 return self.GET("/uri/%s" % urllib.quote(self.filecap))
2938 d.addCallback(_check2)
2940 self.failUnlessReallyEqual(res, file_contents)
2941 d.addCallback(_check3)
2944 def test_PUT_mkdir(self):
2945 d = self.PUT("/uri?t=mkdir", "")
2947 n = self.s.create_node_from_uri(uri.strip())
2948 d2 = self.failUnlessNodeKeysAre(n, [])
2949 d2.addCallback(lambda res:
2950 self.GET("/uri/%s?t=json" % uri))
2952 d.addCallback(_check)
2953 d.addCallback(self.failUnlessIsEmptyJSON)
2956 def test_POST_check(self):
2957 d = self.POST(self.public_url + "/foo", t="check", name="bar.txt")
2959 # this returns a string form of the results, which are probably
2960 # None since we're using fake filenodes.
2961 # TODO: verify that the check actually happened, by changing
2962 # FakeCHKFileNode to count how many times .check() has been
2965 d.addCallback(_done)
2968 def test_bad_method(self):
2969 url = self.webish_url + self.public_url + "/foo/bar.txt"
2970 d = self.shouldHTTPError("test_bad_method",
2971 501, "Not Implemented",
2972 "I don't know how to treat a BOGUS request.",
2973 client.getPage, url, method="BOGUS")
2976 def test_short_url(self):
2977 url = self.webish_url + "/uri"
2978 d = self.shouldHTTPError("test_short_url", 501, "Not Implemented",
2979 "I don't know how to treat a DELETE request.",
2980 client.getPage, url, method="DELETE")
2983 def test_ophandle_bad(self):
2984 url = self.webish_url + "/operations/bogus?t=status"
2985 d = self.shouldHTTPError("test_ophandle_bad", 404, "404 Not Found",
2986 "unknown/expired handle 'bogus'",
2987 client.getPage, url)
2990 def test_ophandle_cancel(self):
2991 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=128",
2992 followRedirect=True)
2993 d.addCallback(lambda ignored:
2994 self.GET("/operations/128?t=status&output=JSON"))
2996 data = simplejson.loads(res)
2997 self.failUnless("finished" in data, res)
2998 monitor = self.ws.root.child_operations.handles["128"][0]
2999 d = self.POST("/operations/128?t=cancel&output=JSON")
3001 data = simplejson.loads(res)
3002 self.failUnless("finished" in data, res)
3003 # t=cancel causes the handle to be forgotten
3004 self.failUnless(monitor.is_cancelled())
3005 d.addCallback(_check2)
3007 d.addCallback(_check1)
3008 d.addCallback(lambda ignored:
3009 self.shouldHTTPError("test_ophandle_cancel",
3010 404, "404 Not Found",
3011 "unknown/expired handle '128'",
3013 "/operations/128?t=status&output=JSON"))
3016 def test_ophandle_retainfor(self):
3017 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=129&retain-for=60",
3018 followRedirect=True)
3019 d.addCallback(lambda ignored:
3020 self.GET("/operations/129?t=status&output=JSON&retain-for=0"))
3022 data = simplejson.loads(res)
3023 self.failUnless("finished" in data, res)
3024 d.addCallback(_check1)
3025 # the retain-for=0 will cause the handle to be expired very soon
3026 d.addCallback(lambda ign:
3027 self.clock.advance(2.0))
3028 d.addCallback(lambda ignored:
3029 self.shouldHTTPError("test_ophandle_retainfor",
3030 404, "404 Not Found",
3031 "unknown/expired handle '129'",
3033 "/operations/129?t=status&output=JSON"))
3036 def test_ophandle_release_after_complete(self):
3037 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=130",
3038 followRedirect=True)
3039 d.addCallback(self.wait_for_operation, "130")
3040 d.addCallback(lambda ignored:
3041 self.GET("/operations/130?t=status&output=JSON&release-after-complete=true"))
3042 # the release-after-complete=true will cause the handle to be expired
3043 d.addCallback(lambda ignored:
3044 self.shouldHTTPError("test_ophandle_release_after_complete",
3045 404, "404 Not Found",
3046 "unknown/expired handle '130'",
3048 "/operations/130?t=status&output=JSON"))
3051 def test_uncollected_ophandle_expiration(self):
3052 # uncollected ophandles should expire after 4 days
3053 def _make_uncollected_ophandle(ophandle):
3054 d = self.POST(self.public_url +
3055 "/foo/?t=start-manifest&ophandle=%d" % ophandle,
3056 followRedirect=False)
3057 # When we start the operation, the webapi server will want
3058 # to redirect us to the page for the ophandle, so we get
3059 # confirmation that the operation has started. If the
3060 # manifest operation has finished by the time we get there,
3061 # following that redirect (by setting followRedirect=True
3062 # above) has the side effect of collecting the ophandle that
3063 # we've just created, which means that we can't use the
3064 # ophandle to test the uncollected timeout anymore. So,
3065 # instead, catch the 302 here and don't follow it.
3066 d.addBoth(self.should302, "uncollected_ophandle_creation")
3068 # Create an ophandle, don't collect it, then advance the clock by
3069 # 4 days - 1 second and make sure that the ophandle is still there.
3070 d = _make_uncollected_ophandle(131)
3071 d.addCallback(lambda ign:
3072 self.clock.advance((96*60*60) - 1)) # 96 hours = 4 days
3073 d.addCallback(lambda ign:
3074 self.GET("/operations/131?t=status&output=JSON"))
3076 data = simplejson.loads(res)
3077 self.failUnless("finished" in data, res)
3078 d.addCallback(_check1)
3079 # Create an ophandle, don't collect it, then try to collect it
3080 # after 4 days. It should be gone.
3081 d.addCallback(lambda ign:
3082 _make_uncollected_ophandle(132))
3083 d.addCallback(lambda ign:
3084 self.clock.advance(96*60*60))
3085 d.addCallback(lambda ign:
3086 self.shouldHTTPError("test_uncollected_ophandle_expired_after_100_hours",
3087 404, "404 Not Found",
3088 "unknown/expired handle '132'",
3090 "/operations/132?t=status&output=JSON"))
3093 def test_collected_ophandle_expiration(self):
3094 # collected ophandles should expire after 1 day
3095 def _make_collected_ophandle(ophandle):
3096 d = self.POST(self.public_url +
3097 "/foo/?t=start-manifest&ophandle=%d" % ophandle,
3098 followRedirect=True)
3099 # By following the initial redirect, we collect the ophandle
3100 # we've just created.
3102 # Create a collected ophandle, then collect it after 23 hours
3103 # and 59 seconds to make sure that it is still there.
3104 d = _make_collected_ophandle(133)
3105 d.addCallback(lambda ign:
3106 self.clock.advance((24*60*60) - 1))
3107 d.addCallback(lambda ign:
3108 self.GET("/operations/133?t=status&output=JSON"))
3110 data = simplejson.loads(res)
3111 self.failUnless("finished" in data, res)
3112 d.addCallback(_check1)
3113 # Create another uncollected ophandle, then try to collect it
3114 # after 24 hours to make sure that it is gone.
3115 d.addCallback(lambda ign:
3116 _make_collected_ophandle(134))
3117 d.addCallback(lambda ign:
3118 self.clock.advance(24*60*60))
3119 d.addCallback(lambda ign:
3120 self.shouldHTTPError("test_collected_ophandle_expired_after_1000_minutes",
3121 404, "404 Not Found",
3122 "unknown/expired handle '134'",
3124 "/operations/134?t=status&output=JSON"))
3127 def test_incident(self):
3128 d = self.POST("/report_incident", details="eek")
3130 self.failUnless("Thank you for your report!" in res, res)
3131 d.addCallback(_done)
3134 def test_static(self):
3135 webdir = os.path.join(self.staticdir, "subdir")
3136 fileutil.make_dirs(webdir)
3137 f = open(os.path.join(webdir, "hello.txt"), "wb")
3141 d = self.GET("/static/subdir/hello.txt")
3143 self.failUnlessReallyEqual(res, "hello")
3144 d.addCallback(_check)
3148 class Util(ShouldFailMixin, testutil.ReallyEqualMixin, unittest.TestCase):
3149 def test_load_file(self):
3150 # This will raise an exception unless a well-formed XML file is found under that name.
3151 common.getxmlfile('directory.xhtml').load()
3153 def test_parse_replace_arg(self):
3154 self.failUnlessReallyEqual(common.parse_replace_arg("true"), True)
3155 self.failUnlessReallyEqual(common.parse_replace_arg("false"), False)
3156 self.failUnlessReallyEqual(common.parse_replace_arg("only-files"),
3158 self.shouldFail(AssertionError, "test_parse_replace_arg", "",
3159 common.parse_replace_arg, "only_fles")
3161 def test_abbreviate_time(self):
3162 self.failUnlessReallyEqual(common.abbreviate_time(None), "")
3163 self.failUnlessReallyEqual(common.abbreviate_time(1.234), "1.23s")
3164 self.failUnlessReallyEqual(common.abbreviate_time(0.123), "123ms")
3165 self.failUnlessReallyEqual(common.abbreviate_time(0.00123), "1.2ms")
3166 self.failUnlessReallyEqual(common.abbreviate_time(0.000123), "123us")
3167 self.failUnlessReallyEqual(common.abbreviate_time(-123000), "-123000000000us")
3169 def test_abbreviate_rate(self):
3170 self.failUnlessReallyEqual(common.abbreviate_rate(None), "")
3171 self.failUnlessReallyEqual(common.abbreviate_rate(1234000), "1.23MBps")
3172 self.failUnlessReallyEqual(common.abbreviate_rate(12340), "12.3kBps")
3173 self.failUnlessReallyEqual(common.abbreviate_rate(123), "123Bps")
3175 def test_abbreviate_size(self):
3176 self.failUnlessReallyEqual(common.abbreviate_size(None), "")
3177 self.failUnlessReallyEqual(common.abbreviate_size(1.23*1000*1000*1000), "1.23GB")
3178 self.failUnlessReallyEqual(common.abbreviate_size(1.23*1000*1000), "1.23MB")
3179 self.failUnlessReallyEqual(common.abbreviate_size(1230), "1.2kB")
3180 self.failUnlessReallyEqual(common.abbreviate_size(123), "123B")
3182 def test_plural(self):
3184 return "%d second%s" % (s, status.plural(s))
3185 self.failUnlessReallyEqual(convert(0), "0 seconds")
3186 self.failUnlessReallyEqual(convert(1), "1 second")
3187 self.failUnlessReallyEqual(convert(2), "2 seconds")
3189 return "has share%s: %s" % (status.plural(s), ",".join(s))
3190 self.failUnlessReallyEqual(convert2([]), "has shares: ")
3191 self.failUnlessReallyEqual(convert2(["1"]), "has share: 1")
3192 self.failUnlessReallyEqual(convert2(["1","2"]), "has shares: 1,2")
3195 class Grid(GridTestMixin, WebErrorMixin, ShouldFailMixin, testutil.ReallyEqualMixin, unittest.TestCase):
3197 def CHECK(self, ign, which, args, clientnum=0):
3198 fileurl = self.fileurls[which]
3199 url = fileurl + "?" + args
3200 return self.GET(url, method="POST", clientnum=clientnum)
3202 def test_filecheck(self):
3203 self.basedir = "web/Grid/filecheck"
3205 c0 = self.g.clients[0]
3208 d = c0.upload(upload.Data(DATA, convergence=""))
3209 def _stash_uri(ur, which):
3210 self.uris[which] = ur.uri
3211 d.addCallback(_stash_uri, "good")
3212 d.addCallback(lambda ign:
3213 c0.upload(upload.Data(DATA+"1", convergence="")))
3214 d.addCallback(_stash_uri, "sick")
3215 d.addCallback(lambda ign:
3216 c0.upload(upload.Data(DATA+"2", convergence="")))
3217 d.addCallback(_stash_uri, "dead")
3218 def _stash_mutable_uri(n, which):
3219 self.uris[which] = n.get_uri()
3220 assert isinstance(self.uris[which], str)
3221 d.addCallback(lambda ign: c0.create_mutable_file(DATA+"3"))
3222 d.addCallback(_stash_mutable_uri, "corrupt")
3223 d.addCallback(lambda ign:
3224 c0.upload(upload.Data("literal", convergence="")))
3225 d.addCallback(_stash_uri, "small")
3226 d.addCallback(lambda ign: c0.create_immutable_dirnode({}))
3227 d.addCallback(_stash_mutable_uri, "smalldir")
3229 def _compute_fileurls(ignored):
3231 for which in self.uris:
3232 self.fileurls[which] = "uri/" + urllib.quote(self.uris[which])
3233 d.addCallback(_compute_fileurls)
3235 def _clobber_shares(ignored):
3236 good_shares = self.find_uri_shares(self.uris["good"])
3237 self.failUnlessReallyEqual(len(good_shares), 10)
3238 sick_shares = self.find_uri_shares(self.uris["sick"])
3239 os.unlink(sick_shares[0][2])
3240 dead_shares = self.find_uri_shares(self.uris["dead"])
3241 for i in range(1, 10):
3242 os.unlink(dead_shares[i][2])
3243 c_shares = self.find_uri_shares(self.uris["corrupt"])
3244 cso = CorruptShareOptions()
3245 cso.stdout = StringIO()
3246 cso.parseOptions([c_shares[0][2]])
3248 d.addCallback(_clobber_shares)
3250 d.addCallback(self.CHECK, "good", "t=check")
3251 def _got_html_good(res):
3252 self.failUnless("Healthy" in res, res)
3253 self.failIf("Not Healthy" in res, res)
3254 d.addCallback(_got_html_good)
3255 d.addCallback(self.CHECK, "good", "t=check&return_to=somewhere")
3256 def _got_html_good_return_to(res):
3257 self.failUnless("Healthy" in res, res)
3258 self.failIf("Not Healthy" in res, res)
3259 self.failUnless('<a href="somewhere">Return to file'
3261 d.addCallback(_got_html_good_return_to)
3262 d.addCallback(self.CHECK, "good", "t=check&output=json")
3263 def _got_json_good(res):
3264 r = simplejson.loads(res)
3265 self.failUnlessEqual(r["summary"], "Healthy")
3266 self.failUnless(r["results"]["healthy"])
3267 self.failIf(r["results"]["needs-rebalancing"])
3268 self.failUnless(r["results"]["recoverable"])
3269 d.addCallback(_got_json_good)
3271 d.addCallback(self.CHECK, "small", "t=check")
3272 def _got_html_small(res):
3273 self.failUnless("Literal files are always healthy" in res, res)
3274 self.failIf("Not Healthy" in res, res)
3275 d.addCallback(_got_html_small)
3276 d.addCallback(self.CHECK, "small", "t=check&return_to=somewhere")
3277 def _got_html_small_return_to(res):
3278 self.failUnless("Literal files are always healthy" in res, res)
3279 self.failIf("Not Healthy" in res, res)
3280 self.failUnless('<a href="somewhere">Return to file'
3282 d.addCallback(_got_html_small_return_to)
3283 d.addCallback(self.CHECK, "small", "t=check&output=json")
3284 def _got_json_small(res):
3285 r = simplejson.loads(res)
3286 self.failUnlessEqual(r["storage-index"], "")
3287 self.failUnless(r["results"]["healthy"])
3288 d.addCallback(_got_json_small)
3290 d.addCallback(self.CHECK, "smalldir", "t=check")
3291 def _got_html_smalldir(res):
3292 self.failUnless("Literal files are always healthy" in res, res)
3293 self.failIf("Not Healthy" in res, res)
3294 d.addCallback(_got_html_smalldir)
3295 d.addCallback(self.CHECK, "smalldir", "t=check&output=json")
3296 def _got_json_smalldir(res):
3297 r = simplejson.loads(res)
3298 self.failUnlessEqual(r["storage-index"], "")
3299 self.failUnless(r["results"]["healthy"])
3300 d.addCallback(_got_json_smalldir)
3302 d.addCallback(self.CHECK, "sick", "t=check")
3303 def _got_html_sick(res):
3304 self.failUnless("Not Healthy" in res, res)
3305 d.addCallback(_got_html_sick)
3306 d.addCallback(self.CHECK, "sick", "t=check&output=json")
3307 def _got_json_sick(res):
3308 r = simplejson.loads(res)
3309 self.failUnlessEqual(r["summary"],
3310 "Not Healthy: 9 shares (enc 3-of-10)")
3311 self.failIf(r["results"]["healthy"])
3312 self.failIf(r["results"]["needs-rebalancing"])
3313 self.failUnless(r["results"]["recoverable"])
3314 d.addCallback(_got_json_sick)
3316 d.addCallback(self.CHECK, "dead", "t=check")
3317 def _got_html_dead(res):
3318 self.failUnless("Not Healthy" in res, res)
3319 d.addCallback(_got_html_dead)
3320 d.addCallback(self.CHECK, "dead", "t=check&output=json")
3321 def _got_json_dead(res):
3322 r = simplejson.loads(res)
3323 self.failUnlessEqual(r["summary"],
3324 "Not Healthy: 1 shares (enc 3-of-10)")
3325 self.failIf(r["results"]["healthy"])
3326 self.failIf(r["results"]["needs-rebalancing"])
3327 self.failIf(r["results"]["recoverable"])
3328 d.addCallback(_got_json_dead)
3330 d.addCallback(self.CHECK, "corrupt", "t=check&verify=true")
3331 def _got_html_corrupt(res):
3332 self.failUnless("Not Healthy! : Unhealthy" in res, res)
3333 d.addCallback(_got_html_corrupt)
3334 d.addCallback(self.CHECK, "corrupt", "t=check&verify=true&output=json")
3335 def _got_json_corrupt(res):
3336 r = simplejson.loads(res)
3337 self.failUnless("Unhealthy: 9 shares (enc 3-of-10)" in r["summary"],
3339 self.failIf(r["results"]["healthy"])
3340 self.failUnless(r["results"]["recoverable"])
3341 self.failUnlessReallyEqual(r["results"]["count-shares-good"], 9)
3342 self.failUnlessReallyEqual(r["results"]["count-corrupt-shares"], 1)
3343 d.addCallback(_got_json_corrupt)
3345 d.addErrback(self.explain_web_error)
3348 def test_repair_html(self):
3349 self.basedir = "web/Grid/repair_html"
3351 c0 = self.g.clients[0]
3354 d = c0.upload(upload.Data(DATA, convergence=""))
3355 def _stash_uri(ur, which):
3356 self.uris[which] = ur.uri
3357 d.addCallback(_stash_uri, "good")
3358 d.addCallback(lambda ign:
3359 c0.upload(upload.Data(DATA+"1", convergence="")))
3360 d.addCallback(_stash_uri, "sick")
3361 d.addCallback(lambda ign:
3362 c0.upload(upload.Data(DATA+"2", convergence="")))
3363 d.addCallback(_stash_uri, "dead")
3364 def _stash_mutable_uri(n, which):
3365 self.uris[which] = n.get_uri()
3366 assert isinstance(self.uris[which], str)
3367 d.addCallback(lambda ign: c0.create_mutable_file(DATA+"3"))
3368 d.addCallback(_stash_mutable_uri, "corrupt")
3370 def _compute_fileurls(ignored):
3372 for which in self.uris:
3373 self.fileurls[which] = "uri/" + urllib.quote(self.uris[which])
3374 d.addCallback(_compute_fileurls)
3376 def _clobber_shares(ignored):
3377 good_shares = self.find_uri_shares(self.uris["good"])
3378 self.failUnlessReallyEqual(len(good_shares), 10)
3379 sick_shares = self.find_uri_shares(self.uris["sick"])
3380 os.unlink(sick_shares[0][2])
3381 dead_shares = self.find_uri_shares(self.uris["dead"])
3382 for i in range(1, 10):
3383 os.unlink(dead_shares[i][2])
3384 c_shares = self.find_uri_shares(self.uris["corrupt"])
3385 cso = CorruptShareOptions()
3386 cso.stdout = StringIO()
3387 cso.parseOptions([c_shares[0][2]])
3389 d.addCallback(_clobber_shares)
3391 d.addCallback(self.CHECK, "good", "t=check&repair=true")
3392 def _got_html_good(res):
3393 self.failUnless("Healthy" in res, res)
3394 self.failIf("Not Healthy" in res, res)
3395 self.failUnless("No repair necessary" in res, res)
3396 d.addCallback(_got_html_good)
3398 d.addCallback(self.CHECK, "sick", "t=check&repair=true")
3399 def _got_html_sick(res):
3400 self.failUnless("Healthy : healthy" in res, res)
3401 self.failIf("Not Healthy" in res, res)
3402 self.failUnless("Repair successful" in res, res)
3403 d.addCallback(_got_html_sick)
3405 # repair of a dead file will fail, of course, but it isn't yet
3406 # clear how this should be reported. Right now it shows up as
3409 #d.addCallback(self.CHECK, "dead", "t=check&repair=true")
3410 #def _got_html_dead(res):
3412 # self.failUnless("Healthy : healthy" in res, res)
3413 # self.failIf("Not Healthy" in res, res)
3414 # self.failUnless("No repair necessary" in res, res)
3415 #d.addCallback(_got_html_dead)
3417 d.addCallback(self.CHECK, "corrupt", "t=check&verify=true&repair=true")
3418 def _got_html_corrupt(res):
3419 self.failUnless("Healthy : Healthy" in res, res)
3420 self.failIf("Not Healthy" in res, res)
3421 self.failUnless("Repair successful" in res, res)
3422 d.addCallback(_got_html_corrupt)
3424 d.addErrback(self.explain_web_error)
3427 def test_repair_json(self):
3428 self.basedir = "web/Grid/repair_json"
3430 c0 = self.g.clients[0]
3433 d = c0.upload(upload.Data(DATA+"1", convergence=""))
3434 def _stash_uri(ur, which):
3435 self.uris[which] = ur.uri
3436 d.addCallback(_stash_uri, "sick")
3438 def _compute_fileurls(ignored):
3440 for which in self.uris:
3441 self.fileurls[which] = "uri/" + urllib.quote(self.uris[which])
3442 d.addCallback(_compute_fileurls)
3444 def _clobber_shares(ignored):
3445 sick_shares = self.find_uri_shares(self.uris["sick"])
3446 os.unlink(sick_shares[0][2])
3447 d.addCallback(_clobber_shares)
3449 d.addCallback(self.CHECK, "sick", "t=check&repair=true&output=json")
3450 def _got_json_sick(res):
3451 r = simplejson.loads(res)
3452 self.failUnlessReallyEqual(r["repair-attempted"], True)
3453 self.failUnlessReallyEqual(r["repair-successful"], True)
3454 self.failUnlessEqual(r["pre-repair-results"]["summary"],
3455 "Not Healthy: 9 shares (enc 3-of-10)")
3456 self.failIf(r["pre-repair-results"]["results"]["healthy"])
3457 self.failUnlessEqual(r["post-repair-results"]["summary"], "healthy")
3458 self.failUnless(r["post-repair-results"]["results"]["healthy"])
3459 d.addCallback(_got_json_sick)
3461 d.addErrback(self.explain_web_error)
3464 def test_unknown(self, immutable=False):
3465 self.basedir = "web/Grid/unknown"
3467 self.basedir = "web/Grid/unknown-immutable"
3470 c0 = self.g.clients[0]
3474 # the future cap format may contain slashes, which must be tolerated
3475 expected_info_url = "uri/%s?t=info" % urllib.quote(unknown_rwcap,
3479 name = u"future-imm"
3480 future_node = UnknownNode(None, unknown_immcap, deep_immutable=True)
3481 d = c0.create_immutable_dirnode({name: (future_node, {})})
3484 future_node = UnknownNode(unknown_rwcap, unknown_rocap)
3485 d = c0.create_dirnode()
3487 def _stash_root_and_create_file(n):
3489 self.rooturl = "uri/" + urllib.quote(n.get_uri()) + "/"
3490 self.rourl = "uri/" + urllib.quote(n.get_readonly_uri()) + "/"
3492 return self.rootnode.set_node(name, future_node)
3493 d.addCallback(_stash_root_and_create_file)
3495 # make sure directory listing tolerates unknown nodes
3496 d.addCallback(lambda ign: self.GET(self.rooturl))
3497 def _check_directory_html(res, expected_type_suffix):
3498 pattern = re.compile(r'<td>\?%s</td>[ \t\n\r]*'
3499 '<td>%s</td>' % (expected_type_suffix, str(name)),
3501 self.failUnless(re.search(pattern, res), res)
3502 # find the More Info link for name, should be relative
3503 mo = re.search(r'<a href="([^"]+)">More Info</a>', res)
3504 info_url = mo.group(1)
3505 self.failUnlessReallyEqual(info_url, "%s?t=info" % (str(name),))
3507 d.addCallback(_check_directory_html, "-IMM")
3509 d.addCallback(_check_directory_html, "")
3511 d.addCallback(lambda ign: self.GET(self.rooturl+"?t=json"))
3512 def _check_directory_json(res, expect_rw_uri):
3513 data = simplejson.loads(res)
3514 self.failUnlessEqual(data[0], "dirnode")
3515 f = data[1]["children"][name]
3516 self.failUnlessEqual(f[0], "unknown")
3518 self.failUnlessReallyEqual(to_str(f[1]["rw_uri"]), unknown_rwcap, data)
3520 self.failIfIn("rw_uri", f[1])
3522 self.failUnlessReallyEqual(to_str(f[1]["ro_uri"]), unknown_immcap, data)
3524 self.failUnlessReallyEqual(to_str(f[1]["ro_uri"]), unknown_rocap, data)
3525 self.failUnless("metadata" in f[1])
3526 d.addCallback(_check_directory_json, expect_rw_uri=not immutable)
3528 def _check_info(res, expect_rw_uri, expect_ro_uri):
3529 self.failUnlessIn("Object Type: <span>unknown</span>", res)
3531 self.failUnlessIn(unknown_rwcap, res)
3534 self.failUnlessIn(unknown_immcap, res)
3536 self.failUnlessIn(unknown_rocap, res)
3538 self.failIfIn(unknown_rocap, res)
3539 self.failIfIn("Raw data as", res)
3540 self.failIfIn("Directory writecap", res)
3541 self.failIfIn("Checker Operations", res)
3542 self.failIfIn("Mutable File Operations", res)
3543 self.failIfIn("Directory Operations", res)
3545 # FIXME: these should have expect_rw_uri=not immutable; I don't know
3546 # why they fail. Possibly related to ticket #922.
3548 d.addCallback(lambda ign: self.GET(expected_info_url))
3549 d.addCallback(_check_info, expect_rw_uri=False, expect_ro_uri=False)
3550 d.addCallback(lambda ign: self.GET("%s%s?t=info" % (self.rooturl, str(name))))
3551 d.addCallback(_check_info, expect_rw_uri=False, expect_ro_uri=True)
3553 def _check_json(res, expect_rw_uri):
3554 data = simplejson.loads(res)
3555 self.failUnlessEqual(data[0], "unknown")
3557 self.failUnlessReallyEqual(to_str(data[1]["rw_uri"]), unknown_rwcap, data)
3559 self.failIfIn("rw_uri", data[1])
3562 self.failUnlessReallyEqual(to_str(data[1]["ro_uri"]), unknown_immcap, data)
3563 self.failUnlessReallyEqual(data[1]["mutable"], False)
3565 self.failUnlessReallyEqual(to_str(data[1]["ro_uri"]), unknown_rocap, data)
3566 self.failUnlessReallyEqual(data[1]["mutable"], True)
3568 self.failUnlessReallyEqual(to_str(data[1]["ro_uri"]), unknown_rocap, data)
3569 self.failIf("mutable" in data[1], data[1])
3571 # TODO: check metadata contents
3572 self.failUnless("metadata" in data[1])
3574 d.addCallback(lambda ign: self.GET("%s%s?t=json" % (self.rooturl, str(name))))
3575 d.addCallback(_check_json, expect_rw_uri=not immutable)
3577 # and make sure that a read-only version of the directory can be
3578 # rendered too. This version will not have unknown_rwcap, whether
3579 # or not future_node was immutable.
3580 d.addCallback(lambda ign: self.GET(self.rourl))
3582 d.addCallback(_check_directory_html, "-IMM")
3584 d.addCallback(_check_directory_html, "-RO")
3586 d.addCallback(lambda ign: self.GET(self.rourl+"?t=json"))
3587 d.addCallback(_check_directory_json, expect_rw_uri=False)
3589 d.addCallback(lambda ign: self.GET("%s%s?t=json" % (self.rourl, str(name))))
3590 d.addCallback(_check_json, expect_rw_uri=False)
3592 # TODO: check that getting t=info from the Info link in the ro directory
3593 # works, and does not include the writecap URI.
3596 def test_immutable_unknown(self):
3597 return self.test_unknown(immutable=True)
3599 def test_mutant_dirnodes_are_omitted(self):
3600 self.basedir = "web/Grid/mutant_dirnodes_are_omitted"
3603 c = self.g.clients[0]
3608 lonely_uri = "URI:LIT:n5xgk" # LIT for "one"
3609 mut_write_uri = "URI:SSK:vfvcbdfbszyrsaxchgevhmmlii:euw4iw7bbnkrrwpzuburbhppuxhc3gwxv26f6imekhz7zyw2ojnq"
3610 mut_read_uri = "URI:SSK-RO:e3mdrzfwhoq42hy5ubcz6rp3o4:ybyibhnp3vvwuq2vaw2ckjmesgkklfs6ghxleztqidihjyofgw7q"
3612 # This method tests mainly dirnode, but we'd have to duplicate code in order to
3613 # test the dirnode and web layers separately.
3615 # 'lonely' is a valid LIT child, 'ro' is a mutant child with an SSK-RO readcap,
3616 # and 'write-in-ro' is a mutant child with an SSK writecap in the ro_uri field.
3617 # When the directory is read, the mutants should be silently disposed of, leaving
3618 # their lonely sibling.
3619 # We don't test the case of a retrieving a cap from the encrypted rw_uri field,
3620 # because immutable directories don't have a writecap and therefore that field
3621 # isn't (and can't be) decrypted.
3622 # TODO: The field still exists in the netstring. Technically we should check what
3623 # happens if something is put there (_unpack_contents should raise ValueError),
3624 # but that can wait.
3626 lonely_child = nm.create_from_cap(lonely_uri)
3627 mutant_ro_child = nm.create_from_cap(mut_read_uri)
3628 mutant_write_in_ro_child = nm.create_from_cap(mut_write_uri)
3630 def _by_hook_or_by_crook():
3632 for n in [mutant_ro_child, mutant_write_in_ro_child]:
3633 n.is_allowed_in_immutable_directory = _by_hook_or_by_crook
3635 mutant_write_in_ro_child.get_write_uri = lambda: None
3636 mutant_write_in_ro_child.get_readonly_uri = lambda: mut_write_uri
3638 kids = {u"lonely": (lonely_child, {}),
3639 u"ro": (mutant_ro_child, {}),
3640 u"write-in-ro": (mutant_write_in_ro_child, {}),
3642 d = c.create_immutable_dirnode(kids)
3645 self.failUnless(isinstance(dn, dirnode.DirectoryNode))
3646 self.failIf(dn.is_mutable())
3647 self.failUnless(dn.is_readonly())
3648 # This checks that if we somehow ended up calling dn._decrypt_rwcapdata, it would fail.
3649 self.failIf(hasattr(dn._node, 'get_writekey'))
3651 self.failUnless("RO-IMM" in rep)
3653 self.failUnlessIn("CHK", cap.to_string())
3656 self.rooturl = "uri/" + urllib.quote(dn.get_uri()) + "/"
3657 return download_to_data(dn._node)
3658 d.addCallback(_created)
3660 def _check_data(data):
3661 # Decode the netstring representation of the directory to check that all children
3662 # are present. This is a bit of an abstraction violation, but there's not really
3663 # any other way to do it given that the real DirectoryNode._unpack_contents would
3664 # strip the mutant children out (which is what we're trying to test, later).
3667 while position < len(data):
3668 entries, position = split_netstring(data, 1, position)
3670 (name_utf8, ro_uri, rwcapdata, metadata_s), subpos = split_netstring(entry, 4)
3671 name = name_utf8.decode("utf-8")
3672 self.failUnless(rwcapdata == "")
3673 self.failUnless(name in kids)
3674 (expected_child, ign) = kids[name]
3675 self.failUnlessReallyEqual(ro_uri, expected_child.get_readonly_uri())
3678 self.failUnlessReallyEqual(numkids, 3)
3679 return self.rootnode.list()
3680 d.addCallback(_check_data)
3682 # Now when we use the real directory listing code, the mutants should be absent.
3683 def _check_kids(children):
3684 self.failUnlessReallyEqual(sorted(children.keys()), [u"lonely"])
3685 lonely_node, lonely_metadata = children[u"lonely"]
3687 self.failUnlessReallyEqual(lonely_node.get_write_uri(), None)
3688 self.failUnlessReallyEqual(lonely_node.get_readonly_uri(), lonely_uri)
3689 d.addCallback(_check_kids)
3691 d.addCallback(lambda ign: nm.create_from_cap(self.cap.to_string()))
3692 d.addCallback(lambda n: n.list())
3693 d.addCallback(_check_kids) # again with dirnode recreated from cap
3695 # Make sure the lonely child can be listed in HTML...
3696 d.addCallback(lambda ign: self.GET(self.rooturl))
3697 def _check_html(res):
3698 self.failIfIn("URI:SSK", res)
3699 get_lonely = "".join([r'<td>FILE</td>',
3701 r'<a href="[^"]+%s[^"]+">lonely</a>' % (urllib.quote(lonely_uri),),
3703 r'\s+<td>%d</td>' % len("one"),
3705 self.failUnless(re.search(get_lonely, res), res)
3707 # find the More Info link for name, should be relative
3708 mo = re.search(r'<a href="([^"]+)">More Info</a>', res)
3709 info_url = mo.group(1)
3710 self.failUnless(info_url.endswith(urllib.quote(lonely_uri) + "?t=info"), info_url)
3711 d.addCallback(_check_html)
3714 d.addCallback(lambda ign: self.GET(self.rooturl+"?t=json"))
3715 def _check_json(res):
3716 data = simplejson.loads(res)
3717 self.failUnlessEqual(data[0], "dirnode")
3718 listed_children = data[1]["children"]
3719 self.failUnlessReallyEqual(sorted(listed_children.keys()), [u"lonely"])
3720 ll_type, ll_data = listed_children[u"lonely"]
3721 self.failUnlessEqual(ll_type, "filenode")
3722 self.failIf("rw_uri" in ll_data)
3723 self.failUnlessReallyEqual(to_str(ll_data["ro_uri"]), lonely_uri)
3724 d.addCallback(_check_json)
3727 def test_deep_check(self):
3728 self.basedir = "web/Grid/deep_check"
3730 c0 = self.g.clients[0]
3734 d = c0.create_dirnode()
3735 def _stash_root_and_create_file(n):
3737 self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri()) + "/"
3738 return n.add_file(u"good", upload.Data(DATA, convergence=""))
3739 d.addCallback(_stash_root_and_create_file)
3740 def _stash_uri(fn, which):
3741 self.uris[which] = fn.get_uri()
3743 d.addCallback(_stash_uri, "good")
3744 d.addCallback(lambda ign:
3745 self.rootnode.add_file(u"small",
3746 upload.Data("literal",
3748 d.addCallback(_stash_uri, "small")
3749 d.addCallback(lambda ign:
3750 self.rootnode.add_file(u"sick",
3751 upload.Data(DATA+"1",
3753 d.addCallback(_stash_uri, "sick")
3755 # this tests that deep-check and stream-manifest will ignore
3756 # UnknownNode instances. Hopefully this will also cover deep-stats.
3757 future_node = UnknownNode(unknown_rwcap, unknown_rocap)
3758 d.addCallback(lambda ign: self.rootnode.set_node(u"future", future_node))
3760 def _clobber_shares(ignored):
3761 self.delete_shares_numbered(self.uris["sick"], [0,1])
3762 d.addCallback(_clobber_shares)
3770 d.addCallback(self.CHECK, "root", "t=stream-deep-check")
3773 units = [simplejson.loads(line)
3774 for line in res.splitlines()
3777 print "response is:", res
3778 print "undecodeable line was '%s'" % line
3780 self.failUnlessReallyEqual(len(units), 5+1)
3781 # should be parent-first
3783 self.failUnlessEqual(u0["path"], [])
3784 self.failUnlessEqual(u0["type"], "directory")
3785 self.failUnlessReallyEqual(to_str(u0["cap"]), self.rootnode.get_uri())
3786 u0cr = u0["check-results"]
3787 self.failUnlessReallyEqual(u0cr["results"]["count-shares-good"], 10)
3789 ugood = [u for u in units
3790 if u["type"] == "file" and u["path"] == [u"good"]][0]
3791 self.failUnlessReallyEqual(to_str(ugood["cap"]), self.uris["good"])
3792 ugoodcr = ugood["check-results"]
3793 self.failUnlessReallyEqual(ugoodcr["results"]["count-shares-good"], 10)
3796 self.failUnlessEqual(stats["type"], "stats")
3798 self.failUnlessReallyEqual(s["count-immutable-files"], 2)
3799 self.failUnlessReallyEqual(s["count-literal-files"], 1)
3800 self.failUnlessReallyEqual(s["count-directories"], 1)
3801 self.failUnlessReallyEqual(s["count-unknown"], 1)
3802 d.addCallback(_done)
3804 d.addCallback(self.CHECK, "root", "t=stream-manifest")
3805 def _check_manifest(res):
3806 self.failUnless(res.endswith("\n"))
3807 units = [simplejson.loads(t) for t in res[:-1].split("\n")]
3808 self.failUnlessReallyEqual(len(units), 5+1)
3809 self.failUnlessEqual(units[-1]["type"], "stats")
3811 self.failUnlessEqual(first["path"], [])
3812 self.failUnlessEqual(to_str(first["cap"]), self.rootnode.get_uri())
3813 self.failUnlessEqual(first["type"], "directory")
3814 stats = units[-1]["stats"]
3815 self.failUnlessReallyEqual(stats["count-immutable-files"], 2)
3816 self.failUnlessReallyEqual(stats["count-literal-files"], 1)
3817 self.failUnlessReallyEqual(stats["count-mutable-files"], 0)
3818 self.failUnlessReallyEqual(stats["count-immutable-files"], 2)
3819 self.failUnlessReallyEqual(stats["count-unknown"], 1)
3820 d.addCallback(_check_manifest)
3822 # now add root/subdir and root/subdir/grandchild, then make subdir
3823 # unrecoverable, then see what happens
3825 d.addCallback(lambda ign:
3826 self.rootnode.create_subdirectory(u"subdir"))
3827 d.addCallback(_stash_uri, "subdir")
3828 d.addCallback(lambda subdir_node:
3829 subdir_node.add_file(u"grandchild",
3830 upload.Data(DATA+"2",
3832 d.addCallback(_stash_uri, "grandchild")
3834 d.addCallback(lambda ign:
3835 self.delete_shares_numbered(self.uris["subdir"],
3843 # root/subdir [unrecoverable]
3844 # root/subdir/grandchild
3846 # how should a streaming-JSON API indicate fatal error?
3847 # answer: emit ERROR: instead of a JSON string
3849 d.addCallback(self.CHECK, "root", "t=stream-manifest")
3850 def _check_broken_manifest(res):
3851 lines = res.splitlines()
3853 for (i,line) in enumerate(lines)
3854 if line.startswith("ERROR:")]
3856 self.fail("no ERROR: in output: %s" % (res,))
3857 first_error = error_lines[0]
3858 error_line = lines[first_error]
3859 error_msg = lines[first_error+1:]
3860 error_msg_s = "\n".join(error_msg) + "\n"
3861 self.failUnlessIn("ERROR: UnrecoverableFileError(no recoverable versions)",
3863 self.failUnless(len(error_msg) > 2, error_msg_s) # some traceback
3864 units = [simplejson.loads(line) for line in lines[:first_error]]
3865 self.failUnlessReallyEqual(len(units), 6) # includes subdir
3866 last_unit = units[-1]
3867 self.failUnlessEqual(last_unit["path"], ["subdir"])
3868 d.addCallback(_check_broken_manifest)
3870 d.addCallback(self.CHECK, "root", "t=stream-deep-check")
3871 def _check_broken_deepcheck(res):
3872 lines = res.splitlines()
3874 for (i,line) in enumerate(lines)
3875 if line.startswith("ERROR:")]
3877 self.fail("no ERROR: in output: %s" % (res,))
3878 first_error = error_lines[0]
3879 error_line = lines[first_error]
3880 error_msg = lines[first_error+1:]
3881 error_msg_s = "\n".join(error_msg) + "\n"
3882 self.failUnlessIn("ERROR: UnrecoverableFileError(no recoverable versions)",
3884 self.failUnless(len(error_msg) > 2, error_msg_s) # some traceback
3885 units = [simplejson.loads(line) for line in lines[:first_error]]
3886 self.failUnlessReallyEqual(len(units), 6) # includes subdir
3887 last_unit = units[-1]
3888 self.failUnlessEqual(last_unit["path"], ["subdir"])
3889 r = last_unit["check-results"]["results"]
3890 self.failUnlessReallyEqual(r["count-recoverable-versions"], 0)
3891 self.failUnlessReallyEqual(r["count-shares-good"], 1)
3892 self.failUnlessReallyEqual(r["recoverable"], False)
3893 d.addCallback(_check_broken_deepcheck)
3895 d.addErrback(self.explain_web_error)
3898 def test_deep_check_and_repair(self):
3899 self.basedir = "web/Grid/deep_check_and_repair"
3901 c0 = self.g.clients[0]
3905 d = c0.create_dirnode()
3906 def _stash_root_and_create_file(n):
3908 self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri()) + "/"
3909 return n.add_file(u"good", upload.Data(DATA, convergence=""))
3910 d.addCallback(_stash_root_and_create_file)
3911 def _stash_uri(fn, which):
3912 self.uris[which] = fn.get_uri()
3913 d.addCallback(_stash_uri, "good")
3914 d.addCallback(lambda ign:
3915 self.rootnode.add_file(u"small",
3916 upload.Data("literal",
3918 d.addCallback(_stash_uri, "small")
3919 d.addCallback(lambda ign:
3920 self.rootnode.add_file(u"sick",
3921 upload.Data(DATA+"1",
3923 d.addCallback(_stash_uri, "sick")
3924 #d.addCallback(lambda ign:
3925 # self.rootnode.add_file(u"dead",
3926 # upload.Data(DATA+"2",
3928 #d.addCallback(_stash_uri, "dead")
3930 #d.addCallback(lambda ign: c0.create_mutable_file("mutable"))
3931 #d.addCallback(lambda fn: self.rootnode.set_node(u"corrupt", fn))
3932 #d.addCallback(_stash_uri, "corrupt")
3934 def _clobber_shares(ignored):
3935 good_shares = self.find_uri_shares(self.uris["good"])
3936 self.failUnlessReallyEqual(len(good_shares), 10)
3937 sick_shares = self.find_uri_shares(self.uris["sick"])
3938 os.unlink(sick_shares[0][2])
3939 #dead_shares = self.find_uri_shares(self.uris["dead"])
3940 #for i in range(1, 10):
3941 # os.unlink(dead_shares[i][2])
3943 #c_shares = self.find_uri_shares(self.uris["corrupt"])
3944 #cso = CorruptShareOptions()
3945 #cso.stdout = StringIO()
3946 #cso.parseOptions([c_shares[0][2]])
3948 d.addCallback(_clobber_shares)
3951 # root/good CHK, 10 shares
3953 # root/sick CHK, 9 shares
3955 d.addCallback(self.CHECK, "root", "t=stream-deep-check&repair=true")
3957 units = [simplejson.loads(line)
3958 for line in res.splitlines()
3960 self.failUnlessReallyEqual(len(units), 4+1)
3961 # should be parent-first
3963 self.failUnlessEqual(u0["path"], [])
3964 self.failUnlessEqual(u0["type"], "directory")
3965 self.failUnlessReallyEqual(to_str(u0["cap"]), self.rootnode.get_uri())
3966 u0crr = u0["check-and-repair-results"]
3967 self.failUnlessReallyEqual(u0crr["repair-attempted"], False)
3968 self.failUnlessReallyEqual(u0crr["pre-repair-results"]["results"]["count-shares-good"], 10)
3970 ugood = [u for u in units
3971 if u["type"] == "file" and u["path"] == [u"good"]][0]
3972 self.failUnlessEqual(to_str(ugood["cap"]), self.uris["good"])
3973 ugoodcrr = ugood["check-and-repair-results"]
3974 self.failUnlessReallyEqual(ugoodcrr["repair-attempted"], False)
3975 self.failUnlessReallyEqual(ugoodcrr["pre-repair-results"]["results"]["count-shares-good"], 10)
3977 usick = [u for u in units
3978 if u["type"] == "file" and u["path"] == [u"sick"]][0]
3979 self.failUnlessReallyEqual(to_str(usick["cap"]), self.uris["sick"])
3980 usickcrr = usick["check-and-repair-results"]
3981 self.failUnlessReallyEqual(usickcrr["repair-attempted"], True)
3982 self.failUnlessReallyEqual(usickcrr["repair-successful"], True)
3983 self.failUnlessReallyEqual(usickcrr["pre-repair-results"]["results"]["count-shares-good"], 9)
3984 self.failUnlessReallyEqual(usickcrr["post-repair-results"]["results"]["count-shares-good"], 10)
3987 self.failUnlessEqual(stats["type"], "stats")
3989 self.failUnlessReallyEqual(s["count-immutable-files"], 2)
3990 self.failUnlessReallyEqual(s["count-literal-files"], 1)
3991 self.failUnlessReallyEqual(s["count-directories"], 1)
3992 d.addCallback(_done)
3994 d.addErrback(self.explain_web_error)
3997 def _count_leases(self, ignored, which):
3998 u = self.uris[which]
3999 shares = self.find_uri_shares(u)
4001 for shnum, serverid, fn in shares:
4002 sf = get_share_file(fn)
4003 num_leases = len(list(sf.get_leases()))
4004 lease_counts.append( (fn, num_leases) )
4007 def _assert_leasecount(self, lease_counts, expected):
4008 for (fn, num_leases) in lease_counts:
4009 if num_leases != expected:
4010 self.fail("expected %d leases, have %d, on %s" %
4011 (expected, num_leases, fn))
4013 def test_add_lease(self):
4014 self.basedir = "web/Grid/add_lease"
4015 self.set_up_grid(num_clients=2)
4016 c0 = self.g.clients[0]
4019 d = c0.upload(upload.Data(DATA, convergence=""))
4020 def _stash_uri(ur, which):
4021 self.uris[which] = ur.uri
4022 d.addCallback(_stash_uri, "one")
4023 d.addCallback(lambda ign:
4024 c0.upload(upload.Data(DATA+"1", convergence="")))
4025 d.addCallback(_stash_uri, "two")
4026 def _stash_mutable_uri(n, which):
4027 self.uris[which] = n.get_uri()
4028 assert isinstance(self.uris[which], str)
4029 d.addCallback(lambda ign: c0.create_mutable_file(DATA+"2"))
4030 d.addCallback(_stash_mutable_uri, "mutable")
4032 def _compute_fileurls(ignored):
4034 for which in self.uris:
4035 self.fileurls[which] = "uri/" + urllib.quote(self.uris[which])
4036 d.addCallback(_compute_fileurls)
4038 d.addCallback(self._count_leases, "one")
4039 d.addCallback(self._assert_leasecount, 1)
4040 d.addCallback(self._count_leases, "two")
4041 d.addCallback(self._assert_leasecount, 1)
4042 d.addCallback(self._count_leases, "mutable")
4043 d.addCallback(self._assert_leasecount, 1)
4045 d.addCallback(self.CHECK, "one", "t=check") # no add-lease
4046 def _got_html_good(res):
4047 self.failUnless("Healthy" in res, res)
4048 self.failIf("Not Healthy" in res, res)
4049 d.addCallback(_got_html_good)
4051 d.addCallback(self._count_leases, "one")
4052 d.addCallback(self._assert_leasecount, 1)
4053 d.addCallback(self._count_leases, "two")
4054 d.addCallback(self._assert_leasecount, 1)
4055 d.addCallback(self._count_leases, "mutable")
4056 d.addCallback(self._assert_leasecount, 1)
4058 # this CHECK uses the original client, which uses the same
4059 # lease-secrets, so it will just renew the original lease
4060 d.addCallback(self.CHECK, "one", "t=check&add-lease=true")
4061 d.addCallback(_got_html_good)
4063 d.addCallback(self._count_leases, "one")
4064 d.addCallback(self._assert_leasecount, 1)
4065 d.addCallback(self._count_leases, "two")
4066 d.addCallback(self._assert_leasecount, 1)
4067 d.addCallback(self._count_leases, "mutable")
4068 d.addCallback(self._assert_leasecount, 1)
4070 # this CHECK uses an alternate client, which adds a second lease
4071 d.addCallback(self.CHECK, "one", "t=check&add-lease=true", clientnum=1)
4072 d.addCallback(_got_html_good)
4074 d.addCallback(self._count_leases, "one")
4075 d.addCallback(self._assert_leasecount, 2)
4076 d.addCallback(self._count_leases, "two")
4077 d.addCallback(self._assert_leasecount, 1)
4078 d.addCallback(self._count_leases, "mutable")
4079 d.addCallback(self._assert_leasecount, 1)
4081 d.addCallback(self.CHECK, "mutable", "t=check&add-lease=true")
4082 d.addCallback(_got_html_good)
4084 d.addCallback(self._count_leases, "one")
4085 d.addCallback(self._assert_leasecount, 2)
4086 d.addCallback(self._count_leases, "two")
4087 d.addCallback(self._assert_leasecount, 1)
4088 d.addCallback(self._count_leases, "mutable")
4089 d.addCallback(self._assert_leasecount, 1)
4091 d.addCallback(self.CHECK, "mutable", "t=check&add-lease=true",
4093 d.addCallback(_got_html_good)
4095 d.addCallback(self._count_leases, "one")
4096 d.addCallback(self._assert_leasecount, 2)
4097 d.addCallback(self._count_leases, "two")
4098 d.addCallback(self._assert_leasecount, 1)
4099 d.addCallback(self._count_leases, "mutable")
4100 d.addCallback(self._assert_leasecount, 2)
4102 d.addErrback(self.explain_web_error)
4105 def test_deep_add_lease(self):
4106 self.basedir = "web/Grid/deep_add_lease"
4107 self.set_up_grid(num_clients=2)
4108 c0 = self.g.clients[0]
4112 d = c0.create_dirnode()
4113 def _stash_root_and_create_file(n):
4115 self.uris["root"] = n.get_uri()
4116 self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri()) + "/"
4117 return n.add_file(u"one", upload.Data(DATA, convergence=""))
4118 d.addCallback(_stash_root_and_create_file)
4119 def _stash_uri(fn, which):
4120 self.uris[which] = fn.get_uri()
4121 d.addCallback(_stash_uri, "one")
4122 d.addCallback(lambda ign:
4123 self.rootnode.add_file(u"small",
4124 upload.Data("literal",
4126 d.addCallback(_stash_uri, "small")
4128 d.addCallback(lambda ign: c0.create_mutable_file("mutable"))
4129 d.addCallback(lambda fn: self.rootnode.set_node(u"mutable", fn))
4130 d.addCallback(_stash_uri, "mutable")
4132 d.addCallback(self.CHECK, "root", "t=stream-deep-check") # no add-lease
4134 units = [simplejson.loads(line)
4135 for line in res.splitlines()
4137 # root, one, small, mutable, stats
4138 self.failUnlessReallyEqual(len(units), 4+1)
4139 d.addCallback(_done)
4141 d.addCallback(self._count_leases, "root")
4142 d.addCallback(self._assert_leasecount, 1)
4143 d.addCallback(self._count_leases, "one")
4144 d.addCallback(self._assert_leasecount, 1)
4145 d.addCallback(self._count_leases, "mutable")
4146 d.addCallback(self._assert_leasecount, 1)
4148 d.addCallback(self.CHECK, "root", "t=stream-deep-check&add-lease=true")
4149 d.addCallback(_done)
4151 d.addCallback(self._count_leases, "root")
4152 d.addCallback(self._assert_leasecount, 1)
4153 d.addCallback(self._count_leases, "one")
4154 d.addCallback(self._assert_leasecount, 1)
4155 d.addCallback(self._count_leases, "mutable")
4156 d.addCallback(self._assert_leasecount, 1)
4158 d.addCallback(self.CHECK, "root", "t=stream-deep-check&add-lease=true",
4160 d.addCallback(_done)
4162 d.addCallback(self._count_leases, "root")
4163 d.addCallback(self._assert_leasecount, 2)
4164 d.addCallback(self._count_leases, "one")
4165 d.addCallback(self._assert_leasecount, 2)
4166 d.addCallback(self._count_leases, "mutable")
4167 d.addCallback(self._assert_leasecount, 2)
4169 d.addErrback(self.explain_web_error)
4173 def test_exceptions(self):
4174 self.basedir = "web/Grid/exceptions"
4175 self.set_up_grid(num_clients=1, num_servers=2)
4176 c0 = self.g.clients[0]
4177 c0.DEFAULT_ENCODING_PARAMETERS['happy'] = 2
4180 d = c0.create_dirnode()
4182 self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri()) + "/"
4183 self.fileurls["imaginary"] = self.fileurls["root"] + "imaginary"
4185 d.addCallback(_stash_root)
4186 d.addCallback(lambda ign: c0.upload(upload.Data(DATA, convergence="")))
4188 self.fileurls["1share"] = "uri/" + urllib.quote(ur.uri)
4189 self.delete_shares_numbered(ur.uri, range(1,10))
4191 u = uri.from_string(ur.uri)
4192 u.key = testutil.flip_bit(u.key, 0)
4193 baduri = u.to_string()
4194 self.fileurls["0shares"] = "uri/" + urllib.quote(baduri)
4195 d.addCallback(_stash_bad)
4196 d.addCallback(lambda ign: c0.create_dirnode())
4197 def _mangle_dirnode_1share(n):
4199 url = self.fileurls["dir-1share"] = "uri/" + urllib.quote(u) + "/"
4200 self.fileurls["dir-1share-json"] = url + "?t=json"
4201 self.delete_shares_numbered(u, range(1,10))
4202 d.addCallback(_mangle_dirnode_1share)
4203 d.addCallback(lambda ign: c0.create_dirnode())
4204 def _mangle_dirnode_0share(n):
4206 url = self.fileurls["dir-0share"] = "uri/" + urllib.quote(u) + "/"
4207 self.fileurls["dir-0share-json"] = url + "?t=json"
4208 self.delete_shares_numbered(u, range(0,10))
4209 d.addCallback(_mangle_dirnode_0share)
4211 # NotEnoughSharesError should be reported sensibly, with a
4212 # text/plain explanation of the problem, and perhaps some
4213 # information on which shares *could* be found.
4215 d.addCallback(lambda ignored:
4216 self.shouldHTTPError("GET unrecoverable",
4217 410, "Gone", "NoSharesError",
4218 self.GET, self.fileurls["0shares"]))
4219 def _check_zero_shares(body):
4220 self.failIf("<html>" in body, body)
4221 body = " ".join(body.strip().split())
4222 exp = ("NoSharesError: no shares could be found. "
4223 "Zero shares usually indicates a corrupt URI, or that "
4224 "no servers were connected, but it might also indicate "
4225 "severe corruption. You should perform a filecheck on "
4226 "this object to learn more. The full error message is: "
4227 "no shares (need 3). Last failure: None")
4228 self.failUnlessReallyEqual(exp, body)
4229 d.addCallback(_check_zero_shares)
4232 d.addCallback(lambda ignored:
4233 self.shouldHTTPError("GET 1share",
4234 410, "Gone", "NotEnoughSharesError",
4235 self.GET, self.fileurls["1share"]))
4236 def _check_one_share(body):
4237 self.failIf("<html>" in body, body)
4238 body = " ".join(body.strip().split())
4239 msg = ("NotEnoughSharesError: This indicates that some "
4240 "servers were unavailable, or that shares have been "
4241 "lost to server departure, hard drive failure, or disk "
4242 "corruption. You should perform a filecheck on "
4243 "this object to learn more. The full error message is:"
4244 " ran out of shares: %d complete, %d pending, 0 overdue,"
4245 " 0 unused, need 3. Last failure: None")
4248 self.failUnless(body == msg1 or body == msg2, body)
4249 d.addCallback(_check_one_share)
4251 d.addCallback(lambda ignored:
4252 self.shouldHTTPError("GET imaginary",
4253 404, "Not Found", None,
4254 self.GET, self.fileurls["imaginary"]))
4255 def _missing_child(body):
4256 self.failUnless("No such child: imaginary" in body, body)
4257 d.addCallback(_missing_child)
4259 d.addCallback(lambda ignored: self.GET(self.fileurls["dir-0share"]))
4260 def _check_0shares_dir_html(body):
4261 self.failUnless("<html>" in body, body)
4262 # we should see the regular page, but without the child table or
4264 body = " ".join(body.strip().split())
4265 self.failUnlessIn('href="?t=info">More info on this directory',
4267 exp = ("UnrecoverableFileError: the directory (or mutable file) "
4268 "could not be retrieved, because there were insufficient "
4269 "good shares. This might indicate that no servers were "
4270 "connected, insufficient servers were connected, the URI "
4271 "was corrupt, or that shares have been lost due to server "
4272 "departure, hard drive failure, or disk corruption. You "
4273 "should perform a filecheck on this object to learn more.")
4274 self.failUnlessIn(exp, body)
4275 self.failUnlessIn("No upload forms: directory is unreadable", body)
4276 d.addCallback(_check_0shares_dir_html)
4278 d.addCallback(lambda ignored: self.GET(self.fileurls["dir-1share"]))
4279 def _check_1shares_dir_html(body):
4280 # at some point, we'll split UnrecoverableFileError into 0-shares
4281 # and some-shares like we did for immutable files (since there
4282 # are different sorts of advice to offer in each case). For now,
4283 # they present the same way.
4284 self.failUnless("<html>" in body, body)
4285 body = " ".join(body.strip().split())
4286 self.failUnlessIn('href="?t=info">More info on this directory',
4288 exp = ("UnrecoverableFileError: the directory (or mutable file) "
4289 "could not be retrieved, because there were insufficient "
4290 "good shares. This might indicate that no servers were "
4291 "connected, insufficient servers were connected, the URI "
4292 "was corrupt, or that shares have been lost due to server "
4293 "departure, hard drive failure, or disk corruption. You "
4294 "should perform a filecheck on this object to learn more.")
4295 self.failUnlessIn(exp, body)
4296 self.failUnlessIn("No upload forms: directory is unreadable", body)
4297 d.addCallback(_check_1shares_dir_html)
4299 d.addCallback(lambda ignored:
4300 self.shouldHTTPError("GET dir-0share-json",
4301 410, "Gone", "UnrecoverableFileError",
4303 self.fileurls["dir-0share-json"]))
4304 def _check_unrecoverable_file(body):
4305 self.failIf("<html>" in body, body)
4306 body = " ".join(body.strip().split())
4307 exp = ("UnrecoverableFileError: the directory (or mutable file) "
4308 "could not be retrieved, because there were insufficient "
4309 "good shares. This might indicate that no servers were "
4310 "connected, insufficient servers were connected, the URI "
4311 "was corrupt, or that shares have been lost due to server "
4312 "departure, hard drive failure, or disk corruption. You "
4313 "should perform a filecheck on this object to learn more.")
4314 self.failUnlessReallyEqual(exp, body)
4315 d.addCallback(_check_unrecoverable_file)
4317 d.addCallback(lambda ignored:
4318 self.shouldHTTPError("GET dir-1share-json",
4319 410, "Gone", "UnrecoverableFileError",
4321 self.fileurls["dir-1share-json"]))
4322 d.addCallback(_check_unrecoverable_file)
4324 d.addCallback(lambda ignored:
4325 self.shouldHTTPError("GET imaginary",
4326 404, "Not Found", None,
4327 self.GET, self.fileurls["imaginary"]))
4329 # attach a webapi child that throws a random error, to test how it
4331 w = c0.getServiceNamed("webish")
4332 w.root.putChild("ERRORBOOM", ErrorBoom())
4334 # "Accept: */*" : should get a text/html stack trace
4335 # "Accept: text/plain" : should get a text/plain stack trace
4336 # "Accept: text/plain, application/octet-stream" : text/plain (CLI)
4337 # no Accept header: should get a text/html stack trace
4339 d.addCallback(lambda ignored:
4340 self.shouldHTTPError("GET errorboom_html",
4341 500, "Internal Server Error", None,
4342 self.GET, "ERRORBOOM",
4343 headers={"accept": ["*/*"]}))
4344 def _internal_error_html1(body):
4345 self.failUnless("<html>" in body, "expected HTML, not '%s'" % body)
4346 d.addCallback(_internal_error_html1)
4348 d.addCallback(lambda ignored:
4349 self.shouldHTTPError("GET errorboom_text",
4350 500, "Internal Server Error", None,
4351 self.GET, "ERRORBOOM",
4352 headers={"accept": ["text/plain"]}))
4353 def _internal_error_text2(body):
4354 self.failIf("<html>" in body, body)
4355 self.failUnless(body.startswith("Traceback "), body)
4356 d.addCallback(_internal_error_text2)
4358 CLI_accepts = "text/plain, application/octet-stream"
4359 d.addCallback(lambda ignored:
4360 self.shouldHTTPError("GET errorboom_text",
4361 500, "Internal Server Error", None,
4362 self.GET, "ERRORBOOM",
4363 headers={"accept": [CLI_accepts]}))
4364 def _internal_error_text3(body):
4365 self.failIf("<html>" in body, body)
4366 self.failUnless(body.startswith("Traceback "), body)
4367 d.addCallback(_internal_error_text3)
4369 d.addCallback(lambda ignored:
4370 self.shouldHTTPError("GET errorboom_text",
4371 500, "Internal Server Error", None,
4372 self.GET, "ERRORBOOM"))
4373 def _internal_error_html4(body):
4374 self.failUnless("<html>" in body, "expected HTML, not '%s'" % body)
4375 d.addCallback(_internal_error_html4)
4377 def _flush_errors(res):
4378 # Trial: please ignore the CompletelyUnhandledError in the logs
4379 self.flushLoggedErrors(CompletelyUnhandledError)
4381 d.addBoth(_flush_errors)
4385 class CompletelyUnhandledError(Exception):
4387 class ErrorBoom(rend.Page):
4388 def beforeRender(self, ctx):
4389 raise CompletelyUnhandledError("whoops")