1 import os.path, re, urllib
3 from StringIO import StringIO
4 from twisted.application import service
5 from twisted.trial import unittest
6 from twisted.internet import defer, reactor
7 from twisted.web import client, error, http
8 from twisted.python import failure, log
10 from allmydata import interfaces, uri, webish
11 from allmydata.storage.mutable import MutableShareFile
12 from allmydata.storage.immutable import ShareFile
13 from allmydata.immutable import upload, download
14 from allmydata.web import status, common
15 from allmydata.scripts.debug import CorruptShareOptions, corrupt_share
16 from allmydata.util import fileutil, base32
17 from allmydata.util.assertutil import precondition
18 from allmydata.test.common import FakeDirectoryNode, FakeCHKFileNode, \
19 FakeMutableFileNode, create_chk_filenode, WebErrorMixin, ShouldFailMixin
20 from allmydata.interfaces import IURI, INewDirectoryURI, \
21 IReadonlyNewDirectoryURI, IFileURI, IMutableFileURI, IMutableFileNode
22 from allmydata.mutable import servermap, publish, retrieve
23 import common_util as testutil
24 from allmydata.test.no_network import GridTestMixin
26 from allmydata.test.common_web import HTTPClientGETFactory, \
29 # create a fake uploader/downloader, and a couple of fake dirnodes, then
30 # create a webserver that works against them
32 class FakeIntroducerClient:
33 def get_all_connectors(self):
35 def get_all_connections_for(self, service_name):
37 def get_all_peerids(self):
40 class FakeStatsProvider:
42 stats = {'stats': {}, 'counters': {}}
45 class FakeClient(service.MultiService):
46 nodeid = "fake_nodeid"
47 nickname = "fake_nickname"
48 basedir = "fake_basedir"
49 def get_versions(self):
50 return {'allmydata': "fake",
55 introducer_furl = "None"
56 introducer_client = FakeIntroducerClient()
57 _all_upload_status = [upload.UploadStatus()]
58 _all_download_status = [download.DownloadStatus()]
59 _all_mapupdate_statuses = [servermap.UpdateStatus()]
60 _all_publish_statuses = [publish.PublishStatus()]
61 _all_retrieve_statuses = [retrieve.RetrieveStatus()]
62 convergence = "some random string"
63 stats_provider = FakeStatsProvider()
65 def connected_to_introducer(self):
68 def get_nickname_for_peerid(self, peerid):
71 def get_permuted_peers(self, service_name, key):
74 def create_node_from_uri(self, auri):
75 precondition(isinstance(auri, str), auri)
76 u = uri.from_string(auri)
77 if (INewDirectoryURI.providedBy(u)
78 or IReadonlyNewDirectoryURI.providedBy(u)):
79 return FakeDirectoryNode(self).init_from_uri(u)
80 if IFileURI.providedBy(u):
81 return FakeCHKFileNode(u, self)
82 assert IMutableFileURI.providedBy(u), u
83 return FakeMutableFileNode(self).init_from_uri(u)
85 def create_empty_dirnode(self):
86 n = FakeDirectoryNode(self)
88 d.addCallback(lambda res: n)
91 MUTABLE_SIZELIMIT = FakeMutableFileNode.MUTABLE_SIZELIMIT
92 def create_mutable_file(self, contents=""):
93 n = FakeMutableFileNode(self)
94 return n.create(contents)
96 def upload(self, uploadable):
97 d = uploadable.get_size()
98 d.addCallback(lambda size: uploadable.read(size))
100 data = "".join(datav)
101 n = create_chk_filenode(self, data)
102 results = upload.UploadResults()
103 results.uri = n.get_uri()
105 d.addCallback(_got_data)
108 def list_all_upload_statuses(self):
109 return self._all_upload_status
110 def list_all_download_statuses(self):
111 return self._all_download_status
112 def list_all_mapupdate_statuses(self):
113 return self._all_mapupdate_statuses
114 def list_all_publish_statuses(self):
115 return self._all_publish_statuses
116 def list_all_retrieve_statuses(self):
117 return self._all_retrieve_statuses
118 def list_all_helper_statuses(self):
121 class WebMixin(object):
123 self.s = FakeClient()
124 self.s.startService()
125 self.staticdir = self.mktemp()
126 self.ws = s = webish.WebishServer(self.s, "0", staticdir=self.staticdir)
127 s.setServiceParent(self.s)
128 self.webish_port = port = s.listener._port.getHost().port
129 self.webish_url = "http://localhost:%d" % port
131 l = [ self.s.create_empty_dirnode() for x in range(6) ]
132 d = defer.DeferredList(l)
134 self.public_root = res[0][1]
135 assert interfaces.IDirectoryNode.providedBy(self.public_root), res
136 self.public_url = "/uri/" + self.public_root.get_uri()
137 self.private_root = res[1][1]
141 self._foo_uri = foo.get_uri()
142 self._foo_readonly_uri = foo.get_readonly_uri()
143 self._foo_verifycap = foo.get_verify_cap().to_string()
144 # NOTE: we ignore the deferred on all set_uri() calls, because we
145 # know the fake nodes do these synchronously
146 self.public_root.set_uri(u"foo", foo.get_uri())
148 self.BAR_CONTENTS, n, self._bar_txt_uri = self.makefile(0)
149 foo.set_uri(u"bar.txt", self._bar_txt_uri)
150 self._bar_txt_verifycap = n.get_verify_cap().to_string()
152 foo.set_uri(u"empty", res[3][1].get_uri())
153 sub_uri = res[4][1].get_uri()
154 self._sub_uri = sub_uri
155 foo.set_uri(u"sub", sub_uri)
156 sub = self.s.create_node_from_uri(sub_uri)
158 _ign, n, blocking_uri = self.makefile(1)
159 foo.set_uri(u"blockingfile", blocking_uri)
161 unicode_filename = u"n\u00fc.txt" # n u-umlaut . t x t
162 # ok, unicode calls it LATIN SMALL LETTER U WITH DIAERESIS but I
163 # still think of it as an umlaut
164 foo.set_uri(unicode_filename, self._bar_txt_uri)
166 _ign, n, baz_file = self.makefile(2)
167 self._baz_file_uri = baz_file
168 sub.set_uri(u"baz.txt", baz_file)
170 _ign, n, self._bad_file_uri = self.makefile(3)
171 # this uri should not be downloadable
172 del FakeCHKFileNode.all_contents[self._bad_file_uri]
175 self.public_root.set_uri(u"reedownlee", rodir.get_readonly_uri())
176 rodir.set_uri(u"nor", baz_file)
181 # public/foo/blockingfile
184 # public/foo/sub/baz.txt
186 # public/reedownlee/nor
187 self.NEWFILE_CONTENTS = "newfile contents\n"
189 return foo.get_metadata_for(u"bar.txt")
191 def _got_metadata(metadata):
192 self._bar_txt_metadata = metadata
193 d.addCallback(_got_metadata)
196 def makefile(self, number):
197 contents = "contents of file %s\n" % number
198 n = create_chk_filenode(self.s, contents)
199 return contents, n, n.get_uri()
202 return self.s.stopService()
204 def failUnlessIsBarDotTxt(self, res):
205 self.failUnlessEqual(res, self.BAR_CONTENTS, res)
207 def failUnlessIsBarJSON(self, res):
208 data = simplejson.loads(res)
209 self.failUnless(isinstance(data, list))
210 self.failUnlessEqual(data[0], u"filenode")
211 self.failUnless(isinstance(data[1], dict))
212 self.failIf(data[1]["mutable"])
213 self.failIf("rw_uri" in data[1]) # immutable
214 self.failUnlessEqual(data[1]["ro_uri"], self._bar_txt_uri)
215 self.failUnlessEqual(data[1]["verify_uri"], self._bar_txt_verifycap)
216 self.failUnlessEqual(data[1]["size"], len(self.BAR_CONTENTS))
218 def failUnlessIsFooJSON(self, res):
219 data = simplejson.loads(res)
220 self.failUnless(isinstance(data, list))
221 self.failUnlessEqual(data[0], "dirnode", res)
222 self.failUnless(isinstance(data[1], dict))
223 self.failUnless(data[1]["mutable"])
224 self.failUnless("rw_uri" in data[1]) # mutable
225 self.failUnlessEqual(data[1]["rw_uri"], self._foo_uri)
226 self.failUnlessEqual(data[1]["ro_uri"], self._foo_readonly_uri)
227 self.failUnlessEqual(data[1]["verify_uri"], self._foo_verifycap)
229 kidnames = sorted([unicode(n) for n in data[1]["children"]])
230 self.failUnlessEqual(kidnames,
231 [u"bar.txt", u"blockingfile", u"empty",
232 u"n\u00fc.txt", u"sub"])
233 kids = dict( [(unicode(name),value)
235 in data[1]["children"].iteritems()] )
236 self.failUnlessEqual(kids[u"sub"][0], "dirnode")
237 self.failUnless("metadata" in kids[u"sub"][1])
238 self.failUnless("ctime" in kids[u"sub"][1]["metadata"])
239 self.failUnless("mtime" in kids[u"sub"][1]["metadata"])
240 self.failUnlessEqual(kids[u"bar.txt"][0], "filenode")
241 self.failUnlessEqual(kids[u"bar.txt"][1]["size"], len(self.BAR_CONTENTS))
242 self.failUnlessEqual(kids[u"bar.txt"][1]["ro_uri"], self._bar_txt_uri)
243 self.failUnlessEqual(kids[u"bar.txt"][1]["verify_uri"],
244 self._bar_txt_verifycap)
245 self.failUnlessEqual(kids[u"bar.txt"][1]["metadata"]["ctime"],
246 self._bar_txt_metadata["ctime"])
247 self.failUnlessEqual(kids[u"n\u00fc.txt"][1]["ro_uri"],
250 def GET(self, urlpath, followRedirect=False, return_response=False,
252 # if return_response=True, this fires with (data, statuscode,
253 # respheaders) instead of just data.
254 assert not isinstance(urlpath, unicode)
255 url = self.webish_url + urlpath
256 factory = HTTPClientGETFactory(url, method="GET",
257 followRedirect=followRedirect, **kwargs)
258 reactor.connectTCP("localhost", self.webish_port, factory)
261 return (data, factory.status, factory.response_headers)
263 d.addCallback(_got_data)
264 return factory.deferred
266 def HEAD(self, urlpath, return_response=False, **kwargs):
267 # this requires some surgery, because twisted.web.client doesn't want
268 # to give us back the response headers.
269 factory = HTTPClientHEADFactory(urlpath, method="HEAD", **kwargs)
270 reactor.connectTCP("localhost", self.webish_port, factory)
273 return (data, factory.status, factory.response_headers)
275 d.addCallback(_got_data)
276 return factory.deferred
278 def PUT(self, urlpath, data, **kwargs):
279 url = self.webish_url + urlpath
280 return client.getPage(url, method="PUT", postdata=data, **kwargs)
282 def DELETE(self, urlpath):
283 url = self.webish_url + urlpath
284 return client.getPage(url, method="DELETE")
286 def POST(self, urlpath, followRedirect=False, **fields):
287 url = self.webish_url + urlpath
288 sepbase = "boogabooga"
292 form.append('Content-Disposition: form-data; name="_charset"')
296 for name, value in fields.iteritems():
297 if isinstance(value, tuple):
298 filename, value = value
299 form.append('Content-Disposition: form-data; name="%s"; '
300 'filename="%s"' % (name, filename.encode("utf-8")))
302 form.append('Content-Disposition: form-data; name="%s"' % name)
304 if isinstance(value, unicode):
305 value = value.encode("utf-8")
308 assert isinstance(value, str)
312 body = "\r\n".join(form) + "\r\n"
313 headers = {"content-type": "multipart/form-data; boundary=%s" % sepbase,
315 return client.getPage(url, method="POST", postdata=body,
316 headers=headers, followRedirect=followRedirect)
318 def shouldFail(self, res, expected_failure, which,
319 substring=None, response_substring=None):
320 if isinstance(res, failure.Failure):
321 res.trap(expected_failure)
323 self.failUnless(substring in str(res),
324 "substring '%s' not in '%s'"
325 % (substring, str(res)))
326 if response_substring:
327 self.failUnless(response_substring in res.value.response,
328 "response substring '%s' not in '%s'"
329 % (response_substring, res.value.response))
331 self.fail("%s was supposed to raise %s, not get '%s'" %
332 (which, expected_failure, res))
334 def shouldFail2(self, expected_failure, which, substring,
336 callable, *args, **kwargs):
337 assert substring is None or isinstance(substring, str)
338 assert response_substring is None or isinstance(response_substring, str)
339 d = defer.maybeDeferred(callable, *args, **kwargs)
341 if isinstance(res, failure.Failure):
342 res.trap(expected_failure)
344 self.failUnless(substring in str(res),
345 "%s: substring '%s' not in '%s'"
346 % (which, substring, str(res)))
347 if response_substring:
348 self.failUnless(response_substring in res.value.response,
349 "%s: response substring '%s' not in '%s'"
351 response_substring, res.value.response))
353 self.fail("%s was supposed to raise %s, not get '%s'" %
354 (which, expected_failure, res))
358 def should404(self, res, which):
359 if isinstance(res, failure.Failure):
360 res.trap(error.Error)
361 self.failUnlessEqual(res.value.status, "404")
363 self.fail("%s was supposed to Error(404), not get '%s'" %
367 class Web(WebMixin, WebErrorMixin, testutil.StallMixin, unittest.TestCase):
368 def test_create(self):
371 def test_welcome(self):
374 self.failUnless('Welcome To AllMyData' in res)
375 self.failUnless('Tahoe' in res)
377 self.s.basedir = 'web/test_welcome'
378 fileutil.make_dirs("web/test_welcome")
379 fileutil.make_dirs("web/test_welcome/private")
381 d.addCallback(_check)
384 def test_provisioning(self):
385 d = self.GET("/provisioning/")
387 self.failUnless('Tahoe Provisioning Tool' in res)
388 fields = {'filled': True,
389 "num_users": int(50e3),
390 "files_per_user": 1000,
391 "space_per_user": int(1e9),
392 "sharing_ratio": 1.0,
393 "encoding_parameters": "3-of-10-5",
395 "ownership_mode": "A",
396 "download_rate": 100,
401 return self.POST("/provisioning/", **fields)
403 d.addCallback(_check)
405 self.failUnless('Tahoe Provisioning Tool' in res)
406 self.failUnless("Share space consumed: 167.01TB" in res)
408 fields = {'filled': True,
409 "num_users": int(50e6),
410 "files_per_user": 1000,
411 "space_per_user": int(5e9),
412 "sharing_ratio": 1.0,
413 "encoding_parameters": "25-of-100-50",
414 "num_servers": 30000,
415 "ownership_mode": "E",
416 "drive_failure_model": "U",
418 "download_rate": 1000,
423 return self.POST("/provisioning/", **fields)
424 d.addCallback(_check2)
426 self.failUnless("Share space consumed: huge!" in res)
427 fields = {'filled': True}
428 return self.POST("/provisioning/", **fields)
429 d.addCallback(_check3)
431 self.failUnless("Share space consumed:" in res)
432 d.addCallback(_check4)
435 def test_reliability_tool(self):
437 from allmydata import reliability
438 _hush_pyflakes = reliability
440 raise unittest.SkipTest("reliability tool requires NumPy")
442 d = self.GET("/reliability/")
444 self.failUnless('Tahoe Reliability Tool' in res)
445 fields = {'drive_lifetime': "8Y",
450 "check_period": "1M",
451 "report_period": "3M",
454 return self.POST("/reliability/", **fields)
456 d.addCallback(_check)
458 self.failUnless('Tahoe Reliability Tool' in res)
459 r = r'Probability of loss \(no maintenance\):\s+<span>0.033591'
460 self.failUnless(re.search(r, res), res)
461 d.addCallback(_check2)
464 def test_status(self):
465 dl_num = self.s.list_all_download_statuses()[0].get_counter()
466 ul_num = self.s.list_all_upload_statuses()[0].get_counter()
467 mu_num = self.s.list_all_mapupdate_statuses()[0].get_counter()
468 pub_num = self.s.list_all_publish_statuses()[0].get_counter()
469 ret_num = self.s.list_all_retrieve_statuses()[0].get_counter()
470 d = self.GET("/status", followRedirect=True)
472 self.failUnless('Upload and Download Status' in res, res)
473 self.failUnless('"down-%d"' % dl_num in res, res)
474 self.failUnless('"up-%d"' % ul_num in res, res)
475 self.failUnless('"mapupdate-%d"' % mu_num in res, res)
476 self.failUnless('"publish-%d"' % pub_num in res, res)
477 self.failUnless('"retrieve-%d"' % ret_num in res, res)
478 d.addCallback(_check)
479 d.addCallback(lambda res: self.GET("/status/?t=json"))
480 def _check_json(res):
481 data = simplejson.loads(res)
482 self.failUnless(isinstance(data, dict))
483 active = data["active"]
484 # TODO: test more. We need a way to fake an active operation
486 d.addCallback(_check_json)
488 d.addCallback(lambda res: self.GET("/status/down-%d" % dl_num))
490 self.failUnless("File Download Status" in res, res)
491 d.addCallback(_check_dl)
492 d.addCallback(lambda res: self.GET("/status/up-%d" % ul_num))
494 self.failUnless("File Upload Status" in res, res)
495 d.addCallback(_check_ul)
496 d.addCallback(lambda res: self.GET("/status/mapupdate-%d" % mu_num))
497 def _check_mapupdate(res):
498 self.failUnless("Mutable File Servermap Update Status" in res, res)
499 d.addCallback(_check_mapupdate)
500 d.addCallback(lambda res: self.GET("/status/publish-%d" % pub_num))
501 def _check_publish(res):
502 self.failUnless("Mutable File Publish Status" in res, res)
503 d.addCallback(_check_publish)
504 d.addCallback(lambda res: self.GET("/status/retrieve-%d" % ret_num))
505 def _check_retrieve(res):
506 self.failUnless("Mutable File Retrieve Status" in res, res)
507 d.addCallback(_check_retrieve)
511 def test_status_numbers(self):
512 drrm = status.DownloadResultsRendererMixin()
513 self.failUnlessEqual(drrm.render_time(None, None), "")
514 self.failUnlessEqual(drrm.render_time(None, 2.5), "2.50s")
515 self.failUnlessEqual(drrm.render_time(None, 0.25), "250ms")
516 self.failUnlessEqual(drrm.render_time(None, 0.0021), "2.1ms")
517 self.failUnlessEqual(drrm.render_time(None, 0.000123), "123us")
518 self.failUnlessEqual(drrm.render_rate(None, None), "")
519 self.failUnlessEqual(drrm.render_rate(None, 2500000), "2.50MBps")
520 self.failUnlessEqual(drrm.render_rate(None, 30100), "30.1kBps")
521 self.failUnlessEqual(drrm.render_rate(None, 123), "123Bps")
523 urrm = status.UploadResultsRendererMixin()
524 self.failUnlessEqual(urrm.render_time(None, None), "")
525 self.failUnlessEqual(urrm.render_time(None, 2.5), "2.50s")
526 self.failUnlessEqual(urrm.render_time(None, 0.25), "250ms")
527 self.failUnlessEqual(urrm.render_time(None, 0.0021), "2.1ms")
528 self.failUnlessEqual(urrm.render_time(None, 0.000123), "123us")
529 self.failUnlessEqual(urrm.render_rate(None, None), "")
530 self.failUnlessEqual(urrm.render_rate(None, 2500000), "2.50MBps")
531 self.failUnlessEqual(urrm.render_rate(None, 30100), "30.1kBps")
532 self.failUnlessEqual(urrm.render_rate(None, 123), "123Bps")
534 def test_GET_FILEURL(self):
535 d = self.GET(self.public_url + "/foo/bar.txt")
536 d.addCallback(self.failUnlessIsBarDotTxt)
539 def test_GET_FILEURL_range(self):
540 headers = {"range": "bytes=1-10"}
541 d = self.GET(self.public_url + "/foo/bar.txt", headers=headers,
542 return_response=True)
543 def _got((res, status, headers)):
544 self.failUnlessEqual(int(status), 206)
545 self.failUnless(headers.has_key("content-range"))
546 self.failUnlessEqual(headers["content-range"][0],
547 "bytes 1-10/%d" % len(self.BAR_CONTENTS))
548 self.failUnlessEqual(res, self.BAR_CONTENTS[1:11])
552 def test_GET_FILEURL_partial_range(self):
553 headers = {"range": "bytes=5-"}
554 length = len(self.BAR_CONTENTS)
555 d = self.GET(self.public_url + "/foo/bar.txt", headers=headers,
556 return_response=True)
557 def _got((res, status, headers)):
558 self.failUnlessEqual(int(status), 206)
559 self.failUnless(headers.has_key("content-range"))
560 self.failUnlessEqual(headers["content-range"][0],
561 "bytes 5-%d/%d" % (length-1, length))
562 self.failUnlessEqual(res, self.BAR_CONTENTS[5:])
566 def test_HEAD_FILEURL_range(self):
567 headers = {"range": "bytes=1-10"}
568 d = self.HEAD(self.public_url + "/foo/bar.txt", headers=headers,
569 return_response=True)
570 def _got((res, status, headers)):
571 self.failUnlessEqual(res, "")
572 self.failUnlessEqual(int(status), 206)
573 self.failUnless(headers.has_key("content-range"))
574 self.failUnlessEqual(headers["content-range"][0],
575 "bytes 1-10/%d" % len(self.BAR_CONTENTS))
579 def test_HEAD_FILEURL_partial_range(self):
580 headers = {"range": "bytes=5-"}
581 length = len(self.BAR_CONTENTS)
582 d = self.HEAD(self.public_url + "/foo/bar.txt", headers=headers,
583 return_response=True)
584 def _got((res, status, headers)):
585 self.failUnlessEqual(int(status), 206)
586 self.failUnless(headers.has_key("content-range"))
587 self.failUnlessEqual(headers["content-range"][0],
588 "bytes 5-%d/%d" % (length-1, length))
592 def test_GET_FILEURL_range_bad(self):
593 headers = {"range": "BOGUS=fizbop-quarnak"}
594 d = self.shouldFail2(error.Error, "test_GET_FILEURL_range_bad",
596 "Syntactically invalid http range header",
597 self.GET, self.public_url + "/foo/bar.txt",
601 def test_HEAD_FILEURL(self):
602 d = self.HEAD(self.public_url + "/foo/bar.txt", return_response=True)
603 def _got((res, status, headers)):
604 self.failUnlessEqual(res, "")
605 self.failUnlessEqual(headers["content-length"][0],
606 str(len(self.BAR_CONTENTS)))
607 self.failUnlessEqual(headers["content-type"], ["text/plain"])
611 def test_GET_FILEURL_named(self):
612 base = "/file/%s" % urllib.quote(self._bar_txt_uri)
613 base2 = "/named/%s" % urllib.quote(self._bar_txt_uri)
614 d = self.GET(base + "/@@name=/blah.txt")
615 d.addCallback(self.failUnlessIsBarDotTxt)
616 d.addCallback(lambda res: self.GET(base + "/blah.txt"))
617 d.addCallback(self.failUnlessIsBarDotTxt)
618 d.addCallback(lambda res: self.GET(base + "/ignore/lots/blah.txt"))
619 d.addCallback(self.failUnlessIsBarDotTxt)
620 d.addCallback(lambda res: self.GET(base2 + "/@@name=/blah.txt"))
621 d.addCallback(self.failUnlessIsBarDotTxt)
622 save_url = base + "?save=true&filename=blah.txt"
623 d.addCallback(lambda res: self.GET(save_url))
624 d.addCallback(self.failUnlessIsBarDotTxt) # TODO: check headers
625 u_filename = u"n\u00e9wer.txt" # n e-acute w e r . t x t
626 u_fn_e = urllib.quote(u_filename.encode("utf-8"))
627 u_url = base + "?save=true&filename=" + u_fn_e
628 d.addCallback(lambda res: self.GET(u_url))
629 d.addCallback(self.failUnlessIsBarDotTxt) # TODO: check headers
632 def test_PUT_FILEURL_named_bad(self):
633 base = "/file/%s" % urllib.quote(self._bar_txt_uri)
634 d = self.shouldFail2(error.Error, "test_PUT_FILEURL_named_bad",
636 "/file can only be used with GET or HEAD",
637 self.PUT, base + "/@@name=/blah.txt", "")
640 def test_GET_DIRURL_named_bad(self):
641 base = "/file/%s" % urllib.quote(self._foo_uri)
642 d = self.shouldFail2(error.Error, "test_PUT_DIRURL_named_bad",
645 self.GET, base + "/@@name=/blah.txt")
648 def test_GET_slash_file_bad(self):
649 d = self.shouldFail2(error.Error, "test_GET_slash_file_bad",
651 "/file must be followed by a file-cap and a name",
655 def test_GET_unhandled_URI_named(self):
656 contents, n, newuri = self.makefile(12)
657 verifier_cap = n.get_verify_cap().to_string()
658 base = "/file/%s" % urllib.quote(verifier_cap)
659 # client.create_node_from_uri() can't handle verify-caps
660 d = self.shouldFail2(error.Error, "GET_unhandled_URI_named",
662 "is not a valid file- or directory- cap",
666 def test_GET_unhandled_URI(self):
667 contents, n, newuri = self.makefile(12)
668 verifier_cap = n.get_verify_cap().to_string()
669 base = "/uri/%s" % urllib.quote(verifier_cap)
670 # client.create_node_from_uri() can't handle verify-caps
671 d = self.shouldFail2(error.Error, "test_GET_unhandled_URI",
673 "is not a valid file- or directory- cap",
677 def test_GET_FILE_URI(self):
678 base = "/uri/%s" % urllib.quote(self._bar_txt_uri)
680 d.addCallback(self.failUnlessIsBarDotTxt)
683 def test_GET_FILE_URI_badchild(self):
684 base = "/uri/%s/boguschild" % urllib.quote(self._bar_txt_uri)
685 errmsg = "Files have no children, certainly not named 'boguschild'"
686 d = self.shouldFail2(error.Error, "test_GET_FILE_URI_badchild",
687 "400 Bad Request", errmsg,
691 def test_PUT_FILE_URI_badchild(self):
692 base = "/uri/%s/boguschild" % urllib.quote(self._bar_txt_uri)
693 errmsg = "Cannot create directory 'boguschild', because its parent is a file, not a directory"
694 d = self.shouldFail2(error.Error, "test_GET_FILE_URI_badchild",
695 "400 Bad Request", errmsg,
699 def test_GET_FILEURL_save(self):
700 d = self.GET(self.public_url + "/foo/bar.txt?filename=bar.txt&save=true")
701 # TODO: look at the headers, expect a Content-Disposition: attachment
703 d.addCallback(self.failUnlessIsBarDotTxt)
706 def test_GET_FILEURL_missing(self):
707 d = self.GET(self.public_url + "/foo/missing")
708 d.addBoth(self.should404, "test_GET_FILEURL_missing")
711 def test_PUT_NEWFILEURL(self):
712 d = self.PUT(self.public_url + "/foo/new.txt", self.NEWFILE_CONTENTS)
713 # TODO: we lose the response code, so we can't check this
714 #self.failUnlessEqual(responsecode, 201)
715 d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"new.txt")
716 d.addCallback(lambda res:
717 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
718 self.NEWFILE_CONTENTS))
721 def test_PUT_NEWFILEURL_range_bad(self):
722 headers = {"content-range": "bytes 1-10/%d" % len(self.NEWFILE_CONTENTS)}
723 target = self.public_url + "/foo/new.txt"
724 d = self.shouldFail2(error.Error, "test_PUT_NEWFILEURL_range_bad",
725 "501 Not Implemented",
726 "Content-Range in PUT not yet supported",
727 # (and certainly not for immutable files)
728 self.PUT, target, self.NEWFILE_CONTENTS[1:11],
730 d.addCallback(lambda res:
731 self.failIfNodeHasChild(self._foo_node, u"new.txt"))
734 def test_PUT_NEWFILEURL_mutable(self):
735 d = self.PUT(self.public_url + "/foo/new.txt?mutable=true",
736 self.NEWFILE_CONTENTS)
737 # TODO: we lose the response code, so we can't check this
738 #self.failUnlessEqual(responsecode, 201)
740 u = uri.from_string_mutable_filenode(res)
741 self.failUnless(u.is_mutable())
742 self.failIf(u.is_readonly())
744 d.addCallback(_check_uri)
745 d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"new.txt")
746 d.addCallback(lambda res:
747 self.failUnlessMutableChildContentsAre(self._foo_node,
749 self.NEWFILE_CONTENTS))
752 def test_PUT_NEWFILEURL_mutable_toobig(self):
753 d = self.shouldFail2(error.Error, "test_PUT_NEWFILEURL_mutable_toobig",
754 "413 Request Entity Too Large",
755 "SDMF is limited to one segment, and 10001 > 10000",
757 self.public_url + "/foo/new.txt?mutable=true",
758 "b" * (self.s.MUTABLE_SIZELIMIT+1))
761 def test_PUT_NEWFILEURL_replace(self):
762 d = self.PUT(self.public_url + "/foo/bar.txt", self.NEWFILE_CONTENTS)
763 # TODO: we lose the response code, so we can't check this
764 #self.failUnlessEqual(responsecode, 200)
765 d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"bar.txt")
766 d.addCallback(lambda res:
767 self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
768 self.NEWFILE_CONTENTS))
771 def test_PUT_NEWFILEURL_bad_t(self):
772 d = self.shouldFail2(error.Error, "PUT_bad_t", "400 Bad Request",
773 "PUT to a file: bad t=bogus",
774 self.PUT, self.public_url + "/foo/bar.txt?t=bogus",
778 def test_PUT_NEWFILEURL_no_replace(self):
779 d = self.PUT(self.public_url + "/foo/bar.txt?replace=false",
780 self.NEWFILE_CONTENTS)
781 d.addBoth(self.shouldFail, error.Error, "PUT_NEWFILEURL_no_replace",
783 "There was already a child by that name, and you asked me "
787 def test_PUT_NEWFILEURL_mkdirs(self):
788 d = self.PUT(self.public_url + "/foo/newdir/new.txt", self.NEWFILE_CONTENTS)
790 d.addCallback(self.failUnlessURIMatchesChild, fn, u"newdir/new.txt")
791 d.addCallback(lambda res: self.failIfNodeHasChild(fn, u"new.txt"))
792 d.addCallback(lambda res: self.failUnlessNodeHasChild(fn, u"newdir"))
793 d.addCallback(lambda res:
794 self.failUnlessChildContentsAre(fn, u"newdir/new.txt",
795 self.NEWFILE_CONTENTS))
798 def test_PUT_NEWFILEURL_blocked(self):
799 d = self.PUT(self.public_url + "/foo/blockingfile/new.txt",
800 self.NEWFILE_CONTENTS)
801 d.addBoth(self.shouldFail, error.Error, "PUT_NEWFILEURL_blocked",
803 "Unable to create directory 'blockingfile': a file was in the way")
806 def test_DELETE_FILEURL(self):
807 d = self.DELETE(self.public_url + "/foo/bar.txt")
808 d.addCallback(lambda res:
809 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
812 def test_DELETE_FILEURL_missing(self):
813 d = self.DELETE(self.public_url + "/foo/missing")
814 d.addBoth(self.should404, "test_DELETE_FILEURL_missing")
817 def test_DELETE_FILEURL_missing2(self):
818 d = self.DELETE(self.public_url + "/missing/missing")
819 d.addBoth(self.should404, "test_DELETE_FILEURL_missing2")
822 def test_GET_FILEURL_json(self):
823 # twisted.web.http.parse_qs ignores any query args without an '=', so
824 # I can't do "GET /path?json", I have to do "GET /path/t=json"
825 # instead. This may make it tricky to emulate the S3 interface
827 d = self.GET(self.public_url + "/foo/bar.txt?t=json")
828 d.addCallback(self.failUnlessIsBarJSON)
831 def test_GET_FILEURL_json_missing(self):
832 d = self.GET(self.public_url + "/foo/missing?json")
833 d.addBoth(self.should404, "test_GET_FILEURL_json_missing")
836 def test_GET_FILEURL_uri(self):
837 d = self.GET(self.public_url + "/foo/bar.txt?t=uri")
839 self.failUnlessEqual(res, self._bar_txt_uri)
840 d.addCallback(_check)
841 d.addCallback(lambda res:
842 self.GET(self.public_url + "/foo/bar.txt?t=readonly-uri"))
844 # for now, for files, uris and readonly-uris are the same
845 self.failUnlessEqual(res, self._bar_txt_uri)
846 d.addCallback(_check2)
849 def test_GET_FILEURL_badtype(self):
850 d = self.shouldHTTPError("GET t=bogus", 400, "Bad Request",
853 self.public_url + "/foo/bar.txt?t=bogus")
856 def test_GET_FILEURL_uri_missing(self):
857 d = self.GET(self.public_url + "/foo/missing?t=uri")
858 d.addBoth(self.should404, "test_GET_FILEURL_uri_missing")
861 def test_GET_DIRURL(self):
862 # the addSlash means we get a redirect here
863 # from /uri/$URI/foo/ , we need ../../../ to get back to the root
865 d = self.GET(self.public_url + "/foo", followRedirect=True)
867 self.failUnless(('<a href="%s">Return to Welcome page' % ROOT)
869 # the FILE reference points to a URI, but it should end in bar.txt
870 bar_url = ("%s/file/%s/@@named=/bar.txt" %
871 (ROOT, urllib.quote(self._bar_txt_uri)))
872 get_bar = "".join([r'<td>',
873 r'<a href="%s">bar.txt</a>' % bar_url,
876 r'\s+<td>%d</td>' % len(self.BAR_CONTENTS),
878 self.failUnless(re.search(get_bar, res), res)
879 for line in res.split("\n"):
880 # find the line that contains the delete button for bar.txt
881 if ("form action" in line and
882 'value="delete"' in line and
883 'value="bar.txt"' in line):
884 # the form target should use a relative URL
885 foo_url = urllib.quote("%s/uri/%s/" % (ROOT, self._foo_uri))
886 self.failUnless(('action="%s"' % foo_url) in line, line)
887 # and the when_done= should too
888 #done_url = urllib.quote(???)
889 #self.failUnless(('name="when_done" value="%s"' % done_url)
893 self.fail("unable to find delete-bar.txt line", res)
895 # the DIR reference just points to a URI
896 sub_url = ("%s/uri/%s/" % (ROOT, urllib.quote(self._sub_uri)))
897 get_sub = ((r'<td><a href="%s">sub</a></td>' % sub_url)
898 + r'\s+<td>DIR</td>')
899 self.failUnless(re.search(get_sub, res), res)
900 d.addCallback(_check)
902 # look at a directory which is readonly
903 d.addCallback(lambda res:
904 self.GET(self.public_url + "/reedownlee", followRedirect=True))
906 self.failUnless("(readonly)" in res, res)
907 self.failIf("Upload a file" in res, res)
908 d.addCallback(_check2)
910 # and at a directory that contains a readonly directory
911 d.addCallback(lambda res:
912 self.GET(self.public_url, followRedirect=True))
914 self.failUnless(re.search(r'<td><a href="[\.\/]+/uri/URI%3ADIR2-RO%3A[^"]+">reedownlee</a>'
915 '</td>\s+<td>DIR-RO</td>', res))
916 d.addCallback(_check3)
920 def test_GET_DIRURL_badtype(self):
921 d = self.shouldHTTPError("test_GET_DIRURL_badtype",
925 self.public_url + "/foo?t=bogus")
928 def test_GET_DIRURL_json(self):
929 d = self.GET(self.public_url + "/foo?t=json")
930 d.addCallback(self.failUnlessIsFooJSON)
934 def test_POST_DIRURL_manifest_no_ophandle(self):
935 d = self.shouldFail2(error.Error,
936 "test_POST_DIRURL_manifest_no_ophandle",
938 "slow operation requires ophandle=",
939 self.POST, self.public_url, t="start-manifest")
942 def test_POST_DIRURL_manifest(self):
943 d = defer.succeed(None)
944 def getman(ignored, output):
945 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=125",
947 d.addCallback(self.wait_for_operation, "125")
948 d.addCallback(self.get_operation_results, "125", output)
950 d.addCallback(getman, None)
951 def _got_html(manifest):
952 self.failUnless("Manifest of SI=" in manifest)
953 self.failUnless("<td>sub</td>" in manifest)
954 self.failUnless(self._sub_uri in manifest)
955 self.failUnless("<td>sub/baz.txt</td>" in manifest)
956 d.addCallback(_got_html)
958 # both t=status and unadorned GET should be identical
959 d.addCallback(lambda res: self.GET("/operations/125"))
960 d.addCallback(_got_html)
962 d.addCallback(getman, "html")
963 d.addCallback(_got_html)
964 d.addCallback(getman, "text")
965 def _got_text(manifest):
966 self.failUnless("\nsub " + self._sub_uri + "\n" in manifest)
967 self.failUnless("\nsub/baz.txt URI:CHK:" in manifest)
968 d.addCallback(_got_text)
969 d.addCallback(getman, "JSON")
971 data = res["manifest"]
973 for (path_list, cap) in data:
974 got[tuple(path_list)] = cap
975 self.failUnlessEqual(got[(u"sub",)], self._sub_uri)
976 self.failUnless((u"sub",u"baz.txt") in got)
977 self.failUnless("finished" in res)
978 self.failUnless("origin" in res)
979 self.failUnless("storage-index" in res)
980 self.failUnless("verifycaps" in res)
981 self.failUnless("stats" in res)
982 d.addCallback(_got_json)
985 def test_POST_DIRURL_deepsize_no_ophandle(self):
986 d = self.shouldFail2(error.Error,
987 "test_POST_DIRURL_deepsize_no_ophandle",
989 "slow operation requires ophandle=",
990 self.POST, self.public_url, t="start-deep-size")
993 def test_POST_DIRURL_deepsize(self):
994 d = self.POST(self.public_url + "/foo/?t=start-deep-size&ophandle=126",
996 d.addCallback(self.wait_for_operation, "126")
997 d.addCallback(self.get_operation_results, "126", "json")
999 self.failUnlessEqual(data["finished"], True)
1001 self.failUnless(size > 1000)
1002 d.addCallback(_got_json)
1003 d.addCallback(self.get_operation_results, "126", "text")
1005 mo = re.search(r'^size: (\d+)$', res, re.M)
1006 self.failUnless(mo, res)
1007 size = int(mo.group(1))
1008 # with directories, the size varies.
1009 self.failUnless(size > 1000)
1010 d.addCallback(_got_text)
1013 def test_POST_DIRURL_deepstats_no_ophandle(self):
1014 d = self.shouldFail2(error.Error,
1015 "test_POST_DIRURL_deepstats_no_ophandle",
1017 "slow operation requires ophandle=",
1018 self.POST, self.public_url, t="start-deep-stats")
1021 def test_POST_DIRURL_deepstats(self):
1022 d = self.POST(self.public_url + "/foo/?t=start-deep-stats&ophandle=127",
1023 followRedirect=True)
1024 d.addCallback(self.wait_for_operation, "127")
1025 d.addCallback(self.get_operation_results, "127", "json")
1026 def _got_json(stats):
1027 expected = {"count-immutable-files": 3,
1028 "count-mutable-files": 0,
1029 "count-literal-files": 0,
1031 "count-directories": 3,
1032 "size-immutable-files": 57,
1033 "size-literal-files": 0,
1034 #"size-directories": 1912, # varies
1035 #"largest-directory": 1590,
1036 "largest-directory-children": 5,
1037 "largest-immutable-file": 19,
1039 for k,v in expected.iteritems():
1040 self.failUnlessEqual(stats[k], v,
1041 "stats[%s] was %s, not %s" %
1043 self.failUnlessEqual(stats["size-files-histogram"],
1045 d.addCallback(_got_json)
1048 def test_POST_DIRURL_stream_manifest(self):
1049 d = self.POST(self.public_url + "/foo/?t=stream-manifest")
1051 self.failUnless(res.endswith("\n"))
1052 units = [simplejson.loads(t) for t in res[:-1].split("\n")]
1053 self.failUnlessEqual(len(units), 7)
1054 self.failUnlessEqual(units[-1]["type"], "stats")
1056 self.failUnlessEqual(first["path"], [])
1057 self.failUnlessEqual(first["cap"], self._foo_uri)
1058 self.failUnlessEqual(first["type"], "directory")
1059 baz = [u for u in units[:-1] if u["cap"] == self._baz_file_uri][0]
1060 self.failUnlessEqual(baz["path"], ["sub", "baz.txt"])
1061 self.failIfEqual(baz["storage-index"], None)
1062 self.failIfEqual(baz["verifycap"], None)
1063 self.failIfEqual(baz["repaircap"], None)
1065 d.addCallback(_check)
1068 def test_GET_DIRURL_uri(self):
1069 d = self.GET(self.public_url + "/foo?t=uri")
1071 self.failUnlessEqual(res, self._foo_uri)
1072 d.addCallback(_check)
1075 def test_GET_DIRURL_readonly_uri(self):
1076 d = self.GET(self.public_url + "/foo?t=readonly-uri")
1078 self.failUnlessEqual(res, self._foo_readonly_uri)
1079 d.addCallback(_check)
1082 def test_PUT_NEWDIRURL(self):
1083 d = self.PUT(self.public_url + "/foo/newdir?t=mkdir", "")
1084 d.addCallback(lambda res:
1085 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1086 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1087 d.addCallback(self.failUnlessNodeKeysAre, [])
1090 def test_PUT_NEWDIRURL_exists(self):
1091 d = self.PUT(self.public_url + "/foo/sub?t=mkdir", "")
1092 d.addCallback(lambda res:
1093 self.failUnlessNodeHasChild(self._foo_node, u"sub"))
1094 d.addCallback(lambda res: self._foo_node.get(u"sub"))
1095 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
1098 def test_PUT_NEWDIRURL_blocked(self):
1099 d = self.shouldFail2(error.Error, "PUT_NEWDIRURL_blocked",
1100 "409 Conflict", "Unable to create directory 'bar.txt': a file was in the way",
1102 self.public_url + "/foo/bar.txt/sub?t=mkdir", "")
1103 d.addCallback(lambda res:
1104 self.failUnlessNodeHasChild(self._foo_node, u"sub"))
1105 d.addCallback(lambda res: self._foo_node.get(u"sub"))
1106 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
1109 def test_PUT_NEWDIRURL_mkdir_p(self):
1110 d = defer.succeed(None)
1111 d.addCallback(lambda res: self.POST(self.public_url + "/foo", t='mkdir', name='mkp'))
1112 d.addCallback(lambda res: self.failUnlessNodeHasChild(self._foo_node, u"mkp"))
1113 d.addCallback(lambda res: self._foo_node.get(u"mkp"))
1114 def mkdir_p(mkpnode):
1115 url = '/uri/%s?t=mkdir-p&path=/sub1/sub2' % urllib.quote(mkpnode.get_uri())
1117 def made_subsub(ssuri):
1118 d = self._foo_node.get_child_at_path(u"mkp/sub1/sub2")
1119 d.addCallback(lambda ssnode: self.failUnlessEqual(ssnode.get_uri(), ssuri))
1121 d.addCallback(lambda uri2: self.failUnlessEqual(uri2, ssuri))
1123 d.addCallback(made_subsub)
1125 d.addCallback(mkdir_p)
1128 def test_PUT_NEWDIRURL_mkdirs(self):
1129 d = self.PUT(self.public_url + "/foo/subdir/newdir?t=mkdir", "")
1130 d.addCallback(lambda res:
1131 self.failIfNodeHasChild(self._foo_node, u"newdir"))
1132 d.addCallback(lambda res:
1133 self.failUnlessNodeHasChild(self._foo_node, u"subdir"))
1134 d.addCallback(lambda res:
1135 self._foo_node.get_child_at_path(u"subdir/newdir"))
1136 d.addCallback(self.failUnlessNodeKeysAre, [])
1139 def test_DELETE_DIRURL(self):
1140 d = self.DELETE(self.public_url + "/foo")
1141 d.addCallback(lambda res:
1142 self.failIfNodeHasChild(self.public_root, u"foo"))
1145 def test_DELETE_DIRURL_missing(self):
1146 d = self.DELETE(self.public_url + "/foo/missing")
1147 d.addBoth(self.should404, "test_DELETE_DIRURL_missing")
1148 d.addCallback(lambda res:
1149 self.failUnlessNodeHasChild(self.public_root, u"foo"))
1152 def test_DELETE_DIRURL_missing2(self):
1153 d = self.DELETE(self.public_url + "/missing")
1154 d.addBoth(self.should404, "test_DELETE_DIRURL_missing2")
1157 def dump_root(self):
1159 w = webish.DirnodeWalkerMixin()
1160 def visitor(childpath, childnode, metadata):
1162 d = w.walk(self.public_root, visitor)
1165 def failUnlessNodeKeysAre(self, node, expected_keys):
1166 for k in expected_keys:
1167 assert isinstance(k, unicode)
1169 def _check(children):
1170 self.failUnlessEqual(sorted(children.keys()), sorted(expected_keys))
1171 d.addCallback(_check)
1173 def failUnlessNodeHasChild(self, node, name):
1174 assert isinstance(name, unicode)
1176 def _check(children):
1177 self.failUnless(name in children)
1178 d.addCallback(_check)
1180 def failIfNodeHasChild(self, node, name):
1181 assert isinstance(name, unicode)
1183 def _check(children):
1184 self.failIf(name in children)
1185 d.addCallback(_check)
1188 def failUnlessChildContentsAre(self, node, name, expected_contents):
1189 assert isinstance(name, unicode)
1190 d = node.get_child_at_path(name)
1191 d.addCallback(lambda node: node.download_to_data())
1192 def _check(contents):
1193 self.failUnlessEqual(contents, expected_contents)
1194 d.addCallback(_check)
1197 def failUnlessMutableChildContentsAre(self, node, name, expected_contents):
1198 assert isinstance(name, unicode)
1199 d = node.get_child_at_path(name)
1200 d.addCallback(lambda node: node.download_best_version())
1201 def _check(contents):
1202 self.failUnlessEqual(contents, expected_contents)
1203 d.addCallback(_check)
1206 def failUnlessChildURIIs(self, node, name, expected_uri):
1207 assert isinstance(name, unicode)
1208 d = node.get_child_at_path(name)
1210 self.failUnlessEqual(child.get_uri(), expected_uri.strip())
1211 d.addCallback(_check)
1214 def failUnlessURIMatchesChild(self, got_uri, node, name):
1215 assert isinstance(name, unicode)
1216 d = node.get_child_at_path(name)
1218 self.failUnlessEqual(got_uri.strip(), child.get_uri())
1219 d.addCallback(_check)
1222 def failUnlessCHKURIHasContents(self, got_uri, contents):
1223 self.failUnless(FakeCHKFileNode.all_contents[got_uri] == contents)
1225 def test_POST_upload(self):
1226 d = self.POST(self.public_url + "/foo", t="upload",
1227 file=("new.txt", self.NEWFILE_CONTENTS))
1229 d.addCallback(self.failUnlessURIMatchesChild, fn, u"new.txt")
1230 d.addCallback(lambda res:
1231 self.failUnlessChildContentsAre(fn, u"new.txt",
1232 self.NEWFILE_CONTENTS))
1235 def test_POST_upload_unicode(self):
1236 filename = u"n\u00e9wer.txt" # n e-acute w e r . t x t
1237 d = self.POST(self.public_url + "/foo", t="upload",
1238 file=(filename, self.NEWFILE_CONTENTS))
1240 d.addCallback(self.failUnlessURIMatchesChild, fn, filename)
1241 d.addCallback(lambda res:
1242 self.failUnlessChildContentsAre(fn, filename,
1243 self.NEWFILE_CONTENTS))
1244 target_url = self.public_url + "/foo/" + filename.encode("utf-8")
1245 d.addCallback(lambda res: self.GET(target_url))
1246 d.addCallback(lambda contents: self.failUnlessEqual(contents,
1247 self.NEWFILE_CONTENTS,
1251 def test_POST_upload_unicode_named(self):
1252 filename = u"n\u00e9wer.txt" # n e-acute w e r . t x t
1253 d = self.POST(self.public_url + "/foo", t="upload",
1255 file=("overridden", self.NEWFILE_CONTENTS))
1257 d.addCallback(self.failUnlessURIMatchesChild, fn, filename)
1258 d.addCallback(lambda res:
1259 self.failUnlessChildContentsAre(fn, filename,
1260 self.NEWFILE_CONTENTS))
1261 target_url = self.public_url + "/foo/" + filename.encode("utf-8")
1262 d.addCallback(lambda res: self.GET(target_url))
1263 d.addCallback(lambda contents: self.failUnlessEqual(contents,
1264 self.NEWFILE_CONTENTS,
1268 def test_POST_upload_no_link(self):
1269 d = self.POST("/uri", t="upload",
1270 file=("new.txt", self.NEWFILE_CONTENTS))
1271 def _check_upload_results(page):
1272 # this should be a page which describes the results of the upload
1273 # that just finished.
1274 self.failUnless("Upload Results:" in page)
1275 self.failUnless("URI:" in page)
1276 uri_re = re.compile("URI: <tt><span>(.*)</span>")
1277 mo = uri_re.search(page)
1278 self.failUnless(mo, page)
1279 new_uri = mo.group(1)
1281 d.addCallback(_check_upload_results)
1282 d.addCallback(self.failUnlessCHKURIHasContents, self.NEWFILE_CONTENTS)
1285 def test_POST_upload_no_link_whendone(self):
1286 d = self.POST("/uri", t="upload", when_done="/",
1287 file=("new.txt", self.NEWFILE_CONTENTS))
1288 d.addBoth(self.shouldRedirect, "/")
1291 def shouldRedirect2(self, which, checker, callable, *args, **kwargs):
1292 d = defer.maybeDeferred(callable, *args, **kwargs)
1294 if isinstance(res, failure.Failure):
1295 res.trap(error.PageRedirect)
1296 statuscode = res.value.status
1297 target = res.value.location
1298 return checker(statuscode, target)
1299 self.fail("%s: callable was supposed to redirect, not return '%s'"
1304 def test_POST_upload_no_link_whendone_results(self):
1305 def check(statuscode, target):
1306 self.failUnlessEqual(statuscode, str(http.FOUND))
1307 self.failUnless(target.startswith(self.webish_url), target)
1308 return client.getPage(target, method="GET")
1309 d = self.shouldRedirect2("test_POST_upload_no_link_whendone_results",
1311 self.POST, "/uri", t="upload",
1312 when_done="/uri/%(uri)s",
1313 file=("new.txt", self.NEWFILE_CONTENTS))
1314 d.addCallback(lambda res:
1315 self.failUnlessEqual(res, self.NEWFILE_CONTENTS))
1318 def test_POST_upload_no_link_mutable(self):
1319 d = self.POST("/uri", t="upload", mutable="true",
1320 file=("new.txt", self.NEWFILE_CONTENTS))
1321 def _check(new_uri):
1322 new_uri = new_uri.strip()
1323 self.new_uri = new_uri
1325 self.failUnless(IMutableFileURI.providedBy(u))
1326 self.failUnless(u.storage_index in FakeMutableFileNode.all_contents)
1327 n = self.s.create_node_from_uri(new_uri)
1328 return n.download_best_version()
1329 d.addCallback(_check)
1331 self.failUnlessEqual(data, self.NEWFILE_CONTENTS)
1332 return self.GET("/uri/%s" % urllib.quote(self.new_uri))
1333 d.addCallback(_check2)
1335 self.failUnlessEqual(data, self.NEWFILE_CONTENTS)
1336 return self.GET("/file/%s" % urllib.quote(self.new_uri))
1337 d.addCallback(_check3)
1339 self.failUnlessEqual(data, self.NEWFILE_CONTENTS)
1340 d.addCallback(_check4)
1343 def test_POST_upload_no_link_mutable_toobig(self):
1344 d = self.shouldFail2(error.Error,
1345 "test_POST_upload_no_link_mutable_toobig",
1346 "413 Request Entity Too Large",
1347 "SDMF is limited to one segment, and 10001 > 10000",
1349 "/uri", t="upload", mutable="true",
1351 "b" * (self.s.MUTABLE_SIZELIMIT+1)) )
1354 def test_POST_upload_mutable(self):
1355 # this creates a mutable file
1356 d = self.POST(self.public_url + "/foo", t="upload", mutable="true",
1357 file=("new.txt", self.NEWFILE_CONTENTS))
1359 d.addCallback(self.failUnlessURIMatchesChild, fn, u"new.txt")
1360 d.addCallback(lambda res:
1361 self.failUnlessMutableChildContentsAre(fn, u"new.txt",
1362 self.NEWFILE_CONTENTS))
1363 d.addCallback(lambda res: self._foo_node.get(u"new.txt"))
1365 self.failUnless(IMutableFileNode.providedBy(newnode))
1366 self.failUnless(newnode.is_mutable())
1367 self.failIf(newnode.is_readonly())
1368 self._mutable_node = newnode
1369 self._mutable_uri = newnode.get_uri()
1372 # now upload it again and make sure that the URI doesn't change
1373 NEWER_CONTENTS = self.NEWFILE_CONTENTS + "newer\n"
1374 d.addCallback(lambda res:
1375 self.POST(self.public_url + "/foo", t="upload",
1377 file=("new.txt", NEWER_CONTENTS)))
1378 d.addCallback(self.failUnlessURIMatchesChild, fn, u"new.txt")
1379 d.addCallback(lambda res:
1380 self.failUnlessMutableChildContentsAre(fn, u"new.txt",
1382 d.addCallback(lambda res: self._foo_node.get(u"new.txt"))
1384 self.failUnless(IMutableFileNode.providedBy(newnode))
1385 self.failUnless(newnode.is_mutable())
1386 self.failIf(newnode.is_readonly())
1387 self.failUnlessEqual(self._mutable_uri, newnode.get_uri())
1388 d.addCallback(_got2)
1390 # upload a second time, using PUT instead of POST
1391 NEW2_CONTENTS = NEWER_CONTENTS + "overwrite with PUT\n"
1392 d.addCallback(lambda res:
1393 self.PUT(self.public_url + "/foo/new.txt", NEW2_CONTENTS))
1394 d.addCallback(self.failUnlessURIMatchesChild, fn, u"new.txt")
1395 d.addCallback(lambda res:
1396 self.failUnlessMutableChildContentsAre(fn, u"new.txt",
1399 # finally list the directory, since mutable files are displayed
1400 # slightly differently
1402 d.addCallback(lambda res:
1403 self.GET(self.public_url + "/foo/",
1404 followRedirect=True))
1405 def _check_page(res):
1406 # TODO: assert more about the contents
1407 self.failUnless("SSK" in res)
1409 d.addCallback(_check_page)
1411 d.addCallback(lambda res: self._foo_node.get(u"new.txt"))
1413 self.failUnless(IMutableFileNode.providedBy(newnode))
1414 self.failUnless(newnode.is_mutable())
1415 self.failIf(newnode.is_readonly())
1416 self.failUnlessEqual(self._mutable_uri, newnode.get_uri())
1417 d.addCallback(_got3)
1419 # look at the JSON form of the enclosing directory
1420 d.addCallback(lambda res:
1421 self.GET(self.public_url + "/foo/?t=json",
1422 followRedirect=True))
1423 def _check_page_json(res):
1424 parsed = simplejson.loads(res)
1425 self.failUnlessEqual(parsed[0], "dirnode")
1426 children = dict( [(unicode(name),value)
1428 in parsed[1]["children"].iteritems()] )
1429 self.failUnless("new.txt" in children)
1430 new_json = children["new.txt"]
1431 self.failUnlessEqual(new_json[0], "filenode")
1432 self.failUnless(new_json[1]["mutable"])
1433 self.failUnlessEqual(new_json[1]["rw_uri"], self._mutable_uri)
1434 ro_uri = unicode(self._mutable_node.get_readonly().to_string())
1435 self.failUnlessEqual(new_json[1]["ro_uri"], ro_uri)
1436 d.addCallback(_check_page_json)
1438 # and the JSON form of the file
1439 d.addCallback(lambda res:
1440 self.GET(self.public_url + "/foo/new.txt?t=json"))
1441 def _check_file_json(res):
1442 parsed = simplejson.loads(res)
1443 self.failUnlessEqual(parsed[0], "filenode")
1444 self.failUnless(parsed[1]["mutable"])
1445 self.failUnlessEqual(parsed[1]["rw_uri"], self._mutable_uri)
1446 ro_uri = unicode(self._mutable_node.get_readonly().to_string())
1447 self.failUnlessEqual(parsed[1]["ro_uri"], ro_uri)
1448 d.addCallback(_check_file_json)
1450 # and look at t=uri and t=readonly-uri
1451 d.addCallback(lambda res:
1452 self.GET(self.public_url + "/foo/new.txt?t=uri"))
1453 d.addCallback(lambda res: self.failUnlessEqual(res, self._mutable_uri))
1454 d.addCallback(lambda res:
1455 self.GET(self.public_url + "/foo/new.txt?t=readonly-uri"))
1456 def _check_ro_uri(res):
1457 ro_uri = unicode(self._mutable_node.get_readonly().to_string())
1458 self.failUnlessEqual(res, ro_uri)
1459 d.addCallback(_check_ro_uri)
1461 # make sure we can get to it from /uri/URI
1462 d.addCallback(lambda res:
1463 self.GET("/uri/%s" % urllib.quote(self._mutable_uri)))
1464 d.addCallback(lambda res:
1465 self.failUnlessEqual(res, NEW2_CONTENTS))
1467 # and that HEAD computes the size correctly
1468 d.addCallback(lambda res:
1469 self.HEAD(self.public_url + "/foo/new.txt",
1470 return_response=True))
1471 def _got_headers((res, status, headers)):
1472 self.failUnlessEqual(res, "")
1473 self.failUnlessEqual(headers["content-length"][0],
1474 str(len(NEW2_CONTENTS)))
1475 self.failUnlessEqual(headers["content-type"], ["text/plain"])
1476 d.addCallback(_got_headers)
1478 # make sure that size errors are displayed correctly for overwrite
1479 d.addCallback(lambda res:
1480 self.shouldFail2(error.Error,
1481 "test_POST_upload_mutable-toobig",
1482 "413 Request Entity Too Large",
1483 "SDMF is limited to one segment, and 10001 > 10000",
1485 self.public_url + "/foo", t="upload",
1488 "b" * (self.s.MUTABLE_SIZELIMIT+1)),
1491 d.addErrback(self.dump_error)
1494 def test_POST_upload_mutable_toobig(self):
1495 d = self.shouldFail2(error.Error,
1496 "test_POST_upload_no_link_mutable_toobig",
1497 "413 Request Entity Too Large",
1498 "SDMF is limited to one segment, and 10001 > 10000",
1500 self.public_url + "/foo",
1501 t="upload", mutable="true",
1503 "b" * (self.s.MUTABLE_SIZELIMIT+1)) )
1506 def dump_error(self, f):
1507 # if the web server returns an error code (like 400 Bad Request),
1508 # web.client.getPage puts the HTTP response body into the .response
1509 # attribute of the exception object that it gives back. It does not
1510 # appear in the Failure's repr(), so the ERROR that trial displays
1511 # will be rather terse and unhelpful. addErrback this method to the
1512 # end of your chain to get more information out of these errors.
1513 if f.check(error.Error):
1514 print "web.error.Error:"
1516 print f.value.response
1519 def test_POST_upload_replace(self):
1520 d = self.POST(self.public_url + "/foo", t="upload",
1521 file=("bar.txt", self.NEWFILE_CONTENTS))
1523 d.addCallback(self.failUnlessURIMatchesChild, fn, u"bar.txt")
1524 d.addCallback(lambda res:
1525 self.failUnlessChildContentsAre(fn, u"bar.txt",
1526 self.NEWFILE_CONTENTS))
1529 def test_POST_upload_no_replace_ok(self):
1530 d = self.POST(self.public_url + "/foo?replace=false", t="upload",
1531 file=("new.txt", self.NEWFILE_CONTENTS))
1532 d.addCallback(lambda res: self.GET(self.public_url + "/foo/new.txt"))
1533 d.addCallback(lambda res: self.failUnlessEqual(res,
1534 self.NEWFILE_CONTENTS))
1537 def test_POST_upload_no_replace_queryarg(self):
1538 d = self.POST(self.public_url + "/foo?replace=false", t="upload",
1539 file=("bar.txt", self.NEWFILE_CONTENTS))
1540 d.addBoth(self.shouldFail, error.Error,
1541 "POST_upload_no_replace_queryarg",
1543 "There was already a child by that name, and you asked me "
1544 "to not replace it")
1545 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
1546 d.addCallback(self.failUnlessIsBarDotTxt)
1549 def test_POST_upload_no_replace_field(self):
1550 d = self.POST(self.public_url + "/foo", t="upload", replace="false",
1551 file=("bar.txt", self.NEWFILE_CONTENTS))
1552 d.addBoth(self.shouldFail, error.Error, "POST_upload_no_replace_field",
1554 "There was already a child by that name, and you asked me "
1555 "to not replace it")
1556 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
1557 d.addCallback(self.failUnlessIsBarDotTxt)
1560 def test_POST_upload_whendone(self):
1561 d = self.POST(self.public_url + "/foo", t="upload", when_done="/THERE",
1562 file=("new.txt", self.NEWFILE_CONTENTS))
1563 d.addBoth(self.shouldRedirect, "/THERE")
1565 d.addCallback(lambda res:
1566 self.failUnlessChildContentsAre(fn, u"new.txt",
1567 self.NEWFILE_CONTENTS))
1570 def test_POST_upload_named(self):
1572 d = self.POST(self.public_url + "/foo", t="upload",
1573 name="new.txt", file=self.NEWFILE_CONTENTS)
1574 d.addCallback(self.failUnlessURIMatchesChild, fn, u"new.txt")
1575 d.addCallback(lambda res:
1576 self.failUnlessChildContentsAre(fn, u"new.txt",
1577 self.NEWFILE_CONTENTS))
1580 def test_POST_upload_named_badfilename(self):
1581 d = self.POST(self.public_url + "/foo", t="upload",
1582 name="slashes/are/bad.txt", file=self.NEWFILE_CONTENTS)
1583 d.addBoth(self.shouldFail, error.Error,
1584 "test_POST_upload_named_badfilename",
1586 "name= may not contain a slash",
1588 # make sure that nothing was added
1589 d.addCallback(lambda res:
1590 self.failUnlessNodeKeysAre(self._foo_node,
1591 [u"bar.txt", u"blockingfile",
1592 u"empty", u"n\u00fc.txt",
1596 def test_POST_FILEURL_check(self):
1597 bar_url = self.public_url + "/foo/bar.txt"
1598 d = self.POST(bar_url, t="check")
1600 self.failUnless("Healthy :" in res)
1601 d.addCallback(_check)
1602 redir_url = "http://allmydata.org/TARGET"
1603 def _check2(statuscode, target):
1604 self.failUnlessEqual(statuscode, str(http.FOUND))
1605 self.failUnlessEqual(target, redir_url)
1606 d.addCallback(lambda res:
1607 self.shouldRedirect2("test_POST_FILEURL_check",
1611 when_done=redir_url))
1612 d.addCallback(lambda res:
1613 self.POST(bar_url, t="check", return_to=redir_url))
1615 self.failUnless("Healthy :" in res)
1616 self.failUnless("Return to parent directory" in res)
1617 self.failUnless(redir_url in res)
1618 d.addCallback(_check3)
1620 d.addCallback(lambda res:
1621 self.POST(bar_url, t="check", output="JSON"))
1622 def _check_json(res):
1623 data = simplejson.loads(res)
1624 self.failUnless("storage-index" in data)
1625 self.failUnless(data["results"]["healthy"])
1626 d.addCallback(_check_json)
1630 def test_POST_FILEURL_check_and_repair(self):
1631 bar_url = self.public_url + "/foo/bar.txt"
1632 d = self.POST(bar_url, t="check", repair="true")
1634 self.failUnless("Healthy :" in res)
1635 d.addCallback(_check)
1636 redir_url = "http://allmydata.org/TARGET"
1637 def _check2(statuscode, target):
1638 self.failUnlessEqual(statuscode, str(http.FOUND))
1639 self.failUnlessEqual(target, redir_url)
1640 d.addCallback(lambda res:
1641 self.shouldRedirect2("test_POST_FILEURL_check_and_repair",
1644 t="check", repair="true",
1645 when_done=redir_url))
1646 d.addCallback(lambda res:
1647 self.POST(bar_url, t="check", return_to=redir_url))
1649 self.failUnless("Healthy :" in res)
1650 self.failUnless("Return to parent directory" in res)
1651 self.failUnless(redir_url in res)
1652 d.addCallback(_check3)
1655 def test_POST_DIRURL_check(self):
1656 foo_url = self.public_url + "/foo/"
1657 d = self.POST(foo_url, t="check")
1659 self.failUnless("Healthy :" in res, res)
1660 d.addCallback(_check)
1661 redir_url = "http://allmydata.org/TARGET"
1662 def _check2(statuscode, target):
1663 self.failUnlessEqual(statuscode, str(http.FOUND))
1664 self.failUnlessEqual(target, redir_url)
1665 d.addCallback(lambda res:
1666 self.shouldRedirect2("test_POST_DIRURL_check",
1670 when_done=redir_url))
1671 d.addCallback(lambda res:
1672 self.POST(foo_url, t="check", return_to=redir_url))
1674 self.failUnless("Healthy :" in res, res)
1675 self.failUnless("Return to parent directory" in res)
1676 self.failUnless(redir_url in res)
1677 d.addCallback(_check3)
1679 d.addCallback(lambda res:
1680 self.POST(foo_url, t="check", output="JSON"))
1681 def _check_json(res):
1682 data = simplejson.loads(res)
1683 self.failUnless("storage-index" in data)
1684 self.failUnless(data["results"]["healthy"])
1685 d.addCallback(_check_json)
1689 def test_POST_DIRURL_check_and_repair(self):
1690 foo_url = self.public_url + "/foo/"
1691 d = self.POST(foo_url, t="check", repair="true")
1693 self.failUnless("Healthy :" in res, res)
1694 d.addCallback(_check)
1695 redir_url = "http://allmydata.org/TARGET"
1696 def _check2(statuscode, target):
1697 self.failUnlessEqual(statuscode, str(http.FOUND))
1698 self.failUnlessEqual(target, redir_url)
1699 d.addCallback(lambda res:
1700 self.shouldRedirect2("test_POST_DIRURL_check_and_repair",
1703 t="check", repair="true",
1704 when_done=redir_url))
1705 d.addCallback(lambda res:
1706 self.POST(foo_url, t="check", return_to=redir_url))
1708 self.failUnless("Healthy :" in res)
1709 self.failUnless("Return to parent directory" in res)
1710 self.failUnless(redir_url in res)
1711 d.addCallback(_check3)
1714 def wait_for_operation(self, ignored, ophandle):
1715 url = "/operations/" + ophandle
1716 url += "?t=status&output=JSON"
1719 data = simplejson.loads(res)
1720 if not data["finished"]:
1721 d = self.stall(delay=1.0)
1722 d.addCallback(self.wait_for_operation, ophandle)
1728 def get_operation_results(self, ignored, ophandle, output=None):
1729 url = "/operations/" + ophandle
1732 url += "&output=" + output
1735 if output and output.lower() == "json":
1736 return simplejson.loads(res)
1741 def test_POST_DIRURL_deepcheck_no_ophandle(self):
1742 d = self.shouldFail2(error.Error,
1743 "test_POST_DIRURL_deepcheck_no_ophandle",
1745 "slow operation requires ophandle=",
1746 self.POST, self.public_url, t="start-deep-check")
1749 def test_POST_DIRURL_deepcheck(self):
1750 def _check_redirect(statuscode, target):
1751 self.failUnlessEqual(statuscode, str(http.FOUND))
1752 self.failUnless(target.endswith("/operations/123"))
1753 d = self.shouldRedirect2("test_POST_DIRURL_deepcheck", _check_redirect,
1754 self.POST, self.public_url,
1755 t="start-deep-check", ophandle="123")
1756 d.addCallback(self.wait_for_operation, "123")
1757 def _check_json(data):
1758 self.failUnlessEqual(data["finished"], True)
1759 self.failUnlessEqual(data["count-objects-checked"], 8)
1760 self.failUnlessEqual(data["count-objects-healthy"], 8)
1761 d.addCallback(_check_json)
1762 d.addCallback(self.get_operation_results, "123", "html")
1763 def _check_html(res):
1764 self.failUnless("Objects Checked: <span>8</span>" in res)
1765 self.failUnless("Objects Healthy: <span>8</span>" in res)
1766 d.addCallback(_check_html)
1768 d.addCallback(lambda res:
1769 self.GET("/operations/123/"))
1770 d.addCallback(_check_html) # should be the same as without the slash
1772 d.addCallback(lambda res:
1773 self.shouldFail2(error.Error, "one", "404 Not Found",
1774 "No detailed results for SI bogus",
1775 self.GET, "/operations/123/bogus"))
1777 foo_si = self._foo_node.get_storage_index()
1778 foo_si_s = base32.b2a(foo_si)
1779 d.addCallback(lambda res:
1780 self.GET("/operations/123/%s?output=JSON" % foo_si_s))
1781 def _check_foo_json(res):
1782 data = simplejson.loads(res)
1783 self.failUnlessEqual(data["storage-index"], foo_si_s)
1784 self.failUnless(data["results"]["healthy"])
1785 d.addCallback(_check_foo_json)
1788 def test_POST_DIRURL_deepcheck_and_repair(self):
1789 d = self.POST(self.public_url, t="start-deep-check", repair="true",
1790 ophandle="124", output="json", followRedirect=True)
1791 d.addCallback(self.wait_for_operation, "124")
1792 def _check_json(data):
1793 self.failUnlessEqual(data["finished"], True)
1794 self.failUnlessEqual(data["count-objects-checked"], 8)
1795 self.failUnlessEqual(data["count-objects-healthy-pre-repair"], 8)
1796 self.failUnlessEqual(data["count-objects-unhealthy-pre-repair"], 0)
1797 self.failUnlessEqual(data["count-corrupt-shares-pre-repair"], 0)
1798 self.failUnlessEqual(data["count-repairs-attempted"], 0)
1799 self.failUnlessEqual(data["count-repairs-successful"], 0)
1800 self.failUnlessEqual(data["count-repairs-unsuccessful"], 0)
1801 self.failUnlessEqual(data["count-objects-healthy-post-repair"], 8)
1802 self.failUnlessEqual(data["count-objects-unhealthy-post-repair"], 0)
1803 self.failUnlessEqual(data["count-corrupt-shares-post-repair"], 0)
1804 d.addCallback(_check_json)
1805 d.addCallback(self.get_operation_results, "124", "html")
1806 def _check_html(res):
1807 self.failUnless("Objects Checked: <span>8</span>" in res)
1809 self.failUnless("Objects Healthy (before repair): <span>8</span>" in res)
1810 self.failUnless("Objects Unhealthy (before repair): <span>0</span>" in res)
1811 self.failUnless("Corrupt Shares (before repair): <span>0</span>" in res)
1813 self.failUnless("Repairs Attempted: <span>0</span>" in res)
1814 self.failUnless("Repairs Successful: <span>0</span>" in res)
1815 self.failUnless("Repairs Unsuccessful: <span>0</span>" in res)
1817 self.failUnless("Objects Healthy (after repair): <span>8</span>" in res)
1818 self.failUnless("Objects Unhealthy (after repair): <span>0</span>" in res)
1819 self.failUnless("Corrupt Shares (after repair): <span>0</span>" in res)
1820 d.addCallback(_check_html)
1823 def test_POST_FILEURL_bad_t(self):
1824 d = self.shouldFail2(error.Error, "POST_bad_t", "400 Bad Request",
1825 "POST to file: bad t=bogus",
1826 self.POST, self.public_url + "/foo/bar.txt",
1830 def test_POST_mkdir(self): # return value?
1831 d = self.POST(self.public_url + "/foo", t="mkdir", name="newdir")
1832 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1833 d.addCallback(self.failUnlessNodeKeysAre, [])
1836 def test_POST_mkdir_2(self):
1837 d = self.POST(self.public_url + "/foo/newdir?t=mkdir", "")
1838 d.addCallback(lambda res:
1839 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1840 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1841 d.addCallback(self.failUnlessNodeKeysAre, [])
1844 def test_POST_mkdirs_2(self):
1845 d = self.POST(self.public_url + "/foo/bardir/newdir?t=mkdir", "")
1846 d.addCallback(lambda res:
1847 self.failUnlessNodeHasChild(self._foo_node, u"bardir"))
1848 d.addCallback(lambda res: self._foo_node.get(u"bardir"))
1849 d.addCallback(lambda bardirnode: bardirnode.get(u"newdir"))
1850 d.addCallback(self.failUnlessNodeKeysAre, [])
1853 def test_POST_mkdir_no_parentdir_noredirect(self):
1854 d = self.POST("/uri?t=mkdir")
1855 def _after_mkdir(res):
1856 uri.NewDirectoryURI.init_from_string(res)
1857 d.addCallback(_after_mkdir)
1860 def test_POST_mkdir_no_parentdir_redirect(self):
1861 d = self.POST("/uri?t=mkdir&redirect_to_result=true")
1862 d.addBoth(self.shouldRedirect, None, statuscode='303')
1863 def _check_target(target):
1864 target = urllib.unquote(target)
1865 self.failUnless(target.startswith("uri/URI:DIR2:"), target)
1866 d.addCallback(_check_target)
1869 def test_POST_noparent_bad(self):
1870 d = self.shouldHTTPError("POST /uri?t=bogus", 400, "Bad Request",
1871 "/uri accepts only PUT, PUT?t=mkdir, "
1872 "POST?t=upload, and POST?t=mkdir",
1873 self.POST, "/uri?t=bogus")
1876 def test_welcome_page_mkdir_button(self):
1877 # Fetch the welcome page.
1879 def _after_get_welcome_page(res):
1880 MKDIR_BUTTON_RE=re.compile('<form action="([^"]*)" method="post".*<input type="hidden" name="t" value="([^"]*)" /><input type="hidden" name="([^"]*)" value="([^"]*)" /><input type="submit" value="Create Directory!" />', re.I)
1881 mo = MKDIR_BUTTON_RE.search(res)
1882 formaction = mo.group(1)
1884 formaname = mo.group(3)
1885 formavalue = mo.group(4)
1886 return (formaction, formt, formaname, formavalue)
1887 d.addCallback(_after_get_welcome_page)
1888 def _after_parse_form(res):
1889 (formaction, formt, formaname, formavalue) = res
1890 return self.POST("/%s?t=%s&%s=%s" % (formaction, formt, formaname, formavalue))
1891 d.addCallback(_after_parse_form)
1892 d.addBoth(self.shouldRedirect, None, statuscode='303')
1895 def test_POST_mkdir_replace(self): # return value?
1896 d = self.POST(self.public_url + "/foo", t="mkdir", name="sub")
1897 d.addCallback(lambda res: self._foo_node.get(u"sub"))
1898 d.addCallback(self.failUnlessNodeKeysAre, [])
1901 def test_POST_mkdir_no_replace_queryarg(self): # return value?
1902 d = self.POST(self.public_url + "/foo?replace=false", t="mkdir", name="sub")
1903 d.addBoth(self.shouldFail, error.Error,
1904 "POST_mkdir_no_replace_queryarg",
1906 "There was already a child by that name, and you asked me "
1907 "to not replace it")
1908 d.addCallback(lambda res: self._foo_node.get(u"sub"))
1909 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
1912 def test_POST_mkdir_no_replace_field(self): # return value?
1913 d = self.POST(self.public_url + "/foo", t="mkdir", name="sub",
1915 d.addBoth(self.shouldFail, error.Error, "POST_mkdir_no_replace_field",
1917 "There was already a child by that name, and you asked me "
1918 "to not replace it")
1919 d.addCallback(lambda res: self._foo_node.get(u"sub"))
1920 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
1923 def test_POST_mkdir_whendone_field(self):
1924 d = self.POST(self.public_url + "/foo",
1925 t="mkdir", name="newdir", when_done="/THERE")
1926 d.addBoth(self.shouldRedirect, "/THERE")
1927 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1928 d.addCallback(self.failUnlessNodeKeysAre, [])
1931 def test_POST_mkdir_whendone_queryarg(self):
1932 d = self.POST(self.public_url + "/foo?when_done=/THERE",
1933 t="mkdir", name="newdir")
1934 d.addBoth(self.shouldRedirect, "/THERE")
1935 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1936 d.addCallback(self.failUnlessNodeKeysAre, [])
1939 def test_POST_bad_t(self):
1940 d = self.shouldFail2(error.Error, "POST_bad_t", "400 Bad Request",
1941 "POST to a directory with bad t=BOGUS",
1942 self.POST, self.public_url + "/foo", t="BOGUS")
1945 def test_POST_set_children(self):
1946 contents9, n9, newuri9 = self.makefile(9)
1947 contents10, n10, newuri10 = self.makefile(10)
1948 contents11, n11, newuri11 = self.makefile(11)
1951 "atomic_added_1": [ "filenode", { "rw_uri": "%s",
1954 "ctime": 1002777696.7564139,
1955 "mtime": 1002777696.7564139
1958 "atomic_added_2": [ "filenode", { "rw_uri": "%s",
1961 "ctime": 1002777696.7564139,
1962 "mtime": 1002777696.7564139
1965 "atomic_added_3": [ "filenode", { "rw_uri": "%s",
1968 "ctime": 1002777696.7564139,
1969 "mtime": 1002777696.7564139
1972 }""" % (newuri9, newuri10, newuri11)
1974 url = self.webish_url + self.public_url + "/foo" + "?t=set_children"
1976 d = client.getPage(url, method="POST", postdata=reqbody)
1978 self.failUnlessURIMatchesChild(newuri9, self._foo_node, u"atomic_added_1")
1979 self.failUnlessURIMatchesChild(newuri10, self._foo_node, u"atomic_added_2")
1980 self.failUnlessURIMatchesChild(newuri11, self._foo_node, u"atomic_added_3")
1982 d.addCallback(_then)
1983 d.addErrback(self.dump_error)
1986 def test_POST_put_uri(self):
1987 contents, n, newuri = self.makefile(8)
1988 d = self.POST(self.public_url + "/foo", t="uri", name="new.txt", uri=newuri)
1989 d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"new.txt")
1990 d.addCallback(lambda res:
1991 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
1995 def test_POST_put_uri_replace(self):
1996 contents, n, newuri = self.makefile(8)
1997 d = self.POST(self.public_url + "/foo", t="uri", name="bar.txt", uri=newuri)
1998 d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"bar.txt")
1999 d.addCallback(lambda res:
2000 self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
2004 def test_POST_put_uri_no_replace_queryarg(self):
2005 contents, n, newuri = self.makefile(8)
2006 d = self.POST(self.public_url + "/foo?replace=false", t="uri",
2007 name="bar.txt", uri=newuri)
2008 d.addBoth(self.shouldFail, error.Error,
2009 "POST_put_uri_no_replace_queryarg",
2011 "There was already a child by that name, and you asked me "
2012 "to not replace it")
2013 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
2014 d.addCallback(self.failUnlessIsBarDotTxt)
2017 def test_POST_put_uri_no_replace_field(self):
2018 contents, n, newuri = self.makefile(8)
2019 d = self.POST(self.public_url + "/foo", t="uri", replace="false",
2020 name="bar.txt", uri=newuri)
2021 d.addBoth(self.shouldFail, error.Error,
2022 "POST_put_uri_no_replace_field",
2024 "There was already a child by that name, and you asked me "
2025 "to not replace it")
2026 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
2027 d.addCallback(self.failUnlessIsBarDotTxt)
2030 def test_POST_delete(self):
2031 d = self.POST(self.public_url + "/foo", t="delete", name="bar.txt")
2032 d.addCallback(lambda res: self._foo_node.list())
2033 def _check(children):
2034 self.failIf(u"bar.txt" in children)
2035 d.addCallback(_check)
2038 def test_POST_rename_file(self):
2039 d = self.POST(self.public_url + "/foo", t="rename",
2040 from_name="bar.txt", to_name='wibble.txt')
2041 d.addCallback(lambda res:
2042 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
2043 d.addCallback(lambda res:
2044 self.failUnlessNodeHasChild(self._foo_node, u"wibble.txt"))
2045 d.addCallback(lambda res: self.GET(self.public_url + "/foo/wibble.txt"))
2046 d.addCallback(self.failUnlessIsBarDotTxt)
2047 d.addCallback(lambda res: self.GET(self.public_url + "/foo/wibble.txt?t=json"))
2048 d.addCallback(self.failUnlessIsBarJSON)
2051 def test_POST_rename_file_redundant(self):
2052 d = self.POST(self.public_url + "/foo", t="rename",
2053 from_name="bar.txt", to_name='bar.txt')
2054 d.addCallback(lambda res:
2055 self.failUnlessNodeHasChild(self._foo_node, u"bar.txt"))
2056 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
2057 d.addCallback(self.failUnlessIsBarDotTxt)
2058 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt?t=json"))
2059 d.addCallback(self.failUnlessIsBarJSON)
2062 def test_POST_rename_file_replace(self):
2063 # rename a file and replace a directory with it
2064 d = self.POST(self.public_url + "/foo", t="rename",
2065 from_name="bar.txt", to_name='empty')
2066 d.addCallback(lambda res:
2067 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
2068 d.addCallback(lambda res:
2069 self.failUnlessNodeHasChild(self._foo_node, u"empty"))
2070 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty"))
2071 d.addCallback(self.failUnlessIsBarDotTxt)
2072 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
2073 d.addCallback(self.failUnlessIsBarJSON)
2076 def test_POST_rename_file_no_replace_queryarg(self):
2077 # rename a file and replace a directory with it
2078 d = self.POST(self.public_url + "/foo?replace=false", t="rename",
2079 from_name="bar.txt", to_name='empty')
2080 d.addBoth(self.shouldFail, error.Error,
2081 "POST_rename_file_no_replace_queryarg",
2083 "There was already a child by that name, and you asked me "
2084 "to not replace it")
2085 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
2086 d.addCallback(self.failUnlessIsEmptyJSON)
2089 def test_POST_rename_file_no_replace_field(self):
2090 # rename a file and replace a directory with it
2091 d = self.POST(self.public_url + "/foo", t="rename", replace="false",
2092 from_name="bar.txt", to_name='empty')
2093 d.addBoth(self.shouldFail, error.Error,
2094 "POST_rename_file_no_replace_field",
2096 "There was already a child by that name, and you asked me "
2097 "to not replace it")
2098 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
2099 d.addCallback(self.failUnlessIsEmptyJSON)
2102 def failUnlessIsEmptyJSON(self, res):
2103 data = simplejson.loads(res)
2104 self.failUnlessEqual(data[0], "dirnode", data)
2105 self.failUnlessEqual(len(data[1]["children"]), 0)
2107 def test_POST_rename_file_slash_fail(self):
2108 d = self.POST(self.public_url + "/foo", t="rename",
2109 from_name="bar.txt", to_name='kirk/spock.txt')
2110 d.addBoth(self.shouldFail, error.Error,
2111 "test_POST_rename_file_slash_fail",
2113 "to_name= may not contain a slash",
2115 d.addCallback(lambda res:
2116 self.failUnlessNodeHasChild(self._foo_node, u"bar.txt"))
2119 def test_POST_rename_dir(self):
2120 d = self.POST(self.public_url, t="rename",
2121 from_name="foo", to_name='plunk')
2122 d.addCallback(lambda res:
2123 self.failIfNodeHasChild(self.public_root, u"foo"))
2124 d.addCallback(lambda res:
2125 self.failUnlessNodeHasChild(self.public_root, u"plunk"))
2126 d.addCallback(lambda res: self.GET(self.public_url + "/plunk?t=json"))
2127 d.addCallback(self.failUnlessIsFooJSON)
2130 def shouldRedirect(self, res, target=None, statuscode=None, which=""):
2131 """ If target is not None then the redirection has to go to target. If
2132 statuscode is not None then the redirection has to be accomplished with
2133 that HTTP status code."""
2134 if not isinstance(res, failure.Failure):
2135 to_where = (target is None) and "somewhere" or ("to " + target)
2136 self.fail("%s: we were expecting to get redirected %s, not get an"
2137 " actual page: %s" % (which, to_where, res))
2138 res.trap(error.PageRedirect)
2139 if statuscode is not None:
2140 self.failUnlessEqual(res.value.status, statuscode,
2141 "%s: not a redirect" % which)
2142 if target is not None:
2143 # the PageRedirect does not seem to capture the uri= query arg
2144 # properly, so we can't check for it.
2145 realtarget = self.webish_url + target
2146 self.failUnlessEqual(res.value.location, realtarget,
2147 "%s: wrong target" % which)
2148 return res.value.location
2150 def test_GET_URI_form(self):
2151 base = "/uri?uri=%s" % self._bar_txt_uri
2152 # this is supposed to give us a redirect to /uri/$URI, plus arguments
2153 targetbase = "/uri/%s" % urllib.quote(self._bar_txt_uri)
2155 d.addBoth(self.shouldRedirect, targetbase)
2156 d.addCallback(lambda res: self.GET(base+"&filename=bar.txt"))
2157 d.addBoth(self.shouldRedirect, targetbase+"?filename=bar.txt")
2158 d.addCallback(lambda res: self.GET(base+"&t=json"))
2159 d.addBoth(self.shouldRedirect, targetbase+"?t=json")
2160 d.addCallback(self.log, "about to get file by uri")
2161 d.addCallback(lambda res: self.GET(base, followRedirect=True))
2162 d.addCallback(self.failUnlessIsBarDotTxt)
2163 d.addCallback(self.log, "got file by uri, about to get dir by uri")
2164 d.addCallback(lambda res: self.GET("/uri?uri=%s&t=json" % self._foo_uri,
2165 followRedirect=True))
2166 d.addCallback(self.failUnlessIsFooJSON)
2167 d.addCallback(self.log, "got dir by uri")
2171 def test_GET_URI_form_bad(self):
2172 d = self.shouldFail2(error.Error, "test_GET_URI_form_bad",
2173 "400 Bad Request", "GET /uri requires uri=",
2177 def test_GET_rename_form(self):
2178 d = self.GET(self.public_url + "/foo?t=rename-form&name=bar.txt",
2179 followRedirect=True)
2181 self.failUnless('name="when_done" value="."' in res, res)
2182 self.failUnless(re.search(r'name="from_name" value="bar\.txt"', res))
2183 d.addCallback(_check)
2186 def log(self, res, msg):
2187 #print "MSG: %s RES: %s" % (msg, res)
2191 def test_GET_URI_URL(self):
2192 base = "/uri/%s" % self._bar_txt_uri
2194 d.addCallback(self.failUnlessIsBarDotTxt)
2195 d.addCallback(lambda res: self.GET(base+"?filename=bar.txt"))
2196 d.addCallback(self.failUnlessIsBarDotTxt)
2197 d.addCallback(lambda res: self.GET(base+"?filename=bar.txt&save=true"))
2198 d.addCallback(self.failUnlessIsBarDotTxt)
2201 def test_GET_URI_URL_dir(self):
2202 base = "/uri/%s?t=json" % self._foo_uri
2204 d.addCallback(self.failUnlessIsFooJSON)
2207 def test_GET_URI_URL_missing(self):
2208 base = "/uri/%s" % self._bad_file_uri
2209 d = self.shouldHTTPError("test_GET_URI_URL_missing",
2210 http.GONE, None, "NotEnoughSharesError",
2212 # TODO: how can we exercise both sides of WebDownloadTarget.fail
2213 # here? we must arrange for a download to fail after target.open()
2214 # has been called, and then inspect the response to see that it is
2215 # shorter than we expected.
2218 def test_PUT_DIRURL_uri(self):
2219 d = self.s.create_empty_dirnode()
2221 new_uri = dn.get_uri()
2222 # replace /foo with a new (empty) directory
2223 d = self.PUT(self.public_url + "/foo?t=uri", new_uri)
2224 d.addCallback(lambda res:
2225 self.failUnlessEqual(res.strip(), new_uri))
2226 d.addCallback(lambda res:
2227 self.failUnlessChildURIIs(self.public_root,
2231 d.addCallback(_made_dir)
2234 def test_PUT_DIRURL_uri_noreplace(self):
2235 d = self.s.create_empty_dirnode()
2237 new_uri = dn.get_uri()
2238 # replace /foo with a new (empty) directory, but ask that
2239 # replace=false, so it should fail
2240 d = self.shouldFail2(error.Error, "test_PUT_DIRURL_uri_noreplace",
2241 "409 Conflict", "There was already a child by that name, and you asked me to not replace it",
2243 self.public_url + "/foo?t=uri&replace=false",
2245 d.addCallback(lambda res:
2246 self.failUnlessChildURIIs(self.public_root,
2250 d.addCallback(_made_dir)
2253 def test_PUT_DIRURL_bad_t(self):
2254 d = self.shouldFail2(error.Error, "test_PUT_DIRURL_bad_t",
2255 "400 Bad Request", "PUT to a directory",
2256 self.PUT, self.public_url + "/foo?t=BOGUS", "")
2257 d.addCallback(lambda res:
2258 self.failUnlessChildURIIs(self.public_root,
2263 def test_PUT_NEWFILEURL_uri(self):
2264 contents, n, new_uri = self.makefile(8)
2265 d = self.PUT(self.public_url + "/foo/new.txt?t=uri", new_uri)
2266 d.addCallback(lambda res: self.failUnlessEqual(res.strip(), new_uri))
2267 d.addCallback(lambda res:
2268 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
2272 def test_PUT_NEWFILEURL_uri_replace(self):
2273 contents, n, new_uri = self.makefile(8)
2274 d = self.PUT(self.public_url + "/foo/bar.txt?t=uri", new_uri)
2275 d.addCallback(lambda res: self.failUnlessEqual(res.strip(), new_uri))
2276 d.addCallback(lambda res:
2277 self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
2281 def test_PUT_NEWFILEURL_uri_no_replace(self):
2282 contents, n, new_uri = self.makefile(8)
2283 d = self.PUT(self.public_url + "/foo/bar.txt?t=uri&replace=false", new_uri)
2284 d.addBoth(self.shouldFail, error.Error, "PUT_NEWFILEURL_uri_no_replace",
2286 "There was already a child by that name, and you asked me "
2287 "to not replace it")
2290 def test_PUT_NEWFILE_URI(self):
2291 file_contents = "New file contents here\n"
2292 d = self.PUT("/uri", file_contents)
2294 assert isinstance(uri, str), uri
2295 self.failUnless(uri in FakeCHKFileNode.all_contents)
2296 self.failUnlessEqual(FakeCHKFileNode.all_contents[uri],
2298 return self.GET("/uri/%s" % uri)
2299 d.addCallback(_check)
2301 self.failUnlessEqual(res, file_contents)
2302 d.addCallback(_check2)
2305 def test_PUT_NEWFILE_URI_only_PUT(self):
2306 d = self.PUT("/uri?t=bogus", "")
2307 d.addBoth(self.shouldFail, error.Error,
2308 "PUT_NEWFILE_URI_only_PUT",
2310 "/uri accepts only PUT, PUT?t=mkdir, POST?t=upload, and POST?t=mkdir")
2313 def test_PUT_NEWFILE_URI_mutable(self):
2314 file_contents = "New file contents here\n"
2315 d = self.PUT("/uri?mutable=true", file_contents)
2316 def _check_mutable(uri):
2319 self.failUnless(IMutableFileURI.providedBy(u))
2320 self.failUnless(u.storage_index in FakeMutableFileNode.all_contents)
2321 n = self.s.create_node_from_uri(uri)
2322 return n.download_best_version()
2323 d.addCallback(_check_mutable)
2324 def _check2_mutable(data):
2325 self.failUnlessEqual(data, file_contents)
2326 d.addCallback(_check2_mutable)
2330 self.failUnless(uri.to_string() in FakeCHKFileNode.all_contents)
2331 self.failUnlessEqual(FakeCHKFileNode.all_contents[uri.to_string()],
2333 return self.GET("/uri/%s" % uri)
2334 d.addCallback(_check)
2336 self.failUnlessEqual(res, file_contents)
2337 d.addCallback(_check2)
2340 def test_PUT_mkdir(self):
2341 d = self.PUT("/uri?t=mkdir", "")
2343 n = self.s.create_node_from_uri(uri.strip())
2344 d2 = self.failUnlessNodeKeysAre(n, [])
2345 d2.addCallback(lambda res:
2346 self.GET("/uri/%s?t=json" % uri))
2348 d.addCallback(_check)
2349 d.addCallback(self.failUnlessIsEmptyJSON)
2352 def test_POST_check(self):
2353 d = self.POST(self.public_url + "/foo", t="check", name="bar.txt")
2355 # this returns a string form of the results, which are probably
2356 # None since we're using fake filenodes.
2357 # TODO: verify that the check actually happened, by changing
2358 # FakeCHKFileNode to count how many times .check() has been
2361 d.addCallback(_done)
2364 def test_bad_method(self):
2365 url = self.webish_url + self.public_url + "/foo/bar.txt"
2366 d = self.shouldHTTPError("test_bad_method",
2367 501, "Not Implemented",
2368 "I don't know how to treat a BOGUS request.",
2369 client.getPage, url, method="BOGUS")
2372 def test_short_url(self):
2373 url = self.webish_url + "/uri"
2374 d = self.shouldHTTPError("test_short_url", 501, "Not Implemented",
2375 "I don't know how to treat a DELETE request.",
2376 client.getPage, url, method="DELETE")
2379 def test_ophandle_bad(self):
2380 url = self.webish_url + "/operations/bogus?t=status"
2381 d = self.shouldHTTPError("test_ophandle_bad", 404, "404 Not Found",
2382 "unknown/expired handle 'bogus'",
2383 client.getPage, url)
2386 def test_ophandle_cancel(self):
2387 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=128",
2388 followRedirect=True)
2389 d.addCallback(lambda ignored:
2390 self.GET("/operations/128?t=status&output=JSON"))
2392 data = simplejson.loads(res)
2393 self.failUnless("finished" in data, res)
2394 monitor = self.ws.root.child_operations.handles["128"][0]
2395 d = self.POST("/operations/128?t=cancel&output=JSON")
2397 data = simplejson.loads(res)
2398 self.failUnless("finished" in data, res)
2399 # t=cancel causes the handle to be forgotten
2400 self.failUnless(monitor.is_cancelled())
2401 d.addCallback(_check2)
2403 d.addCallback(_check1)
2404 d.addCallback(lambda ignored:
2405 self.shouldHTTPError("test_ophandle_cancel",
2406 404, "404 Not Found",
2407 "unknown/expired handle '128'",
2409 "/operations/128?t=status&output=JSON"))
2412 def test_ophandle_retainfor(self):
2413 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=129&retain-for=60",
2414 followRedirect=True)
2415 d.addCallback(lambda ignored:
2416 self.GET("/operations/129?t=status&output=JSON&retain-for=0"))
2418 data = simplejson.loads(res)
2419 self.failUnless("finished" in data, res)
2420 d.addCallback(_check1)
2421 # the retain-for=0 will cause the handle to be expired very soon
2422 d.addCallback(self.stall, 2.0)
2423 d.addCallback(lambda ignored:
2424 self.shouldHTTPError("test_ophandle_retainfor",
2425 404, "404 Not Found",
2426 "unknown/expired handle '129'",
2428 "/operations/129?t=status&output=JSON"))
2431 def test_ophandle_release_after_complete(self):
2432 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=130",
2433 followRedirect=True)
2434 d.addCallback(self.wait_for_operation, "130")
2435 d.addCallback(lambda ignored:
2436 self.GET("/operations/130?t=status&output=JSON&release-after-complete=true"))
2437 # the release-after-complete=true will cause the handle to be expired
2438 d.addCallback(lambda ignored:
2439 self.shouldHTTPError("test_ophandle_release_after_complete",
2440 404, "404 Not Found",
2441 "unknown/expired handle '130'",
2443 "/operations/130?t=status&output=JSON"))
2446 def test_incident(self):
2447 d = self.POST("/report_incident", details="eek")
2449 self.failUnless("Thank you for your report!" in res, res)
2450 d.addCallback(_done)
2453 def test_static(self):
2454 webdir = os.path.join(self.staticdir, "subdir")
2455 fileutil.make_dirs(webdir)
2456 f = open(os.path.join(webdir, "hello.txt"), "wb")
2460 d = self.GET("/static/subdir/hello.txt")
2462 self.failUnlessEqual(res, "hello")
2463 d.addCallback(_check)
2467 class Util(unittest.TestCase):
2468 def test_abbreviate_time(self):
2469 self.failUnlessEqual(common.abbreviate_time(None), "")
2470 self.failUnlessEqual(common.abbreviate_time(1.234), "1.23s")
2471 self.failUnlessEqual(common.abbreviate_time(0.123), "123ms")
2472 self.failUnlessEqual(common.abbreviate_time(0.00123), "1.2ms")
2473 self.failUnlessEqual(common.abbreviate_time(0.000123), "123us")
2475 def test_abbreviate_rate(self):
2476 self.failUnlessEqual(common.abbreviate_rate(None), "")
2477 self.failUnlessEqual(common.abbreviate_rate(1234000), "1.23MBps")
2478 self.failUnlessEqual(common.abbreviate_rate(12340), "12.3kBps")
2479 self.failUnlessEqual(common.abbreviate_rate(123), "123Bps")
2481 def test_abbreviate_size(self):
2482 self.failUnlessEqual(common.abbreviate_size(None), "")
2483 self.failUnlessEqual(common.abbreviate_size(1.23*1000*1000*1000), "1.23GB")
2484 self.failUnlessEqual(common.abbreviate_size(1.23*1000*1000), "1.23MB")
2485 self.failUnlessEqual(common.abbreviate_size(1230), "1.2kB")
2486 self.failUnlessEqual(common.abbreviate_size(123), "123B")
2488 def test_plural(self):
2490 return "%d second%s" % (s, status.plural(s))
2491 self.failUnlessEqual(convert(0), "0 seconds")
2492 self.failUnlessEqual(convert(1), "1 second")
2493 self.failUnlessEqual(convert(2), "2 seconds")
2495 return "has share%s: %s" % (status.plural(s), ",".join(s))
2496 self.failUnlessEqual(convert2([]), "has shares: ")
2497 self.failUnlessEqual(convert2(["1"]), "has share: 1")
2498 self.failUnlessEqual(convert2(["1","2"]), "has shares: 1,2")
2501 class Grid(GridTestMixin, WebErrorMixin, unittest.TestCase, ShouldFailMixin):
2503 def CHECK(self, ign, which, args, clientnum=0):
2504 fileurl = self.fileurls[which]
2505 url = fileurl + "?" + args
2506 return self.GET(url, method="POST", clientnum=clientnum)
2508 def test_filecheck(self):
2509 self.basedir = "web/Grid/filecheck"
2511 c0 = self.g.clients[0]
2514 d = c0.upload(upload.Data(DATA, convergence=""))
2515 def _stash_uri(ur, which):
2516 self.uris[which] = ur.uri
2517 d.addCallback(_stash_uri, "good")
2518 d.addCallback(lambda ign:
2519 c0.upload(upload.Data(DATA+"1", convergence="")))
2520 d.addCallback(_stash_uri, "sick")
2521 d.addCallback(lambda ign:
2522 c0.upload(upload.Data(DATA+"2", convergence="")))
2523 d.addCallback(_stash_uri, "dead")
2524 def _stash_mutable_uri(n, which):
2525 self.uris[which] = n.get_uri()
2526 assert isinstance(self.uris[which], str)
2527 d.addCallback(lambda ign: c0.create_mutable_file(DATA+"3"))
2528 d.addCallback(_stash_mutable_uri, "corrupt")
2529 d.addCallback(lambda ign:
2530 c0.upload(upload.Data("literal", convergence="")))
2531 d.addCallback(_stash_uri, "small")
2533 def _compute_fileurls(ignored):
2535 for which in self.uris:
2536 self.fileurls[which] = "uri/" + urllib.quote(self.uris[which])
2537 d.addCallback(_compute_fileurls)
2539 def _clobber_shares(ignored):
2540 good_shares = self.find_shares(self.uris["good"])
2541 self.failUnlessEqual(len(good_shares), 10)
2542 sick_shares = self.find_shares(self.uris["sick"])
2543 os.unlink(sick_shares[0][2])
2544 dead_shares = self.find_shares(self.uris["dead"])
2545 for i in range(1, 10):
2546 os.unlink(dead_shares[i][2])
2547 c_shares = self.find_shares(self.uris["corrupt"])
2548 cso = CorruptShareOptions()
2549 cso.stdout = StringIO()
2550 cso.parseOptions([c_shares[0][2]])
2552 d.addCallback(_clobber_shares)
2554 d.addCallback(self.CHECK, "good", "t=check")
2555 def _got_html_good(res):
2556 self.failUnless("Healthy" in res, res)
2557 self.failIf("Not Healthy" in res, res)
2558 d.addCallback(_got_html_good)
2559 d.addCallback(self.CHECK, "good", "t=check&return_to=somewhere")
2560 def _got_html_good_return_to(res):
2561 self.failUnless("Healthy" in res, res)
2562 self.failIf("Not Healthy" in res, res)
2563 self.failUnless('<a href="somewhere">Return to parent directory'
2565 d.addCallback(_got_html_good_return_to)
2566 d.addCallback(self.CHECK, "good", "t=check&output=json")
2567 def _got_json_good(res):
2568 r = simplejson.loads(res)
2569 self.failUnlessEqual(r["summary"], "Healthy")
2570 self.failUnless(r["results"]["healthy"])
2571 self.failIf(r["results"]["needs-rebalancing"])
2572 self.failUnless(r["results"]["recoverable"])
2573 d.addCallback(_got_json_good)
2575 d.addCallback(self.CHECK, "small", "t=check")
2576 def _got_html_small(res):
2577 self.failUnless("Literal files are always healthy" in res, res)
2578 self.failIf("Not Healthy" in res, res)
2579 d.addCallback(_got_html_small)
2580 d.addCallback(self.CHECK, "small", "t=check&return_to=somewhere")
2581 def _got_html_small_return_to(res):
2582 self.failUnless("Literal files are always healthy" in res, res)
2583 self.failIf("Not Healthy" in res, res)
2584 self.failUnless('<a href="somewhere">Return to parent directory'
2586 d.addCallback(_got_html_small_return_to)
2587 d.addCallback(self.CHECK, "small", "t=check&output=json")
2588 def _got_json_small(res):
2589 r = simplejson.loads(res)
2590 self.failUnlessEqual(r["storage-index"], "")
2591 self.failUnless(r["results"]["healthy"])
2592 d.addCallback(_got_json_small)
2594 d.addCallback(self.CHECK, "sick", "t=check")
2595 def _got_html_sick(res):
2596 self.failUnless("Not Healthy" in res, res)
2597 d.addCallback(_got_html_sick)
2598 d.addCallback(self.CHECK, "sick", "t=check&output=json")
2599 def _got_json_sick(res):
2600 r = simplejson.loads(res)
2601 self.failUnlessEqual(r["summary"],
2602 "Not Healthy: 9 shares (enc 3-of-10)")
2603 self.failIf(r["results"]["healthy"])
2604 self.failIf(r["results"]["needs-rebalancing"])
2605 self.failUnless(r["results"]["recoverable"])
2606 d.addCallback(_got_json_sick)
2608 d.addCallback(self.CHECK, "dead", "t=check")
2609 def _got_html_dead(res):
2610 self.failUnless("Not Healthy" in res, res)
2611 d.addCallback(_got_html_dead)
2612 d.addCallback(self.CHECK, "dead", "t=check&output=json")
2613 def _got_json_dead(res):
2614 r = simplejson.loads(res)
2615 self.failUnlessEqual(r["summary"],
2616 "Not Healthy: 1 shares (enc 3-of-10)")
2617 self.failIf(r["results"]["healthy"])
2618 self.failIf(r["results"]["needs-rebalancing"])
2619 self.failIf(r["results"]["recoverable"])
2620 d.addCallback(_got_json_dead)
2622 d.addCallback(self.CHECK, "corrupt", "t=check&verify=true")
2623 def _got_html_corrupt(res):
2624 self.failUnless("Not Healthy! : Unhealthy" in res, res)
2625 d.addCallback(_got_html_corrupt)
2626 d.addCallback(self.CHECK, "corrupt", "t=check&verify=true&output=json")
2627 def _got_json_corrupt(res):
2628 r = simplejson.loads(res)
2629 self.failUnless("Unhealthy: 9 shares (enc 3-of-10)" in r["summary"],
2631 self.failIf(r["results"]["healthy"])
2632 self.failUnless(r["results"]["recoverable"])
2633 self.failUnlessEqual(r["results"]["count-shares-good"], 9)
2634 self.failUnlessEqual(r["results"]["count-corrupt-shares"], 1)
2635 d.addCallback(_got_json_corrupt)
2637 d.addErrback(self.explain_web_error)
2640 def test_repair_html(self):
2641 self.basedir = "web/Grid/repair_html"
2643 c0 = self.g.clients[0]
2646 d = c0.upload(upload.Data(DATA, convergence=""))
2647 def _stash_uri(ur, which):
2648 self.uris[which] = ur.uri
2649 d.addCallback(_stash_uri, "good")
2650 d.addCallback(lambda ign:
2651 c0.upload(upload.Data(DATA+"1", convergence="")))
2652 d.addCallback(_stash_uri, "sick")
2653 d.addCallback(lambda ign:
2654 c0.upload(upload.Data(DATA+"2", convergence="")))
2655 d.addCallback(_stash_uri, "dead")
2656 def _stash_mutable_uri(n, which):
2657 self.uris[which] = n.get_uri()
2658 assert isinstance(self.uris[which], str)
2659 d.addCallback(lambda ign: c0.create_mutable_file(DATA+"3"))
2660 d.addCallback(_stash_mutable_uri, "corrupt")
2662 def _compute_fileurls(ignored):
2664 for which in self.uris:
2665 self.fileurls[which] = "uri/" + urllib.quote(self.uris[which])
2666 d.addCallback(_compute_fileurls)
2668 def _clobber_shares(ignored):
2669 good_shares = self.find_shares(self.uris["good"])
2670 self.failUnlessEqual(len(good_shares), 10)
2671 sick_shares = self.find_shares(self.uris["sick"])
2672 os.unlink(sick_shares[0][2])
2673 dead_shares = self.find_shares(self.uris["dead"])
2674 for i in range(1, 10):
2675 os.unlink(dead_shares[i][2])
2676 c_shares = self.find_shares(self.uris["corrupt"])
2677 cso = CorruptShareOptions()
2678 cso.stdout = StringIO()
2679 cso.parseOptions([c_shares[0][2]])
2681 d.addCallback(_clobber_shares)
2683 d.addCallback(self.CHECK, "good", "t=check&repair=true")
2684 def _got_html_good(res):
2685 self.failUnless("Healthy" in res, res)
2686 self.failIf("Not Healthy" in res, res)
2687 self.failUnless("No repair necessary" in res, res)
2688 d.addCallback(_got_html_good)
2690 d.addCallback(self.CHECK, "sick", "t=check&repair=true")
2691 def _got_html_sick(res):
2692 self.failUnless("Healthy : healthy" in res, res)
2693 self.failIf("Not Healthy" in res, res)
2694 self.failUnless("Repair successful" in res, res)
2695 d.addCallback(_got_html_sick)
2697 # repair of a dead file will fail, of course, but it isn't yet
2698 # clear how this should be reported. Right now it shows up as
2701 #d.addCallback(self.CHECK, "dead", "t=check&repair=true")
2702 #def _got_html_dead(res):
2704 # self.failUnless("Healthy : healthy" in res, res)
2705 # self.failIf("Not Healthy" in res, res)
2706 # self.failUnless("No repair necessary" in res, res)
2707 #d.addCallback(_got_html_dead)
2709 d.addCallback(self.CHECK, "corrupt", "t=check&verify=true&repair=true")
2710 def _got_html_corrupt(res):
2711 self.failUnless("Healthy : Healthy" in res, res)
2712 self.failIf("Not Healthy" in res, res)
2713 self.failUnless("Repair successful" in res, res)
2714 d.addCallback(_got_html_corrupt)
2716 d.addErrback(self.explain_web_error)
2719 def test_repair_json(self):
2720 self.basedir = "web/Grid/repair_json"
2722 c0 = self.g.clients[0]
2725 d = c0.upload(upload.Data(DATA+"1", convergence=""))
2726 def _stash_uri(ur, which):
2727 self.uris[which] = ur.uri
2728 d.addCallback(_stash_uri, "sick")
2730 def _compute_fileurls(ignored):
2732 for which in self.uris:
2733 self.fileurls[which] = "uri/" + urllib.quote(self.uris[which])
2734 d.addCallback(_compute_fileurls)
2736 def _clobber_shares(ignored):
2737 sick_shares = self.find_shares(self.uris["sick"])
2738 os.unlink(sick_shares[0][2])
2739 d.addCallback(_clobber_shares)
2741 d.addCallback(self.CHECK, "sick", "t=check&repair=true&output=json")
2742 def _got_json_sick(res):
2743 r = simplejson.loads(res)
2744 self.failUnlessEqual(r["repair-attempted"], True)
2745 self.failUnlessEqual(r["repair-successful"], True)
2746 self.failUnlessEqual(r["pre-repair-results"]["summary"],
2747 "Not Healthy: 9 shares (enc 3-of-10)")
2748 self.failIf(r["pre-repair-results"]["results"]["healthy"])
2749 self.failUnlessEqual(r["post-repair-results"]["summary"], "healthy")
2750 self.failUnless(r["post-repair-results"]["results"]["healthy"])
2751 d.addCallback(_got_json_sick)
2753 d.addErrback(self.explain_web_error)
2756 def test_deep_check(self):
2757 self.basedir = "web/Grid/deep_check"
2759 c0 = self.g.clients[0]
2763 d = c0.create_empty_dirnode()
2764 def _stash_root_and_create_file(n):
2766 self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri()) + "/"
2767 return n.add_file(u"good", upload.Data(DATA, convergence=""))
2768 d.addCallback(_stash_root_and_create_file)
2769 def _stash_uri(fn, which):
2770 self.uris[which] = fn.get_uri()
2772 d.addCallback(_stash_uri, "good")
2773 d.addCallback(lambda ign:
2774 self.rootnode.add_file(u"small",
2775 upload.Data("literal",
2777 d.addCallback(_stash_uri, "small")
2778 d.addCallback(lambda ign:
2779 self.rootnode.add_file(u"sick",
2780 upload.Data(DATA+"1",
2782 d.addCallback(_stash_uri, "sick")
2784 def _clobber_shares(ignored):
2785 self.delete_shares_numbered(self.uris["sick"], [0,1])
2786 d.addCallback(_clobber_shares)
2793 d.addCallback(self.CHECK, "root", "t=stream-deep-check")
2795 units = [simplejson.loads(line)
2796 for line in res.splitlines()
2798 self.failUnlessEqual(len(units), 4+1)
2799 # should be parent-first
2801 self.failUnlessEqual(u0["path"], [])
2802 self.failUnlessEqual(u0["type"], "directory")
2803 self.failUnlessEqual(u0["cap"], self.rootnode.get_uri())
2804 u0cr = u0["check-results"]
2805 self.failUnlessEqual(u0cr["results"]["count-shares-good"], 10)
2807 ugood = [u for u in units
2808 if u["type"] == "file" and u["path"] == [u"good"]][0]
2809 self.failUnlessEqual(ugood["cap"], self.uris["good"])
2810 ugoodcr = ugood["check-results"]
2811 self.failUnlessEqual(ugoodcr["results"]["count-shares-good"], 10)
2814 self.failUnlessEqual(stats["type"], "stats")
2816 self.failUnlessEqual(s["count-immutable-files"], 2)
2817 self.failUnlessEqual(s["count-literal-files"], 1)
2818 self.failUnlessEqual(s["count-directories"], 1)
2819 d.addCallback(_done)
2821 # now add root/subdir and root/subdir/grandchild, then make subdir
2822 # unrecoverable, then see what happens
2824 d.addCallback(lambda ign:
2825 self.rootnode.create_empty_directory(u"subdir"))
2826 d.addCallback(_stash_uri, "subdir")
2827 d.addCallback(lambda subdir_node:
2828 subdir_node.add_file(u"grandchild",
2829 upload.Data(DATA+"2",
2831 d.addCallback(_stash_uri, "grandchild")
2833 d.addCallback(lambda ign:
2834 self.delete_shares_numbered(self.uris["subdir"],
2841 # root/subdir [unrecoverable]
2842 # root/subdir/grandchild
2844 # how should a streaming-JSON API indicate fatal error?
2845 # answer: emit ERROR: instead of a JSON string
2847 d.addCallback(self.CHECK, "root", "t=stream-manifest")
2848 def _check_broken_manifest(res):
2849 lines = res.splitlines()
2851 for (i,line) in enumerate(lines)
2852 if line.startswith("ERROR:")]
2854 self.fail("no ERROR: in output: %s" % (res,))
2855 first_error = error_lines[0]
2856 error_line = lines[first_error]
2857 error_msg = lines[first_error+1:]
2858 error_msg_s = "\n".join(error_msg) + "\n"
2859 self.failUnlessIn("ERROR: UnrecoverableFileError(no recoverable versions)",
2861 self.failUnless(len(error_msg) > 2, error_msg_s) # some traceback
2862 units = [simplejson.loads(line) for line in lines[:first_error]]
2863 self.failUnlessEqual(len(units), 5) # includes subdir
2864 last_unit = units[-1]
2865 self.failUnlessEqual(last_unit["path"], ["subdir"])
2866 d.addCallback(_check_broken_manifest)
2868 d.addCallback(self.CHECK, "root", "t=stream-deep-check")
2869 def _check_broken_deepcheck(res):
2870 lines = res.splitlines()
2872 for (i,line) in enumerate(lines)
2873 if line.startswith("ERROR:")]
2875 self.fail("no ERROR: in output: %s" % (res,))
2876 first_error = error_lines[0]
2877 error_line = lines[first_error]
2878 error_msg = lines[first_error+1:]
2879 error_msg_s = "\n".join(error_msg) + "\n"
2880 self.failUnlessIn("ERROR: UnrecoverableFileError(no recoverable versions)",
2882 self.failUnless(len(error_msg) > 2, error_msg_s) # some traceback
2883 units = [simplejson.loads(line) for line in lines[:first_error]]
2884 self.failUnlessEqual(len(units), 5) # includes subdir
2885 last_unit = units[-1]
2886 self.failUnlessEqual(last_unit["path"], ["subdir"])
2887 r = last_unit["check-results"]["results"]
2888 self.failUnlessEqual(r["count-recoverable-versions"], 0)
2889 self.failUnlessEqual(r["count-shares-good"], 1)
2890 self.failUnlessEqual(r["recoverable"], False)
2891 d.addCallback(_check_broken_deepcheck)
2893 d.addErrback(self.explain_web_error)
2896 def test_deep_check_and_repair(self):
2897 self.basedir = "web/Grid/deep_check_and_repair"
2899 c0 = self.g.clients[0]
2903 d = c0.create_empty_dirnode()
2904 def _stash_root_and_create_file(n):
2906 self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri()) + "/"
2907 return n.add_file(u"good", upload.Data(DATA, convergence=""))
2908 d.addCallback(_stash_root_and_create_file)
2909 def _stash_uri(fn, which):
2910 self.uris[which] = fn.get_uri()
2911 d.addCallback(_stash_uri, "good")
2912 d.addCallback(lambda ign:
2913 self.rootnode.add_file(u"small",
2914 upload.Data("literal",
2916 d.addCallback(_stash_uri, "small")
2917 d.addCallback(lambda ign:
2918 self.rootnode.add_file(u"sick",
2919 upload.Data(DATA+"1",
2921 d.addCallback(_stash_uri, "sick")
2922 #d.addCallback(lambda ign:
2923 # self.rootnode.add_file(u"dead",
2924 # upload.Data(DATA+"2",
2926 #d.addCallback(_stash_uri, "dead")
2928 #d.addCallback(lambda ign: c0.create_mutable_file("mutable"))
2929 #d.addCallback(lambda fn: self.rootnode.set_node(u"corrupt", fn))
2930 #d.addCallback(_stash_uri, "corrupt")
2932 def _clobber_shares(ignored):
2933 good_shares = self.find_shares(self.uris["good"])
2934 self.failUnlessEqual(len(good_shares), 10)
2935 sick_shares = self.find_shares(self.uris["sick"])
2936 os.unlink(sick_shares[0][2])
2937 #dead_shares = self.find_shares(self.uris["dead"])
2938 #for i in range(1, 10):
2939 # os.unlink(dead_shares[i][2])
2941 #c_shares = self.find_shares(self.uris["corrupt"])
2942 #cso = CorruptShareOptions()
2943 #cso.stdout = StringIO()
2944 #cso.parseOptions([c_shares[0][2]])
2946 d.addCallback(_clobber_shares)
2949 # root/good CHK, 10 shares
2951 # root/sick CHK, 9 shares
2953 d.addCallback(self.CHECK, "root", "t=stream-deep-check&repair=true")
2955 units = [simplejson.loads(line)
2956 for line in res.splitlines()
2958 self.failUnlessEqual(len(units), 4+1)
2959 # should be parent-first
2961 self.failUnlessEqual(u0["path"], [])
2962 self.failUnlessEqual(u0["type"], "directory")
2963 self.failUnlessEqual(u0["cap"], self.rootnode.get_uri())
2964 u0crr = u0["check-and-repair-results"]
2965 self.failUnlessEqual(u0crr["repair-attempted"], False)
2966 self.failUnlessEqual(u0crr["pre-repair-results"]["results"]["count-shares-good"], 10)
2968 ugood = [u for u in units
2969 if u["type"] == "file" and u["path"] == [u"good"]][0]
2970 self.failUnlessEqual(ugood["cap"], self.uris["good"])
2971 ugoodcrr = ugood["check-and-repair-results"]
2972 self.failUnlessEqual(u0crr["repair-attempted"], False)
2973 self.failUnlessEqual(u0crr["pre-repair-results"]["results"]["count-shares-good"], 10)
2975 usick = [u for u in units
2976 if u["type"] == "file" and u["path"] == [u"sick"]][0]
2977 self.failUnlessEqual(usick["cap"], self.uris["sick"])
2978 usickcrr = usick["check-and-repair-results"]
2979 self.failUnlessEqual(usickcrr["repair-attempted"], True)
2980 self.failUnlessEqual(usickcrr["repair-successful"], True)
2981 self.failUnlessEqual(usickcrr["pre-repair-results"]["results"]["count-shares-good"], 9)
2982 self.failUnlessEqual(usickcrr["post-repair-results"]["results"]["count-shares-good"], 10)
2985 self.failUnlessEqual(stats["type"], "stats")
2987 self.failUnlessEqual(s["count-immutable-files"], 2)
2988 self.failUnlessEqual(s["count-literal-files"], 1)
2989 self.failUnlessEqual(s["count-directories"], 1)
2990 d.addCallback(_done)
2992 d.addErrback(self.explain_web_error)
2995 def _count_leases(self, ignored, which):
2996 u = self.uris[which]
2997 shares = self.find_shares(u)
2999 for shnum, serverid, fn in shares:
3000 if u.startswith("URI:SSK") or u.startswith("URI:DIR2"):
3001 sf = MutableShareFile(fn)
3002 num_leases = len(sf.debug_get_leases())
3003 elif u.startswith("URI:CHK"):
3005 num_leases = len(list(sf.iter_leases()))
3007 raise ValueError("can't count leases on %s" % u)
3008 lease_counts.append( (fn, num_leases) )
3011 def _assert_leasecount(self, lease_counts, expected):
3012 for (fn, num_leases) in lease_counts:
3013 if num_leases != expected:
3014 self.fail("expected %d leases, have %d, on %s" %
3015 (expected, num_leases, fn))
3017 def test_add_lease(self):
3018 self.basedir = "web/Grid/add_lease"
3019 self.set_up_grid(num_clients=2)
3020 c0 = self.g.clients[0]
3023 d = c0.upload(upload.Data(DATA, convergence=""))
3024 def _stash_uri(ur, which):
3025 self.uris[which] = ur.uri
3026 d.addCallback(_stash_uri, "one")
3027 d.addCallback(lambda ign:
3028 c0.upload(upload.Data(DATA+"1", convergence="")))
3029 d.addCallback(_stash_uri, "two")
3030 def _stash_mutable_uri(n, which):
3031 self.uris[which] = n.get_uri()
3032 assert isinstance(self.uris[which], str)
3033 d.addCallback(lambda ign: c0.create_mutable_file(DATA+"2"))
3034 d.addCallback(_stash_mutable_uri, "mutable")
3036 def _compute_fileurls(ignored):
3038 for which in self.uris:
3039 self.fileurls[which] = "uri/" + urllib.quote(self.uris[which])
3040 d.addCallback(_compute_fileurls)
3042 d.addCallback(self._count_leases, "one")
3043 d.addCallback(self._assert_leasecount, 1)
3044 d.addCallback(self._count_leases, "two")
3045 d.addCallback(self._assert_leasecount, 1)
3046 d.addCallback(self._count_leases, "mutable")
3047 d.addCallback(self._assert_leasecount, 1)
3049 d.addCallback(self.CHECK, "one", "t=check") # no add-lease
3050 def _got_html_good(res):
3051 self.failUnless("Healthy" in res, res)
3052 self.failIf("Not Healthy" in res, res)
3053 d.addCallback(_got_html_good)
3055 d.addCallback(self._count_leases, "one")
3056 d.addCallback(self._assert_leasecount, 1)
3057 d.addCallback(self._count_leases, "two")
3058 d.addCallback(self._assert_leasecount, 1)
3059 d.addCallback(self._count_leases, "mutable")
3060 d.addCallback(self._assert_leasecount, 1)
3062 # this CHECK uses the original client, which uses the same
3063 # lease-secrets, so it will just renew the original lease
3064 d.addCallback(self.CHECK, "one", "t=check&add-lease=true")
3065 d.addCallback(_got_html_good)
3067 d.addCallback(self._count_leases, "one")
3068 d.addCallback(self._assert_leasecount, 1)
3069 d.addCallback(self._count_leases, "two")
3070 d.addCallback(self._assert_leasecount, 1)
3071 d.addCallback(self._count_leases, "mutable")
3072 d.addCallback(self._assert_leasecount, 1)
3074 # this CHECK uses an alternate client, which adds a second lease
3075 d.addCallback(self.CHECK, "one", "t=check&add-lease=true", clientnum=1)
3076 d.addCallback(_got_html_good)
3078 d.addCallback(self._count_leases, "one")
3079 d.addCallback(self._assert_leasecount, 2)
3080 d.addCallback(self._count_leases, "two")
3081 d.addCallback(self._assert_leasecount, 1)
3082 d.addCallback(self._count_leases, "mutable")
3083 d.addCallback(self._assert_leasecount, 1)
3085 d.addCallback(self.CHECK, "mutable", "t=check&add-lease=true")
3086 d.addCallback(_got_html_good)
3088 d.addCallback(self._count_leases, "one")
3089 d.addCallback(self._assert_leasecount, 2)
3090 d.addCallback(self._count_leases, "two")
3091 d.addCallback(self._assert_leasecount, 1)
3092 d.addCallback(self._count_leases, "mutable")
3093 d.addCallback(self._assert_leasecount, 1)
3095 d.addCallback(self.CHECK, "mutable", "t=check&add-lease=true",
3097 d.addCallback(_got_html_good)
3099 d.addCallback(self._count_leases, "one")
3100 d.addCallback(self._assert_leasecount, 2)
3101 d.addCallback(self._count_leases, "two")
3102 d.addCallback(self._assert_leasecount, 1)
3103 d.addCallback(self._count_leases, "mutable")
3104 d.addCallback(self._assert_leasecount, 2)
3106 d.addErrback(self.explain_web_error)
3109 def test_deep_add_lease(self):
3110 self.basedir = "web/Grid/deep_add_lease"
3111 self.set_up_grid(num_clients=2)
3112 c0 = self.g.clients[0]
3116 d = c0.create_empty_dirnode()
3117 def _stash_root_and_create_file(n):
3119 self.uris["root"] = n.get_uri()
3120 self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri()) + "/"
3121 return n.add_file(u"one", upload.Data(DATA, convergence=""))
3122 d.addCallback(_stash_root_and_create_file)
3123 def _stash_uri(fn, which):
3124 self.uris[which] = fn.get_uri()
3125 d.addCallback(_stash_uri, "one")
3126 d.addCallback(lambda ign:
3127 self.rootnode.add_file(u"small",
3128 upload.Data("literal",
3130 d.addCallback(_stash_uri, "small")
3132 d.addCallback(lambda ign: c0.create_mutable_file("mutable"))
3133 d.addCallback(lambda fn: self.rootnode.set_node(u"mutable", fn))
3134 d.addCallback(_stash_uri, "mutable")
3136 d.addCallback(self.CHECK, "root", "t=stream-deep-check") # no add-lease
3138 units = [simplejson.loads(line)
3139 for line in res.splitlines()
3141 # root, one, small, mutable, stats
3142 self.failUnlessEqual(len(units), 4+1)
3143 d.addCallback(_done)
3145 d.addCallback(self._count_leases, "root")
3146 d.addCallback(self._assert_leasecount, 1)
3147 d.addCallback(self._count_leases, "one")
3148 d.addCallback(self._assert_leasecount, 1)
3149 d.addCallback(self._count_leases, "mutable")
3150 d.addCallback(self._assert_leasecount, 1)
3152 d.addCallback(self.CHECK, "root", "t=stream-deep-check&add-lease=true")
3153 d.addCallback(_done)
3155 d.addCallback(self._count_leases, "root")
3156 d.addCallback(self._assert_leasecount, 1)
3157 d.addCallback(self._count_leases, "one")
3158 d.addCallback(self._assert_leasecount, 1)
3159 d.addCallback(self._count_leases, "mutable")
3160 d.addCallback(self._assert_leasecount, 1)
3162 d.addCallback(self.CHECK, "root", "t=stream-deep-check&add-lease=true",
3164 d.addCallback(_done)
3166 d.addCallback(self._count_leases, "root")
3167 d.addCallback(self._assert_leasecount, 2)
3168 d.addCallback(self._count_leases, "one")
3169 d.addCallback(self._assert_leasecount, 2)
3170 d.addCallback(self._count_leases, "mutable")
3171 d.addCallback(self._assert_leasecount, 2)
3173 d.addErrback(self.explain_web_error)
3177 def test_exceptions(self):
3178 self.basedir = "web/Grid/exceptions"
3179 self.set_up_grid(num_clients=1, num_servers=2)
3180 c0 = self.g.clients[0]
3183 d = c0.create_empty_dirnode()
3185 self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri()) + "/"
3186 self.fileurls["imaginary"] = self.fileurls["root"] + "imaginary"
3188 d.addCallback(_stash_root)
3189 d.addCallback(lambda ign: c0.upload(upload.Data(DATA, convergence="")))
3191 self.fileurls["1share"] = "uri/" + urllib.quote(ur.uri)
3192 self.delete_shares_numbered(ur.uri, range(1,10))
3194 u = uri.from_string(ur.uri)
3195 u.key = testutil.flip_bit(u.key, 0)
3196 baduri = u.to_string()
3197 self.fileurls["0shares"] = "uri/" + urllib.quote(baduri)
3198 d.addCallback(_stash_bad)
3200 # NotEnoughSharesError should be reported sensibly, with a
3201 # text/plain explanation of the problem, and perhaps some
3202 # information on which shares *could* be found.
3204 d.addCallback(lambda ignored:
3205 self.shouldHTTPError("GET unrecoverable",
3206 410, "Gone", "NotEnoughSharesError",
3207 self.GET, self.fileurls["0shares"]))
3208 def _check_zero_shares(body):
3209 self.failIf("<html>" in body, body)
3210 body = " ".join(body.strip().split())
3211 exp = ("NotEnoughSharesError: no shares could be found. "
3212 "Zero shares usually indicates a corrupt URI, or that "
3213 "no servers were connected, but it might also indicate "
3214 "severe corruption. You should perform a filecheck on "
3215 "this object to learn more.")
3216 self.failUnlessEqual(exp, body)
3217 d.addCallback(_check_zero_shares)
3219 d.addCallback(lambda ignored:
3220 self.shouldHTTPError("GET 1share",
3221 410, "Gone", "NotEnoughSharesError",
3222 self.GET, self.fileurls["1share"]))
3223 def _check_one_share(body):
3224 self.failIf("<html>" in body, body)
3225 body = " ".join(body.strip().split())
3226 exp = ("NotEnoughSharesError: 1 share found, but we need "
3227 "3 to recover the file. This indicates that some "
3228 "servers were unavailable, or that shares have been "
3229 "lost to server departure, hard drive failure, or disk "
3230 "corruption. You should perform a filecheck on "
3231 "this object to learn more.")
3232 self.failUnlessEqual(exp, body)
3233 d.addCallback(_check_one_share)
3235 d.addCallback(lambda ignored:
3236 self.shouldHTTPError("GET imaginary",
3237 404, "Not Found", None,
3238 self.GET, self.fileurls["imaginary"]))
3239 def _missing_child(body):
3240 self.failUnless("No such child: imaginary" in body, body)
3241 d.addCallback(_missing_child)
3243 # attach a webapi child that throws a random error, to test how it
3245 w = c0.getServiceNamed("webish")
3246 w.root.putChild("ERRORBOOM", ErrorBoom())
3248 d.addCallback(lambda ignored:
3249 self.shouldHTTPError("GET errorboom_html",
3250 500, "Internal Server Error", None,
3251 self.GET, "ERRORBOOM"))
3252 def _internal_error_html(body):
3253 # test that a weird exception during a webapi operation with
3254 # Accept:*/* results in a text/html stack trace, while one
3255 # without that Accept: line gets us a text/plain stack trace
3256 self.failUnless("<html>" in body, "expected HTML, not '%s'" % body)
3257 d.addCallback(_internal_error_html)
3259 d.addCallback(lambda ignored:
3260 self.shouldHTTPError("GET errorboom_text",
3261 500, "Internal Server Error", None,
3262 self.GET, "ERRORBOOM",
3263 headers={"accept": ["text/plain"]}))
3264 def _internal_error_text(body):
3265 # test that a weird exception during a webapi operation with
3266 # Accept:*/* results in a text/html stack trace, while one
3267 # without that Accept: line gets us a text/plain stack trace
3268 self.failIf("<html>" in body, body)
3269 self.failUnless(body.startswith("Traceback "), body)
3270 d.addCallback(_internal_error_text)
3272 def _flush_errors(res):
3273 # Trial: please ignore the CompletelyUnhandledError in the logs
3274 self.flushLoggedErrors(CompletelyUnhandledError)
3276 d.addBoth(_flush_errors)
3280 class CompletelyUnhandledError(Exception):
3282 class ErrorBoom(rend.Page):
3283 def beforeRender(self, ctx):
3284 raise CompletelyUnhandledError("whoops")