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
9 from allmydata import interfaces, uri, webish
10 from allmydata.storage.mutable import MutableShareFile
11 from allmydata.storage.immutable import ShareFile
12 from allmydata.immutable import upload, download
13 from allmydata.web import status, common
14 from allmydata.scripts.debug import CorruptShareOptions, corrupt_share
15 from allmydata.util import fileutil, base32
16 from allmydata.util.assertutil import precondition
17 from allmydata.test.common import FakeDirectoryNode, FakeCHKFileNode, \
18 FakeMutableFileNode, create_chk_filenode, WebErrorMixin
19 from allmydata.interfaces import IURI, INewDirectoryURI, \
20 IReadonlyNewDirectoryURI, IFileURI, IMutableFileURI, IMutableFileNode
21 from allmydata.mutable import servermap, publish, retrieve
22 import common_util as testutil
23 from allmydata.test.no_network import GridTestMixin
25 # create a fake uploader/downloader, and a couple of fake dirnodes, then
26 # create a webserver that works against them
28 class FakeIntroducerClient:
29 def get_all_connectors(self):
31 def get_all_connections_for(self, service_name):
33 def get_all_peerids(self):
36 class FakeClient(service.MultiService):
37 nodeid = "fake_nodeid"
38 nickname = "fake_nickname"
39 basedir = "fake_basedir"
40 def get_versions(self):
41 return {'allmydata': "fake",
46 introducer_furl = "None"
47 introducer_client = FakeIntroducerClient()
48 _all_upload_status = [upload.UploadStatus()]
49 _all_download_status = [download.DownloadStatus()]
50 _all_mapupdate_statuses = [servermap.UpdateStatus()]
51 _all_publish_statuses = [publish.PublishStatus()]
52 _all_retrieve_statuses = [retrieve.RetrieveStatus()]
53 convergence = "some random string"
55 def connected_to_introducer(self):
58 def get_nickname_for_peerid(self, peerid):
61 def get_permuted_peers(self, service_name, key):
64 def create_node_from_uri(self, auri):
65 precondition(isinstance(auri, str), auri)
66 u = uri.from_string(auri)
67 if (INewDirectoryURI.providedBy(u)
68 or IReadonlyNewDirectoryURI.providedBy(u)):
69 return FakeDirectoryNode(self).init_from_uri(u)
70 if IFileURI.providedBy(u):
71 return FakeCHKFileNode(u, self)
72 assert IMutableFileURI.providedBy(u), u
73 return FakeMutableFileNode(self).init_from_uri(u)
75 def create_empty_dirnode(self):
76 n = FakeDirectoryNode(self)
78 d.addCallback(lambda res: n)
81 MUTABLE_SIZELIMIT = FakeMutableFileNode.MUTABLE_SIZELIMIT
82 def create_mutable_file(self, contents=""):
83 n = FakeMutableFileNode(self)
84 return n.create(contents)
86 def upload(self, uploadable):
87 d = uploadable.get_size()
88 d.addCallback(lambda size: uploadable.read(size))
91 n = create_chk_filenode(self, data)
92 results = upload.UploadResults()
93 results.uri = n.get_uri()
95 d.addCallback(_got_data)
98 def list_all_upload_statuses(self):
99 return self._all_upload_status
100 def list_all_download_statuses(self):
101 return self._all_download_status
102 def list_all_mapupdate_statuses(self):
103 return self._all_mapupdate_statuses
104 def list_all_publish_statuses(self):
105 return self._all_publish_statuses
106 def list_all_retrieve_statuses(self):
107 return self._all_retrieve_statuses
108 def list_all_helper_statuses(self):
111 class MyGetter(client.HTTPPageGetter):
112 handleStatus_206 = lambda self: self.handleStatus_200()
114 class HTTPClientHEADFactory(client.HTTPClientFactory):
117 def noPage(self, reason):
118 # Twisted-2.5.0 and earlier had a bug, in which they would raise an
119 # exception when the response to a HEAD request had no body (when in
120 # fact they are defined to never have a body). This was fixed in
121 # Twisted-8.0 . To work around this, we catch the
122 # PartialDownloadError and make it disappear.
123 if (reason.check(client.PartialDownloadError)
124 and self.method.upper() == "HEAD"):
127 return client.HTTPClientFactory.noPage(self, reason)
129 class HTTPClientGETFactory(client.HTTPClientFactory):
132 class WebMixin(object):
134 self.s = FakeClient()
135 self.s.startService()
136 self.staticdir = self.mktemp()
137 self.ws = s = webish.WebishServer("0", staticdir=self.staticdir)
138 s.setServiceParent(self.s)
139 self.webish_port = port = s.listener._port.getHost().port
140 self.webish_url = "http://localhost:%d" % port
142 l = [ self.s.create_empty_dirnode() for x in range(6) ]
143 d = defer.DeferredList(l)
145 self.public_root = res[0][1]
146 assert interfaces.IDirectoryNode.providedBy(self.public_root), res
147 self.public_url = "/uri/" + self.public_root.get_uri()
148 self.private_root = res[1][1]
152 self._foo_uri = foo.get_uri()
153 self._foo_readonly_uri = foo.get_readonly_uri()
154 self._foo_verifycap = foo.get_verify_cap().to_string()
155 # NOTE: we ignore the deferred on all set_uri() calls, because we
156 # know the fake nodes do these synchronously
157 self.public_root.set_uri(u"foo", foo.get_uri())
159 self.BAR_CONTENTS, n, self._bar_txt_uri = self.makefile(0)
160 foo.set_uri(u"bar.txt", self._bar_txt_uri)
161 self._bar_txt_verifycap = n.get_verify_cap().to_string()
163 foo.set_uri(u"empty", res[3][1].get_uri())
164 sub_uri = res[4][1].get_uri()
165 self._sub_uri = sub_uri
166 foo.set_uri(u"sub", sub_uri)
167 sub = self.s.create_node_from_uri(sub_uri)
169 _ign, n, blocking_uri = self.makefile(1)
170 foo.set_uri(u"blockingfile", blocking_uri)
172 unicode_filename = u"n\u00fc.txt" # n u-umlaut . t x t
173 # ok, unicode calls it LATIN SMALL LETTER U WITH DIAERESIS but I
174 # still think of it as an umlaut
175 foo.set_uri(unicode_filename, self._bar_txt_uri)
177 _ign, n, baz_file = self.makefile(2)
178 self._baz_file_uri = baz_file
179 sub.set_uri(u"baz.txt", baz_file)
181 _ign, n, self._bad_file_uri = self.makefile(3)
182 # this uri should not be downloadable
183 del FakeCHKFileNode.all_contents[self._bad_file_uri]
186 self.public_root.set_uri(u"reedownlee", rodir.get_readonly_uri())
187 rodir.set_uri(u"nor", baz_file)
192 # public/foo/blockingfile
195 # public/foo/sub/baz.txt
197 # public/reedownlee/nor
198 self.NEWFILE_CONTENTS = "newfile contents\n"
200 return foo.get_metadata_for(u"bar.txt")
202 def _got_metadata(metadata):
203 self._bar_txt_metadata = metadata
204 d.addCallback(_got_metadata)
207 def makefile(self, number):
208 contents = "contents of file %s\n" % number
209 n = create_chk_filenode(self.s, contents)
210 return contents, n, n.get_uri()
213 return self.s.stopService()
215 def failUnlessIsBarDotTxt(self, res):
216 self.failUnlessEqual(res, self.BAR_CONTENTS, res)
218 def failUnlessIsBarJSON(self, res):
219 data = simplejson.loads(res)
220 self.failUnless(isinstance(data, list))
221 self.failUnlessEqual(data[0], u"filenode")
222 self.failUnless(isinstance(data[1], dict))
223 self.failIf(data[1]["mutable"])
224 self.failIf("rw_uri" in data[1]) # immutable
225 self.failUnlessEqual(data[1]["ro_uri"], self._bar_txt_uri)
226 self.failUnlessEqual(data[1]["verify_uri"], self._bar_txt_verifycap)
227 self.failUnlessEqual(data[1]["size"], len(self.BAR_CONTENTS))
229 def failUnlessIsFooJSON(self, res):
230 data = simplejson.loads(res)
231 self.failUnless(isinstance(data, list))
232 self.failUnlessEqual(data[0], "dirnode", res)
233 self.failUnless(isinstance(data[1], dict))
234 self.failUnless(data[1]["mutable"])
235 self.failUnless("rw_uri" in data[1]) # mutable
236 self.failUnlessEqual(data[1]["rw_uri"], self._foo_uri)
237 self.failUnlessEqual(data[1]["ro_uri"], self._foo_readonly_uri)
238 self.failUnlessEqual(data[1]["verify_uri"], self._foo_verifycap)
240 kidnames = sorted([unicode(n) for n in data[1]["children"]])
241 self.failUnlessEqual(kidnames,
242 [u"bar.txt", u"blockingfile", u"empty",
243 u"n\u00fc.txt", u"sub"])
244 kids = dict( [(unicode(name),value)
246 in data[1]["children"].iteritems()] )
247 self.failUnlessEqual(kids[u"sub"][0], "dirnode")
248 self.failUnless("metadata" in kids[u"sub"][1])
249 self.failUnless("ctime" in kids[u"sub"][1]["metadata"])
250 self.failUnless("mtime" in kids[u"sub"][1]["metadata"])
251 self.failUnlessEqual(kids[u"bar.txt"][0], "filenode")
252 self.failUnlessEqual(kids[u"bar.txt"][1]["size"], len(self.BAR_CONTENTS))
253 self.failUnlessEqual(kids[u"bar.txt"][1]["ro_uri"], self._bar_txt_uri)
254 self.failUnlessEqual(kids[u"bar.txt"][1]["verify_uri"],
255 self._bar_txt_verifycap)
256 self.failUnlessEqual(kids[u"bar.txt"][1]["metadata"]["ctime"],
257 self._bar_txt_metadata["ctime"])
258 self.failUnlessEqual(kids[u"n\u00fc.txt"][1]["ro_uri"],
261 def GET(self, urlpath, followRedirect=False, return_response=False,
263 # if return_response=True, this fires with (data, statuscode,
264 # respheaders) instead of just data.
265 assert not isinstance(urlpath, unicode)
266 url = self.webish_url + urlpath
267 factory = HTTPClientGETFactory(url, method="GET",
268 followRedirect=followRedirect, **kwargs)
269 reactor.connectTCP("localhost", self.webish_port, factory)
272 return (data, factory.status, factory.response_headers)
274 d.addCallback(_got_data)
275 return factory.deferred
277 def HEAD(self, urlpath, return_response=False, **kwargs):
278 # this requires some surgery, because twisted.web.client doesn't want
279 # to give us back the response headers.
280 factory = HTTPClientHEADFactory(urlpath, method="HEAD", **kwargs)
281 reactor.connectTCP("localhost", self.webish_port, factory)
284 return (data, factory.status, factory.response_headers)
286 d.addCallback(_got_data)
287 return factory.deferred
289 def PUT(self, urlpath, data, **kwargs):
290 url = self.webish_url + urlpath
291 return client.getPage(url, method="PUT", postdata=data, **kwargs)
293 def DELETE(self, urlpath):
294 url = self.webish_url + urlpath
295 return client.getPage(url, method="DELETE")
297 def POST(self, urlpath, followRedirect=False, **fields):
298 url = self.webish_url + urlpath
299 sepbase = "boogabooga"
303 form.append('Content-Disposition: form-data; name="_charset"')
307 for name, value in fields.iteritems():
308 if isinstance(value, tuple):
309 filename, value = value
310 form.append('Content-Disposition: form-data; name="%s"; '
311 'filename="%s"' % (name, filename.encode("utf-8")))
313 form.append('Content-Disposition: form-data; name="%s"' % name)
315 if isinstance(value, unicode):
316 value = value.encode("utf-8")
319 assert isinstance(value, str)
323 body = "\r\n".join(form) + "\r\n"
324 headers = {"content-type": "multipart/form-data; boundary=%s" % sepbase,
326 return client.getPage(url, method="POST", postdata=body,
327 headers=headers, followRedirect=followRedirect)
329 def shouldFail(self, res, expected_failure, which,
330 substring=None, response_substring=None):
331 if isinstance(res, failure.Failure):
332 res.trap(expected_failure)
334 self.failUnless(substring in str(res),
335 "substring '%s' not in '%s'"
336 % (substring, str(res)))
337 if response_substring:
338 self.failUnless(response_substring in res.value.response,
339 "response substring '%s' not in '%s'"
340 % (response_substring, res.value.response))
342 self.fail("%s was supposed to raise %s, not get '%s'" %
343 (which, expected_failure, res))
345 def shouldFail2(self, expected_failure, which, substring,
347 callable, *args, **kwargs):
348 assert substring is None or isinstance(substring, str)
349 assert response_substring is None or isinstance(response_substring, str)
350 d = defer.maybeDeferred(callable, *args, **kwargs)
352 if isinstance(res, failure.Failure):
353 res.trap(expected_failure)
355 self.failUnless(substring in str(res),
356 "%s: substring '%s' not in '%s'"
357 % (which, substring, str(res)))
358 if response_substring:
359 self.failUnless(response_substring in res.value.response,
360 "%s: response substring '%s' not in '%s'"
362 response_substring, res.value.response))
364 self.fail("%s was supposed to raise %s, not get '%s'" %
365 (which, expected_failure, res))
369 def should404(self, res, which):
370 if isinstance(res, failure.Failure):
371 res.trap(error.Error)
372 self.failUnlessEqual(res.value.status, "404")
374 self.fail("%s was supposed to Error(404), not get '%s'" %
377 def shouldHTTPError(self, res, which, code=None, substring=None,
378 response_substring=None):
379 if isinstance(res, failure.Failure):
380 res.trap(error.Error)
382 self.failUnlessEqual(res.value.status, str(code))
384 self.failUnless(substring in str(res),
385 "substring '%s' not in '%s'"
386 % (substring, str(res)))
387 if response_substring:
388 self.failUnless(response_substring in res.value.response,
389 "response substring '%s' not in '%s'"
390 % (response_substring, res.value.response))
392 self.fail("%s was supposed to Error(%s), not get '%s'" %
395 def shouldHTTPError2(self, which,
396 code=None, substring=None, response_substring=None,
397 callable=None, *args, **kwargs):
398 assert substring is None or isinstance(substring, str)
400 d = defer.maybeDeferred(callable, *args, **kwargs)
401 d.addBoth(self.shouldHTTPError, which,
402 code, substring, response_substring)
406 class Web(WebMixin, testutil.StallMixin, unittest.TestCase):
407 def test_create(self):
410 def test_welcome(self):
413 self.failUnless('Welcome To AllMyData' in res)
414 self.failUnless('Tahoe' in res)
416 self.s.basedir = 'web/test_welcome'
417 fileutil.make_dirs("web/test_welcome")
418 fileutil.make_dirs("web/test_welcome/private")
420 d.addCallback(_check)
423 def test_provisioning(self):
424 d = self.GET("/provisioning/")
426 self.failUnless('Tahoe Provisioning Tool' in res)
427 fields = {'filled': True,
428 "num_users": int(50e3),
429 "files_per_user": 1000,
430 "space_per_user": int(1e9),
431 "sharing_ratio": 1.0,
432 "encoding_parameters": "3-of-10-5",
434 "ownership_mode": "A",
435 "download_rate": 100,
440 return self.POST("/provisioning/", **fields)
442 d.addCallback(_check)
444 self.failUnless('Tahoe Provisioning Tool' in res)
445 self.failUnless("Share space consumed: 167.01TB" in res)
447 fields = {'filled': True,
448 "num_users": int(50e6),
449 "files_per_user": 1000,
450 "space_per_user": int(5e9),
451 "sharing_ratio": 1.0,
452 "encoding_parameters": "25-of-100-50",
453 "num_servers": 30000,
454 "ownership_mode": "E",
455 "drive_failure_model": "U",
457 "download_rate": 1000,
462 return self.POST("/provisioning/", **fields)
463 d.addCallback(_check2)
465 self.failUnless("Share space consumed: huge!" in res)
466 fields = {'filled': True}
467 return self.POST("/provisioning/", **fields)
468 d.addCallback(_check3)
470 self.failUnless("Share space consumed:" in res)
471 d.addCallback(_check4)
474 def test_reliability_tool(self):
476 from allmydata import reliability
477 _hush_pyflakes = reliability
479 raise unittest.SkipTest("reliability tool requires Numeric")
481 d = self.GET("/reliability/")
483 self.failUnless('Tahoe Reliability Tool' in res)
484 fields = {'drive_lifetime': "8Y",
489 "check_period": "1M",
490 "report_period": "3M",
493 return self.POST("/reliability/", **fields)
495 d.addCallback(_check)
497 self.failUnless('Tahoe Reliability Tool' in res)
498 r = r'Probability of loss \(no maintenance\):\s+<span>0.033591'
499 self.failUnless(re.search(r, res), res)
500 d.addCallback(_check2)
503 def test_status(self):
504 dl_num = self.s.list_all_download_statuses()[0].get_counter()
505 ul_num = self.s.list_all_upload_statuses()[0].get_counter()
506 mu_num = self.s.list_all_mapupdate_statuses()[0].get_counter()
507 pub_num = self.s.list_all_publish_statuses()[0].get_counter()
508 ret_num = self.s.list_all_retrieve_statuses()[0].get_counter()
509 d = self.GET("/status", followRedirect=True)
511 self.failUnless('Upload and Download Status' in res, res)
512 self.failUnless('"down-%d"' % dl_num in res, res)
513 self.failUnless('"up-%d"' % ul_num in res, res)
514 self.failUnless('"mapupdate-%d"' % mu_num in res, res)
515 self.failUnless('"publish-%d"' % pub_num in res, res)
516 self.failUnless('"retrieve-%d"' % ret_num in res, res)
517 d.addCallback(_check)
518 d.addCallback(lambda res: self.GET("/status/?t=json"))
519 def _check_json(res):
520 data = simplejson.loads(res)
521 self.failUnless(isinstance(data, dict))
522 active = data["active"]
523 # TODO: test more. We need a way to fake an active operation
525 d.addCallback(_check_json)
527 d.addCallback(lambda res: self.GET("/status/down-%d" % dl_num))
529 self.failUnless("File Download Status" in res, res)
530 d.addCallback(_check_dl)
531 d.addCallback(lambda res: self.GET("/status/up-%d" % ul_num))
533 self.failUnless("File Upload Status" in res, res)
534 d.addCallback(_check_ul)
535 d.addCallback(lambda res: self.GET("/status/mapupdate-%d" % mu_num))
536 def _check_mapupdate(res):
537 self.failUnless("Mutable File Servermap Update Status" in res, res)
538 d.addCallback(_check_mapupdate)
539 d.addCallback(lambda res: self.GET("/status/publish-%d" % pub_num))
540 def _check_publish(res):
541 self.failUnless("Mutable File Publish Status" in res, res)
542 d.addCallback(_check_publish)
543 d.addCallback(lambda res: self.GET("/status/retrieve-%d" % ret_num))
544 def _check_retrieve(res):
545 self.failUnless("Mutable File Retrieve Status" in res, res)
546 d.addCallback(_check_retrieve)
550 def test_status_numbers(self):
551 drrm = status.DownloadResultsRendererMixin()
552 self.failUnlessEqual(drrm.render_time(None, None), "")
553 self.failUnlessEqual(drrm.render_time(None, 2.5), "2.50s")
554 self.failUnlessEqual(drrm.render_time(None, 0.25), "250ms")
555 self.failUnlessEqual(drrm.render_time(None, 0.0021), "2.1ms")
556 self.failUnlessEqual(drrm.render_time(None, 0.000123), "123us")
557 self.failUnlessEqual(drrm.render_rate(None, None), "")
558 self.failUnlessEqual(drrm.render_rate(None, 2500000), "2.50MBps")
559 self.failUnlessEqual(drrm.render_rate(None, 30100), "30.1kBps")
560 self.failUnlessEqual(drrm.render_rate(None, 123), "123Bps")
562 urrm = status.UploadResultsRendererMixin()
563 self.failUnlessEqual(urrm.render_time(None, None), "")
564 self.failUnlessEqual(urrm.render_time(None, 2.5), "2.50s")
565 self.failUnlessEqual(urrm.render_time(None, 0.25), "250ms")
566 self.failUnlessEqual(urrm.render_time(None, 0.0021), "2.1ms")
567 self.failUnlessEqual(urrm.render_time(None, 0.000123), "123us")
568 self.failUnlessEqual(urrm.render_rate(None, None), "")
569 self.failUnlessEqual(urrm.render_rate(None, 2500000), "2.50MBps")
570 self.failUnlessEqual(urrm.render_rate(None, 30100), "30.1kBps")
571 self.failUnlessEqual(urrm.render_rate(None, 123), "123Bps")
573 def test_GET_FILEURL(self):
574 d = self.GET(self.public_url + "/foo/bar.txt")
575 d.addCallback(self.failUnlessIsBarDotTxt)
578 def test_GET_FILEURL_range(self):
579 headers = {"range": "bytes=1-10"}
580 d = self.GET(self.public_url + "/foo/bar.txt", headers=headers,
581 return_response=True)
582 def _got((res, status, headers)):
583 self.failUnlessEqual(int(status), 206)
584 self.failUnless(headers.has_key("content-range"))
585 self.failUnlessEqual(headers["content-range"][0],
586 "bytes 1-10/%d" % len(self.BAR_CONTENTS))
587 self.failUnlessEqual(res, self.BAR_CONTENTS[1:11])
591 def test_GET_FILEURL_partial_range(self):
592 headers = {"range": "bytes=5-"}
593 length = len(self.BAR_CONTENTS)
594 d = self.GET(self.public_url + "/foo/bar.txt", headers=headers,
595 return_response=True)
596 def _got((res, status, headers)):
597 self.failUnlessEqual(int(status), 206)
598 self.failUnless(headers.has_key("content-range"))
599 self.failUnlessEqual(headers["content-range"][0],
600 "bytes 5-%d/%d" % (length-1, length))
601 self.failUnlessEqual(res, self.BAR_CONTENTS[5:])
605 def test_HEAD_FILEURL_range(self):
606 headers = {"range": "bytes=1-10"}
607 d = self.HEAD(self.public_url + "/foo/bar.txt", headers=headers,
608 return_response=True)
609 def _got((res, status, headers)):
610 self.failUnlessEqual(res, "")
611 self.failUnlessEqual(int(status), 206)
612 self.failUnless(headers.has_key("content-range"))
613 self.failUnlessEqual(headers["content-range"][0],
614 "bytes 1-10/%d" % len(self.BAR_CONTENTS))
618 def test_HEAD_FILEURL_partial_range(self):
619 headers = {"range": "bytes=5-"}
620 length = len(self.BAR_CONTENTS)
621 d = self.HEAD(self.public_url + "/foo/bar.txt", headers=headers,
622 return_response=True)
623 def _got((res, status, headers)):
624 self.failUnlessEqual(int(status), 206)
625 self.failUnless(headers.has_key("content-range"))
626 self.failUnlessEqual(headers["content-range"][0],
627 "bytes 5-%d/%d" % (length-1, length))
631 def test_GET_FILEURL_range_bad(self):
632 headers = {"range": "BOGUS=fizbop-quarnak"}
633 d = self.shouldFail2(error.Error, "test_GET_FILEURL_range_bad",
635 "Syntactically invalid http range header",
636 self.GET, self.public_url + "/foo/bar.txt",
640 def test_HEAD_FILEURL(self):
641 d = self.HEAD(self.public_url + "/foo/bar.txt", return_response=True)
642 def _got((res, status, headers)):
643 self.failUnlessEqual(res, "")
644 self.failUnlessEqual(headers["content-length"][0],
645 str(len(self.BAR_CONTENTS)))
646 self.failUnlessEqual(headers["content-type"], ["text/plain"])
650 def test_GET_FILEURL_named(self):
651 base = "/file/%s" % urllib.quote(self._bar_txt_uri)
652 base2 = "/named/%s" % urllib.quote(self._bar_txt_uri)
653 d = self.GET(base + "/@@name=/blah.txt")
654 d.addCallback(self.failUnlessIsBarDotTxt)
655 d.addCallback(lambda res: self.GET(base + "/blah.txt"))
656 d.addCallback(self.failUnlessIsBarDotTxt)
657 d.addCallback(lambda res: self.GET(base + "/ignore/lots/blah.txt"))
658 d.addCallback(self.failUnlessIsBarDotTxt)
659 d.addCallback(lambda res: self.GET(base2 + "/@@name=/blah.txt"))
660 d.addCallback(self.failUnlessIsBarDotTxt)
661 save_url = base + "?save=true&filename=blah.txt"
662 d.addCallback(lambda res: self.GET(save_url))
663 d.addCallback(self.failUnlessIsBarDotTxt) # TODO: check headers
664 u_filename = u"n\u00e9wer.txt" # n e-acute w e r . t x t
665 u_fn_e = urllib.quote(u_filename.encode("utf-8"))
666 u_url = base + "?save=true&filename=" + u_fn_e
667 d.addCallback(lambda res: self.GET(u_url))
668 d.addCallback(self.failUnlessIsBarDotTxt) # TODO: check headers
671 def test_PUT_FILEURL_named_bad(self):
672 base = "/file/%s" % urllib.quote(self._bar_txt_uri)
673 d = self.shouldFail2(error.Error, "test_PUT_FILEURL_named_bad",
675 "/file can only be used with GET or HEAD",
676 self.PUT, base + "/@@name=/blah.txt", "")
679 def test_GET_DIRURL_named_bad(self):
680 base = "/file/%s" % urllib.quote(self._foo_uri)
681 d = self.shouldFail2(error.Error, "test_PUT_DIRURL_named_bad",
684 self.GET, base + "/@@name=/blah.txt")
687 def test_GET_slash_file_bad(self):
688 d = self.shouldFail2(error.Error, "test_GET_slash_file_bad",
690 "/file must be followed by a file-cap and a name",
694 def test_GET_unhandled_URI_named(self):
695 contents, n, newuri = self.makefile(12)
696 verifier_cap = n.get_verify_cap().to_string()
697 base = "/file/%s" % urllib.quote(verifier_cap)
698 # client.create_node_from_uri() can't handle verify-caps
699 d = self.shouldFail2(error.Error, "GET_unhandled_URI_named",
701 "is not a valid file- or directory- cap",
705 def test_GET_unhandled_URI(self):
706 contents, n, newuri = self.makefile(12)
707 verifier_cap = n.get_verify_cap().to_string()
708 base = "/uri/%s" % urllib.quote(verifier_cap)
709 # client.create_node_from_uri() can't handle verify-caps
710 d = self.shouldFail2(error.Error, "test_GET_unhandled_URI",
712 "is not a valid file- or directory- cap",
716 def test_GET_FILE_URI(self):
717 base = "/uri/%s" % urllib.quote(self._bar_txt_uri)
719 d.addCallback(self.failUnlessIsBarDotTxt)
722 def test_GET_FILE_URI_badchild(self):
723 base = "/uri/%s/boguschild" % urllib.quote(self._bar_txt_uri)
724 errmsg = "Files have no children, certainly not named 'boguschild'"
725 d = self.shouldFail2(error.Error, "test_GET_FILE_URI_badchild",
726 "400 Bad Request", errmsg,
730 def test_PUT_FILE_URI_badchild(self):
731 base = "/uri/%s/boguschild" % urllib.quote(self._bar_txt_uri)
732 errmsg = "Cannot create directory 'boguschild', because its parent is a file, not a directory"
733 d = self.shouldFail2(error.Error, "test_GET_FILE_URI_badchild",
734 "400 Bad Request", errmsg,
738 def test_GET_FILEURL_save(self):
739 d = self.GET(self.public_url + "/foo/bar.txt?filename=bar.txt&save=true")
740 # TODO: look at the headers, expect a Content-Disposition: attachment
742 d.addCallback(self.failUnlessIsBarDotTxt)
745 def test_GET_FILEURL_missing(self):
746 d = self.GET(self.public_url + "/foo/missing")
747 d.addBoth(self.should404, "test_GET_FILEURL_missing")
750 def test_PUT_NEWFILEURL(self):
751 d = self.PUT(self.public_url + "/foo/new.txt", self.NEWFILE_CONTENTS)
752 # TODO: we lose the response code, so we can't check this
753 #self.failUnlessEqual(responsecode, 201)
754 d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"new.txt")
755 d.addCallback(lambda res:
756 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
757 self.NEWFILE_CONTENTS))
760 def test_PUT_NEWFILEURL_range_bad(self):
761 headers = {"content-range": "bytes 1-10/%d" % len(self.NEWFILE_CONTENTS)}
762 target = self.public_url + "/foo/new.txt"
763 d = self.shouldFail2(error.Error, "test_PUT_NEWFILEURL_range_bad",
764 "501 Not Implemented",
765 "Content-Range in PUT not yet supported",
766 # (and certainly not for immutable files)
767 self.PUT, target, self.NEWFILE_CONTENTS[1:11],
769 d.addCallback(lambda res:
770 self.failIfNodeHasChild(self._foo_node, u"new.txt"))
773 def test_PUT_NEWFILEURL_mutable(self):
774 d = self.PUT(self.public_url + "/foo/new.txt?mutable=true",
775 self.NEWFILE_CONTENTS)
776 # TODO: we lose the response code, so we can't check this
777 #self.failUnlessEqual(responsecode, 201)
779 u = uri.from_string_mutable_filenode(res)
780 self.failUnless(u.is_mutable())
781 self.failIf(u.is_readonly())
783 d.addCallback(_check_uri)
784 d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"new.txt")
785 d.addCallback(lambda res:
786 self.failUnlessMutableChildContentsAre(self._foo_node,
788 self.NEWFILE_CONTENTS))
791 def test_PUT_NEWFILEURL_mutable_toobig(self):
792 d = self.shouldFail2(error.Error, "test_PUT_NEWFILEURL_mutable_toobig",
793 "413 Request Entity Too Large",
794 "SDMF is limited to one segment, and 10001 > 10000",
796 self.public_url + "/foo/new.txt?mutable=true",
797 "b" * (self.s.MUTABLE_SIZELIMIT+1))
800 def test_PUT_NEWFILEURL_replace(self):
801 d = self.PUT(self.public_url + "/foo/bar.txt", self.NEWFILE_CONTENTS)
802 # TODO: we lose the response code, so we can't check this
803 #self.failUnlessEqual(responsecode, 200)
804 d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"bar.txt")
805 d.addCallback(lambda res:
806 self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
807 self.NEWFILE_CONTENTS))
810 def test_PUT_NEWFILEURL_bad_t(self):
811 d = self.shouldFail2(error.Error, "PUT_bad_t", "400 Bad Request",
812 "PUT to a file: bad t=bogus",
813 self.PUT, self.public_url + "/foo/bar.txt?t=bogus",
817 def test_PUT_NEWFILEURL_no_replace(self):
818 d = self.PUT(self.public_url + "/foo/bar.txt?replace=false",
819 self.NEWFILE_CONTENTS)
820 d.addBoth(self.shouldFail, error.Error, "PUT_NEWFILEURL_no_replace",
822 "There was already a child by that name, and you asked me "
826 def test_PUT_NEWFILEURL_mkdirs(self):
827 d = self.PUT(self.public_url + "/foo/newdir/new.txt", self.NEWFILE_CONTENTS)
829 d.addCallback(self.failUnlessURIMatchesChild, fn, u"newdir/new.txt")
830 d.addCallback(lambda res: self.failIfNodeHasChild(fn, u"new.txt"))
831 d.addCallback(lambda res: self.failUnlessNodeHasChild(fn, u"newdir"))
832 d.addCallback(lambda res:
833 self.failUnlessChildContentsAre(fn, u"newdir/new.txt",
834 self.NEWFILE_CONTENTS))
837 def test_PUT_NEWFILEURL_blocked(self):
838 d = self.PUT(self.public_url + "/foo/blockingfile/new.txt",
839 self.NEWFILE_CONTENTS)
840 d.addBoth(self.shouldFail, error.Error, "PUT_NEWFILEURL_blocked",
842 "Unable to create directory 'blockingfile': a file was in the way")
845 def test_DELETE_FILEURL(self):
846 d = self.DELETE(self.public_url + "/foo/bar.txt")
847 d.addCallback(lambda res:
848 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
851 def test_DELETE_FILEURL_missing(self):
852 d = self.DELETE(self.public_url + "/foo/missing")
853 d.addBoth(self.should404, "test_DELETE_FILEURL_missing")
856 def test_DELETE_FILEURL_missing2(self):
857 d = self.DELETE(self.public_url + "/missing/missing")
858 d.addBoth(self.should404, "test_DELETE_FILEURL_missing2")
861 def test_GET_FILEURL_json(self):
862 # twisted.web.http.parse_qs ignores any query args without an '=', so
863 # I can't do "GET /path?json", I have to do "GET /path/t=json"
864 # instead. This may make it tricky to emulate the S3 interface
866 d = self.GET(self.public_url + "/foo/bar.txt?t=json")
867 d.addCallback(self.failUnlessIsBarJSON)
870 def test_GET_FILEURL_json_missing(self):
871 d = self.GET(self.public_url + "/foo/missing?json")
872 d.addBoth(self.should404, "test_GET_FILEURL_json_missing")
875 def test_GET_FILEURL_uri(self):
876 d = self.GET(self.public_url + "/foo/bar.txt?t=uri")
878 self.failUnlessEqual(res, self._bar_txt_uri)
879 d.addCallback(_check)
880 d.addCallback(lambda res:
881 self.GET(self.public_url + "/foo/bar.txt?t=readonly-uri"))
883 # for now, for files, uris and readonly-uris are the same
884 self.failUnlessEqual(res, self._bar_txt_uri)
885 d.addCallback(_check2)
888 def test_GET_FILEURL_badtype(self):
889 d = self.shouldHTTPError2("GET t=bogus", 400, "Bad Request",
892 self.public_url + "/foo/bar.txt?t=bogus")
895 def test_GET_FILEURL_uri_missing(self):
896 d = self.GET(self.public_url + "/foo/missing?t=uri")
897 d.addBoth(self.should404, "test_GET_FILEURL_uri_missing")
900 def test_GET_DIRURL(self):
901 # the addSlash means we get a redirect here
902 # from /uri/$URI/foo/ , we need ../../../ to get back to the root
904 d = self.GET(self.public_url + "/foo", followRedirect=True)
906 self.failUnless(('<a href="%s">Return to Welcome page' % ROOT)
908 # the FILE reference points to a URI, but it should end in bar.txt
909 bar_url = ("%s/file/%s/@@named=/bar.txt" %
910 (ROOT, urllib.quote(self._bar_txt_uri)))
911 get_bar = "".join([r'<td>',
912 r'<a href="%s">bar.txt</a>' % bar_url,
915 r'\s+<td>%d</td>' % len(self.BAR_CONTENTS),
917 self.failUnless(re.search(get_bar, res), res)
918 for line in res.split("\n"):
919 # find the line that contains the delete button for bar.txt
920 if ("form action" in line and
921 'value="delete"' in line and
922 'value="bar.txt"' in line):
923 # the form target should use a relative URL
924 foo_url = urllib.quote("%s/uri/%s/" % (ROOT, self._foo_uri))
925 self.failUnless(('action="%s"' % foo_url) in line, line)
926 # and the when_done= should too
927 #done_url = urllib.quote(???)
928 #self.failUnless(('name="when_done" value="%s"' % done_url)
932 self.fail("unable to find delete-bar.txt line", res)
934 # the DIR reference just points to a URI
935 sub_url = ("%s/uri/%s/" % (ROOT, urllib.quote(self._sub_uri)))
936 get_sub = ((r'<td><a href="%s">sub</a></td>' % sub_url)
937 + r'\s+<td>DIR</td>')
938 self.failUnless(re.search(get_sub, res), res)
939 d.addCallback(_check)
941 # look at a directory which is readonly
942 d.addCallback(lambda res:
943 self.GET(self.public_url + "/reedownlee", followRedirect=True))
945 self.failUnless("(readonly)" in res, res)
946 self.failIf("Upload a file" in res, res)
947 d.addCallback(_check2)
949 # and at a directory that contains a readonly directory
950 d.addCallback(lambda res:
951 self.GET(self.public_url, followRedirect=True))
953 self.failUnless(re.search(r'<td><a href="[\.\/]+/uri/URI%3ADIR2-RO%3A[^"]+">reedownlee</a>'
954 '</td>\s+<td>DIR-RO</td>', res))
955 d.addCallback(_check3)
959 def test_GET_DIRURL_badtype(self):
960 d = self.shouldHTTPError2("test_GET_DIRURL_badtype",
964 self.public_url + "/foo?t=bogus")
967 def test_GET_DIRURL_json(self):
968 d = self.GET(self.public_url + "/foo?t=json")
969 d.addCallback(self.failUnlessIsFooJSON)
973 def test_POST_DIRURL_manifest_no_ophandle(self):
974 d = self.shouldFail2(error.Error,
975 "test_POST_DIRURL_manifest_no_ophandle",
977 "slow operation requires ophandle=",
978 self.POST, self.public_url, t="start-manifest")
981 def test_POST_DIRURL_manifest(self):
982 d = defer.succeed(None)
983 def getman(ignored, output):
984 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=125",
986 d.addCallback(self.wait_for_operation, "125")
987 d.addCallback(self.get_operation_results, "125", output)
989 d.addCallback(getman, None)
990 def _got_html(manifest):
991 self.failUnless("Manifest of SI=" in manifest)
992 self.failUnless("<td>sub</td>" in manifest)
993 self.failUnless(self._sub_uri in manifest)
994 self.failUnless("<td>sub/baz.txt</td>" in manifest)
995 d.addCallback(_got_html)
997 # both t=status and unadorned GET should be identical
998 d.addCallback(lambda res: self.GET("/operations/125"))
999 d.addCallback(_got_html)
1001 d.addCallback(getman, "html")
1002 d.addCallback(_got_html)
1003 d.addCallback(getman, "text")
1004 def _got_text(manifest):
1005 self.failUnless("\nsub " + self._sub_uri + "\n" in manifest)
1006 self.failUnless("\nsub/baz.txt URI:CHK:" in manifest)
1007 d.addCallback(_got_text)
1008 d.addCallback(getman, "JSON")
1010 data = res["manifest"]
1012 for (path_list, cap) in data:
1013 got[tuple(path_list)] = cap
1014 self.failUnlessEqual(got[(u"sub",)], self._sub_uri)
1015 self.failUnless((u"sub",u"baz.txt") in got)
1016 self.failUnless("finished" in res)
1017 self.failUnless("origin" in res)
1018 self.failUnless("storage-index" in res)
1019 self.failUnless("verifycaps" in res)
1020 self.failUnless("stats" in res)
1021 d.addCallback(_got_json)
1024 def test_POST_DIRURL_deepsize_no_ophandle(self):
1025 d = self.shouldFail2(error.Error,
1026 "test_POST_DIRURL_deepsize_no_ophandle",
1028 "slow operation requires ophandle=",
1029 self.POST, self.public_url, t="start-deep-size")
1032 def test_POST_DIRURL_deepsize(self):
1033 d = self.POST(self.public_url + "/foo/?t=start-deep-size&ophandle=126",
1034 followRedirect=True)
1035 d.addCallback(self.wait_for_operation, "126")
1036 d.addCallback(self.get_operation_results, "126", "json")
1037 def _got_json(data):
1038 self.failUnlessEqual(data["finished"], True)
1040 self.failUnless(size > 1000)
1041 d.addCallback(_got_json)
1042 d.addCallback(self.get_operation_results, "126", "text")
1044 mo = re.search(r'^size: (\d+)$', res, re.M)
1045 self.failUnless(mo, res)
1046 size = int(mo.group(1))
1047 # with directories, the size varies.
1048 self.failUnless(size > 1000)
1049 d.addCallback(_got_text)
1052 def test_POST_DIRURL_deepstats_no_ophandle(self):
1053 d = self.shouldFail2(error.Error,
1054 "test_POST_DIRURL_deepstats_no_ophandle",
1056 "slow operation requires ophandle=",
1057 self.POST, self.public_url, t="start-deep-stats")
1060 def test_POST_DIRURL_deepstats(self):
1061 d = self.POST(self.public_url + "/foo/?t=start-deep-stats&ophandle=127",
1062 followRedirect=True)
1063 d.addCallback(self.wait_for_operation, "127")
1064 d.addCallback(self.get_operation_results, "127", "json")
1065 def _got_json(stats):
1066 expected = {"count-immutable-files": 3,
1067 "count-mutable-files": 0,
1068 "count-literal-files": 0,
1070 "count-directories": 3,
1071 "size-immutable-files": 57,
1072 "size-literal-files": 0,
1073 #"size-directories": 1912, # varies
1074 #"largest-directory": 1590,
1075 "largest-directory-children": 5,
1076 "largest-immutable-file": 19,
1078 for k,v in expected.iteritems():
1079 self.failUnlessEqual(stats[k], v,
1080 "stats[%s] was %s, not %s" %
1082 self.failUnlessEqual(stats["size-files-histogram"],
1084 d.addCallback(_got_json)
1087 def test_POST_DIRURL_stream_manifest(self):
1088 d = self.POST(self.public_url + "/foo/?t=stream-manifest")
1090 self.failUnless(res.endswith("\n"))
1091 units = [simplejson.loads(t) for t in res[:-1].split("\n")]
1092 self.failUnlessEqual(len(units), 7)
1093 self.failUnlessEqual(units[-1]["type"], "stats")
1095 self.failUnlessEqual(first["path"], [])
1096 self.failUnlessEqual(first["cap"], self._foo_uri)
1097 self.failUnlessEqual(first["type"], "directory")
1098 baz = [u for u in units[:-1] if u["cap"] == self._baz_file_uri][0]
1099 self.failUnlessEqual(baz["path"], ["sub", "baz.txt"])
1100 self.failIfEqual(baz["storage-index"], None)
1101 self.failIfEqual(baz["verifycap"], None)
1102 self.failIfEqual(baz["repaircap"], None)
1104 d.addCallback(_check)
1107 def test_GET_DIRURL_uri(self):
1108 d = self.GET(self.public_url + "/foo?t=uri")
1110 self.failUnlessEqual(res, self._foo_uri)
1111 d.addCallback(_check)
1114 def test_GET_DIRURL_readonly_uri(self):
1115 d = self.GET(self.public_url + "/foo?t=readonly-uri")
1117 self.failUnlessEqual(res, self._foo_readonly_uri)
1118 d.addCallback(_check)
1121 def test_PUT_NEWDIRURL(self):
1122 d = self.PUT(self.public_url + "/foo/newdir?t=mkdir", "")
1123 d.addCallback(lambda res:
1124 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1125 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1126 d.addCallback(self.failUnlessNodeKeysAre, [])
1129 def test_PUT_NEWDIRURL_exists(self):
1130 d = self.PUT(self.public_url + "/foo/sub?t=mkdir", "")
1131 d.addCallback(lambda res:
1132 self.failUnlessNodeHasChild(self._foo_node, u"sub"))
1133 d.addCallback(lambda res: self._foo_node.get(u"sub"))
1134 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
1137 def test_PUT_NEWDIRURL_blocked(self):
1138 d = self.shouldFail2(error.Error, "PUT_NEWDIRURL_blocked",
1139 "409 Conflict", "Unable to create directory 'bar.txt': a file was in the way",
1141 self.public_url + "/foo/bar.txt/sub?t=mkdir", "")
1142 d.addCallback(lambda res:
1143 self.failUnlessNodeHasChild(self._foo_node, u"sub"))
1144 d.addCallback(lambda res: self._foo_node.get(u"sub"))
1145 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
1148 def test_PUT_NEWDIRURL_mkdir_p(self):
1149 d = defer.succeed(None)
1150 d.addCallback(lambda res: self.POST(self.public_url + "/foo", t='mkdir', name='mkp'))
1151 d.addCallback(lambda res: self.failUnlessNodeHasChild(self._foo_node, u"mkp"))
1152 d.addCallback(lambda res: self._foo_node.get(u"mkp"))
1153 def mkdir_p(mkpnode):
1154 url = '/uri/%s?t=mkdir-p&path=/sub1/sub2' % urllib.quote(mkpnode.get_uri())
1156 def made_subsub(ssuri):
1157 d = self._foo_node.get_child_at_path(u"mkp/sub1/sub2")
1158 d.addCallback(lambda ssnode: self.failUnlessEqual(ssnode.get_uri(), ssuri))
1160 d.addCallback(lambda uri2: self.failUnlessEqual(uri2, ssuri))
1162 d.addCallback(made_subsub)
1164 d.addCallback(mkdir_p)
1167 def test_PUT_NEWDIRURL_mkdirs(self):
1168 d = self.PUT(self.public_url + "/foo/subdir/newdir?t=mkdir", "")
1169 d.addCallback(lambda res:
1170 self.failIfNodeHasChild(self._foo_node, u"newdir"))
1171 d.addCallback(lambda res:
1172 self.failUnlessNodeHasChild(self._foo_node, u"subdir"))
1173 d.addCallback(lambda res:
1174 self._foo_node.get_child_at_path(u"subdir/newdir"))
1175 d.addCallback(self.failUnlessNodeKeysAre, [])
1178 def test_DELETE_DIRURL(self):
1179 d = self.DELETE(self.public_url + "/foo")
1180 d.addCallback(lambda res:
1181 self.failIfNodeHasChild(self.public_root, u"foo"))
1184 def test_DELETE_DIRURL_missing(self):
1185 d = self.DELETE(self.public_url + "/foo/missing")
1186 d.addBoth(self.should404, "test_DELETE_DIRURL_missing")
1187 d.addCallback(lambda res:
1188 self.failUnlessNodeHasChild(self.public_root, u"foo"))
1191 def test_DELETE_DIRURL_missing2(self):
1192 d = self.DELETE(self.public_url + "/missing")
1193 d.addBoth(self.should404, "test_DELETE_DIRURL_missing2")
1196 def dump_root(self):
1198 w = webish.DirnodeWalkerMixin()
1199 def visitor(childpath, childnode, metadata):
1201 d = w.walk(self.public_root, visitor)
1204 def failUnlessNodeKeysAre(self, node, expected_keys):
1205 for k in expected_keys:
1206 assert isinstance(k, unicode)
1208 def _check(children):
1209 self.failUnlessEqual(sorted(children.keys()), sorted(expected_keys))
1210 d.addCallback(_check)
1212 def failUnlessNodeHasChild(self, node, name):
1213 assert isinstance(name, unicode)
1215 def _check(children):
1216 self.failUnless(name in children)
1217 d.addCallback(_check)
1219 def failIfNodeHasChild(self, node, name):
1220 assert isinstance(name, unicode)
1222 def _check(children):
1223 self.failIf(name in children)
1224 d.addCallback(_check)
1227 def failUnlessChildContentsAre(self, node, name, expected_contents):
1228 assert isinstance(name, unicode)
1229 d = node.get_child_at_path(name)
1230 d.addCallback(lambda node: node.download_to_data())
1231 def _check(contents):
1232 self.failUnlessEqual(contents, expected_contents)
1233 d.addCallback(_check)
1236 def failUnlessMutableChildContentsAre(self, node, name, expected_contents):
1237 assert isinstance(name, unicode)
1238 d = node.get_child_at_path(name)
1239 d.addCallback(lambda node: node.download_best_version())
1240 def _check(contents):
1241 self.failUnlessEqual(contents, expected_contents)
1242 d.addCallback(_check)
1245 def failUnlessChildURIIs(self, node, name, expected_uri):
1246 assert isinstance(name, unicode)
1247 d = node.get_child_at_path(name)
1249 self.failUnlessEqual(child.get_uri(), expected_uri.strip())
1250 d.addCallback(_check)
1253 def failUnlessURIMatchesChild(self, got_uri, node, name):
1254 assert isinstance(name, unicode)
1255 d = node.get_child_at_path(name)
1257 self.failUnlessEqual(got_uri.strip(), child.get_uri())
1258 d.addCallback(_check)
1261 def failUnlessCHKURIHasContents(self, got_uri, contents):
1262 self.failUnless(FakeCHKFileNode.all_contents[got_uri] == contents)
1264 def test_POST_upload(self):
1265 d = self.POST(self.public_url + "/foo", t="upload",
1266 file=("new.txt", self.NEWFILE_CONTENTS))
1268 d.addCallback(self.failUnlessURIMatchesChild, fn, u"new.txt")
1269 d.addCallback(lambda res:
1270 self.failUnlessChildContentsAre(fn, u"new.txt",
1271 self.NEWFILE_CONTENTS))
1274 def test_POST_upload_unicode(self):
1275 filename = u"n\u00e9wer.txt" # n e-acute w e r . t x t
1276 d = self.POST(self.public_url + "/foo", t="upload",
1277 file=(filename, self.NEWFILE_CONTENTS))
1279 d.addCallback(self.failUnlessURIMatchesChild, fn, filename)
1280 d.addCallback(lambda res:
1281 self.failUnlessChildContentsAre(fn, filename,
1282 self.NEWFILE_CONTENTS))
1283 target_url = self.public_url + "/foo/" + filename.encode("utf-8")
1284 d.addCallback(lambda res: self.GET(target_url))
1285 d.addCallback(lambda contents: self.failUnlessEqual(contents,
1286 self.NEWFILE_CONTENTS,
1290 def test_POST_upload_unicode_named(self):
1291 filename = u"n\u00e9wer.txt" # n e-acute w e r . t x t
1292 d = self.POST(self.public_url + "/foo", t="upload",
1294 file=("overridden", self.NEWFILE_CONTENTS))
1296 d.addCallback(self.failUnlessURIMatchesChild, fn, filename)
1297 d.addCallback(lambda res:
1298 self.failUnlessChildContentsAre(fn, filename,
1299 self.NEWFILE_CONTENTS))
1300 target_url = self.public_url + "/foo/" + filename.encode("utf-8")
1301 d.addCallback(lambda res: self.GET(target_url))
1302 d.addCallback(lambda contents: self.failUnlessEqual(contents,
1303 self.NEWFILE_CONTENTS,
1307 def test_POST_upload_no_link(self):
1308 d = self.POST("/uri", t="upload",
1309 file=("new.txt", self.NEWFILE_CONTENTS))
1310 def _check_upload_results(page):
1311 # this should be a page which describes the results of the upload
1312 # that just finished.
1313 self.failUnless("Upload Results:" in page)
1314 self.failUnless("URI:" in page)
1315 uri_re = re.compile("URI: <tt><span>(.*)</span>")
1316 mo = uri_re.search(page)
1317 self.failUnless(mo, page)
1318 new_uri = mo.group(1)
1320 d.addCallback(_check_upload_results)
1321 d.addCallback(self.failUnlessCHKURIHasContents, self.NEWFILE_CONTENTS)
1324 def test_POST_upload_no_link_whendone(self):
1325 d = self.POST("/uri", t="upload", when_done="/",
1326 file=("new.txt", self.NEWFILE_CONTENTS))
1327 d.addBoth(self.shouldRedirect, "/")
1330 def shouldRedirect2(self, which, checker, callable, *args, **kwargs):
1331 d = defer.maybeDeferred(callable, *args, **kwargs)
1333 if isinstance(res, failure.Failure):
1334 res.trap(error.PageRedirect)
1335 statuscode = res.value.status
1336 target = res.value.location
1337 return checker(statuscode, target)
1338 self.fail("%s: callable was supposed to redirect, not return '%s'"
1343 def test_POST_upload_no_link_whendone_results(self):
1344 def check(statuscode, target):
1345 self.failUnlessEqual(statuscode, str(http.FOUND))
1346 self.failUnless(target.startswith(self.webish_url), target)
1347 return client.getPage(target, method="GET")
1348 d = self.shouldRedirect2("test_POST_upload_no_link_whendone_results",
1350 self.POST, "/uri", t="upload",
1351 when_done="/uri/%(uri)s",
1352 file=("new.txt", self.NEWFILE_CONTENTS))
1353 d.addCallback(lambda res:
1354 self.failUnlessEqual(res, self.NEWFILE_CONTENTS))
1357 def test_POST_upload_no_link_mutable(self):
1358 d = self.POST("/uri", t="upload", mutable="true",
1359 file=("new.txt", self.NEWFILE_CONTENTS))
1360 def _check(new_uri):
1361 new_uri = new_uri.strip()
1362 self.new_uri = new_uri
1364 self.failUnless(IMutableFileURI.providedBy(u))
1365 self.failUnless(u.storage_index in FakeMutableFileNode.all_contents)
1366 n = self.s.create_node_from_uri(new_uri)
1367 return n.download_best_version()
1368 d.addCallback(_check)
1370 self.failUnlessEqual(data, self.NEWFILE_CONTENTS)
1371 return self.GET("/uri/%s" % urllib.quote(self.new_uri))
1372 d.addCallback(_check2)
1374 self.failUnlessEqual(data, self.NEWFILE_CONTENTS)
1375 return self.GET("/file/%s" % urllib.quote(self.new_uri))
1376 d.addCallback(_check3)
1378 self.failUnlessEqual(data, self.NEWFILE_CONTENTS)
1379 d.addCallback(_check4)
1382 def test_POST_upload_no_link_mutable_toobig(self):
1383 d = self.shouldFail2(error.Error,
1384 "test_POST_upload_no_link_mutable_toobig",
1385 "413 Request Entity Too Large",
1386 "SDMF is limited to one segment, and 10001 > 10000",
1388 "/uri", t="upload", mutable="true",
1390 "b" * (self.s.MUTABLE_SIZELIMIT+1)) )
1393 def test_POST_upload_mutable(self):
1394 # this creates a mutable file
1395 d = self.POST(self.public_url + "/foo", t="upload", mutable="true",
1396 file=("new.txt", self.NEWFILE_CONTENTS))
1398 d.addCallback(self.failUnlessURIMatchesChild, fn, u"new.txt")
1399 d.addCallback(lambda res:
1400 self.failUnlessMutableChildContentsAre(fn, u"new.txt",
1401 self.NEWFILE_CONTENTS))
1402 d.addCallback(lambda res: self._foo_node.get(u"new.txt"))
1404 self.failUnless(IMutableFileNode.providedBy(newnode))
1405 self.failUnless(newnode.is_mutable())
1406 self.failIf(newnode.is_readonly())
1407 self._mutable_node = newnode
1408 self._mutable_uri = newnode.get_uri()
1411 # now upload it again and make sure that the URI doesn't change
1412 NEWER_CONTENTS = self.NEWFILE_CONTENTS + "newer\n"
1413 d.addCallback(lambda res:
1414 self.POST(self.public_url + "/foo", t="upload",
1416 file=("new.txt", NEWER_CONTENTS)))
1417 d.addCallback(self.failUnlessURIMatchesChild, fn, u"new.txt")
1418 d.addCallback(lambda res:
1419 self.failUnlessMutableChildContentsAre(fn, u"new.txt",
1421 d.addCallback(lambda res: self._foo_node.get(u"new.txt"))
1423 self.failUnless(IMutableFileNode.providedBy(newnode))
1424 self.failUnless(newnode.is_mutable())
1425 self.failIf(newnode.is_readonly())
1426 self.failUnlessEqual(self._mutable_uri, newnode.get_uri())
1427 d.addCallback(_got2)
1429 # upload a second time, using PUT instead of POST
1430 NEW2_CONTENTS = NEWER_CONTENTS + "overwrite with PUT\n"
1431 d.addCallback(lambda res:
1432 self.PUT(self.public_url + "/foo/new.txt", NEW2_CONTENTS))
1433 d.addCallback(self.failUnlessURIMatchesChild, fn, u"new.txt")
1434 d.addCallback(lambda res:
1435 self.failUnlessMutableChildContentsAre(fn, u"new.txt",
1438 # finally list the directory, since mutable files are displayed
1439 # slightly differently
1441 d.addCallback(lambda res:
1442 self.GET(self.public_url + "/foo/",
1443 followRedirect=True))
1444 def _check_page(res):
1445 # TODO: assert more about the contents
1446 self.failUnless("SSK" in res)
1448 d.addCallback(_check_page)
1450 d.addCallback(lambda res: self._foo_node.get(u"new.txt"))
1452 self.failUnless(IMutableFileNode.providedBy(newnode))
1453 self.failUnless(newnode.is_mutable())
1454 self.failIf(newnode.is_readonly())
1455 self.failUnlessEqual(self._mutable_uri, newnode.get_uri())
1456 d.addCallback(_got3)
1458 # look at the JSON form of the enclosing directory
1459 d.addCallback(lambda res:
1460 self.GET(self.public_url + "/foo/?t=json",
1461 followRedirect=True))
1462 def _check_page_json(res):
1463 parsed = simplejson.loads(res)
1464 self.failUnlessEqual(parsed[0], "dirnode")
1465 children = dict( [(unicode(name),value)
1467 in parsed[1]["children"].iteritems()] )
1468 self.failUnless("new.txt" in children)
1469 new_json = children["new.txt"]
1470 self.failUnlessEqual(new_json[0], "filenode")
1471 self.failUnless(new_json[1]["mutable"])
1472 self.failUnlessEqual(new_json[1]["rw_uri"], self._mutable_uri)
1473 ro_uri = unicode(self._mutable_node.get_readonly().to_string())
1474 self.failUnlessEqual(new_json[1]["ro_uri"], ro_uri)
1475 d.addCallback(_check_page_json)
1477 # and the JSON form of the file
1478 d.addCallback(lambda res:
1479 self.GET(self.public_url + "/foo/new.txt?t=json"))
1480 def _check_file_json(res):
1481 parsed = simplejson.loads(res)
1482 self.failUnlessEqual(parsed[0], "filenode")
1483 self.failUnless(parsed[1]["mutable"])
1484 self.failUnlessEqual(parsed[1]["rw_uri"], self._mutable_uri)
1485 ro_uri = unicode(self._mutable_node.get_readonly().to_string())
1486 self.failUnlessEqual(parsed[1]["ro_uri"], ro_uri)
1487 d.addCallback(_check_file_json)
1489 # and look at t=uri and t=readonly-uri
1490 d.addCallback(lambda res:
1491 self.GET(self.public_url + "/foo/new.txt?t=uri"))
1492 d.addCallback(lambda res: self.failUnlessEqual(res, self._mutable_uri))
1493 d.addCallback(lambda res:
1494 self.GET(self.public_url + "/foo/new.txt?t=readonly-uri"))
1495 def _check_ro_uri(res):
1496 ro_uri = unicode(self._mutable_node.get_readonly().to_string())
1497 self.failUnlessEqual(res, ro_uri)
1498 d.addCallback(_check_ro_uri)
1500 # make sure we can get to it from /uri/URI
1501 d.addCallback(lambda res:
1502 self.GET("/uri/%s" % urllib.quote(self._mutable_uri)))
1503 d.addCallback(lambda res:
1504 self.failUnlessEqual(res, NEW2_CONTENTS))
1506 # and that HEAD computes the size correctly
1507 d.addCallback(lambda res:
1508 self.HEAD(self.public_url + "/foo/new.txt",
1509 return_response=True))
1510 def _got_headers((res, status, headers)):
1511 self.failUnlessEqual(res, "")
1512 self.failUnlessEqual(headers["content-length"][0],
1513 str(len(NEW2_CONTENTS)))
1514 self.failUnlessEqual(headers["content-type"], ["text/plain"])
1515 d.addCallback(_got_headers)
1517 # make sure that size errors are displayed correctly for overwrite
1518 d.addCallback(lambda res:
1519 self.shouldFail2(error.Error,
1520 "test_POST_upload_mutable-toobig",
1521 "413 Request Entity Too Large",
1522 "SDMF is limited to one segment, and 10001 > 10000",
1524 self.public_url + "/foo", t="upload",
1527 "b" * (self.s.MUTABLE_SIZELIMIT+1)),
1530 d.addErrback(self.dump_error)
1533 def test_POST_upload_mutable_toobig(self):
1534 d = self.shouldFail2(error.Error,
1535 "test_POST_upload_no_link_mutable_toobig",
1536 "413 Request Entity Too Large",
1537 "SDMF is limited to one segment, and 10001 > 10000",
1539 self.public_url + "/foo",
1540 t="upload", mutable="true",
1542 "b" * (self.s.MUTABLE_SIZELIMIT+1)) )
1545 def dump_error(self, f):
1546 # if the web server returns an error code (like 400 Bad Request),
1547 # web.client.getPage puts the HTTP response body into the .response
1548 # attribute of the exception object that it gives back. It does not
1549 # appear in the Failure's repr(), so the ERROR that trial displays
1550 # will be rather terse and unhelpful. addErrback this method to the
1551 # end of your chain to get more information out of these errors.
1552 if f.check(error.Error):
1553 print "web.error.Error:"
1555 print f.value.response
1558 def test_POST_upload_replace(self):
1559 d = self.POST(self.public_url + "/foo", t="upload",
1560 file=("bar.txt", self.NEWFILE_CONTENTS))
1562 d.addCallback(self.failUnlessURIMatchesChild, fn, u"bar.txt")
1563 d.addCallback(lambda res:
1564 self.failUnlessChildContentsAre(fn, u"bar.txt",
1565 self.NEWFILE_CONTENTS))
1568 def test_POST_upload_no_replace_ok(self):
1569 d = self.POST(self.public_url + "/foo?replace=false", t="upload",
1570 file=("new.txt", self.NEWFILE_CONTENTS))
1571 d.addCallback(lambda res: self.GET(self.public_url + "/foo/new.txt"))
1572 d.addCallback(lambda res: self.failUnlessEqual(res,
1573 self.NEWFILE_CONTENTS))
1576 def test_POST_upload_no_replace_queryarg(self):
1577 d = self.POST(self.public_url + "/foo?replace=false", t="upload",
1578 file=("bar.txt", self.NEWFILE_CONTENTS))
1579 d.addBoth(self.shouldFail, error.Error,
1580 "POST_upload_no_replace_queryarg",
1582 "There was already a child by that name, and you asked me "
1583 "to not replace it")
1584 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
1585 d.addCallback(self.failUnlessIsBarDotTxt)
1588 def test_POST_upload_no_replace_field(self):
1589 d = self.POST(self.public_url + "/foo", t="upload", replace="false",
1590 file=("bar.txt", self.NEWFILE_CONTENTS))
1591 d.addBoth(self.shouldFail, error.Error, "POST_upload_no_replace_field",
1593 "There was already a child by that name, and you asked me "
1594 "to not replace it")
1595 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
1596 d.addCallback(self.failUnlessIsBarDotTxt)
1599 def test_POST_upload_whendone(self):
1600 d = self.POST(self.public_url + "/foo", t="upload", when_done="/THERE",
1601 file=("new.txt", self.NEWFILE_CONTENTS))
1602 d.addBoth(self.shouldRedirect, "/THERE")
1604 d.addCallback(lambda res:
1605 self.failUnlessChildContentsAre(fn, u"new.txt",
1606 self.NEWFILE_CONTENTS))
1609 def test_POST_upload_named(self):
1611 d = self.POST(self.public_url + "/foo", t="upload",
1612 name="new.txt", file=self.NEWFILE_CONTENTS)
1613 d.addCallback(self.failUnlessURIMatchesChild, fn, u"new.txt")
1614 d.addCallback(lambda res:
1615 self.failUnlessChildContentsAre(fn, u"new.txt",
1616 self.NEWFILE_CONTENTS))
1619 def test_POST_upload_named_badfilename(self):
1620 d = self.POST(self.public_url + "/foo", t="upload",
1621 name="slashes/are/bad.txt", file=self.NEWFILE_CONTENTS)
1622 d.addBoth(self.shouldFail, error.Error,
1623 "test_POST_upload_named_badfilename",
1625 "name= may not contain a slash",
1627 # make sure that nothing was added
1628 d.addCallback(lambda res:
1629 self.failUnlessNodeKeysAre(self._foo_node,
1630 [u"bar.txt", u"blockingfile",
1631 u"empty", u"n\u00fc.txt",
1635 def test_POST_FILEURL_check(self):
1636 bar_url = self.public_url + "/foo/bar.txt"
1637 d = self.POST(bar_url, t="check")
1639 self.failUnless("Healthy :" in res)
1640 d.addCallback(_check)
1641 redir_url = "http://allmydata.org/TARGET"
1642 def _check2(statuscode, target):
1643 self.failUnlessEqual(statuscode, str(http.FOUND))
1644 self.failUnlessEqual(target, redir_url)
1645 d.addCallback(lambda res:
1646 self.shouldRedirect2("test_POST_FILEURL_check",
1650 when_done=redir_url))
1651 d.addCallback(lambda res:
1652 self.POST(bar_url, t="check", return_to=redir_url))
1654 self.failUnless("Healthy :" in res)
1655 self.failUnless("Return to parent directory" in res)
1656 self.failUnless(redir_url in res)
1657 d.addCallback(_check3)
1659 d.addCallback(lambda res:
1660 self.POST(bar_url, t="check", output="JSON"))
1661 def _check_json(res):
1662 data = simplejson.loads(res)
1663 self.failUnless("storage-index" in data)
1664 self.failUnless(data["results"]["healthy"])
1665 d.addCallback(_check_json)
1669 def test_POST_FILEURL_check_and_repair(self):
1670 bar_url = self.public_url + "/foo/bar.txt"
1671 d = self.POST(bar_url, t="check", repair="true")
1673 self.failUnless("Healthy :" in res)
1674 d.addCallback(_check)
1675 redir_url = "http://allmydata.org/TARGET"
1676 def _check2(statuscode, target):
1677 self.failUnlessEqual(statuscode, str(http.FOUND))
1678 self.failUnlessEqual(target, redir_url)
1679 d.addCallback(lambda res:
1680 self.shouldRedirect2("test_POST_FILEURL_check_and_repair",
1683 t="check", repair="true",
1684 when_done=redir_url))
1685 d.addCallback(lambda res:
1686 self.POST(bar_url, t="check", return_to=redir_url))
1688 self.failUnless("Healthy :" in res)
1689 self.failUnless("Return to parent directory" in res)
1690 self.failUnless(redir_url in res)
1691 d.addCallback(_check3)
1694 def test_POST_DIRURL_check(self):
1695 foo_url = self.public_url + "/foo/"
1696 d = self.POST(foo_url, t="check")
1698 self.failUnless("Healthy :" in res, res)
1699 d.addCallback(_check)
1700 redir_url = "http://allmydata.org/TARGET"
1701 def _check2(statuscode, target):
1702 self.failUnlessEqual(statuscode, str(http.FOUND))
1703 self.failUnlessEqual(target, redir_url)
1704 d.addCallback(lambda res:
1705 self.shouldRedirect2("test_POST_DIRURL_check",
1709 when_done=redir_url))
1710 d.addCallback(lambda res:
1711 self.POST(foo_url, t="check", return_to=redir_url))
1713 self.failUnless("Healthy :" in res, res)
1714 self.failUnless("Return to parent directory" in res)
1715 self.failUnless(redir_url in res)
1716 d.addCallback(_check3)
1718 d.addCallback(lambda res:
1719 self.POST(foo_url, t="check", output="JSON"))
1720 def _check_json(res):
1721 data = simplejson.loads(res)
1722 self.failUnless("storage-index" in data)
1723 self.failUnless(data["results"]["healthy"])
1724 d.addCallback(_check_json)
1728 def test_POST_DIRURL_check_and_repair(self):
1729 foo_url = self.public_url + "/foo/"
1730 d = self.POST(foo_url, t="check", repair="true")
1732 self.failUnless("Healthy :" in res, res)
1733 d.addCallback(_check)
1734 redir_url = "http://allmydata.org/TARGET"
1735 def _check2(statuscode, target):
1736 self.failUnlessEqual(statuscode, str(http.FOUND))
1737 self.failUnlessEqual(target, redir_url)
1738 d.addCallback(lambda res:
1739 self.shouldRedirect2("test_POST_DIRURL_check_and_repair",
1742 t="check", repair="true",
1743 when_done=redir_url))
1744 d.addCallback(lambda res:
1745 self.POST(foo_url, t="check", return_to=redir_url))
1747 self.failUnless("Healthy :" in res)
1748 self.failUnless("Return to parent directory" in res)
1749 self.failUnless(redir_url in res)
1750 d.addCallback(_check3)
1753 def wait_for_operation(self, ignored, ophandle):
1754 url = "/operations/" + ophandle
1755 url += "?t=status&output=JSON"
1758 data = simplejson.loads(res)
1759 if not data["finished"]:
1760 d = self.stall(delay=1.0)
1761 d.addCallback(self.wait_for_operation, ophandle)
1767 def get_operation_results(self, ignored, ophandle, output=None):
1768 url = "/operations/" + ophandle
1771 url += "&output=" + output
1774 if output and output.lower() == "json":
1775 return simplejson.loads(res)
1780 def test_POST_DIRURL_deepcheck_no_ophandle(self):
1781 d = self.shouldFail2(error.Error,
1782 "test_POST_DIRURL_deepcheck_no_ophandle",
1784 "slow operation requires ophandle=",
1785 self.POST, self.public_url, t="start-deep-check")
1788 def test_POST_DIRURL_deepcheck(self):
1789 def _check_redirect(statuscode, target):
1790 self.failUnlessEqual(statuscode, str(http.FOUND))
1791 self.failUnless(target.endswith("/operations/123"))
1792 d = self.shouldRedirect2("test_POST_DIRURL_deepcheck", _check_redirect,
1793 self.POST, self.public_url,
1794 t="start-deep-check", ophandle="123")
1795 d.addCallback(self.wait_for_operation, "123")
1796 def _check_json(data):
1797 self.failUnlessEqual(data["finished"], True)
1798 self.failUnlessEqual(data["count-objects-checked"], 8)
1799 self.failUnlessEqual(data["count-objects-healthy"], 8)
1800 d.addCallback(_check_json)
1801 d.addCallback(self.get_operation_results, "123", "html")
1802 def _check_html(res):
1803 self.failUnless("Objects Checked: <span>8</span>" in res)
1804 self.failUnless("Objects Healthy: <span>8</span>" in res)
1805 d.addCallback(_check_html)
1807 d.addCallback(lambda res:
1808 self.GET("/operations/123/"))
1809 d.addCallback(_check_html) # should be the same as without the slash
1811 d.addCallback(lambda res:
1812 self.shouldFail2(error.Error, "one", "404 Not Found",
1813 "No detailed results for SI bogus",
1814 self.GET, "/operations/123/bogus"))
1816 foo_si = self._foo_node.get_storage_index()
1817 foo_si_s = base32.b2a(foo_si)
1818 d.addCallback(lambda res:
1819 self.GET("/operations/123/%s?output=JSON" % foo_si_s))
1820 def _check_foo_json(res):
1821 data = simplejson.loads(res)
1822 self.failUnlessEqual(data["storage-index"], foo_si_s)
1823 self.failUnless(data["results"]["healthy"])
1824 d.addCallback(_check_foo_json)
1827 def test_POST_DIRURL_deepcheck_and_repair(self):
1828 d = self.POST(self.public_url, t="start-deep-check", repair="true",
1829 ophandle="124", output="json", followRedirect=True)
1830 d.addCallback(self.wait_for_operation, "124")
1831 def _check_json(data):
1832 self.failUnlessEqual(data["finished"], True)
1833 self.failUnlessEqual(data["count-objects-checked"], 8)
1834 self.failUnlessEqual(data["count-objects-healthy-pre-repair"], 8)
1835 self.failUnlessEqual(data["count-objects-unhealthy-pre-repair"], 0)
1836 self.failUnlessEqual(data["count-corrupt-shares-pre-repair"], 0)
1837 self.failUnlessEqual(data["count-repairs-attempted"], 0)
1838 self.failUnlessEqual(data["count-repairs-successful"], 0)
1839 self.failUnlessEqual(data["count-repairs-unsuccessful"], 0)
1840 self.failUnlessEqual(data["count-objects-healthy-post-repair"], 8)
1841 self.failUnlessEqual(data["count-objects-unhealthy-post-repair"], 0)
1842 self.failUnlessEqual(data["count-corrupt-shares-post-repair"], 0)
1843 d.addCallback(_check_json)
1844 d.addCallback(self.get_operation_results, "124", "html")
1845 def _check_html(res):
1846 self.failUnless("Objects Checked: <span>8</span>" in res)
1848 self.failUnless("Objects Healthy (before repair): <span>8</span>" in res)
1849 self.failUnless("Objects Unhealthy (before repair): <span>0</span>" in res)
1850 self.failUnless("Corrupt Shares (before repair): <span>0</span>" in res)
1852 self.failUnless("Repairs Attempted: <span>0</span>" in res)
1853 self.failUnless("Repairs Successful: <span>0</span>" in res)
1854 self.failUnless("Repairs Unsuccessful: <span>0</span>" in res)
1856 self.failUnless("Objects Healthy (after repair): <span>8</span>" in res)
1857 self.failUnless("Objects Unhealthy (after repair): <span>0</span>" in res)
1858 self.failUnless("Corrupt Shares (after repair): <span>0</span>" in res)
1859 d.addCallback(_check_html)
1862 def test_POST_FILEURL_bad_t(self):
1863 d = self.shouldFail2(error.Error, "POST_bad_t", "400 Bad Request",
1864 "POST to file: bad t=bogus",
1865 self.POST, self.public_url + "/foo/bar.txt",
1869 def test_POST_mkdir(self): # return value?
1870 d = self.POST(self.public_url + "/foo", t="mkdir", name="newdir")
1871 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1872 d.addCallback(self.failUnlessNodeKeysAre, [])
1875 def test_POST_mkdir_2(self):
1876 d = self.POST(self.public_url + "/foo/newdir?t=mkdir", "")
1877 d.addCallback(lambda res:
1878 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1879 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1880 d.addCallback(self.failUnlessNodeKeysAre, [])
1883 def test_POST_mkdirs_2(self):
1884 d = self.POST(self.public_url + "/foo/bardir/newdir?t=mkdir", "")
1885 d.addCallback(lambda res:
1886 self.failUnlessNodeHasChild(self._foo_node, u"bardir"))
1887 d.addCallback(lambda res: self._foo_node.get(u"bardir"))
1888 d.addCallback(lambda bardirnode: bardirnode.get(u"newdir"))
1889 d.addCallback(self.failUnlessNodeKeysAre, [])
1892 def test_POST_mkdir_no_parentdir_noredirect(self):
1893 d = self.POST("/uri?t=mkdir")
1894 def _after_mkdir(res):
1895 uri.NewDirectoryURI.init_from_string(res)
1896 d.addCallback(_after_mkdir)
1899 def test_POST_mkdir_no_parentdir_redirect(self):
1900 d = self.POST("/uri?t=mkdir&redirect_to_result=true")
1901 d.addBoth(self.shouldRedirect, None, statuscode='303')
1902 def _check_target(target):
1903 target = urllib.unquote(target)
1904 self.failUnless(target.startswith("uri/URI:DIR2:"), target)
1905 d.addCallback(_check_target)
1908 def test_POST_noparent_bad(self):
1909 d = self.shouldHTTPError2("POST /uri?t=bogus", 400, "Bad Request",
1910 "/uri accepts only PUT, PUT?t=mkdir, "
1911 "POST?t=upload, and POST?t=mkdir",
1912 self.POST, "/uri?t=bogus")
1915 def test_welcome_page_mkdir_button(self):
1916 # Fetch the welcome page.
1918 def _after_get_welcome_page(res):
1919 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)
1920 mo = MKDIR_BUTTON_RE.search(res)
1921 formaction = mo.group(1)
1923 formaname = mo.group(3)
1924 formavalue = mo.group(4)
1925 return (formaction, formt, formaname, formavalue)
1926 d.addCallback(_after_get_welcome_page)
1927 def _after_parse_form(res):
1928 (formaction, formt, formaname, formavalue) = res
1929 return self.POST("/%s?t=%s&%s=%s" % (formaction, formt, formaname, formavalue))
1930 d.addCallback(_after_parse_form)
1931 d.addBoth(self.shouldRedirect, None, statuscode='303')
1934 def test_POST_mkdir_replace(self): # return value?
1935 d = self.POST(self.public_url + "/foo", t="mkdir", name="sub")
1936 d.addCallback(lambda res: self._foo_node.get(u"sub"))
1937 d.addCallback(self.failUnlessNodeKeysAre, [])
1940 def test_POST_mkdir_no_replace_queryarg(self): # return value?
1941 d = self.POST(self.public_url + "/foo?replace=false", t="mkdir", name="sub")
1942 d.addBoth(self.shouldFail, error.Error,
1943 "POST_mkdir_no_replace_queryarg",
1945 "There was already a child by that name, and you asked me "
1946 "to not replace it")
1947 d.addCallback(lambda res: self._foo_node.get(u"sub"))
1948 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
1951 def test_POST_mkdir_no_replace_field(self): # return value?
1952 d = self.POST(self.public_url + "/foo", t="mkdir", name="sub",
1954 d.addBoth(self.shouldFail, error.Error, "POST_mkdir_no_replace_field",
1956 "There was already a child by that name, and you asked me "
1957 "to not replace it")
1958 d.addCallback(lambda res: self._foo_node.get(u"sub"))
1959 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
1962 def test_POST_mkdir_whendone_field(self):
1963 d = self.POST(self.public_url + "/foo",
1964 t="mkdir", name="newdir", when_done="/THERE")
1965 d.addBoth(self.shouldRedirect, "/THERE")
1966 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1967 d.addCallback(self.failUnlessNodeKeysAre, [])
1970 def test_POST_mkdir_whendone_queryarg(self):
1971 d = self.POST(self.public_url + "/foo?when_done=/THERE",
1972 t="mkdir", name="newdir")
1973 d.addBoth(self.shouldRedirect, "/THERE")
1974 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1975 d.addCallback(self.failUnlessNodeKeysAre, [])
1978 def test_POST_bad_t(self):
1979 d = self.shouldFail2(error.Error, "POST_bad_t", "400 Bad Request",
1980 "POST to a directory with bad t=BOGUS",
1981 self.POST, self.public_url + "/foo", t="BOGUS")
1984 def test_POST_set_children(self):
1985 contents9, n9, newuri9 = self.makefile(9)
1986 contents10, n10, newuri10 = self.makefile(10)
1987 contents11, n11, newuri11 = self.makefile(11)
1990 "atomic_added_1": [ "filenode", { "rw_uri": "%s",
1993 "ctime": 1002777696.7564139,
1994 "mtime": 1002777696.7564139
1997 "atomic_added_2": [ "filenode", { "rw_uri": "%s",
2000 "ctime": 1002777696.7564139,
2001 "mtime": 1002777696.7564139
2004 "atomic_added_3": [ "filenode", { "rw_uri": "%s",
2007 "ctime": 1002777696.7564139,
2008 "mtime": 1002777696.7564139
2011 }""" % (newuri9, newuri10, newuri11)
2013 url = self.webish_url + self.public_url + "/foo" + "?t=set_children"
2015 d = client.getPage(url, method="POST", postdata=reqbody)
2017 self.failUnlessURIMatchesChild(newuri9, self._foo_node, u"atomic_added_1")
2018 self.failUnlessURIMatchesChild(newuri10, self._foo_node, u"atomic_added_2")
2019 self.failUnlessURIMatchesChild(newuri11, self._foo_node, u"atomic_added_3")
2021 d.addCallback(_then)
2022 d.addErrback(self.dump_error)
2025 def test_POST_put_uri(self):
2026 contents, n, newuri = self.makefile(8)
2027 d = self.POST(self.public_url + "/foo", t="uri", name="new.txt", uri=newuri)
2028 d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"new.txt")
2029 d.addCallback(lambda res:
2030 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
2034 def test_POST_put_uri_replace(self):
2035 contents, n, newuri = self.makefile(8)
2036 d = self.POST(self.public_url + "/foo", t="uri", name="bar.txt", uri=newuri)
2037 d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"bar.txt")
2038 d.addCallback(lambda res:
2039 self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
2043 def test_POST_put_uri_no_replace_queryarg(self):
2044 contents, n, newuri = self.makefile(8)
2045 d = self.POST(self.public_url + "/foo?replace=false", t="uri",
2046 name="bar.txt", uri=newuri)
2047 d.addBoth(self.shouldFail, error.Error,
2048 "POST_put_uri_no_replace_queryarg",
2050 "There was already a child by that name, and you asked me "
2051 "to not replace it")
2052 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
2053 d.addCallback(self.failUnlessIsBarDotTxt)
2056 def test_POST_put_uri_no_replace_field(self):
2057 contents, n, newuri = self.makefile(8)
2058 d = self.POST(self.public_url + "/foo", t="uri", replace="false",
2059 name="bar.txt", uri=newuri)
2060 d.addBoth(self.shouldFail, error.Error,
2061 "POST_put_uri_no_replace_field",
2063 "There was already a child by that name, and you asked me "
2064 "to not replace it")
2065 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
2066 d.addCallback(self.failUnlessIsBarDotTxt)
2069 def test_POST_delete(self):
2070 d = self.POST(self.public_url + "/foo", t="delete", name="bar.txt")
2071 d.addCallback(lambda res: self._foo_node.list())
2072 def _check(children):
2073 self.failIf(u"bar.txt" in children)
2074 d.addCallback(_check)
2077 def test_POST_rename_file(self):
2078 d = self.POST(self.public_url + "/foo", t="rename",
2079 from_name="bar.txt", to_name='wibble.txt')
2080 d.addCallback(lambda res:
2081 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
2082 d.addCallback(lambda res:
2083 self.failUnlessNodeHasChild(self._foo_node, u"wibble.txt"))
2084 d.addCallback(lambda res: self.GET(self.public_url + "/foo/wibble.txt"))
2085 d.addCallback(self.failUnlessIsBarDotTxt)
2086 d.addCallback(lambda res: self.GET(self.public_url + "/foo/wibble.txt?t=json"))
2087 d.addCallback(self.failUnlessIsBarJSON)
2090 def test_POST_rename_file_redundant(self):
2091 d = self.POST(self.public_url + "/foo", t="rename",
2092 from_name="bar.txt", to_name='bar.txt')
2093 d.addCallback(lambda res:
2094 self.failUnlessNodeHasChild(self._foo_node, u"bar.txt"))
2095 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
2096 d.addCallback(self.failUnlessIsBarDotTxt)
2097 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt?t=json"))
2098 d.addCallback(self.failUnlessIsBarJSON)
2101 def test_POST_rename_file_replace(self):
2102 # rename a file and replace a directory with it
2103 d = self.POST(self.public_url + "/foo", t="rename",
2104 from_name="bar.txt", to_name='empty')
2105 d.addCallback(lambda res:
2106 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
2107 d.addCallback(lambda res:
2108 self.failUnlessNodeHasChild(self._foo_node, u"empty"))
2109 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty"))
2110 d.addCallback(self.failUnlessIsBarDotTxt)
2111 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
2112 d.addCallback(self.failUnlessIsBarJSON)
2115 def test_POST_rename_file_no_replace_queryarg(self):
2116 # rename a file and replace a directory with it
2117 d = self.POST(self.public_url + "/foo?replace=false", t="rename",
2118 from_name="bar.txt", to_name='empty')
2119 d.addBoth(self.shouldFail, error.Error,
2120 "POST_rename_file_no_replace_queryarg",
2122 "There was already a child by that name, and you asked me "
2123 "to not replace it")
2124 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
2125 d.addCallback(self.failUnlessIsEmptyJSON)
2128 def test_POST_rename_file_no_replace_field(self):
2129 # rename a file and replace a directory with it
2130 d = self.POST(self.public_url + "/foo", t="rename", replace="false",
2131 from_name="bar.txt", to_name='empty')
2132 d.addBoth(self.shouldFail, error.Error,
2133 "POST_rename_file_no_replace_field",
2135 "There was already a child by that name, and you asked me "
2136 "to not replace it")
2137 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
2138 d.addCallback(self.failUnlessIsEmptyJSON)
2141 def failUnlessIsEmptyJSON(self, res):
2142 data = simplejson.loads(res)
2143 self.failUnlessEqual(data[0], "dirnode", data)
2144 self.failUnlessEqual(len(data[1]["children"]), 0)
2146 def test_POST_rename_file_slash_fail(self):
2147 d = self.POST(self.public_url + "/foo", t="rename",
2148 from_name="bar.txt", to_name='kirk/spock.txt')
2149 d.addBoth(self.shouldFail, error.Error,
2150 "test_POST_rename_file_slash_fail",
2152 "to_name= may not contain a slash",
2154 d.addCallback(lambda res:
2155 self.failUnlessNodeHasChild(self._foo_node, u"bar.txt"))
2158 def test_POST_rename_dir(self):
2159 d = self.POST(self.public_url, t="rename",
2160 from_name="foo", to_name='plunk')
2161 d.addCallback(lambda res:
2162 self.failIfNodeHasChild(self.public_root, u"foo"))
2163 d.addCallback(lambda res:
2164 self.failUnlessNodeHasChild(self.public_root, u"plunk"))
2165 d.addCallback(lambda res: self.GET(self.public_url + "/plunk?t=json"))
2166 d.addCallback(self.failUnlessIsFooJSON)
2169 def shouldRedirect(self, res, target=None, statuscode=None, which=""):
2170 """ If target is not None then the redirection has to go to target. If
2171 statuscode is not None then the redirection has to be accomplished with
2172 that HTTP status code."""
2173 if not isinstance(res, failure.Failure):
2174 to_where = (target is None) and "somewhere" or ("to " + target)
2175 self.fail("%s: we were expecting to get redirected %s, not get an"
2176 " actual page: %s" % (which, to_where, res))
2177 res.trap(error.PageRedirect)
2178 if statuscode is not None:
2179 self.failUnlessEqual(res.value.status, statuscode,
2180 "%s: not a redirect" % which)
2181 if target is not None:
2182 # the PageRedirect does not seem to capture the uri= query arg
2183 # properly, so we can't check for it.
2184 realtarget = self.webish_url + target
2185 self.failUnlessEqual(res.value.location, realtarget,
2186 "%s: wrong target" % which)
2187 return res.value.location
2189 def test_GET_URI_form(self):
2190 base = "/uri?uri=%s" % self._bar_txt_uri
2191 # this is supposed to give us a redirect to /uri/$URI, plus arguments
2192 targetbase = "/uri/%s" % urllib.quote(self._bar_txt_uri)
2194 d.addBoth(self.shouldRedirect, targetbase)
2195 d.addCallback(lambda res: self.GET(base+"&filename=bar.txt"))
2196 d.addBoth(self.shouldRedirect, targetbase+"?filename=bar.txt")
2197 d.addCallback(lambda res: self.GET(base+"&t=json"))
2198 d.addBoth(self.shouldRedirect, targetbase+"?t=json")
2199 d.addCallback(self.log, "about to get file by uri")
2200 d.addCallback(lambda res: self.GET(base, followRedirect=True))
2201 d.addCallback(self.failUnlessIsBarDotTxt)
2202 d.addCallback(self.log, "got file by uri, about to get dir by uri")
2203 d.addCallback(lambda res: self.GET("/uri?uri=%s&t=json" % self._foo_uri,
2204 followRedirect=True))
2205 d.addCallback(self.failUnlessIsFooJSON)
2206 d.addCallback(self.log, "got dir by uri")
2210 def test_GET_URI_form_bad(self):
2211 d = self.shouldFail2(error.Error, "test_GET_URI_form_bad",
2212 "400 Bad Request", "GET /uri requires uri=",
2216 def test_GET_rename_form(self):
2217 d = self.GET(self.public_url + "/foo?t=rename-form&name=bar.txt",
2218 followRedirect=True)
2220 self.failUnless('name="when_done" value="."' in res, res)
2221 self.failUnless(re.search(r'name="from_name" value="bar\.txt"', res))
2222 d.addCallback(_check)
2225 def log(self, res, msg):
2226 #print "MSG: %s RES: %s" % (msg, res)
2230 def test_GET_URI_URL(self):
2231 base = "/uri/%s" % self._bar_txt_uri
2233 d.addCallback(self.failUnlessIsBarDotTxt)
2234 d.addCallback(lambda res: self.GET(base+"?filename=bar.txt"))
2235 d.addCallback(self.failUnlessIsBarDotTxt)
2236 d.addCallback(lambda res: self.GET(base+"?filename=bar.txt&save=true"))
2237 d.addCallback(self.failUnlessIsBarDotTxt)
2240 def test_GET_URI_URL_dir(self):
2241 base = "/uri/%s?t=json" % self._foo_uri
2243 d.addCallback(self.failUnlessIsFooJSON)
2246 def test_GET_URI_URL_missing(self):
2247 base = "/uri/%s" % self._bad_file_uri
2249 d.addBoth(self.shouldHTTPError, "test_GET_URI_URL_missing",
2250 http.GONE, response_substring="NotEnoughSharesError")
2251 # TODO: how can we exercise both sides of WebDownloadTarget.fail
2252 # here? we must arrange for a download to fail after target.open()
2253 # has been called, and then inspect the response to see that it is
2254 # shorter than we expected.
2257 def test_PUT_DIRURL_uri(self):
2258 d = self.s.create_empty_dirnode()
2260 new_uri = dn.get_uri()
2261 # replace /foo with a new (empty) directory
2262 d = self.PUT(self.public_url + "/foo?t=uri", new_uri)
2263 d.addCallback(lambda res:
2264 self.failUnlessEqual(res.strip(), new_uri))
2265 d.addCallback(lambda res:
2266 self.failUnlessChildURIIs(self.public_root,
2270 d.addCallback(_made_dir)
2273 def test_PUT_DIRURL_uri_noreplace(self):
2274 d = self.s.create_empty_dirnode()
2276 new_uri = dn.get_uri()
2277 # replace /foo with a new (empty) directory, but ask that
2278 # replace=false, so it should fail
2279 d = self.shouldFail2(error.Error, "test_PUT_DIRURL_uri_noreplace",
2280 "409 Conflict", "There was already a child by that name, and you asked me to not replace it",
2282 self.public_url + "/foo?t=uri&replace=false",
2284 d.addCallback(lambda res:
2285 self.failUnlessChildURIIs(self.public_root,
2289 d.addCallback(_made_dir)
2292 def test_PUT_DIRURL_bad_t(self):
2293 d = self.shouldFail2(error.Error, "test_PUT_DIRURL_bad_t",
2294 "400 Bad Request", "PUT to a directory",
2295 self.PUT, self.public_url + "/foo?t=BOGUS", "")
2296 d.addCallback(lambda res:
2297 self.failUnlessChildURIIs(self.public_root,
2302 def test_PUT_NEWFILEURL_uri(self):
2303 contents, n, new_uri = self.makefile(8)
2304 d = self.PUT(self.public_url + "/foo/new.txt?t=uri", new_uri)
2305 d.addCallback(lambda res: self.failUnlessEqual(res.strip(), new_uri))
2306 d.addCallback(lambda res:
2307 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
2311 def test_PUT_NEWFILEURL_uri_replace(self):
2312 contents, n, new_uri = self.makefile(8)
2313 d = self.PUT(self.public_url + "/foo/bar.txt?t=uri", new_uri)
2314 d.addCallback(lambda res: self.failUnlessEqual(res.strip(), new_uri))
2315 d.addCallback(lambda res:
2316 self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
2320 def test_PUT_NEWFILEURL_uri_no_replace(self):
2321 contents, n, new_uri = self.makefile(8)
2322 d = self.PUT(self.public_url + "/foo/bar.txt?t=uri&replace=false", new_uri)
2323 d.addBoth(self.shouldFail, error.Error, "PUT_NEWFILEURL_uri_no_replace",
2325 "There was already a child by that name, and you asked me "
2326 "to not replace it")
2329 def test_PUT_NEWFILE_URI(self):
2330 file_contents = "New file contents here\n"
2331 d = self.PUT("/uri", file_contents)
2333 assert isinstance(uri, str), uri
2334 self.failUnless(uri in FakeCHKFileNode.all_contents)
2335 self.failUnlessEqual(FakeCHKFileNode.all_contents[uri],
2337 return self.GET("/uri/%s" % uri)
2338 d.addCallback(_check)
2340 self.failUnlessEqual(res, file_contents)
2341 d.addCallback(_check2)
2344 def test_PUT_NEWFILE_URI_only_PUT(self):
2345 d = self.PUT("/uri?t=bogus", "")
2346 d.addBoth(self.shouldFail, error.Error,
2347 "PUT_NEWFILE_URI_only_PUT",
2349 "/uri accepts only PUT, PUT?t=mkdir, POST?t=upload, and POST?t=mkdir")
2352 def test_PUT_NEWFILE_URI_mutable(self):
2353 file_contents = "New file contents here\n"
2354 d = self.PUT("/uri?mutable=true", file_contents)
2355 def _check_mutable(uri):
2358 self.failUnless(IMutableFileURI.providedBy(u))
2359 self.failUnless(u.storage_index in FakeMutableFileNode.all_contents)
2360 n = self.s.create_node_from_uri(uri)
2361 return n.download_best_version()
2362 d.addCallback(_check_mutable)
2363 def _check2_mutable(data):
2364 self.failUnlessEqual(data, file_contents)
2365 d.addCallback(_check2_mutable)
2369 self.failUnless(uri.to_string() in FakeCHKFileNode.all_contents)
2370 self.failUnlessEqual(FakeCHKFileNode.all_contents[uri.to_string()],
2372 return self.GET("/uri/%s" % uri)
2373 d.addCallback(_check)
2375 self.failUnlessEqual(res, file_contents)
2376 d.addCallback(_check2)
2379 def test_PUT_mkdir(self):
2380 d = self.PUT("/uri?t=mkdir", "")
2382 n = self.s.create_node_from_uri(uri.strip())
2383 d2 = self.failUnlessNodeKeysAre(n, [])
2384 d2.addCallback(lambda res:
2385 self.GET("/uri/%s?t=json" % uri))
2387 d.addCallback(_check)
2388 d.addCallback(self.failUnlessIsEmptyJSON)
2391 def test_POST_check(self):
2392 d = self.POST(self.public_url + "/foo", t="check", name="bar.txt")
2394 # this returns a string form of the results, which are probably
2395 # None since we're using fake filenodes.
2396 # TODO: verify that the check actually happened, by changing
2397 # FakeCHKFileNode to count how many times .check() has been
2400 d.addCallback(_done)
2403 def test_bad_method(self):
2404 url = self.webish_url + self.public_url + "/foo/bar.txt"
2405 d = self.shouldHTTPError2("test_bad_method",
2406 501, "Not Implemented",
2407 "I don't know how to treat a BOGUS request.",
2408 client.getPage, url, method="BOGUS")
2411 def test_short_url(self):
2412 url = self.webish_url + "/uri"
2413 d = self.shouldHTTPError2("test_short_url", 501, "Not Implemented",
2414 "I don't know how to treat a DELETE request.",
2415 client.getPage, url, method="DELETE")
2418 def test_ophandle_bad(self):
2419 url = self.webish_url + "/operations/bogus?t=status"
2420 d = self.shouldHTTPError2("test_ophandle_bad", 404, "404 Not Found",
2421 "unknown/expired handle 'bogus'",
2422 client.getPage, url)
2425 def test_ophandle_cancel(self):
2426 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=128",
2427 followRedirect=True)
2428 d.addCallback(lambda ignored:
2429 self.GET("/operations/128?t=status&output=JSON"))
2431 data = simplejson.loads(res)
2432 self.failUnless("finished" in data, res)
2433 monitor = self.ws.root.child_operations.handles["128"][0]
2434 d = self.POST("/operations/128?t=cancel&output=JSON")
2436 data = simplejson.loads(res)
2437 self.failUnless("finished" in data, res)
2438 # t=cancel causes the handle to be forgotten
2439 self.failUnless(monitor.is_cancelled())
2440 d.addCallback(_check2)
2442 d.addCallback(_check1)
2443 d.addCallback(lambda ignored:
2444 self.shouldHTTPError2("test_ophandle_cancel",
2445 404, "404 Not Found",
2446 "unknown/expired handle '128'",
2448 "/operations/128?t=status&output=JSON"))
2451 def test_ophandle_retainfor(self):
2452 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=129&retain-for=60",
2453 followRedirect=True)
2454 d.addCallback(lambda ignored:
2455 self.GET("/operations/129?t=status&output=JSON&retain-for=0"))
2457 data = simplejson.loads(res)
2458 self.failUnless("finished" in data, res)
2459 d.addCallback(_check1)
2460 # the retain-for=0 will cause the handle to be expired very soon
2461 d.addCallback(self.stall, 2.0)
2462 d.addCallback(lambda ignored:
2463 self.shouldHTTPError2("test_ophandle_retainfor",
2464 404, "404 Not Found",
2465 "unknown/expired handle '129'",
2467 "/operations/129?t=status&output=JSON"))
2470 def test_ophandle_release_after_complete(self):
2471 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=130",
2472 followRedirect=True)
2473 d.addCallback(self.wait_for_operation, "130")
2474 d.addCallback(lambda ignored:
2475 self.GET("/operations/130?t=status&output=JSON&release-after-complete=true"))
2476 # the release-after-complete=true will cause the handle to be expired
2477 d.addCallback(lambda ignored:
2478 self.shouldHTTPError2("test_ophandle_release_after_complete",
2479 404, "404 Not Found",
2480 "unknown/expired handle '130'",
2482 "/operations/130?t=status&output=JSON"))
2485 def test_incident(self):
2486 d = self.POST("/report_incident", details="eek")
2488 self.failUnless("Thank you for your report!" in res, res)
2489 d.addCallback(_done)
2492 def test_static(self):
2493 webdir = os.path.join(self.staticdir, "subdir")
2494 fileutil.make_dirs(webdir)
2495 f = open(os.path.join(webdir, "hello.txt"), "wb")
2499 d = self.GET("/static/subdir/hello.txt")
2501 self.failUnlessEqual(res, "hello")
2502 d.addCallback(_check)
2506 class Util(unittest.TestCase):
2507 def test_abbreviate_time(self):
2508 self.failUnlessEqual(common.abbreviate_time(None), "")
2509 self.failUnlessEqual(common.abbreviate_time(1.234), "1.23s")
2510 self.failUnlessEqual(common.abbreviate_time(0.123), "123ms")
2511 self.failUnlessEqual(common.abbreviate_time(0.00123), "1.2ms")
2512 self.failUnlessEqual(common.abbreviate_time(0.000123), "123us")
2514 def test_abbreviate_rate(self):
2515 self.failUnlessEqual(common.abbreviate_rate(None), "")
2516 self.failUnlessEqual(common.abbreviate_rate(1234000), "1.23MBps")
2517 self.failUnlessEqual(common.abbreviate_rate(12340), "12.3kBps")
2518 self.failUnlessEqual(common.abbreviate_rate(123), "123Bps")
2520 def test_abbreviate_size(self):
2521 self.failUnlessEqual(common.abbreviate_size(None), "")
2522 self.failUnlessEqual(common.abbreviate_size(1.23*1000*1000*1000), "1.23GB")
2523 self.failUnlessEqual(common.abbreviate_size(1.23*1000*1000), "1.23MB")
2524 self.failUnlessEqual(common.abbreviate_size(1230), "1.2kB")
2525 self.failUnlessEqual(common.abbreviate_size(123), "123B")
2528 class Grid(GridTestMixin, WebErrorMixin, unittest.TestCase):
2530 def GET(self, urlpath, followRedirect=False, return_response=False,
2531 method="GET", clientnum=0, **kwargs):
2532 # if return_response=True, this fires with (data, statuscode,
2533 # respheaders) instead of just data.
2534 assert not isinstance(urlpath, unicode)
2535 url = self.client_baseurls[clientnum] + urlpath
2536 factory = HTTPClientGETFactory(url, method=method,
2537 followRedirect=followRedirect, **kwargs)
2538 reactor.connectTCP("localhost", self.client_webports[clientnum],factory)
2539 d = factory.deferred
2540 def _got_data(data):
2541 return (data, factory.status, factory.response_headers)
2543 d.addCallback(_got_data)
2544 return factory.deferred
2546 def CHECK(self, ign, which, args, clientnum=0):
2547 fileurl = self.fileurls[which]
2548 url = fileurl + "?" + args
2549 return self.GET(url, method="POST", clientnum=clientnum)
2551 def test_filecheck(self):
2552 self.basedir = "web/Grid/filecheck"
2554 c0 = self.g.clients[0]
2557 d = c0.upload(upload.Data(DATA, convergence=""))
2558 def _stash_uri(ur, which):
2559 self.uris[which] = ur.uri
2560 d.addCallback(_stash_uri, "good")
2561 d.addCallback(lambda ign:
2562 c0.upload(upload.Data(DATA+"1", convergence="")))
2563 d.addCallback(_stash_uri, "sick")
2564 d.addCallback(lambda ign:
2565 c0.upload(upload.Data(DATA+"2", convergence="")))
2566 d.addCallback(_stash_uri, "dead")
2567 def _stash_mutable_uri(n, which):
2568 self.uris[which] = n.get_uri()
2569 assert isinstance(self.uris[which], str)
2570 d.addCallback(lambda ign: c0.create_mutable_file(DATA+"3"))
2571 d.addCallback(_stash_mutable_uri, "corrupt")
2572 d.addCallback(lambda ign:
2573 c0.upload(upload.Data("literal", convergence="")))
2574 d.addCallback(_stash_uri, "small")
2576 def _compute_fileurls(ignored):
2578 for which in self.uris:
2579 self.fileurls[which] = "uri/" + urllib.quote(self.uris[which])
2580 d.addCallback(_compute_fileurls)
2582 def _clobber_shares(ignored):
2583 good_shares = self.find_shares(self.uris["good"])
2584 self.failUnlessEqual(len(good_shares), 10)
2585 sick_shares = self.find_shares(self.uris["sick"])
2586 os.unlink(sick_shares[0][2])
2587 dead_shares = self.find_shares(self.uris["dead"])
2588 for i in range(1, 10):
2589 os.unlink(dead_shares[i][2])
2590 c_shares = self.find_shares(self.uris["corrupt"])
2591 cso = CorruptShareOptions()
2592 cso.stdout = StringIO()
2593 cso.parseOptions([c_shares[0][2]])
2595 d.addCallback(_clobber_shares)
2597 d.addCallback(self.CHECK, "good", "t=check")
2598 def _got_html_good(res):
2599 self.failUnless("Healthy" in res, res)
2600 self.failIf("Not Healthy" in res, res)
2601 d.addCallback(_got_html_good)
2602 d.addCallback(self.CHECK, "good", "t=check&return_to=somewhere")
2603 def _got_html_good_return_to(res):
2604 self.failUnless("Healthy" in res, res)
2605 self.failIf("Not Healthy" in res, res)
2606 self.failUnless('<a href="somewhere">Return to parent directory'
2608 d.addCallback(_got_html_good_return_to)
2609 d.addCallback(self.CHECK, "good", "t=check&output=json")
2610 def _got_json_good(res):
2611 r = simplejson.loads(res)
2612 self.failUnlessEqual(r["summary"], "Healthy")
2613 self.failUnless(r["results"]["healthy"])
2614 self.failIf(r["results"]["needs-rebalancing"])
2615 self.failUnless(r["results"]["recoverable"])
2616 d.addCallback(_got_json_good)
2618 d.addCallback(self.CHECK, "small", "t=check")
2619 def _got_html_small(res):
2620 self.failUnless("Literal files are always healthy" in res, res)
2621 self.failIf("Not Healthy" in res, res)
2622 d.addCallback(_got_html_small)
2623 d.addCallback(self.CHECK, "small", "t=check&return_to=somewhere")
2624 def _got_html_small_return_to(res):
2625 self.failUnless("Literal files are always healthy" in res, res)
2626 self.failIf("Not Healthy" in res, res)
2627 self.failUnless('<a href="somewhere">Return to parent directory'
2629 d.addCallback(_got_html_small_return_to)
2630 d.addCallback(self.CHECK, "small", "t=check&output=json")
2631 def _got_json_small(res):
2632 r = simplejson.loads(res)
2633 self.failUnlessEqual(r["storage-index"], "")
2634 self.failUnless(r["results"]["healthy"])
2635 d.addCallback(_got_json_small)
2637 d.addCallback(self.CHECK, "sick", "t=check")
2638 def _got_html_sick(res):
2639 self.failUnless("Not Healthy" in res, res)
2640 d.addCallback(_got_html_sick)
2641 d.addCallback(self.CHECK, "sick", "t=check&output=json")
2642 def _got_json_sick(res):
2643 r = simplejson.loads(res)
2644 self.failUnlessEqual(r["summary"],
2645 "Not Healthy: 9 shares (enc 3-of-10)")
2646 self.failIf(r["results"]["healthy"])
2647 self.failIf(r["results"]["needs-rebalancing"])
2648 self.failUnless(r["results"]["recoverable"])
2649 d.addCallback(_got_json_sick)
2651 d.addCallback(self.CHECK, "dead", "t=check")
2652 def _got_html_dead(res):
2653 self.failUnless("Not Healthy" in res, res)
2654 d.addCallback(_got_html_dead)
2655 d.addCallback(self.CHECK, "dead", "t=check&output=json")
2656 def _got_json_dead(res):
2657 r = simplejson.loads(res)
2658 self.failUnlessEqual(r["summary"],
2659 "Not Healthy: 1 shares (enc 3-of-10)")
2660 self.failIf(r["results"]["healthy"])
2661 self.failIf(r["results"]["needs-rebalancing"])
2662 self.failIf(r["results"]["recoverable"])
2663 d.addCallback(_got_json_dead)
2665 d.addCallback(self.CHECK, "corrupt", "t=check&verify=true")
2666 def _got_html_corrupt(res):
2667 self.failUnless("Not Healthy! : Unhealthy" in res, res)
2668 d.addCallback(_got_html_corrupt)
2669 d.addCallback(self.CHECK, "corrupt", "t=check&verify=true&output=json")
2670 def _got_json_corrupt(res):
2671 r = simplejson.loads(res)
2672 self.failUnless("Unhealthy: 9 shares (enc 3-of-10)" in r["summary"],
2674 self.failIf(r["results"]["healthy"])
2675 self.failUnless(r["results"]["recoverable"])
2676 self.failUnlessEqual(r["results"]["count-shares-good"], 9)
2677 self.failUnlessEqual(r["results"]["count-corrupt-shares"], 1)
2678 d.addCallback(_got_json_corrupt)
2680 d.addErrback(self.explain_web_error)
2683 def test_repair_html(self):
2684 self.basedir = "web/Grid/repair_html"
2686 c0 = self.g.clients[0]
2689 d = c0.upload(upload.Data(DATA, convergence=""))
2690 def _stash_uri(ur, which):
2691 self.uris[which] = ur.uri
2692 d.addCallback(_stash_uri, "good")
2693 d.addCallback(lambda ign:
2694 c0.upload(upload.Data(DATA+"1", convergence="")))
2695 d.addCallback(_stash_uri, "sick")
2696 d.addCallback(lambda ign:
2697 c0.upload(upload.Data(DATA+"2", convergence="")))
2698 d.addCallback(_stash_uri, "dead")
2699 def _stash_mutable_uri(n, which):
2700 self.uris[which] = n.get_uri()
2701 assert isinstance(self.uris[which], str)
2702 d.addCallback(lambda ign: c0.create_mutable_file(DATA+"3"))
2703 d.addCallback(_stash_mutable_uri, "corrupt")
2705 def _compute_fileurls(ignored):
2707 for which in self.uris:
2708 self.fileurls[which] = "uri/" + urllib.quote(self.uris[which])
2709 d.addCallback(_compute_fileurls)
2711 def _clobber_shares(ignored):
2712 good_shares = self.find_shares(self.uris["good"])
2713 self.failUnlessEqual(len(good_shares), 10)
2714 sick_shares = self.find_shares(self.uris["sick"])
2715 os.unlink(sick_shares[0][2])
2716 dead_shares = self.find_shares(self.uris["dead"])
2717 for i in range(1, 10):
2718 os.unlink(dead_shares[i][2])
2719 c_shares = self.find_shares(self.uris["corrupt"])
2720 cso = CorruptShareOptions()
2721 cso.stdout = StringIO()
2722 cso.parseOptions([c_shares[0][2]])
2724 d.addCallback(_clobber_shares)
2726 d.addCallback(self.CHECK, "good", "t=check&repair=true")
2727 def _got_html_good(res):
2728 self.failUnless("Healthy" in res, res)
2729 self.failIf("Not Healthy" in res, res)
2730 self.failUnless("No repair necessary" in res, res)
2731 d.addCallback(_got_html_good)
2733 d.addCallback(self.CHECK, "sick", "t=check&repair=true")
2734 def _got_html_sick(res):
2735 self.failUnless("Healthy : healthy" in res, res)
2736 self.failIf("Not Healthy" in res, res)
2737 self.failUnless("Repair successful" in res, res)
2738 d.addCallback(_got_html_sick)
2740 # repair of a dead file will fail, of course, but it isn't yet
2741 # clear how this should be reported. Right now it shows up as
2744 #d.addCallback(self.CHECK, "dead", "t=check&repair=true")
2745 #def _got_html_dead(res):
2747 # self.failUnless("Healthy : healthy" in res, res)
2748 # self.failIf("Not Healthy" in res, res)
2749 # self.failUnless("No repair necessary" in res, res)
2750 #d.addCallback(_got_html_dead)
2752 d.addCallback(self.CHECK, "corrupt", "t=check&verify=true&repair=true")
2753 def _got_html_corrupt(res):
2754 self.failUnless("Healthy : Healthy" in res, res)
2755 self.failIf("Not Healthy" in res, res)
2756 self.failUnless("Repair successful" in res, res)
2757 d.addCallback(_got_html_corrupt)
2759 d.addErrback(self.explain_web_error)
2762 def test_repair_json(self):
2763 self.basedir = "web/Grid/repair_json"
2765 c0 = self.g.clients[0]
2768 d = c0.upload(upload.Data(DATA+"1", convergence=""))
2769 def _stash_uri(ur, which):
2770 self.uris[which] = ur.uri
2771 d.addCallback(_stash_uri, "sick")
2773 def _compute_fileurls(ignored):
2775 for which in self.uris:
2776 self.fileurls[which] = "uri/" + urllib.quote(self.uris[which])
2777 d.addCallback(_compute_fileurls)
2779 def _clobber_shares(ignored):
2780 sick_shares = self.find_shares(self.uris["sick"])
2781 os.unlink(sick_shares[0][2])
2782 d.addCallback(_clobber_shares)
2784 d.addCallback(self.CHECK, "sick", "t=check&repair=true&output=json")
2785 def _got_json_sick(res):
2786 r = simplejson.loads(res)
2787 self.failUnlessEqual(r["repair-attempted"], True)
2788 self.failUnlessEqual(r["repair-successful"], True)
2789 self.failUnlessEqual(r["pre-repair-results"]["summary"],
2790 "Not Healthy: 9 shares (enc 3-of-10)")
2791 self.failIf(r["pre-repair-results"]["results"]["healthy"])
2792 self.failUnlessEqual(r["post-repair-results"]["summary"], "healthy")
2793 self.failUnless(r["post-repair-results"]["results"]["healthy"])
2794 d.addCallback(_got_json_sick)
2796 d.addErrback(self.explain_web_error)
2799 def test_deep_check(self):
2800 self.basedir = "web/Grid/deep_check"
2802 c0 = self.g.clients[0]
2806 d = c0.create_empty_dirnode()
2807 def _stash_root_and_create_file(n):
2809 self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri()) + "/"
2810 return n.add_file(u"good", upload.Data(DATA, convergence=""))
2811 d.addCallback(_stash_root_and_create_file)
2812 def _stash_uri(fn, which):
2813 self.uris[which] = fn.get_uri()
2814 d.addCallback(_stash_uri, "good")
2815 d.addCallback(lambda ign:
2816 self.rootnode.add_file(u"small",
2817 upload.Data("literal",
2819 d.addCallback(_stash_uri, "small")
2821 d.addCallback(self.CHECK, "root", "t=stream-deep-check")
2823 units = [simplejson.loads(line)
2824 for line in res.splitlines()
2826 self.failUnlessEqual(len(units), 3+1)
2827 # should be parent-first
2829 self.failUnlessEqual(u0["path"], [])
2830 self.failUnlessEqual(u0["type"], "directory")
2831 self.failUnlessEqual(u0["cap"], self.rootnode.get_uri())
2832 u0cr = u0["check-results"]
2833 self.failUnlessEqual(u0cr["results"]["count-shares-good"], 10)
2835 ugood = [u for u in units
2836 if u["type"] == "file" and u["path"] == [u"good"]][0]
2837 self.failUnlessEqual(ugood["cap"], self.uris["good"])
2838 ugoodcr = ugood["check-results"]
2839 self.failUnlessEqual(ugoodcr["results"]["count-shares-good"], 10)
2842 self.failUnlessEqual(stats["type"], "stats")
2844 self.failUnlessEqual(s["count-immutable-files"], 1)
2845 self.failUnlessEqual(s["count-literal-files"], 1)
2846 self.failUnlessEqual(s["count-directories"], 1)
2847 d.addCallback(_done)
2849 d.addErrback(self.explain_web_error)
2852 def test_deep_check_and_repair(self):
2853 self.basedir = "web/Grid/deep_check_and_repair"
2855 c0 = self.g.clients[0]
2859 d = c0.create_empty_dirnode()
2860 def _stash_root_and_create_file(n):
2862 self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri()) + "/"
2863 return n.add_file(u"good", upload.Data(DATA, convergence=""))
2864 d.addCallback(_stash_root_and_create_file)
2865 def _stash_uri(fn, which):
2866 self.uris[which] = fn.get_uri()
2867 d.addCallback(_stash_uri, "good")
2868 d.addCallback(lambda ign:
2869 self.rootnode.add_file(u"small",
2870 upload.Data("literal",
2872 d.addCallback(_stash_uri, "small")
2873 d.addCallback(lambda ign:
2874 self.rootnode.add_file(u"sick",
2875 upload.Data(DATA+"1",
2877 d.addCallback(_stash_uri, "sick")
2878 #d.addCallback(lambda ign:
2879 # self.rootnode.add_file(u"dead",
2880 # upload.Data(DATA+"2",
2882 #d.addCallback(_stash_uri, "dead")
2884 #d.addCallback(lambda ign: c0.create_mutable_file("mutable"))
2885 #d.addCallback(lambda fn: self.rootnode.set_node(u"corrupt", fn))
2886 #d.addCallback(_stash_uri, "corrupt")
2888 def _clobber_shares(ignored):
2889 good_shares = self.find_shares(self.uris["good"])
2890 self.failUnlessEqual(len(good_shares), 10)
2891 sick_shares = self.find_shares(self.uris["sick"])
2892 os.unlink(sick_shares[0][2])
2893 #dead_shares = self.find_shares(self.uris["dead"])
2894 #for i in range(1, 10):
2895 # os.unlink(dead_shares[i][2])
2897 #c_shares = self.find_shares(self.uris["corrupt"])
2898 #cso = CorruptShareOptions()
2899 #cso.stdout = StringIO()
2900 #cso.parseOptions([c_shares[0][2]])
2902 d.addCallback(_clobber_shares)
2904 d.addCallback(self.CHECK, "root", "t=stream-deep-check&repair=true")
2906 units = [simplejson.loads(line)
2907 for line in res.splitlines()
2909 self.failUnlessEqual(len(units), 4+1)
2910 # should be parent-first
2912 self.failUnlessEqual(u0["path"], [])
2913 self.failUnlessEqual(u0["type"], "directory")
2914 self.failUnlessEqual(u0["cap"], self.rootnode.get_uri())
2915 u0crr = u0["check-and-repair-results"]
2916 self.failUnlessEqual(u0crr["repair-attempted"], False)
2917 self.failUnlessEqual(u0crr["pre-repair-results"]["results"]["count-shares-good"], 10)
2919 ugood = [u for u in units
2920 if u["type"] == "file" and u["path"] == [u"good"]][0]
2921 self.failUnlessEqual(ugood["cap"], self.uris["good"])
2922 ugoodcrr = ugood["check-and-repair-results"]
2923 self.failUnlessEqual(u0crr["repair-attempted"], False)
2924 self.failUnlessEqual(u0crr["pre-repair-results"]["results"]["count-shares-good"], 10)
2926 usick = [u for u in units
2927 if u["type"] == "file" and u["path"] == [u"sick"]][0]
2928 self.failUnlessEqual(usick["cap"], self.uris["sick"])
2929 usickcrr = usick["check-and-repair-results"]
2930 self.failUnlessEqual(usickcrr["repair-attempted"], True)
2931 self.failUnlessEqual(usickcrr["repair-successful"], True)
2932 self.failUnlessEqual(usickcrr["pre-repair-results"]["results"]["count-shares-good"], 9)
2933 self.failUnlessEqual(usickcrr["post-repair-results"]["results"]["count-shares-good"], 10)
2936 self.failUnlessEqual(stats["type"], "stats")
2938 self.failUnlessEqual(s["count-immutable-files"], 2)
2939 self.failUnlessEqual(s["count-literal-files"], 1)
2940 self.failUnlessEqual(s["count-directories"], 1)
2941 d.addCallback(_done)
2943 d.addErrback(self.explain_web_error)
2946 def _count_leases(self, ignored, which):
2947 u = self.uris[which]
2948 shares = self.find_shares(u)
2950 for shnum, serverid, fn in shares:
2951 if u.startswith("URI:SSK") or u.startswith("URI:DIR2"):
2952 sf = MutableShareFile(fn)
2953 num_leases = len(sf.debug_get_leases())
2954 elif u.startswith("URI:CHK"):
2956 num_leases = len(list(sf.iter_leases()))
2958 raise RuntimeError("can't get leases on %s" % u)
2959 lease_counts.append( (fn, num_leases) )
2962 def _assert_leasecount(self, lease_counts, expected):
2963 for (fn, num_leases) in lease_counts:
2964 if num_leases != expected:
2965 self.fail("expected %d leases, have %d, on %s" %
2966 (expected, num_leases, fn))
2968 def test_add_lease(self):
2969 self.basedir = "web/Grid/add_lease"
2970 self.set_up_grid(num_clients=2)
2971 c0 = self.g.clients[0]
2974 d = c0.upload(upload.Data(DATA, convergence=""))
2975 def _stash_uri(ur, which):
2976 self.uris[which] = ur.uri
2977 d.addCallback(_stash_uri, "one")
2978 d.addCallback(lambda ign:
2979 c0.upload(upload.Data(DATA+"1", convergence="")))
2980 d.addCallback(_stash_uri, "two")
2981 def _stash_mutable_uri(n, which):
2982 self.uris[which] = n.get_uri()
2983 assert isinstance(self.uris[which], str)
2984 d.addCallback(lambda ign: c0.create_mutable_file(DATA+"2"))
2985 d.addCallback(_stash_mutable_uri, "mutable")
2987 def _compute_fileurls(ignored):
2989 for which in self.uris:
2990 self.fileurls[which] = "uri/" + urllib.quote(self.uris[which])
2991 d.addCallback(_compute_fileurls)
2993 d.addCallback(self._count_leases, "one")
2994 d.addCallback(self._assert_leasecount, 1)
2995 d.addCallback(self._count_leases, "two")
2996 d.addCallback(self._assert_leasecount, 1)
2997 d.addCallback(self._count_leases, "mutable")
2998 d.addCallback(self._assert_leasecount, 1)
3000 d.addCallback(self.CHECK, "one", "t=check") # no add-lease
3001 def _got_html_good(res):
3002 self.failUnless("Healthy" in res, res)
3003 self.failIf("Not Healthy" in res, res)
3004 d.addCallback(_got_html_good)
3006 d.addCallback(self._count_leases, "one")
3007 d.addCallback(self._assert_leasecount, 1)
3008 d.addCallback(self._count_leases, "two")
3009 d.addCallback(self._assert_leasecount, 1)
3010 d.addCallback(self._count_leases, "mutable")
3011 d.addCallback(self._assert_leasecount, 1)
3013 # this CHECK uses the original client, which uses the same
3014 # lease-secrets, so it will just renew the original lease
3015 d.addCallback(self.CHECK, "one", "t=check&add-lease=true")
3016 d.addCallback(_got_html_good)
3018 d.addCallback(self._count_leases, "one")
3019 d.addCallback(self._assert_leasecount, 1)
3020 d.addCallback(self._count_leases, "two")
3021 d.addCallback(self._assert_leasecount, 1)
3022 d.addCallback(self._count_leases, "mutable")
3023 d.addCallback(self._assert_leasecount, 1)
3025 # this CHECK uses an alternate client, which adds a second lease
3026 d.addCallback(self.CHECK, "one", "t=check&add-lease=true", clientnum=1)
3027 d.addCallback(_got_html_good)
3029 d.addCallback(self._count_leases, "one")
3030 d.addCallback(self._assert_leasecount, 2)
3031 d.addCallback(self._count_leases, "two")
3032 d.addCallback(self._assert_leasecount, 1)
3033 d.addCallback(self._count_leases, "mutable")
3034 d.addCallback(self._assert_leasecount, 1)
3036 d.addCallback(self.CHECK, "mutable", "t=check&add-lease=true")
3037 d.addCallback(_got_html_good)
3039 d.addCallback(self._count_leases, "one")
3040 d.addCallback(self._assert_leasecount, 2)
3041 d.addCallback(self._count_leases, "two")
3042 d.addCallback(self._assert_leasecount, 1)
3043 d.addCallback(self._count_leases, "mutable")
3044 d.addCallback(self._assert_leasecount, 1)
3046 d.addCallback(self.CHECK, "mutable", "t=check&add-lease=true",
3048 d.addCallback(_got_html_good)
3050 d.addCallback(self._count_leases, "one")
3051 d.addCallback(self._assert_leasecount, 2)
3052 d.addCallback(self._count_leases, "two")
3053 d.addCallback(self._assert_leasecount, 1)
3054 d.addCallback(self._count_leases, "mutable")
3055 d.addCallback(self._assert_leasecount, 2)
3057 d.addErrback(self.explain_web_error)
3060 def test_deep_add_lease(self):
3061 self.basedir = "web/Grid/deep_add_lease"
3062 self.set_up_grid(num_clients=2)
3063 c0 = self.g.clients[0]
3067 d = c0.create_empty_dirnode()
3068 def _stash_root_and_create_file(n):
3070 self.uris["root"] = n.get_uri()
3071 self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri()) + "/"
3072 return n.add_file(u"one", upload.Data(DATA, convergence=""))
3073 d.addCallback(_stash_root_and_create_file)
3074 def _stash_uri(fn, which):
3075 self.uris[which] = fn.get_uri()
3076 d.addCallback(_stash_uri, "one")
3077 d.addCallback(lambda ign:
3078 self.rootnode.add_file(u"small",
3079 upload.Data("literal",
3081 d.addCallback(_stash_uri, "small")
3083 d.addCallback(lambda ign: c0.create_mutable_file("mutable"))
3084 d.addCallback(lambda fn: self.rootnode.set_node(u"mutable", fn))
3085 d.addCallback(_stash_uri, "mutable")
3087 d.addCallback(self.CHECK, "root", "t=stream-deep-check") # no add-lease
3089 units = [simplejson.loads(line)
3090 for line in res.splitlines()
3092 # root, one, small, mutable, stats
3093 self.failUnlessEqual(len(units), 4+1)
3094 d.addCallback(_done)
3096 d.addCallback(self._count_leases, "root")
3097 d.addCallback(self._assert_leasecount, 1)
3098 d.addCallback(self._count_leases, "one")
3099 d.addCallback(self._assert_leasecount, 1)
3100 d.addCallback(self._count_leases, "mutable")
3101 d.addCallback(self._assert_leasecount, 1)
3103 d.addCallback(self.CHECK, "root", "t=stream-deep-check&add-lease=true")
3104 d.addCallback(_done)
3106 d.addCallback(self._count_leases, "root")
3107 d.addCallback(self._assert_leasecount, 1)
3108 d.addCallback(self._count_leases, "one")
3109 d.addCallback(self._assert_leasecount, 1)
3110 d.addCallback(self._count_leases, "mutable")
3111 d.addCallback(self._assert_leasecount, 1)
3113 d.addCallback(self.CHECK, "root", "t=stream-deep-check&add-lease=true",
3115 d.addCallback(_done)
3117 d.addCallback(self._count_leases, "root")
3118 d.addCallback(self._assert_leasecount, 2)
3119 d.addCallback(self._count_leases, "one")
3120 d.addCallback(self._assert_leasecount, 2)
3121 d.addCallback(self._count_leases, "mutable")
3122 d.addCallback(self._assert_leasecount, 2)
3124 d.addErrback(self.explain_web_error)