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 FakeStatsProvider:
38 stats = {'stats': {}, 'counters': {}}
41 class FakeClient(service.MultiService):
42 nodeid = "fake_nodeid"
43 nickname = "fake_nickname"
44 basedir = "fake_basedir"
45 def get_versions(self):
46 return {'allmydata': "fake",
51 introducer_furl = "None"
52 introducer_client = FakeIntroducerClient()
53 _all_upload_status = [upload.UploadStatus()]
54 _all_download_status = [download.DownloadStatus()]
55 _all_mapupdate_statuses = [servermap.UpdateStatus()]
56 _all_publish_statuses = [publish.PublishStatus()]
57 _all_retrieve_statuses = [retrieve.RetrieveStatus()]
58 convergence = "some random string"
59 stats_provider = FakeStatsProvider()
61 def connected_to_introducer(self):
64 def get_nickname_for_peerid(self, peerid):
67 def get_permuted_peers(self, service_name, key):
70 def create_node_from_uri(self, auri):
71 precondition(isinstance(auri, str), auri)
72 u = uri.from_string(auri)
73 if (INewDirectoryURI.providedBy(u)
74 or IReadonlyNewDirectoryURI.providedBy(u)):
75 return FakeDirectoryNode(self).init_from_uri(u)
76 if IFileURI.providedBy(u):
77 return FakeCHKFileNode(u, self)
78 assert IMutableFileURI.providedBy(u), u
79 return FakeMutableFileNode(self).init_from_uri(u)
81 def create_empty_dirnode(self):
82 n = FakeDirectoryNode(self)
84 d.addCallback(lambda res: n)
87 MUTABLE_SIZELIMIT = FakeMutableFileNode.MUTABLE_SIZELIMIT
88 def create_mutable_file(self, contents=""):
89 n = FakeMutableFileNode(self)
90 return n.create(contents)
92 def upload(self, uploadable):
93 d = uploadable.get_size()
94 d.addCallback(lambda size: uploadable.read(size))
97 n = create_chk_filenode(self, data)
98 results = upload.UploadResults()
99 results.uri = n.get_uri()
101 d.addCallback(_got_data)
104 def list_all_upload_statuses(self):
105 return self._all_upload_status
106 def list_all_download_statuses(self):
107 return self._all_download_status
108 def list_all_mapupdate_statuses(self):
109 return self._all_mapupdate_statuses
110 def list_all_publish_statuses(self):
111 return self._all_publish_statuses
112 def list_all_retrieve_statuses(self):
113 return self._all_retrieve_statuses
114 def list_all_helper_statuses(self):
117 class MyGetter(client.HTTPPageGetter):
118 handleStatus_206 = lambda self: self.handleStatus_200()
120 class HTTPClientHEADFactory(client.HTTPClientFactory):
123 def noPage(self, reason):
124 # Twisted-2.5.0 and earlier had a bug, in which they would raise an
125 # exception when the response to a HEAD request had no body (when in
126 # fact they are defined to never have a body). This was fixed in
127 # Twisted-8.0 . To work around this, we catch the
128 # PartialDownloadError and make it disappear.
129 if (reason.check(client.PartialDownloadError)
130 and self.method.upper() == "HEAD"):
133 return client.HTTPClientFactory.noPage(self, reason)
135 class HTTPClientGETFactory(client.HTTPClientFactory):
138 class WebMixin(object):
140 self.s = FakeClient()
141 self.s.startService()
142 self.staticdir = self.mktemp()
143 self.ws = s = webish.WebishServer(self.s, "0", staticdir=self.staticdir)
144 s.setServiceParent(self.s)
145 self.webish_port = port = s.listener._port.getHost().port
146 self.webish_url = "http://localhost:%d" % port
148 l = [ self.s.create_empty_dirnode() for x in range(6) ]
149 d = defer.DeferredList(l)
151 self.public_root = res[0][1]
152 assert interfaces.IDirectoryNode.providedBy(self.public_root), res
153 self.public_url = "/uri/" + self.public_root.get_uri()
154 self.private_root = res[1][1]
158 self._foo_uri = foo.get_uri()
159 self._foo_readonly_uri = foo.get_readonly_uri()
160 self._foo_verifycap = foo.get_verify_cap().to_string()
161 # NOTE: we ignore the deferred on all set_uri() calls, because we
162 # know the fake nodes do these synchronously
163 self.public_root.set_uri(u"foo", foo.get_uri())
165 self.BAR_CONTENTS, n, self._bar_txt_uri = self.makefile(0)
166 foo.set_uri(u"bar.txt", self._bar_txt_uri)
167 self._bar_txt_verifycap = n.get_verify_cap().to_string()
169 foo.set_uri(u"empty", res[3][1].get_uri())
170 sub_uri = res[4][1].get_uri()
171 self._sub_uri = sub_uri
172 foo.set_uri(u"sub", sub_uri)
173 sub = self.s.create_node_from_uri(sub_uri)
175 _ign, n, blocking_uri = self.makefile(1)
176 foo.set_uri(u"blockingfile", blocking_uri)
178 unicode_filename = u"n\u00fc.txt" # n u-umlaut . t x t
179 # ok, unicode calls it LATIN SMALL LETTER U WITH DIAERESIS but I
180 # still think of it as an umlaut
181 foo.set_uri(unicode_filename, self._bar_txt_uri)
183 _ign, n, baz_file = self.makefile(2)
184 self._baz_file_uri = baz_file
185 sub.set_uri(u"baz.txt", baz_file)
187 _ign, n, self._bad_file_uri = self.makefile(3)
188 # this uri should not be downloadable
189 del FakeCHKFileNode.all_contents[self._bad_file_uri]
192 self.public_root.set_uri(u"reedownlee", rodir.get_readonly_uri())
193 rodir.set_uri(u"nor", baz_file)
198 # public/foo/blockingfile
201 # public/foo/sub/baz.txt
203 # public/reedownlee/nor
204 self.NEWFILE_CONTENTS = "newfile contents\n"
206 return foo.get_metadata_for(u"bar.txt")
208 def _got_metadata(metadata):
209 self._bar_txt_metadata = metadata
210 d.addCallback(_got_metadata)
213 def makefile(self, number):
214 contents = "contents of file %s\n" % number
215 n = create_chk_filenode(self.s, contents)
216 return contents, n, n.get_uri()
219 return self.s.stopService()
221 def failUnlessIsBarDotTxt(self, res):
222 self.failUnlessEqual(res, self.BAR_CONTENTS, res)
224 def failUnlessIsBarJSON(self, res):
225 data = simplejson.loads(res)
226 self.failUnless(isinstance(data, list))
227 self.failUnlessEqual(data[0], u"filenode")
228 self.failUnless(isinstance(data[1], dict))
229 self.failIf(data[1]["mutable"])
230 self.failIf("rw_uri" in data[1]) # immutable
231 self.failUnlessEqual(data[1]["ro_uri"], self._bar_txt_uri)
232 self.failUnlessEqual(data[1]["verify_uri"], self._bar_txt_verifycap)
233 self.failUnlessEqual(data[1]["size"], len(self.BAR_CONTENTS))
235 def failUnlessIsFooJSON(self, res):
236 data = simplejson.loads(res)
237 self.failUnless(isinstance(data, list))
238 self.failUnlessEqual(data[0], "dirnode", res)
239 self.failUnless(isinstance(data[1], dict))
240 self.failUnless(data[1]["mutable"])
241 self.failUnless("rw_uri" in data[1]) # mutable
242 self.failUnlessEqual(data[1]["rw_uri"], self._foo_uri)
243 self.failUnlessEqual(data[1]["ro_uri"], self._foo_readonly_uri)
244 self.failUnlessEqual(data[1]["verify_uri"], self._foo_verifycap)
246 kidnames = sorted([unicode(n) for n in data[1]["children"]])
247 self.failUnlessEqual(kidnames,
248 [u"bar.txt", u"blockingfile", u"empty",
249 u"n\u00fc.txt", u"sub"])
250 kids = dict( [(unicode(name),value)
252 in data[1]["children"].iteritems()] )
253 self.failUnlessEqual(kids[u"sub"][0], "dirnode")
254 self.failUnless("metadata" in kids[u"sub"][1])
255 self.failUnless("ctime" in kids[u"sub"][1]["metadata"])
256 self.failUnless("mtime" in kids[u"sub"][1]["metadata"])
257 self.failUnlessEqual(kids[u"bar.txt"][0], "filenode")
258 self.failUnlessEqual(kids[u"bar.txt"][1]["size"], len(self.BAR_CONTENTS))
259 self.failUnlessEqual(kids[u"bar.txt"][1]["ro_uri"], self._bar_txt_uri)
260 self.failUnlessEqual(kids[u"bar.txt"][1]["verify_uri"],
261 self._bar_txt_verifycap)
262 self.failUnlessEqual(kids[u"bar.txt"][1]["metadata"]["ctime"],
263 self._bar_txt_metadata["ctime"])
264 self.failUnlessEqual(kids[u"n\u00fc.txt"][1]["ro_uri"],
267 def GET(self, urlpath, followRedirect=False, return_response=False,
269 # if return_response=True, this fires with (data, statuscode,
270 # respheaders) instead of just data.
271 assert not isinstance(urlpath, unicode)
272 url = self.webish_url + urlpath
273 factory = HTTPClientGETFactory(url, method="GET",
274 followRedirect=followRedirect, **kwargs)
275 reactor.connectTCP("localhost", self.webish_port, factory)
278 return (data, factory.status, factory.response_headers)
280 d.addCallback(_got_data)
281 return factory.deferred
283 def HEAD(self, urlpath, return_response=False, **kwargs):
284 # this requires some surgery, because twisted.web.client doesn't want
285 # to give us back the response headers.
286 factory = HTTPClientHEADFactory(urlpath, method="HEAD", **kwargs)
287 reactor.connectTCP("localhost", self.webish_port, factory)
290 return (data, factory.status, factory.response_headers)
292 d.addCallback(_got_data)
293 return factory.deferred
295 def PUT(self, urlpath, data, **kwargs):
296 url = self.webish_url + urlpath
297 return client.getPage(url, method="PUT", postdata=data, **kwargs)
299 def DELETE(self, urlpath):
300 url = self.webish_url + urlpath
301 return client.getPage(url, method="DELETE")
303 def POST(self, urlpath, followRedirect=False, **fields):
304 url = self.webish_url + urlpath
305 sepbase = "boogabooga"
309 form.append('Content-Disposition: form-data; name="_charset"')
313 for name, value in fields.iteritems():
314 if isinstance(value, tuple):
315 filename, value = value
316 form.append('Content-Disposition: form-data; name="%s"; '
317 'filename="%s"' % (name, filename.encode("utf-8")))
319 form.append('Content-Disposition: form-data; name="%s"' % name)
321 if isinstance(value, unicode):
322 value = value.encode("utf-8")
325 assert isinstance(value, str)
329 body = "\r\n".join(form) + "\r\n"
330 headers = {"content-type": "multipart/form-data; boundary=%s" % sepbase,
332 return client.getPage(url, method="POST", postdata=body,
333 headers=headers, followRedirect=followRedirect)
335 def shouldFail(self, res, expected_failure, which,
336 substring=None, response_substring=None):
337 if isinstance(res, failure.Failure):
338 res.trap(expected_failure)
340 self.failUnless(substring in str(res),
341 "substring '%s' not in '%s'"
342 % (substring, str(res)))
343 if response_substring:
344 self.failUnless(response_substring in res.value.response,
345 "response substring '%s' not in '%s'"
346 % (response_substring, res.value.response))
348 self.fail("%s was supposed to raise %s, not get '%s'" %
349 (which, expected_failure, res))
351 def shouldFail2(self, expected_failure, which, substring,
353 callable, *args, **kwargs):
354 assert substring is None or isinstance(substring, str)
355 assert response_substring is None or isinstance(response_substring, str)
356 d = defer.maybeDeferred(callable, *args, **kwargs)
358 if isinstance(res, failure.Failure):
359 res.trap(expected_failure)
361 self.failUnless(substring in str(res),
362 "%s: substring '%s' not in '%s'"
363 % (which, substring, str(res)))
364 if response_substring:
365 self.failUnless(response_substring in res.value.response,
366 "%s: response substring '%s' not in '%s'"
368 response_substring, res.value.response))
370 self.fail("%s was supposed to raise %s, not get '%s'" %
371 (which, expected_failure, res))
375 def should404(self, res, which):
376 if isinstance(res, failure.Failure):
377 res.trap(error.Error)
378 self.failUnlessEqual(res.value.status, "404")
380 self.fail("%s was supposed to Error(404), not get '%s'" %
383 def shouldHTTPError(self, res, which, code=None, substring=None,
384 response_substring=None):
385 if isinstance(res, failure.Failure):
386 res.trap(error.Error)
388 self.failUnlessEqual(res.value.status, str(code))
390 self.failUnless(substring in str(res),
391 "substring '%s' not in '%s'"
392 % (substring, str(res)))
393 if response_substring:
394 self.failUnless(response_substring in res.value.response,
395 "response substring '%s' not in '%s'"
396 % (response_substring, res.value.response))
398 self.fail("%s was supposed to Error(%s), not get '%s'" %
401 def shouldHTTPError2(self, which,
402 code=None, substring=None, response_substring=None,
403 callable=None, *args, **kwargs):
404 assert substring is None or isinstance(substring, str)
406 d = defer.maybeDeferred(callable, *args, **kwargs)
407 d.addBoth(self.shouldHTTPError, which,
408 code, substring, response_substring)
412 class Web(WebMixin, testutil.StallMixin, unittest.TestCase):
413 def test_create(self):
416 def test_welcome(self):
419 self.failUnless('Welcome To AllMyData' in res)
420 self.failUnless('Tahoe' in res)
422 self.s.basedir = 'web/test_welcome'
423 fileutil.make_dirs("web/test_welcome")
424 fileutil.make_dirs("web/test_welcome/private")
426 d.addCallback(_check)
429 def test_provisioning(self):
430 d = self.GET("/provisioning/")
432 self.failUnless('Tahoe Provisioning Tool' in res)
433 fields = {'filled': True,
434 "num_users": int(50e3),
435 "files_per_user": 1000,
436 "space_per_user": int(1e9),
437 "sharing_ratio": 1.0,
438 "encoding_parameters": "3-of-10-5",
440 "ownership_mode": "A",
441 "download_rate": 100,
446 return self.POST("/provisioning/", **fields)
448 d.addCallback(_check)
450 self.failUnless('Tahoe Provisioning Tool' in res)
451 self.failUnless("Share space consumed: 167.01TB" in res)
453 fields = {'filled': True,
454 "num_users": int(50e6),
455 "files_per_user": 1000,
456 "space_per_user": int(5e9),
457 "sharing_ratio": 1.0,
458 "encoding_parameters": "25-of-100-50",
459 "num_servers": 30000,
460 "ownership_mode": "E",
461 "drive_failure_model": "U",
463 "download_rate": 1000,
468 return self.POST("/provisioning/", **fields)
469 d.addCallback(_check2)
471 self.failUnless("Share space consumed: huge!" in res)
472 fields = {'filled': True}
473 return self.POST("/provisioning/", **fields)
474 d.addCallback(_check3)
476 self.failUnless("Share space consumed:" in res)
477 d.addCallback(_check4)
480 def test_reliability_tool(self):
482 from allmydata import reliability
483 _hush_pyflakes = reliability
485 raise unittest.SkipTest("reliability tool requires NumPy")
487 d = self.GET("/reliability/")
489 self.failUnless('Tahoe Reliability Tool' in res)
490 fields = {'drive_lifetime': "8Y",
495 "check_period": "1M",
496 "report_period": "3M",
499 return self.POST("/reliability/", **fields)
501 d.addCallback(_check)
503 self.failUnless('Tahoe Reliability Tool' in res)
504 r = r'Probability of loss \(no maintenance\):\s+<span>0.033591'
505 self.failUnless(re.search(r, res), res)
506 d.addCallback(_check2)
509 def test_status(self):
510 dl_num = self.s.list_all_download_statuses()[0].get_counter()
511 ul_num = self.s.list_all_upload_statuses()[0].get_counter()
512 mu_num = self.s.list_all_mapupdate_statuses()[0].get_counter()
513 pub_num = self.s.list_all_publish_statuses()[0].get_counter()
514 ret_num = self.s.list_all_retrieve_statuses()[0].get_counter()
515 d = self.GET("/status", followRedirect=True)
517 self.failUnless('Upload and Download Status' in res, res)
518 self.failUnless('"down-%d"' % dl_num in res, res)
519 self.failUnless('"up-%d"' % ul_num in res, res)
520 self.failUnless('"mapupdate-%d"' % mu_num in res, res)
521 self.failUnless('"publish-%d"' % pub_num in res, res)
522 self.failUnless('"retrieve-%d"' % ret_num in res, res)
523 d.addCallback(_check)
524 d.addCallback(lambda res: self.GET("/status/?t=json"))
525 def _check_json(res):
526 data = simplejson.loads(res)
527 self.failUnless(isinstance(data, dict))
528 active = data["active"]
529 # TODO: test more. We need a way to fake an active operation
531 d.addCallback(_check_json)
533 d.addCallback(lambda res: self.GET("/status/down-%d" % dl_num))
535 self.failUnless("File Download Status" in res, res)
536 d.addCallback(_check_dl)
537 d.addCallback(lambda res: self.GET("/status/up-%d" % ul_num))
539 self.failUnless("File Upload Status" in res, res)
540 d.addCallback(_check_ul)
541 d.addCallback(lambda res: self.GET("/status/mapupdate-%d" % mu_num))
542 def _check_mapupdate(res):
543 self.failUnless("Mutable File Servermap Update Status" in res, res)
544 d.addCallback(_check_mapupdate)
545 d.addCallback(lambda res: self.GET("/status/publish-%d" % pub_num))
546 def _check_publish(res):
547 self.failUnless("Mutable File Publish Status" in res, res)
548 d.addCallback(_check_publish)
549 d.addCallback(lambda res: self.GET("/status/retrieve-%d" % ret_num))
550 def _check_retrieve(res):
551 self.failUnless("Mutable File Retrieve Status" in res, res)
552 d.addCallback(_check_retrieve)
556 def test_status_numbers(self):
557 drrm = status.DownloadResultsRendererMixin()
558 self.failUnlessEqual(drrm.render_time(None, None), "")
559 self.failUnlessEqual(drrm.render_time(None, 2.5), "2.50s")
560 self.failUnlessEqual(drrm.render_time(None, 0.25), "250ms")
561 self.failUnlessEqual(drrm.render_time(None, 0.0021), "2.1ms")
562 self.failUnlessEqual(drrm.render_time(None, 0.000123), "123us")
563 self.failUnlessEqual(drrm.render_rate(None, None), "")
564 self.failUnlessEqual(drrm.render_rate(None, 2500000), "2.50MBps")
565 self.failUnlessEqual(drrm.render_rate(None, 30100), "30.1kBps")
566 self.failUnlessEqual(drrm.render_rate(None, 123), "123Bps")
568 urrm = status.UploadResultsRendererMixin()
569 self.failUnlessEqual(urrm.render_time(None, None), "")
570 self.failUnlessEqual(urrm.render_time(None, 2.5), "2.50s")
571 self.failUnlessEqual(urrm.render_time(None, 0.25), "250ms")
572 self.failUnlessEqual(urrm.render_time(None, 0.0021), "2.1ms")
573 self.failUnlessEqual(urrm.render_time(None, 0.000123), "123us")
574 self.failUnlessEqual(urrm.render_rate(None, None), "")
575 self.failUnlessEqual(urrm.render_rate(None, 2500000), "2.50MBps")
576 self.failUnlessEqual(urrm.render_rate(None, 30100), "30.1kBps")
577 self.failUnlessEqual(urrm.render_rate(None, 123), "123Bps")
579 def test_GET_FILEURL(self):
580 d = self.GET(self.public_url + "/foo/bar.txt")
581 d.addCallback(self.failUnlessIsBarDotTxt)
584 def test_GET_FILEURL_range(self):
585 headers = {"range": "bytes=1-10"}
586 d = self.GET(self.public_url + "/foo/bar.txt", headers=headers,
587 return_response=True)
588 def _got((res, status, headers)):
589 self.failUnlessEqual(int(status), 206)
590 self.failUnless(headers.has_key("content-range"))
591 self.failUnlessEqual(headers["content-range"][0],
592 "bytes 1-10/%d" % len(self.BAR_CONTENTS))
593 self.failUnlessEqual(res, self.BAR_CONTENTS[1:11])
597 def test_GET_FILEURL_partial_range(self):
598 headers = {"range": "bytes=5-"}
599 length = len(self.BAR_CONTENTS)
600 d = self.GET(self.public_url + "/foo/bar.txt", headers=headers,
601 return_response=True)
602 def _got((res, status, headers)):
603 self.failUnlessEqual(int(status), 206)
604 self.failUnless(headers.has_key("content-range"))
605 self.failUnlessEqual(headers["content-range"][0],
606 "bytes 5-%d/%d" % (length-1, length))
607 self.failUnlessEqual(res, self.BAR_CONTENTS[5:])
611 def test_HEAD_FILEURL_range(self):
612 headers = {"range": "bytes=1-10"}
613 d = self.HEAD(self.public_url + "/foo/bar.txt", headers=headers,
614 return_response=True)
615 def _got((res, status, headers)):
616 self.failUnlessEqual(res, "")
617 self.failUnlessEqual(int(status), 206)
618 self.failUnless(headers.has_key("content-range"))
619 self.failUnlessEqual(headers["content-range"][0],
620 "bytes 1-10/%d" % len(self.BAR_CONTENTS))
624 def test_HEAD_FILEURL_partial_range(self):
625 headers = {"range": "bytes=5-"}
626 length = len(self.BAR_CONTENTS)
627 d = self.HEAD(self.public_url + "/foo/bar.txt", headers=headers,
628 return_response=True)
629 def _got((res, status, headers)):
630 self.failUnlessEqual(int(status), 206)
631 self.failUnless(headers.has_key("content-range"))
632 self.failUnlessEqual(headers["content-range"][0],
633 "bytes 5-%d/%d" % (length-1, length))
637 def test_GET_FILEURL_range_bad(self):
638 headers = {"range": "BOGUS=fizbop-quarnak"}
639 d = self.shouldFail2(error.Error, "test_GET_FILEURL_range_bad",
641 "Syntactically invalid http range header",
642 self.GET, self.public_url + "/foo/bar.txt",
646 def test_HEAD_FILEURL(self):
647 d = self.HEAD(self.public_url + "/foo/bar.txt", return_response=True)
648 def _got((res, status, headers)):
649 self.failUnlessEqual(res, "")
650 self.failUnlessEqual(headers["content-length"][0],
651 str(len(self.BAR_CONTENTS)))
652 self.failUnlessEqual(headers["content-type"], ["text/plain"])
656 def test_GET_FILEURL_named(self):
657 base = "/file/%s" % urllib.quote(self._bar_txt_uri)
658 base2 = "/named/%s" % urllib.quote(self._bar_txt_uri)
659 d = self.GET(base + "/@@name=/blah.txt")
660 d.addCallback(self.failUnlessIsBarDotTxt)
661 d.addCallback(lambda res: self.GET(base + "/blah.txt"))
662 d.addCallback(self.failUnlessIsBarDotTxt)
663 d.addCallback(lambda res: self.GET(base + "/ignore/lots/blah.txt"))
664 d.addCallback(self.failUnlessIsBarDotTxt)
665 d.addCallback(lambda res: self.GET(base2 + "/@@name=/blah.txt"))
666 d.addCallback(self.failUnlessIsBarDotTxt)
667 save_url = base + "?save=true&filename=blah.txt"
668 d.addCallback(lambda res: self.GET(save_url))
669 d.addCallback(self.failUnlessIsBarDotTxt) # TODO: check headers
670 u_filename = u"n\u00e9wer.txt" # n e-acute w e r . t x t
671 u_fn_e = urllib.quote(u_filename.encode("utf-8"))
672 u_url = base + "?save=true&filename=" + u_fn_e
673 d.addCallback(lambda res: self.GET(u_url))
674 d.addCallback(self.failUnlessIsBarDotTxt) # TODO: check headers
677 def test_PUT_FILEURL_named_bad(self):
678 base = "/file/%s" % urllib.quote(self._bar_txt_uri)
679 d = self.shouldFail2(error.Error, "test_PUT_FILEURL_named_bad",
681 "/file can only be used with GET or HEAD",
682 self.PUT, base + "/@@name=/blah.txt", "")
685 def test_GET_DIRURL_named_bad(self):
686 base = "/file/%s" % urllib.quote(self._foo_uri)
687 d = self.shouldFail2(error.Error, "test_PUT_DIRURL_named_bad",
690 self.GET, base + "/@@name=/blah.txt")
693 def test_GET_slash_file_bad(self):
694 d = self.shouldFail2(error.Error, "test_GET_slash_file_bad",
696 "/file must be followed by a file-cap and a name",
700 def test_GET_unhandled_URI_named(self):
701 contents, n, newuri = self.makefile(12)
702 verifier_cap = n.get_verify_cap().to_string()
703 base = "/file/%s" % urllib.quote(verifier_cap)
704 # client.create_node_from_uri() can't handle verify-caps
705 d = self.shouldFail2(error.Error, "GET_unhandled_URI_named",
707 "is not a valid file- or directory- cap",
711 def test_GET_unhandled_URI(self):
712 contents, n, newuri = self.makefile(12)
713 verifier_cap = n.get_verify_cap().to_string()
714 base = "/uri/%s" % urllib.quote(verifier_cap)
715 # client.create_node_from_uri() can't handle verify-caps
716 d = self.shouldFail2(error.Error, "test_GET_unhandled_URI",
718 "is not a valid file- or directory- cap",
722 def test_GET_FILE_URI(self):
723 base = "/uri/%s" % urllib.quote(self._bar_txt_uri)
725 d.addCallback(self.failUnlessIsBarDotTxt)
728 def test_GET_FILE_URI_badchild(self):
729 base = "/uri/%s/boguschild" % urllib.quote(self._bar_txt_uri)
730 errmsg = "Files have no children, certainly not named 'boguschild'"
731 d = self.shouldFail2(error.Error, "test_GET_FILE_URI_badchild",
732 "400 Bad Request", errmsg,
736 def test_PUT_FILE_URI_badchild(self):
737 base = "/uri/%s/boguschild" % urllib.quote(self._bar_txt_uri)
738 errmsg = "Cannot create directory 'boguschild', because its parent is a file, not a directory"
739 d = self.shouldFail2(error.Error, "test_GET_FILE_URI_badchild",
740 "400 Bad Request", errmsg,
744 def test_GET_FILEURL_save(self):
745 d = self.GET(self.public_url + "/foo/bar.txt?filename=bar.txt&save=true")
746 # TODO: look at the headers, expect a Content-Disposition: attachment
748 d.addCallback(self.failUnlessIsBarDotTxt)
751 def test_GET_FILEURL_missing(self):
752 d = self.GET(self.public_url + "/foo/missing")
753 d.addBoth(self.should404, "test_GET_FILEURL_missing")
756 def test_PUT_NEWFILEURL(self):
757 d = self.PUT(self.public_url + "/foo/new.txt", self.NEWFILE_CONTENTS)
758 # TODO: we lose the response code, so we can't check this
759 #self.failUnlessEqual(responsecode, 201)
760 d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"new.txt")
761 d.addCallback(lambda res:
762 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
763 self.NEWFILE_CONTENTS))
766 def test_PUT_NEWFILEURL_range_bad(self):
767 headers = {"content-range": "bytes 1-10/%d" % len(self.NEWFILE_CONTENTS)}
768 target = self.public_url + "/foo/new.txt"
769 d = self.shouldFail2(error.Error, "test_PUT_NEWFILEURL_range_bad",
770 "501 Not Implemented",
771 "Content-Range in PUT not yet supported",
772 # (and certainly not for immutable files)
773 self.PUT, target, self.NEWFILE_CONTENTS[1:11],
775 d.addCallback(lambda res:
776 self.failIfNodeHasChild(self._foo_node, u"new.txt"))
779 def test_PUT_NEWFILEURL_mutable(self):
780 d = self.PUT(self.public_url + "/foo/new.txt?mutable=true",
781 self.NEWFILE_CONTENTS)
782 # TODO: we lose the response code, so we can't check this
783 #self.failUnlessEqual(responsecode, 201)
785 u = uri.from_string_mutable_filenode(res)
786 self.failUnless(u.is_mutable())
787 self.failIf(u.is_readonly())
789 d.addCallback(_check_uri)
790 d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"new.txt")
791 d.addCallback(lambda res:
792 self.failUnlessMutableChildContentsAre(self._foo_node,
794 self.NEWFILE_CONTENTS))
797 def test_PUT_NEWFILEURL_mutable_toobig(self):
798 d = self.shouldFail2(error.Error, "test_PUT_NEWFILEURL_mutable_toobig",
799 "413 Request Entity Too Large",
800 "SDMF is limited to one segment, and 10001 > 10000",
802 self.public_url + "/foo/new.txt?mutable=true",
803 "b" * (self.s.MUTABLE_SIZELIMIT+1))
806 def test_PUT_NEWFILEURL_replace(self):
807 d = self.PUT(self.public_url + "/foo/bar.txt", self.NEWFILE_CONTENTS)
808 # TODO: we lose the response code, so we can't check this
809 #self.failUnlessEqual(responsecode, 200)
810 d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"bar.txt")
811 d.addCallback(lambda res:
812 self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
813 self.NEWFILE_CONTENTS))
816 def test_PUT_NEWFILEURL_bad_t(self):
817 d = self.shouldFail2(error.Error, "PUT_bad_t", "400 Bad Request",
818 "PUT to a file: bad t=bogus",
819 self.PUT, self.public_url + "/foo/bar.txt?t=bogus",
823 def test_PUT_NEWFILEURL_no_replace(self):
824 d = self.PUT(self.public_url + "/foo/bar.txt?replace=false",
825 self.NEWFILE_CONTENTS)
826 d.addBoth(self.shouldFail, error.Error, "PUT_NEWFILEURL_no_replace",
828 "There was already a child by that name, and you asked me "
832 def test_PUT_NEWFILEURL_mkdirs(self):
833 d = self.PUT(self.public_url + "/foo/newdir/new.txt", self.NEWFILE_CONTENTS)
835 d.addCallback(self.failUnlessURIMatchesChild, fn, u"newdir/new.txt")
836 d.addCallback(lambda res: self.failIfNodeHasChild(fn, u"new.txt"))
837 d.addCallback(lambda res: self.failUnlessNodeHasChild(fn, u"newdir"))
838 d.addCallback(lambda res:
839 self.failUnlessChildContentsAre(fn, u"newdir/new.txt",
840 self.NEWFILE_CONTENTS))
843 def test_PUT_NEWFILEURL_blocked(self):
844 d = self.PUT(self.public_url + "/foo/blockingfile/new.txt",
845 self.NEWFILE_CONTENTS)
846 d.addBoth(self.shouldFail, error.Error, "PUT_NEWFILEURL_blocked",
848 "Unable to create directory 'blockingfile': a file was in the way")
851 def test_DELETE_FILEURL(self):
852 d = self.DELETE(self.public_url + "/foo/bar.txt")
853 d.addCallback(lambda res:
854 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
857 def test_DELETE_FILEURL_missing(self):
858 d = self.DELETE(self.public_url + "/foo/missing")
859 d.addBoth(self.should404, "test_DELETE_FILEURL_missing")
862 def test_DELETE_FILEURL_missing2(self):
863 d = self.DELETE(self.public_url + "/missing/missing")
864 d.addBoth(self.should404, "test_DELETE_FILEURL_missing2")
867 def test_GET_FILEURL_json(self):
868 # twisted.web.http.parse_qs ignores any query args without an '=', so
869 # I can't do "GET /path?json", I have to do "GET /path/t=json"
870 # instead. This may make it tricky to emulate the S3 interface
872 d = self.GET(self.public_url + "/foo/bar.txt?t=json")
873 d.addCallback(self.failUnlessIsBarJSON)
876 def test_GET_FILEURL_json_missing(self):
877 d = self.GET(self.public_url + "/foo/missing?json")
878 d.addBoth(self.should404, "test_GET_FILEURL_json_missing")
881 def test_GET_FILEURL_uri(self):
882 d = self.GET(self.public_url + "/foo/bar.txt?t=uri")
884 self.failUnlessEqual(res, self._bar_txt_uri)
885 d.addCallback(_check)
886 d.addCallback(lambda res:
887 self.GET(self.public_url + "/foo/bar.txt?t=readonly-uri"))
889 # for now, for files, uris and readonly-uris are the same
890 self.failUnlessEqual(res, self._bar_txt_uri)
891 d.addCallback(_check2)
894 def test_GET_FILEURL_badtype(self):
895 d = self.shouldHTTPError2("GET t=bogus", 400, "Bad Request",
898 self.public_url + "/foo/bar.txt?t=bogus")
901 def test_GET_FILEURL_uri_missing(self):
902 d = self.GET(self.public_url + "/foo/missing?t=uri")
903 d.addBoth(self.should404, "test_GET_FILEURL_uri_missing")
906 def test_GET_DIRURL(self):
907 # the addSlash means we get a redirect here
908 # from /uri/$URI/foo/ , we need ../../../ to get back to the root
910 d = self.GET(self.public_url + "/foo", followRedirect=True)
912 self.failUnless(('<a href="%s">Return to Welcome page' % ROOT)
914 # the FILE reference points to a URI, but it should end in bar.txt
915 bar_url = ("%s/file/%s/@@named=/bar.txt" %
916 (ROOT, urllib.quote(self._bar_txt_uri)))
917 get_bar = "".join([r'<td>',
918 r'<a href="%s">bar.txt</a>' % bar_url,
921 r'\s+<td>%d</td>' % len(self.BAR_CONTENTS),
923 self.failUnless(re.search(get_bar, res), res)
924 for line in res.split("\n"):
925 # find the line that contains the delete button for bar.txt
926 if ("form action" in line and
927 'value="delete"' in line and
928 'value="bar.txt"' in line):
929 # the form target should use a relative URL
930 foo_url = urllib.quote("%s/uri/%s/" % (ROOT, self._foo_uri))
931 self.failUnless(('action="%s"' % foo_url) in line, line)
932 # and the when_done= should too
933 #done_url = urllib.quote(???)
934 #self.failUnless(('name="when_done" value="%s"' % done_url)
938 self.fail("unable to find delete-bar.txt line", res)
940 # the DIR reference just points to a URI
941 sub_url = ("%s/uri/%s/" % (ROOT, urllib.quote(self._sub_uri)))
942 get_sub = ((r'<td><a href="%s">sub</a></td>' % sub_url)
943 + r'\s+<td>DIR</td>')
944 self.failUnless(re.search(get_sub, res), res)
945 d.addCallback(_check)
947 # look at a directory which is readonly
948 d.addCallback(lambda res:
949 self.GET(self.public_url + "/reedownlee", followRedirect=True))
951 self.failUnless("(readonly)" in res, res)
952 self.failIf("Upload a file" in res, res)
953 d.addCallback(_check2)
955 # and at a directory that contains a readonly directory
956 d.addCallback(lambda res:
957 self.GET(self.public_url, followRedirect=True))
959 self.failUnless(re.search(r'<td><a href="[\.\/]+/uri/URI%3ADIR2-RO%3A[^"]+">reedownlee</a>'
960 '</td>\s+<td>DIR-RO</td>', res))
961 d.addCallback(_check3)
965 def test_GET_DIRURL_badtype(self):
966 d = self.shouldHTTPError2("test_GET_DIRURL_badtype",
970 self.public_url + "/foo?t=bogus")
973 def test_GET_DIRURL_json(self):
974 d = self.GET(self.public_url + "/foo?t=json")
975 d.addCallback(self.failUnlessIsFooJSON)
979 def test_POST_DIRURL_manifest_no_ophandle(self):
980 d = self.shouldFail2(error.Error,
981 "test_POST_DIRURL_manifest_no_ophandle",
983 "slow operation requires ophandle=",
984 self.POST, self.public_url, t="start-manifest")
987 def test_POST_DIRURL_manifest(self):
988 d = defer.succeed(None)
989 def getman(ignored, output):
990 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=125",
992 d.addCallback(self.wait_for_operation, "125")
993 d.addCallback(self.get_operation_results, "125", output)
995 d.addCallback(getman, None)
996 def _got_html(manifest):
997 self.failUnless("Manifest of SI=" in manifest)
998 self.failUnless("<td>sub</td>" in manifest)
999 self.failUnless(self._sub_uri in manifest)
1000 self.failUnless("<td>sub/baz.txt</td>" in manifest)
1001 d.addCallback(_got_html)
1003 # both t=status and unadorned GET should be identical
1004 d.addCallback(lambda res: self.GET("/operations/125"))
1005 d.addCallback(_got_html)
1007 d.addCallback(getman, "html")
1008 d.addCallback(_got_html)
1009 d.addCallback(getman, "text")
1010 def _got_text(manifest):
1011 self.failUnless("\nsub " + self._sub_uri + "\n" in manifest)
1012 self.failUnless("\nsub/baz.txt URI:CHK:" in manifest)
1013 d.addCallback(_got_text)
1014 d.addCallback(getman, "JSON")
1016 data = res["manifest"]
1018 for (path_list, cap) in data:
1019 got[tuple(path_list)] = cap
1020 self.failUnlessEqual(got[(u"sub",)], self._sub_uri)
1021 self.failUnless((u"sub",u"baz.txt") in got)
1022 self.failUnless("finished" in res)
1023 self.failUnless("origin" in res)
1024 self.failUnless("storage-index" in res)
1025 self.failUnless("verifycaps" in res)
1026 self.failUnless("stats" in res)
1027 d.addCallback(_got_json)
1030 def test_POST_DIRURL_deepsize_no_ophandle(self):
1031 d = self.shouldFail2(error.Error,
1032 "test_POST_DIRURL_deepsize_no_ophandle",
1034 "slow operation requires ophandle=",
1035 self.POST, self.public_url, t="start-deep-size")
1038 def test_POST_DIRURL_deepsize(self):
1039 d = self.POST(self.public_url + "/foo/?t=start-deep-size&ophandle=126",
1040 followRedirect=True)
1041 d.addCallback(self.wait_for_operation, "126")
1042 d.addCallback(self.get_operation_results, "126", "json")
1043 def _got_json(data):
1044 self.failUnlessEqual(data["finished"], True)
1046 self.failUnless(size > 1000)
1047 d.addCallback(_got_json)
1048 d.addCallback(self.get_operation_results, "126", "text")
1050 mo = re.search(r'^size: (\d+)$', res, re.M)
1051 self.failUnless(mo, res)
1052 size = int(mo.group(1))
1053 # with directories, the size varies.
1054 self.failUnless(size > 1000)
1055 d.addCallback(_got_text)
1058 def test_POST_DIRURL_deepstats_no_ophandle(self):
1059 d = self.shouldFail2(error.Error,
1060 "test_POST_DIRURL_deepstats_no_ophandle",
1062 "slow operation requires ophandle=",
1063 self.POST, self.public_url, t="start-deep-stats")
1066 def test_POST_DIRURL_deepstats(self):
1067 d = self.POST(self.public_url + "/foo/?t=start-deep-stats&ophandle=127",
1068 followRedirect=True)
1069 d.addCallback(self.wait_for_operation, "127")
1070 d.addCallback(self.get_operation_results, "127", "json")
1071 def _got_json(stats):
1072 expected = {"count-immutable-files": 3,
1073 "count-mutable-files": 0,
1074 "count-literal-files": 0,
1076 "count-directories": 3,
1077 "size-immutable-files": 57,
1078 "size-literal-files": 0,
1079 #"size-directories": 1912, # varies
1080 #"largest-directory": 1590,
1081 "largest-directory-children": 5,
1082 "largest-immutable-file": 19,
1084 for k,v in expected.iteritems():
1085 self.failUnlessEqual(stats[k], v,
1086 "stats[%s] was %s, not %s" %
1088 self.failUnlessEqual(stats["size-files-histogram"],
1090 d.addCallback(_got_json)
1093 def test_POST_DIRURL_stream_manifest(self):
1094 d = self.POST(self.public_url + "/foo/?t=stream-manifest")
1096 self.failUnless(res.endswith("\n"))
1097 units = [simplejson.loads(t) for t in res[:-1].split("\n")]
1098 self.failUnlessEqual(len(units), 7)
1099 self.failUnlessEqual(units[-1]["type"], "stats")
1101 self.failUnlessEqual(first["path"], [])
1102 self.failUnlessEqual(first["cap"], self._foo_uri)
1103 self.failUnlessEqual(first["type"], "directory")
1104 baz = [u for u in units[:-1] if u["cap"] == self._baz_file_uri][0]
1105 self.failUnlessEqual(baz["path"], ["sub", "baz.txt"])
1106 self.failIfEqual(baz["storage-index"], None)
1107 self.failIfEqual(baz["verifycap"], None)
1108 self.failIfEqual(baz["repaircap"], None)
1110 d.addCallback(_check)
1113 def test_GET_DIRURL_uri(self):
1114 d = self.GET(self.public_url + "/foo?t=uri")
1116 self.failUnlessEqual(res, self._foo_uri)
1117 d.addCallback(_check)
1120 def test_GET_DIRURL_readonly_uri(self):
1121 d = self.GET(self.public_url + "/foo?t=readonly-uri")
1123 self.failUnlessEqual(res, self._foo_readonly_uri)
1124 d.addCallback(_check)
1127 def test_PUT_NEWDIRURL(self):
1128 d = self.PUT(self.public_url + "/foo/newdir?t=mkdir", "")
1129 d.addCallback(lambda res:
1130 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1131 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1132 d.addCallback(self.failUnlessNodeKeysAre, [])
1135 def test_PUT_NEWDIRURL_exists(self):
1136 d = self.PUT(self.public_url + "/foo/sub?t=mkdir", "")
1137 d.addCallback(lambda res:
1138 self.failUnlessNodeHasChild(self._foo_node, u"sub"))
1139 d.addCallback(lambda res: self._foo_node.get(u"sub"))
1140 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
1143 def test_PUT_NEWDIRURL_blocked(self):
1144 d = self.shouldFail2(error.Error, "PUT_NEWDIRURL_blocked",
1145 "409 Conflict", "Unable to create directory 'bar.txt': a file was in the way",
1147 self.public_url + "/foo/bar.txt/sub?t=mkdir", "")
1148 d.addCallback(lambda res:
1149 self.failUnlessNodeHasChild(self._foo_node, u"sub"))
1150 d.addCallback(lambda res: self._foo_node.get(u"sub"))
1151 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
1154 def test_PUT_NEWDIRURL_mkdir_p(self):
1155 d = defer.succeed(None)
1156 d.addCallback(lambda res: self.POST(self.public_url + "/foo", t='mkdir', name='mkp'))
1157 d.addCallback(lambda res: self.failUnlessNodeHasChild(self._foo_node, u"mkp"))
1158 d.addCallback(lambda res: self._foo_node.get(u"mkp"))
1159 def mkdir_p(mkpnode):
1160 url = '/uri/%s?t=mkdir-p&path=/sub1/sub2' % urllib.quote(mkpnode.get_uri())
1162 def made_subsub(ssuri):
1163 d = self._foo_node.get_child_at_path(u"mkp/sub1/sub2")
1164 d.addCallback(lambda ssnode: self.failUnlessEqual(ssnode.get_uri(), ssuri))
1166 d.addCallback(lambda uri2: self.failUnlessEqual(uri2, ssuri))
1168 d.addCallback(made_subsub)
1170 d.addCallback(mkdir_p)
1173 def test_PUT_NEWDIRURL_mkdirs(self):
1174 d = self.PUT(self.public_url + "/foo/subdir/newdir?t=mkdir", "")
1175 d.addCallback(lambda res:
1176 self.failIfNodeHasChild(self._foo_node, u"newdir"))
1177 d.addCallback(lambda res:
1178 self.failUnlessNodeHasChild(self._foo_node, u"subdir"))
1179 d.addCallback(lambda res:
1180 self._foo_node.get_child_at_path(u"subdir/newdir"))
1181 d.addCallback(self.failUnlessNodeKeysAre, [])
1184 def test_DELETE_DIRURL(self):
1185 d = self.DELETE(self.public_url + "/foo")
1186 d.addCallback(lambda res:
1187 self.failIfNodeHasChild(self.public_root, u"foo"))
1190 def test_DELETE_DIRURL_missing(self):
1191 d = self.DELETE(self.public_url + "/foo/missing")
1192 d.addBoth(self.should404, "test_DELETE_DIRURL_missing")
1193 d.addCallback(lambda res:
1194 self.failUnlessNodeHasChild(self.public_root, u"foo"))
1197 def test_DELETE_DIRURL_missing2(self):
1198 d = self.DELETE(self.public_url + "/missing")
1199 d.addBoth(self.should404, "test_DELETE_DIRURL_missing2")
1202 def dump_root(self):
1204 w = webish.DirnodeWalkerMixin()
1205 def visitor(childpath, childnode, metadata):
1207 d = w.walk(self.public_root, visitor)
1210 def failUnlessNodeKeysAre(self, node, expected_keys):
1211 for k in expected_keys:
1212 assert isinstance(k, unicode)
1214 def _check(children):
1215 self.failUnlessEqual(sorted(children.keys()), sorted(expected_keys))
1216 d.addCallback(_check)
1218 def failUnlessNodeHasChild(self, node, name):
1219 assert isinstance(name, unicode)
1221 def _check(children):
1222 self.failUnless(name in children)
1223 d.addCallback(_check)
1225 def failIfNodeHasChild(self, node, name):
1226 assert isinstance(name, unicode)
1228 def _check(children):
1229 self.failIf(name in children)
1230 d.addCallback(_check)
1233 def failUnlessChildContentsAre(self, node, name, expected_contents):
1234 assert isinstance(name, unicode)
1235 d = node.get_child_at_path(name)
1236 d.addCallback(lambda node: node.download_to_data())
1237 def _check(contents):
1238 self.failUnlessEqual(contents, expected_contents)
1239 d.addCallback(_check)
1242 def failUnlessMutableChildContentsAre(self, node, name, expected_contents):
1243 assert isinstance(name, unicode)
1244 d = node.get_child_at_path(name)
1245 d.addCallback(lambda node: node.download_best_version())
1246 def _check(contents):
1247 self.failUnlessEqual(contents, expected_contents)
1248 d.addCallback(_check)
1251 def failUnlessChildURIIs(self, node, name, expected_uri):
1252 assert isinstance(name, unicode)
1253 d = node.get_child_at_path(name)
1255 self.failUnlessEqual(child.get_uri(), expected_uri.strip())
1256 d.addCallback(_check)
1259 def failUnlessURIMatchesChild(self, got_uri, node, name):
1260 assert isinstance(name, unicode)
1261 d = node.get_child_at_path(name)
1263 self.failUnlessEqual(got_uri.strip(), child.get_uri())
1264 d.addCallback(_check)
1267 def failUnlessCHKURIHasContents(self, got_uri, contents):
1268 self.failUnless(FakeCHKFileNode.all_contents[got_uri] == contents)
1270 def test_POST_upload(self):
1271 d = self.POST(self.public_url + "/foo", t="upload",
1272 file=("new.txt", self.NEWFILE_CONTENTS))
1274 d.addCallback(self.failUnlessURIMatchesChild, fn, u"new.txt")
1275 d.addCallback(lambda res:
1276 self.failUnlessChildContentsAre(fn, u"new.txt",
1277 self.NEWFILE_CONTENTS))
1280 def test_POST_upload_unicode(self):
1281 filename = u"n\u00e9wer.txt" # n e-acute w e r . t x t
1282 d = self.POST(self.public_url + "/foo", t="upload",
1283 file=(filename, self.NEWFILE_CONTENTS))
1285 d.addCallback(self.failUnlessURIMatchesChild, fn, filename)
1286 d.addCallback(lambda res:
1287 self.failUnlessChildContentsAre(fn, filename,
1288 self.NEWFILE_CONTENTS))
1289 target_url = self.public_url + "/foo/" + filename.encode("utf-8")
1290 d.addCallback(lambda res: self.GET(target_url))
1291 d.addCallback(lambda contents: self.failUnlessEqual(contents,
1292 self.NEWFILE_CONTENTS,
1296 def test_POST_upload_unicode_named(self):
1297 filename = u"n\u00e9wer.txt" # n e-acute w e r . t x t
1298 d = self.POST(self.public_url + "/foo", t="upload",
1300 file=("overridden", self.NEWFILE_CONTENTS))
1302 d.addCallback(self.failUnlessURIMatchesChild, fn, filename)
1303 d.addCallback(lambda res:
1304 self.failUnlessChildContentsAre(fn, filename,
1305 self.NEWFILE_CONTENTS))
1306 target_url = self.public_url + "/foo/" + filename.encode("utf-8")
1307 d.addCallback(lambda res: self.GET(target_url))
1308 d.addCallback(lambda contents: self.failUnlessEqual(contents,
1309 self.NEWFILE_CONTENTS,
1313 def test_POST_upload_no_link(self):
1314 d = self.POST("/uri", t="upload",
1315 file=("new.txt", self.NEWFILE_CONTENTS))
1316 def _check_upload_results(page):
1317 # this should be a page which describes the results of the upload
1318 # that just finished.
1319 self.failUnless("Upload Results:" in page)
1320 self.failUnless("URI:" in page)
1321 uri_re = re.compile("URI: <tt><span>(.*)</span>")
1322 mo = uri_re.search(page)
1323 self.failUnless(mo, page)
1324 new_uri = mo.group(1)
1326 d.addCallback(_check_upload_results)
1327 d.addCallback(self.failUnlessCHKURIHasContents, self.NEWFILE_CONTENTS)
1330 def test_POST_upload_no_link_whendone(self):
1331 d = self.POST("/uri", t="upload", when_done="/",
1332 file=("new.txt", self.NEWFILE_CONTENTS))
1333 d.addBoth(self.shouldRedirect, "/")
1336 def shouldRedirect2(self, which, checker, callable, *args, **kwargs):
1337 d = defer.maybeDeferred(callable, *args, **kwargs)
1339 if isinstance(res, failure.Failure):
1340 res.trap(error.PageRedirect)
1341 statuscode = res.value.status
1342 target = res.value.location
1343 return checker(statuscode, target)
1344 self.fail("%s: callable was supposed to redirect, not return '%s'"
1349 def test_POST_upload_no_link_whendone_results(self):
1350 def check(statuscode, target):
1351 self.failUnlessEqual(statuscode, str(http.FOUND))
1352 self.failUnless(target.startswith(self.webish_url), target)
1353 return client.getPage(target, method="GET")
1354 d = self.shouldRedirect2("test_POST_upload_no_link_whendone_results",
1356 self.POST, "/uri", t="upload",
1357 when_done="/uri/%(uri)s",
1358 file=("new.txt", self.NEWFILE_CONTENTS))
1359 d.addCallback(lambda res:
1360 self.failUnlessEqual(res, self.NEWFILE_CONTENTS))
1363 def test_POST_upload_no_link_mutable(self):
1364 d = self.POST("/uri", t="upload", mutable="true",
1365 file=("new.txt", self.NEWFILE_CONTENTS))
1366 def _check(new_uri):
1367 new_uri = new_uri.strip()
1368 self.new_uri = new_uri
1370 self.failUnless(IMutableFileURI.providedBy(u))
1371 self.failUnless(u.storage_index in FakeMutableFileNode.all_contents)
1372 n = self.s.create_node_from_uri(new_uri)
1373 return n.download_best_version()
1374 d.addCallback(_check)
1376 self.failUnlessEqual(data, self.NEWFILE_CONTENTS)
1377 return self.GET("/uri/%s" % urllib.quote(self.new_uri))
1378 d.addCallback(_check2)
1380 self.failUnlessEqual(data, self.NEWFILE_CONTENTS)
1381 return self.GET("/file/%s" % urllib.quote(self.new_uri))
1382 d.addCallback(_check3)
1384 self.failUnlessEqual(data, self.NEWFILE_CONTENTS)
1385 d.addCallback(_check4)
1388 def test_POST_upload_no_link_mutable_toobig(self):
1389 d = self.shouldFail2(error.Error,
1390 "test_POST_upload_no_link_mutable_toobig",
1391 "413 Request Entity Too Large",
1392 "SDMF is limited to one segment, and 10001 > 10000",
1394 "/uri", t="upload", mutable="true",
1396 "b" * (self.s.MUTABLE_SIZELIMIT+1)) )
1399 def test_POST_upload_mutable(self):
1400 # this creates a mutable file
1401 d = self.POST(self.public_url + "/foo", t="upload", mutable="true",
1402 file=("new.txt", self.NEWFILE_CONTENTS))
1404 d.addCallback(self.failUnlessURIMatchesChild, fn, u"new.txt")
1405 d.addCallback(lambda res:
1406 self.failUnlessMutableChildContentsAre(fn, u"new.txt",
1407 self.NEWFILE_CONTENTS))
1408 d.addCallback(lambda res: self._foo_node.get(u"new.txt"))
1410 self.failUnless(IMutableFileNode.providedBy(newnode))
1411 self.failUnless(newnode.is_mutable())
1412 self.failIf(newnode.is_readonly())
1413 self._mutable_node = newnode
1414 self._mutable_uri = newnode.get_uri()
1417 # now upload it again and make sure that the URI doesn't change
1418 NEWER_CONTENTS = self.NEWFILE_CONTENTS + "newer\n"
1419 d.addCallback(lambda res:
1420 self.POST(self.public_url + "/foo", t="upload",
1422 file=("new.txt", NEWER_CONTENTS)))
1423 d.addCallback(self.failUnlessURIMatchesChild, fn, u"new.txt")
1424 d.addCallback(lambda res:
1425 self.failUnlessMutableChildContentsAre(fn, u"new.txt",
1427 d.addCallback(lambda res: self._foo_node.get(u"new.txt"))
1429 self.failUnless(IMutableFileNode.providedBy(newnode))
1430 self.failUnless(newnode.is_mutable())
1431 self.failIf(newnode.is_readonly())
1432 self.failUnlessEqual(self._mutable_uri, newnode.get_uri())
1433 d.addCallback(_got2)
1435 # upload a second time, using PUT instead of POST
1436 NEW2_CONTENTS = NEWER_CONTENTS + "overwrite with PUT\n"
1437 d.addCallback(lambda res:
1438 self.PUT(self.public_url + "/foo/new.txt", NEW2_CONTENTS))
1439 d.addCallback(self.failUnlessURIMatchesChild, fn, u"new.txt")
1440 d.addCallback(lambda res:
1441 self.failUnlessMutableChildContentsAre(fn, u"new.txt",
1444 # finally list the directory, since mutable files are displayed
1445 # slightly differently
1447 d.addCallback(lambda res:
1448 self.GET(self.public_url + "/foo/",
1449 followRedirect=True))
1450 def _check_page(res):
1451 # TODO: assert more about the contents
1452 self.failUnless("SSK" in res)
1454 d.addCallback(_check_page)
1456 d.addCallback(lambda res: self._foo_node.get(u"new.txt"))
1458 self.failUnless(IMutableFileNode.providedBy(newnode))
1459 self.failUnless(newnode.is_mutable())
1460 self.failIf(newnode.is_readonly())
1461 self.failUnlessEqual(self._mutable_uri, newnode.get_uri())
1462 d.addCallback(_got3)
1464 # look at the JSON form of the enclosing directory
1465 d.addCallback(lambda res:
1466 self.GET(self.public_url + "/foo/?t=json",
1467 followRedirect=True))
1468 def _check_page_json(res):
1469 parsed = simplejson.loads(res)
1470 self.failUnlessEqual(parsed[0], "dirnode")
1471 children = dict( [(unicode(name),value)
1473 in parsed[1]["children"].iteritems()] )
1474 self.failUnless("new.txt" in children)
1475 new_json = children["new.txt"]
1476 self.failUnlessEqual(new_json[0], "filenode")
1477 self.failUnless(new_json[1]["mutable"])
1478 self.failUnlessEqual(new_json[1]["rw_uri"], self._mutable_uri)
1479 ro_uri = unicode(self._mutable_node.get_readonly().to_string())
1480 self.failUnlessEqual(new_json[1]["ro_uri"], ro_uri)
1481 d.addCallback(_check_page_json)
1483 # and the JSON form of the file
1484 d.addCallback(lambda res:
1485 self.GET(self.public_url + "/foo/new.txt?t=json"))
1486 def _check_file_json(res):
1487 parsed = simplejson.loads(res)
1488 self.failUnlessEqual(parsed[0], "filenode")
1489 self.failUnless(parsed[1]["mutable"])
1490 self.failUnlessEqual(parsed[1]["rw_uri"], self._mutable_uri)
1491 ro_uri = unicode(self._mutable_node.get_readonly().to_string())
1492 self.failUnlessEqual(parsed[1]["ro_uri"], ro_uri)
1493 d.addCallback(_check_file_json)
1495 # and look at t=uri and t=readonly-uri
1496 d.addCallback(lambda res:
1497 self.GET(self.public_url + "/foo/new.txt?t=uri"))
1498 d.addCallback(lambda res: self.failUnlessEqual(res, self._mutable_uri))
1499 d.addCallback(lambda res:
1500 self.GET(self.public_url + "/foo/new.txt?t=readonly-uri"))
1501 def _check_ro_uri(res):
1502 ro_uri = unicode(self._mutable_node.get_readonly().to_string())
1503 self.failUnlessEqual(res, ro_uri)
1504 d.addCallback(_check_ro_uri)
1506 # make sure we can get to it from /uri/URI
1507 d.addCallback(lambda res:
1508 self.GET("/uri/%s" % urllib.quote(self._mutable_uri)))
1509 d.addCallback(lambda res:
1510 self.failUnlessEqual(res, NEW2_CONTENTS))
1512 # and that HEAD computes the size correctly
1513 d.addCallback(lambda res:
1514 self.HEAD(self.public_url + "/foo/new.txt",
1515 return_response=True))
1516 def _got_headers((res, status, headers)):
1517 self.failUnlessEqual(res, "")
1518 self.failUnlessEqual(headers["content-length"][0],
1519 str(len(NEW2_CONTENTS)))
1520 self.failUnlessEqual(headers["content-type"], ["text/plain"])
1521 d.addCallback(_got_headers)
1523 # make sure that size errors are displayed correctly for overwrite
1524 d.addCallback(lambda res:
1525 self.shouldFail2(error.Error,
1526 "test_POST_upload_mutable-toobig",
1527 "413 Request Entity Too Large",
1528 "SDMF is limited to one segment, and 10001 > 10000",
1530 self.public_url + "/foo", t="upload",
1533 "b" * (self.s.MUTABLE_SIZELIMIT+1)),
1536 d.addErrback(self.dump_error)
1539 def test_POST_upload_mutable_toobig(self):
1540 d = self.shouldFail2(error.Error,
1541 "test_POST_upload_no_link_mutable_toobig",
1542 "413 Request Entity Too Large",
1543 "SDMF is limited to one segment, and 10001 > 10000",
1545 self.public_url + "/foo",
1546 t="upload", mutable="true",
1548 "b" * (self.s.MUTABLE_SIZELIMIT+1)) )
1551 def dump_error(self, f):
1552 # if the web server returns an error code (like 400 Bad Request),
1553 # web.client.getPage puts the HTTP response body into the .response
1554 # attribute of the exception object that it gives back. It does not
1555 # appear in the Failure's repr(), so the ERROR that trial displays
1556 # will be rather terse and unhelpful. addErrback this method to the
1557 # end of your chain to get more information out of these errors.
1558 if f.check(error.Error):
1559 print "web.error.Error:"
1561 print f.value.response
1564 def test_POST_upload_replace(self):
1565 d = self.POST(self.public_url + "/foo", t="upload",
1566 file=("bar.txt", self.NEWFILE_CONTENTS))
1568 d.addCallback(self.failUnlessURIMatchesChild, fn, u"bar.txt")
1569 d.addCallback(lambda res:
1570 self.failUnlessChildContentsAre(fn, u"bar.txt",
1571 self.NEWFILE_CONTENTS))
1574 def test_POST_upload_no_replace_ok(self):
1575 d = self.POST(self.public_url + "/foo?replace=false", t="upload",
1576 file=("new.txt", self.NEWFILE_CONTENTS))
1577 d.addCallback(lambda res: self.GET(self.public_url + "/foo/new.txt"))
1578 d.addCallback(lambda res: self.failUnlessEqual(res,
1579 self.NEWFILE_CONTENTS))
1582 def test_POST_upload_no_replace_queryarg(self):
1583 d = self.POST(self.public_url + "/foo?replace=false", t="upload",
1584 file=("bar.txt", self.NEWFILE_CONTENTS))
1585 d.addBoth(self.shouldFail, error.Error,
1586 "POST_upload_no_replace_queryarg",
1588 "There was already a child by that name, and you asked me "
1589 "to not replace it")
1590 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
1591 d.addCallback(self.failUnlessIsBarDotTxt)
1594 def test_POST_upload_no_replace_field(self):
1595 d = self.POST(self.public_url + "/foo", t="upload", replace="false",
1596 file=("bar.txt", self.NEWFILE_CONTENTS))
1597 d.addBoth(self.shouldFail, error.Error, "POST_upload_no_replace_field",
1599 "There was already a child by that name, and you asked me "
1600 "to not replace it")
1601 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
1602 d.addCallback(self.failUnlessIsBarDotTxt)
1605 def test_POST_upload_whendone(self):
1606 d = self.POST(self.public_url + "/foo", t="upload", when_done="/THERE",
1607 file=("new.txt", self.NEWFILE_CONTENTS))
1608 d.addBoth(self.shouldRedirect, "/THERE")
1610 d.addCallback(lambda res:
1611 self.failUnlessChildContentsAre(fn, u"new.txt",
1612 self.NEWFILE_CONTENTS))
1615 def test_POST_upload_named(self):
1617 d = self.POST(self.public_url + "/foo", t="upload",
1618 name="new.txt", file=self.NEWFILE_CONTENTS)
1619 d.addCallback(self.failUnlessURIMatchesChild, fn, u"new.txt")
1620 d.addCallback(lambda res:
1621 self.failUnlessChildContentsAre(fn, u"new.txt",
1622 self.NEWFILE_CONTENTS))
1625 def test_POST_upload_named_badfilename(self):
1626 d = self.POST(self.public_url + "/foo", t="upload",
1627 name="slashes/are/bad.txt", file=self.NEWFILE_CONTENTS)
1628 d.addBoth(self.shouldFail, error.Error,
1629 "test_POST_upload_named_badfilename",
1631 "name= may not contain a slash",
1633 # make sure that nothing was added
1634 d.addCallback(lambda res:
1635 self.failUnlessNodeKeysAre(self._foo_node,
1636 [u"bar.txt", u"blockingfile",
1637 u"empty", u"n\u00fc.txt",
1641 def test_POST_FILEURL_check(self):
1642 bar_url = self.public_url + "/foo/bar.txt"
1643 d = self.POST(bar_url, t="check")
1645 self.failUnless("Healthy :" in res)
1646 d.addCallback(_check)
1647 redir_url = "http://allmydata.org/TARGET"
1648 def _check2(statuscode, target):
1649 self.failUnlessEqual(statuscode, str(http.FOUND))
1650 self.failUnlessEqual(target, redir_url)
1651 d.addCallback(lambda res:
1652 self.shouldRedirect2("test_POST_FILEURL_check",
1656 when_done=redir_url))
1657 d.addCallback(lambda res:
1658 self.POST(bar_url, t="check", return_to=redir_url))
1660 self.failUnless("Healthy :" in res)
1661 self.failUnless("Return to parent directory" in res)
1662 self.failUnless(redir_url in res)
1663 d.addCallback(_check3)
1665 d.addCallback(lambda res:
1666 self.POST(bar_url, t="check", output="JSON"))
1667 def _check_json(res):
1668 data = simplejson.loads(res)
1669 self.failUnless("storage-index" in data)
1670 self.failUnless(data["results"]["healthy"])
1671 d.addCallback(_check_json)
1675 def test_POST_FILEURL_check_and_repair(self):
1676 bar_url = self.public_url + "/foo/bar.txt"
1677 d = self.POST(bar_url, t="check", repair="true")
1679 self.failUnless("Healthy :" in res)
1680 d.addCallback(_check)
1681 redir_url = "http://allmydata.org/TARGET"
1682 def _check2(statuscode, target):
1683 self.failUnlessEqual(statuscode, str(http.FOUND))
1684 self.failUnlessEqual(target, redir_url)
1685 d.addCallback(lambda res:
1686 self.shouldRedirect2("test_POST_FILEURL_check_and_repair",
1689 t="check", repair="true",
1690 when_done=redir_url))
1691 d.addCallback(lambda res:
1692 self.POST(bar_url, t="check", return_to=redir_url))
1694 self.failUnless("Healthy :" in res)
1695 self.failUnless("Return to parent directory" in res)
1696 self.failUnless(redir_url in res)
1697 d.addCallback(_check3)
1700 def test_POST_DIRURL_check(self):
1701 foo_url = self.public_url + "/foo/"
1702 d = self.POST(foo_url, t="check")
1704 self.failUnless("Healthy :" in res, res)
1705 d.addCallback(_check)
1706 redir_url = "http://allmydata.org/TARGET"
1707 def _check2(statuscode, target):
1708 self.failUnlessEqual(statuscode, str(http.FOUND))
1709 self.failUnlessEqual(target, redir_url)
1710 d.addCallback(lambda res:
1711 self.shouldRedirect2("test_POST_DIRURL_check",
1715 when_done=redir_url))
1716 d.addCallback(lambda res:
1717 self.POST(foo_url, t="check", return_to=redir_url))
1719 self.failUnless("Healthy :" in res, res)
1720 self.failUnless("Return to parent directory" in res)
1721 self.failUnless(redir_url in res)
1722 d.addCallback(_check3)
1724 d.addCallback(lambda res:
1725 self.POST(foo_url, t="check", output="JSON"))
1726 def _check_json(res):
1727 data = simplejson.loads(res)
1728 self.failUnless("storage-index" in data)
1729 self.failUnless(data["results"]["healthy"])
1730 d.addCallback(_check_json)
1734 def test_POST_DIRURL_check_and_repair(self):
1735 foo_url = self.public_url + "/foo/"
1736 d = self.POST(foo_url, t="check", repair="true")
1738 self.failUnless("Healthy :" in res, res)
1739 d.addCallback(_check)
1740 redir_url = "http://allmydata.org/TARGET"
1741 def _check2(statuscode, target):
1742 self.failUnlessEqual(statuscode, str(http.FOUND))
1743 self.failUnlessEqual(target, redir_url)
1744 d.addCallback(lambda res:
1745 self.shouldRedirect2("test_POST_DIRURL_check_and_repair",
1748 t="check", repair="true",
1749 when_done=redir_url))
1750 d.addCallback(lambda res:
1751 self.POST(foo_url, t="check", return_to=redir_url))
1753 self.failUnless("Healthy :" in res)
1754 self.failUnless("Return to parent directory" in res)
1755 self.failUnless(redir_url in res)
1756 d.addCallback(_check3)
1759 def wait_for_operation(self, ignored, ophandle):
1760 url = "/operations/" + ophandle
1761 url += "?t=status&output=JSON"
1764 data = simplejson.loads(res)
1765 if not data["finished"]:
1766 d = self.stall(delay=1.0)
1767 d.addCallback(self.wait_for_operation, ophandle)
1773 def get_operation_results(self, ignored, ophandle, output=None):
1774 url = "/operations/" + ophandle
1777 url += "&output=" + output
1780 if output and output.lower() == "json":
1781 return simplejson.loads(res)
1786 def test_POST_DIRURL_deepcheck_no_ophandle(self):
1787 d = self.shouldFail2(error.Error,
1788 "test_POST_DIRURL_deepcheck_no_ophandle",
1790 "slow operation requires ophandle=",
1791 self.POST, self.public_url, t="start-deep-check")
1794 def test_POST_DIRURL_deepcheck(self):
1795 def _check_redirect(statuscode, target):
1796 self.failUnlessEqual(statuscode, str(http.FOUND))
1797 self.failUnless(target.endswith("/operations/123"))
1798 d = self.shouldRedirect2("test_POST_DIRURL_deepcheck", _check_redirect,
1799 self.POST, self.public_url,
1800 t="start-deep-check", ophandle="123")
1801 d.addCallback(self.wait_for_operation, "123")
1802 def _check_json(data):
1803 self.failUnlessEqual(data["finished"], True)
1804 self.failUnlessEqual(data["count-objects-checked"], 8)
1805 self.failUnlessEqual(data["count-objects-healthy"], 8)
1806 d.addCallback(_check_json)
1807 d.addCallback(self.get_operation_results, "123", "html")
1808 def _check_html(res):
1809 self.failUnless("Objects Checked: <span>8</span>" in res)
1810 self.failUnless("Objects Healthy: <span>8</span>" in res)
1811 d.addCallback(_check_html)
1813 d.addCallback(lambda res:
1814 self.GET("/operations/123/"))
1815 d.addCallback(_check_html) # should be the same as without the slash
1817 d.addCallback(lambda res:
1818 self.shouldFail2(error.Error, "one", "404 Not Found",
1819 "No detailed results for SI bogus",
1820 self.GET, "/operations/123/bogus"))
1822 foo_si = self._foo_node.get_storage_index()
1823 foo_si_s = base32.b2a(foo_si)
1824 d.addCallback(lambda res:
1825 self.GET("/operations/123/%s?output=JSON" % foo_si_s))
1826 def _check_foo_json(res):
1827 data = simplejson.loads(res)
1828 self.failUnlessEqual(data["storage-index"], foo_si_s)
1829 self.failUnless(data["results"]["healthy"])
1830 d.addCallback(_check_foo_json)
1833 def test_POST_DIRURL_deepcheck_and_repair(self):
1834 d = self.POST(self.public_url, t="start-deep-check", repair="true",
1835 ophandle="124", output="json", followRedirect=True)
1836 d.addCallback(self.wait_for_operation, "124")
1837 def _check_json(data):
1838 self.failUnlessEqual(data["finished"], True)
1839 self.failUnlessEqual(data["count-objects-checked"], 8)
1840 self.failUnlessEqual(data["count-objects-healthy-pre-repair"], 8)
1841 self.failUnlessEqual(data["count-objects-unhealthy-pre-repair"], 0)
1842 self.failUnlessEqual(data["count-corrupt-shares-pre-repair"], 0)
1843 self.failUnlessEqual(data["count-repairs-attempted"], 0)
1844 self.failUnlessEqual(data["count-repairs-successful"], 0)
1845 self.failUnlessEqual(data["count-repairs-unsuccessful"], 0)
1846 self.failUnlessEqual(data["count-objects-healthy-post-repair"], 8)
1847 self.failUnlessEqual(data["count-objects-unhealthy-post-repair"], 0)
1848 self.failUnlessEqual(data["count-corrupt-shares-post-repair"], 0)
1849 d.addCallback(_check_json)
1850 d.addCallback(self.get_operation_results, "124", "html")
1851 def _check_html(res):
1852 self.failUnless("Objects Checked: <span>8</span>" in res)
1854 self.failUnless("Objects Healthy (before repair): <span>8</span>" in res)
1855 self.failUnless("Objects Unhealthy (before repair): <span>0</span>" in res)
1856 self.failUnless("Corrupt Shares (before repair): <span>0</span>" in res)
1858 self.failUnless("Repairs Attempted: <span>0</span>" in res)
1859 self.failUnless("Repairs Successful: <span>0</span>" in res)
1860 self.failUnless("Repairs Unsuccessful: <span>0</span>" in res)
1862 self.failUnless("Objects Healthy (after repair): <span>8</span>" in res)
1863 self.failUnless("Objects Unhealthy (after repair): <span>0</span>" in res)
1864 self.failUnless("Corrupt Shares (after repair): <span>0</span>" in res)
1865 d.addCallback(_check_html)
1868 def test_POST_FILEURL_bad_t(self):
1869 d = self.shouldFail2(error.Error, "POST_bad_t", "400 Bad Request",
1870 "POST to file: bad t=bogus",
1871 self.POST, self.public_url + "/foo/bar.txt",
1875 def test_POST_mkdir(self): # return value?
1876 d = self.POST(self.public_url + "/foo", t="mkdir", name="newdir")
1877 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1878 d.addCallback(self.failUnlessNodeKeysAre, [])
1881 def test_POST_mkdir_2(self):
1882 d = self.POST(self.public_url + "/foo/newdir?t=mkdir", "")
1883 d.addCallback(lambda res:
1884 self.failUnlessNodeHasChild(self._foo_node, u"newdir"))
1885 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1886 d.addCallback(self.failUnlessNodeKeysAre, [])
1889 def test_POST_mkdirs_2(self):
1890 d = self.POST(self.public_url + "/foo/bardir/newdir?t=mkdir", "")
1891 d.addCallback(lambda res:
1892 self.failUnlessNodeHasChild(self._foo_node, u"bardir"))
1893 d.addCallback(lambda res: self._foo_node.get(u"bardir"))
1894 d.addCallback(lambda bardirnode: bardirnode.get(u"newdir"))
1895 d.addCallback(self.failUnlessNodeKeysAre, [])
1898 def test_POST_mkdir_no_parentdir_noredirect(self):
1899 d = self.POST("/uri?t=mkdir")
1900 def _after_mkdir(res):
1901 uri.NewDirectoryURI.init_from_string(res)
1902 d.addCallback(_after_mkdir)
1905 def test_POST_mkdir_no_parentdir_redirect(self):
1906 d = self.POST("/uri?t=mkdir&redirect_to_result=true")
1907 d.addBoth(self.shouldRedirect, None, statuscode='303')
1908 def _check_target(target):
1909 target = urllib.unquote(target)
1910 self.failUnless(target.startswith("uri/URI:DIR2:"), target)
1911 d.addCallback(_check_target)
1914 def test_POST_noparent_bad(self):
1915 d = self.shouldHTTPError2("POST /uri?t=bogus", 400, "Bad Request",
1916 "/uri accepts only PUT, PUT?t=mkdir, "
1917 "POST?t=upload, and POST?t=mkdir",
1918 self.POST, "/uri?t=bogus")
1921 def test_welcome_page_mkdir_button(self):
1922 # Fetch the welcome page.
1924 def _after_get_welcome_page(res):
1925 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)
1926 mo = MKDIR_BUTTON_RE.search(res)
1927 formaction = mo.group(1)
1929 formaname = mo.group(3)
1930 formavalue = mo.group(4)
1931 return (formaction, formt, formaname, formavalue)
1932 d.addCallback(_after_get_welcome_page)
1933 def _after_parse_form(res):
1934 (formaction, formt, formaname, formavalue) = res
1935 return self.POST("/%s?t=%s&%s=%s" % (formaction, formt, formaname, formavalue))
1936 d.addCallback(_after_parse_form)
1937 d.addBoth(self.shouldRedirect, None, statuscode='303')
1940 def test_POST_mkdir_replace(self): # return value?
1941 d = self.POST(self.public_url + "/foo", t="mkdir", name="sub")
1942 d.addCallback(lambda res: self._foo_node.get(u"sub"))
1943 d.addCallback(self.failUnlessNodeKeysAre, [])
1946 def test_POST_mkdir_no_replace_queryarg(self): # return value?
1947 d = self.POST(self.public_url + "/foo?replace=false", t="mkdir", name="sub")
1948 d.addBoth(self.shouldFail, error.Error,
1949 "POST_mkdir_no_replace_queryarg",
1951 "There was already a child by that name, and you asked me "
1952 "to not replace it")
1953 d.addCallback(lambda res: self._foo_node.get(u"sub"))
1954 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
1957 def test_POST_mkdir_no_replace_field(self): # return value?
1958 d = self.POST(self.public_url + "/foo", t="mkdir", name="sub",
1960 d.addBoth(self.shouldFail, error.Error, "POST_mkdir_no_replace_field",
1962 "There was already a child by that name, and you asked me "
1963 "to not replace it")
1964 d.addCallback(lambda res: self._foo_node.get(u"sub"))
1965 d.addCallback(self.failUnlessNodeKeysAre, [u"baz.txt"])
1968 def test_POST_mkdir_whendone_field(self):
1969 d = self.POST(self.public_url + "/foo",
1970 t="mkdir", name="newdir", when_done="/THERE")
1971 d.addBoth(self.shouldRedirect, "/THERE")
1972 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1973 d.addCallback(self.failUnlessNodeKeysAre, [])
1976 def test_POST_mkdir_whendone_queryarg(self):
1977 d = self.POST(self.public_url + "/foo?when_done=/THERE",
1978 t="mkdir", name="newdir")
1979 d.addBoth(self.shouldRedirect, "/THERE")
1980 d.addCallback(lambda res: self._foo_node.get(u"newdir"))
1981 d.addCallback(self.failUnlessNodeKeysAre, [])
1984 def test_POST_bad_t(self):
1985 d = self.shouldFail2(error.Error, "POST_bad_t", "400 Bad Request",
1986 "POST to a directory with bad t=BOGUS",
1987 self.POST, self.public_url + "/foo", t="BOGUS")
1990 def test_POST_set_children(self):
1991 contents9, n9, newuri9 = self.makefile(9)
1992 contents10, n10, newuri10 = self.makefile(10)
1993 contents11, n11, newuri11 = self.makefile(11)
1996 "atomic_added_1": [ "filenode", { "rw_uri": "%s",
1999 "ctime": 1002777696.7564139,
2000 "mtime": 1002777696.7564139
2003 "atomic_added_2": [ "filenode", { "rw_uri": "%s",
2006 "ctime": 1002777696.7564139,
2007 "mtime": 1002777696.7564139
2010 "atomic_added_3": [ "filenode", { "rw_uri": "%s",
2013 "ctime": 1002777696.7564139,
2014 "mtime": 1002777696.7564139
2017 }""" % (newuri9, newuri10, newuri11)
2019 url = self.webish_url + self.public_url + "/foo" + "?t=set_children"
2021 d = client.getPage(url, method="POST", postdata=reqbody)
2023 self.failUnlessURIMatchesChild(newuri9, self._foo_node, u"atomic_added_1")
2024 self.failUnlessURIMatchesChild(newuri10, self._foo_node, u"atomic_added_2")
2025 self.failUnlessURIMatchesChild(newuri11, self._foo_node, u"atomic_added_3")
2027 d.addCallback(_then)
2028 d.addErrback(self.dump_error)
2031 def test_POST_put_uri(self):
2032 contents, n, newuri = self.makefile(8)
2033 d = self.POST(self.public_url + "/foo", t="uri", name="new.txt", uri=newuri)
2034 d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"new.txt")
2035 d.addCallback(lambda res:
2036 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
2040 def test_POST_put_uri_replace(self):
2041 contents, n, newuri = self.makefile(8)
2042 d = self.POST(self.public_url + "/foo", t="uri", name="bar.txt", uri=newuri)
2043 d.addCallback(self.failUnlessURIMatchesChild, self._foo_node, u"bar.txt")
2044 d.addCallback(lambda res:
2045 self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
2049 def test_POST_put_uri_no_replace_queryarg(self):
2050 contents, n, newuri = self.makefile(8)
2051 d = self.POST(self.public_url + "/foo?replace=false", t="uri",
2052 name="bar.txt", uri=newuri)
2053 d.addBoth(self.shouldFail, error.Error,
2054 "POST_put_uri_no_replace_queryarg",
2056 "There was already a child by that name, and you asked me "
2057 "to not replace it")
2058 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
2059 d.addCallback(self.failUnlessIsBarDotTxt)
2062 def test_POST_put_uri_no_replace_field(self):
2063 contents, n, newuri = self.makefile(8)
2064 d = self.POST(self.public_url + "/foo", t="uri", replace="false",
2065 name="bar.txt", uri=newuri)
2066 d.addBoth(self.shouldFail, error.Error,
2067 "POST_put_uri_no_replace_field",
2069 "There was already a child by that name, and you asked me "
2070 "to not replace it")
2071 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
2072 d.addCallback(self.failUnlessIsBarDotTxt)
2075 def test_POST_delete(self):
2076 d = self.POST(self.public_url + "/foo", t="delete", name="bar.txt")
2077 d.addCallback(lambda res: self._foo_node.list())
2078 def _check(children):
2079 self.failIf(u"bar.txt" in children)
2080 d.addCallback(_check)
2083 def test_POST_rename_file(self):
2084 d = self.POST(self.public_url + "/foo", t="rename",
2085 from_name="bar.txt", to_name='wibble.txt')
2086 d.addCallback(lambda res:
2087 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
2088 d.addCallback(lambda res:
2089 self.failUnlessNodeHasChild(self._foo_node, u"wibble.txt"))
2090 d.addCallback(lambda res: self.GET(self.public_url + "/foo/wibble.txt"))
2091 d.addCallback(self.failUnlessIsBarDotTxt)
2092 d.addCallback(lambda res: self.GET(self.public_url + "/foo/wibble.txt?t=json"))
2093 d.addCallback(self.failUnlessIsBarJSON)
2096 def test_POST_rename_file_redundant(self):
2097 d = self.POST(self.public_url + "/foo", t="rename",
2098 from_name="bar.txt", to_name='bar.txt')
2099 d.addCallback(lambda res:
2100 self.failUnlessNodeHasChild(self._foo_node, u"bar.txt"))
2101 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt"))
2102 d.addCallback(self.failUnlessIsBarDotTxt)
2103 d.addCallback(lambda res: self.GET(self.public_url + "/foo/bar.txt?t=json"))
2104 d.addCallback(self.failUnlessIsBarJSON)
2107 def test_POST_rename_file_replace(self):
2108 # rename a file and replace a directory with it
2109 d = self.POST(self.public_url + "/foo", t="rename",
2110 from_name="bar.txt", to_name='empty')
2111 d.addCallback(lambda res:
2112 self.failIfNodeHasChild(self._foo_node, u"bar.txt"))
2113 d.addCallback(lambda res:
2114 self.failUnlessNodeHasChild(self._foo_node, u"empty"))
2115 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty"))
2116 d.addCallback(self.failUnlessIsBarDotTxt)
2117 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
2118 d.addCallback(self.failUnlessIsBarJSON)
2121 def test_POST_rename_file_no_replace_queryarg(self):
2122 # rename a file and replace a directory with it
2123 d = self.POST(self.public_url + "/foo?replace=false", t="rename",
2124 from_name="bar.txt", to_name='empty')
2125 d.addBoth(self.shouldFail, error.Error,
2126 "POST_rename_file_no_replace_queryarg",
2128 "There was already a child by that name, and you asked me "
2129 "to not replace it")
2130 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
2131 d.addCallback(self.failUnlessIsEmptyJSON)
2134 def test_POST_rename_file_no_replace_field(self):
2135 # rename a file and replace a directory with it
2136 d = self.POST(self.public_url + "/foo", t="rename", replace="false",
2137 from_name="bar.txt", to_name='empty')
2138 d.addBoth(self.shouldFail, error.Error,
2139 "POST_rename_file_no_replace_field",
2141 "There was already a child by that name, and you asked me "
2142 "to not replace it")
2143 d.addCallback(lambda res: self.GET(self.public_url + "/foo/empty?t=json"))
2144 d.addCallback(self.failUnlessIsEmptyJSON)
2147 def failUnlessIsEmptyJSON(self, res):
2148 data = simplejson.loads(res)
2149 self.failUnlessEqual(data[0], "dirnode", data)
2150 self.failUnlessEqual(len(data[1]["children"]), 0)
2152 def test_POST_rename_file_slash_fail(self):
2153 d = self.POST(self.public_url + "/foo", t="rename",
2154 from_name="bar.txt", to_name='kirk/spock.txt')
2155 d.addBoth(self.shouldFail, error.Error,
2156 "test_POST_rename_file_slash_fail",
2158 "to_name= may not contain a slash",
2160 d.addCallback(lambda res:
2161 self.failUnlessNodeHasChild(self._foo_node, u"bar.txt"))
2164 def test_POST_rename_dir(self):
2165 d = self.POST(self.public_url, t="rename",
2166 from_name="foo", to_name='plunk')
2167 d.addCallback(lambda res:
2168 self.failIfNodeHasChild(self.public_root, u"foo"))
2169 d.addCallback(lambda res:
2170 self.failUnlessNodeHasChild(self.public_root, u"plunk"))
2171 d.addCallback(lambda res: self.GET(self.public_url + "/plunk?t=json"))
2172 d.addCallback(self.failUnlessIsFooJSON)
2175 def shouldRedirect(self, res, target=None, statuscode=None, which=""):
2176 """ If target is not None then the redirection has to go to target. If
2177 statuscode is not None then the redirection has to be accomplished with
2178 that HTTP status code."""
2179 if not isinstance(res, failure.Failure):
2180 to_where = (target is None) and "somewhere" or ("to " + target)
2181 self.fail("%s: we were expecting to get redirected %s, not get an"
2182 " actual page: %s" % (which, to_where, res))
2183 res.trap(error.PageRedirect)
2184 if statuscode is not None:
2185 self.failUnlessEqual(res.value.status, statuscode,
2186 "%s: not a redirect" % which)
2187 if target is not None:
2188 # the PageRedirect does not seem to capture the uri= query arg
2189 # properly, so we can't check for it.
2190 realtarget = self.webish_url + target
2191 self.failUnlessEqual(res.value.location, realtarget,
2192 "%s: wrong target" % which)
2193 return res.value.location
2195 def test_GET_URI_form(self):
2196 base = "/uri?uri=%s" % self._bar_txt_uri
2197 # this is supposed to give us a redirect to /uri/$URI, plus arguments
2198 targetbase = "/uri/%s" % urllib.quote(self._bar_txt_uri)
2200 d.addBoth(self.shouldRedirect, targetbase)
2201 d.addCallback(lambda res: self.GET(base+"&filename=bar.txt"))
2202 d.addBoth(self.shouldRedirect, targetbase+"?filename=bar.txt")
2203 d.addCallback(lambda res: self.GET(base+"&t=json"))
2204 d.addBoth(self.shouldRedirect, targetbase+"?t=json")
2205 d.addCallback(self.log, "about to get file by uri")
2206 d.addCallback(lambda res: self.GET(base, followRedirect=True))
2207 d.addCallback(self.failUnlessIsBarDotTxt)
2208 d.addCallback(self.log, "got file by uri, about to get dir by uri")
2209 d.addCallback(lambda res: self.GET("/uri?uri=%s&t=json" % self._foo_uri,
2210 followRedirect=True))
2211 d.addCallback(self.failUnlessIsFooJSON)
2212 d.addCallback(self.log, "got dir by uri")
2216 def test_GET_URI_form_bad(self):
2217 d = self.shouldFail2(error.Error, "test_GET_URI_form_bad",
2218 "400 Bad Request", "GET /uri requires uri=",
2222 def test_GET_rename_form(self):
2223 d = self.GET(self.public_url + "/foo?t=rename-form&name=bar.txt",
2224 followRedirect=True)
2226 self.failUnless('name="when_done" value="."' in res, res)
2227 self.failUnless(re.search(r'name="from_name" value="bar\.txt"', res))
2228 d.addCallback(_check)
2231 def log(self, res, msg):
2232 #print "MSG: %s RES: %s" % (msg, res)
2236 def test_GET_URI_URL(self):
2237 base = "/uri/%s" % self._bar_txt_uri
2239 d.addCallback(self.failUnlessIsBarDotTxt)
2240 d.addCallback(lambda res: self.GET(base+"?filename=bar.txt"))
2241 d.addCallback(self.failUnlessIsBarDotTxt)
2242 d.addCallback(lambda res: self.GET(base+"?filename=bar.txt&save=true"))
2243 d.addCallback(self.failUnlessIsBarDotTxt)
2246 def test_GET_URI_URL_dir(self):
2247 base = "/uri/%s?t=json" % self._foo_uri
2249 d.addCallback(self.failUnlessIsFooJSON)
2252 def test_GET_URI_URL_missing(self):
2253 base = "/uri/%s" % self._bad_file_uri
2255 d.addBoth(self.shouldHTTPError, "test_GET_URI_URL_missing",
2256 http.GONE, response_substring="NotEnoughSharesError")
2257 # TODO: how can we exercise both sides of WebDownloadTarget.fail
2258 # here? we must arrange for a download to fail after target.open()
2259 # has been called, and then inspect the response to see that it is
2260 # shorter than we expected.
2263 def test_PUT_DIRURL_uri(self):
2264 d = self.s.create_empty_dirnode()
2266 new_uri = dn.get_uri()
2267 # replace /foo with a new (empty) directory
2268 d = self.PUT(self.public_url + "/foo?t=uri", new_uri)
2269 d.addCallback(lambda res:
2270 self.failUnlessEqual(res.strip(), new_uri))
2271 d.addCallback(lambda res:
2272 self.failUnlessChildURIIs(self.public_root,
2276 d.addCallback(_made_dir)
2279 def test_PUT_DIRURL_uri_noreplace(self):
2280 d = self.s.create_empty_dirnode()
2282 new_uri = dn.get_uri()
2283 # replace /foo with a new (empty) directory, but ask that
2284 # replace=false, so it should fail
2285 d = self.shouldFail2(error.Error, "test_PUT_DIRURL_uri_noreplace",
2286 "409 Conflict", "There was already a child by that name, and you asked me to not replace it",
2288 self.public_url + "/foo?t=uri&replace=false",
2290 d.addCallback(lambda res:
2291 self.failUnlessChildURIIs(self.public_root,
2295 d.addCallback(_made_dir)
2298 def test_PUT_DIRURL_bad_t(self):
2299 d = self.shouldFail2(error.Error, "test_PUT_DIRURL_bad_t",
2300 "400 Bad Request", "PUT to a directory",
2301 self.PUT, self.public_url + "/foo?t=BOGUS", "")
2302 d.addCallback(lambda res:
2303 self.failUnlessChildURIIs(self.public_root,
2308 def test_PUT_NEWFILEURL_uri(self):
2309 contents, n, new_uri = self.makefile(8)
2310 d = self.PUT(self.public_url + "/foo/new.txt?t=uri", new_uri)
2311 d.addCallback(lambda res: self.failUnlessEqual(res.strip(), new_uri))
2312 d.addCallback(lambda res:
2313 self.failUnlessChildContentsAre(self._foo_node, u"new.txt",
2317 def test_PUT_NEWFILEURL_uri_replace(self):
2318 contents, n, new_uri = self.makefile(8)
2319 d = self.PUT(self.public_url + "/foo/bar.txt?t=uri", new_uri)
2320 d.addCallback(lambda res: self.failUnlessEqual(res.strip(), new_uri))
2321 d.addCallback(lambda res:
2322 self.failUnlessChildContentsAre(self._foo_node, u"bar.txt",
2326 def test_PUT_NEWFILEURL_uri_no_replace(self):
2327 contents, n, new_uri = self.makefile(8)
2328 d = self.PUT(self.public_url + "/foo/bar.txt?t=uri&replace=false", new_uri)
2329 d.addBoth(self.shouldFail, error.Error, "PUT_NEWFILEURL_uri_no_replace",
2331 "There was already a child by that name, and you asked me "
2332 "to not replace it")
2335 def test_PUT_NEWFILE_URI(self):
2336 file_contents = "New file contents here\n"
2337 d = self.PUT("/uri", file_contents)
2339 assert isinstance(uri, str), uri
2340 self.failUnless(uri in FakeCHKFileNode.all_contents)
2341 self.failUnlessEqual(FakeCHKFileNode.all_contents[uri],
2343 return self.GET("/uri/%s" % uri)
2344 d.addCallback(_check)
2346 self.failUnlessEqual(res, file_contents)
2347 d.addCallback(_check2)
2350 def test_PUT_NEWFILE_URI_only_PUT(self):
2351 d = self.PUT("/uri?t=bogus", "")
2352 d.addBoth(self.shouldFail, error.Error,
2353 "PUT_NEWFILE_URI_only_PUT",
2355 "/uri accepts only PUT, PUT?t=mkdir, POST?t=upload, and POST?t=mkdir")
2358 def test_PUT_NEWFILE_URI_mutable(self):
2359 file_contents = "New file contents here\n"
2360 d = self.PUT("/uri?mutable=true", file_contents)
2361 def _check_mutable(uri):
2364 self.failUnless(IMutableFileURI.providedBy(u))
2365 self.failUnless(u.storage_index in FakeMutableFileNode.all_contents)
2366 n = self.s.create_node_from_uri(uri)
2367 return n.download_best_version()
2368 d.addCallback(_check_mutable)
2369 def _check2_mutable(data):
2370 self.failUnlessEqual(data, file_contents)
2371 d.addCallback(_check2_mutable)
2375 self.failUnless(uri.to_string() in FakeCHKFileNode.all_contents)
2376 self.failUnlessEqual(FakeCHKFileNode.all_contents[uri.to_string()],
2378 return self.GET("/uri/%s" % uri)
2379 d.addCallback(_check)
2381 self.failUnlessEqual(res, file_contents)
2382 d.addCallback(_check2)
2385 def test_PUT_mkdir(self):
2386 d = self.PUT("/uri?t=mkdir", "")
2388 n = self.s.create_node_from_uri(uri.strip())
2389 d2 = self.failUnlessNodeKeysAre(n, [])
2390 d2.addCallback(lambda res:
2391 self.GET("/uri/%s?t=json" % uri))
2393 d.addCallback(_check)
2394 d.addCallback(self.failUnlessIsEmptyJSON)
2397 def test_POST_check(self):
2398 d = self.POST(self.public_url + "/foo", t="check", name="bar.txt")
2400 # this returns a string form of the results, which are probably
2401 # None since we're using fake filenodes.
2402 # TODO: verify that the check actually happened, by changing
2403 # FakeCHKFileNode to count how many times .check() has been
2406 d.addCallback(_done)
2409 def test_bad_method(self):
2410 url = self.webish_url + self.public_url + "/foo/bar.txt"
2411 d = self.shouldHTTPError2("test_bad_method",
2412 501, "Not Implemented",
2413 "I don't know how to treat a BOGUS request.",
2414 client.getPage, url, method="BOGUS")
2417 def test_short_url(self):
2418 url = self.webish_url + "/uri"
2419 d = self.shouldHTTPError2("test_short_url", 501, "Not Implemented",
2420 "I don't know how to treat a DELETE request.",
2421 client.getPage, url, method="DELETE")
2424 def test_ophandle_bad(self):
2425 url = self.webish_url + "/operations/bogus?t=status"
2426 d = self.shouldHTTPError2("test_ophandle_bad", 404, "404 Not Found",
2427 "unknown/expired handle 'bogus'",
2428 client.getPage, url)
2431 def test_ophandle_cancel(self):
2432 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=128",
2433 followRedirect=True)
2434 d.addCallback(lambda ignored:
2435 self.GET("/operations/128?t=status&output=JSON"))
2437 data = simplejson.loads(res)
2438 self.failUnless("finished" in data, res)
2439 monitor = self.ws.root.child_operations.handles["128"][0]
2440 d = self.POST("/operations/128?t=cancel&output=JSON")
2442 data = simplejson.loads(res)
2443 self.failUnless("finished" in data, res)
2444 # t=cancel causes the handle to be forgotten
2445 self.failUnless(monitor.is_cancelled())
2446 d.addCallback(_check2)
2448 d.addCallback(_check1)
2449 d.addCallback(lambda ignored:
2450 self.shouldHTTPError2("test_ophandle_cancel",
2451 404, "404 Not Found",
2452 "unknown/expired handle '128'",
2454 "/operations/128?t=status&output=JSON"))
2457 def test_ophandle_retainfor(self):
2458 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=129&retain-for=60",
2459 followRedirect=True)
2460 d.addCallback(lambda ignored:
2461 self.GET("/operations/129?t=status&output=JSON&retain-for=0"))
2463 data = simplejson.loads(res)
2464 self.failUnless("finished" in data, res)
2465 d.addCallback(_check1)
2466 # the retain-for=0 will cause the handle to be expired very soon
2467 d.addCallback(self.stall, 2.0)
2468 d.addCallback(lambda ignored:
2469 self.shouldHTTPError2("test_ophandle_retainfor",
2470 404, "404 Not Found",
2471 "unknown/expired handle '129'",
2473 "/operations/129?t=status&output=JSON"))
2476 def test_ophandle_release_after_complete(self):
2477 d = self.POST(self.public_url + "/foo/?t=start-manifest&ophandle=130",
2478 followRedirect=True)
2479 d.addCallback(self.wait_for_operation, "130")
2480 d.addCallback(lambda ignored:
2481 self.GET("/operations/130?t=status&output=JSON&release-after-complete=true"))
2482 # the release-after-complete=true will cause the handle to be expired
2483 d.addCallback(lambda ignored:
2484 self.shouldHTTPError2("test_ophandle_release_after_complete",
2485 404, "404 Not Found",
2486 "unknown/expired handle '130'",
2488 "/operations/130?t=status&output=JSON"))
2491 def test_incident(self):
2492 d = self.POST("/report_incident", details="eek")
2494 self.failUnless("Thank you for your report!" in res, res)
2495 d.addCallback(_done)
2498 def test_static(self):
2499 webdir = os.path.join(self.staticdir, "subdir")
2500 fileutil.make_dirs(webdir)
2501 f = open(os.path.join(webdir, "hello.txt"), "wb")
2505 d = self.GET("/static/subdir/hello.txt")
2507 self.failUnlessEqual(res, "hello")
2508 d.addCallback(_check)
2512 class Util(unittest.TestCase):
2513 def test_abbreviate_time(self):
2514 self.failUnlessEqual(common.abbreviate_time(None), "")
2515 self.failUnlessEqual(common.abbreviate_time(1.234), "1.23s")
2516 self.failUnlessEqual(common.abbreviate_time(0.123), "123ms")
2517 self.failUnlessEqual(common.abbreviate_time(0.00123), "1.2ms")
2518 self.failUnlessEqual(common.abbreviate_time(0.000123), "123us")
2520 def test_abbreviate_rate(self):
2521 self.failUnlessEqual(common.abbreviate_rate(None), "")
2522 self.failUnlessEqual(common.abbreviate_rate(1234000), "1.23MBps")
2523 self.failUnlessEqual(common.abbreviate_rate(12340), "12.3kBps")
2524 self.failUnlessEqual(common.abbreviate_rate(123), "123Bps")
2526 def test_abbreviate_size(self):
2527 self.failUnlessEqual(common.abbreviate_size(None), "")
2528 self.failUnlessEqual(common.abbreviate_size(1.23*1000*1000*1000), "1.23GB")
2529 self.failUnlessEqual(common.abbreviate_size(1.23*1000*1000), "1.23MB")
2530 self.failUnlessEqual(common.abbreviate_size(1230), "1.2kB")
2531 self.failUnlessEqual(common.abbreviate_size(123), "123B")
2534 class Grid(GridTestMixin, WebErrorMixin, unittest.TestCase):
2536 def GET(self, urlpath, followRedirect=False, return_response=False,
2537 method="GET", clientnum=0, **kwargs):
2538 # if return_response=True, this fires with (data, statuscode,
2539 # respheaders) instead of just data.
2540 assert not isinstance(urlpath, unicode)
2541 url = self.client_baseurls[clientnum] + urlpath
2542 factory = HTTPClientGETFactory(url, method=method,
2543 followRedirect=followRedirect, **kwargs)
2544 reactor.connectTCP("localhost", self.client_webports[clientnum],factory)
2545 d = factory.deferred
2546 def _got_data(data):
2547 return (data, factory.status, factory.response_headers)
2549 d.addCallback(_got_data)
2550 return factory.deferred
2552 def CHECK(self, ign, which, args, clientnum=0):
2553 fileurl = self.fileurls[which]
2554 url = fileurl + "?" + args
2555 return self.GET(url, method="POST", clientnum=clientnum)
2557 def test_filecheck(self):
2558 self.basedir = "web/Grid/filecheck"
2560 c0 = self.g.clients[0]
2563 d = c0.upload(upload.Data(DATA, convergence=""))
2564 def _stash_uri(ur, which):
2565 self.uris[which] = ur.uri
2566 d.addCallback(_stash_uri, "good")
2567 d.addCallback(lambda ign:
2568 c0.upload(upload.Data(DATA+"1", convergence="")))
2569 d.addCallback(_stash_uri, "sick")
2570 d.addCallback(lambda ign:
2571 c0.upload(upload.Data(DATA+"2", convergence="")))
2572 d.addCallback(_stash_uri, "dead")
2573 def _stash_mutable_uri(n, which):
2574 self.uris[which] = n.get_uri()
2575 assert isinstance(self.uris[which], str)
2576 d.addCallback(lambda ign: c0.create_mutable_file(DATA+"3"))
2577 d.addCallback(_stash_mutable_uri, "corrupt")
2578 d.addCallback(lambda ign:
2579 c0.upload(upload.Data("literal", convergence="")))
2580 d.addCallback(_stash_uri, "small")
2582 def _compute_fileurls(ignored):
2584 for which in self.uris:
2585 self.fileurls[which] = "uri/" + urllib.quote(self.uris[which])
2586 d.addCallback(_compute_fileurls)
2588 def _clobber_shares(ignored):
2589 good_shares = self.find_shares(self.uris["good"])
2590 self.failUnlessEqual(len(good_shares), 10)
2591 sick_shares = self.find_shares(self.uris["sick"])
2592 os.unlink(sick_shares[0][2])
2593 dead_shares = self.find_shares(self.uris["dead"])
2594 for i in range(1, 10):
2595 os.unlink(dead_shares[i][2])
2596 c_shares = self.find_shares(self.uris["corrupt"])
2597 cso = CorruptShareOptions()
2598 cso.stdout = StringIO()
2599 cso.parseOptions([c_shares[0][2]])
2601 d.addCallback(_clobber_shares)
2603 d.addCallback(self.CHECK, "good", "t=check")
2604 def _got_html_good(res):
2605 self.failUnless("Healthy" in res, res)
2606 self.failIf("Not Healthy" in res, res)
2607 d.addCallback(_got_html_good)
2608 d.addCallback(self.CHECK, "good", "t=check&return_to=somewhere")
2609 def _got_html_good_return_to(res):
2610 self.failUnless("Healthy" in res, res)
2611 self.failIf("Not Healthy" in res, res)
2612 self.failUnless('<a href="somewhere">Return to parent directory'
2614 d.addCallback(_got_html_good_return_to)
2615 d.addCallback(self.CHECK, "good", "t=check&output=json")
2616 def _got_json_good(res):
2617 r = simplejson.loads(res)
2618 self.failUnlessEqual(r["summary"], "Healthy")
2619 self.failUnless(r["results"]["healthy"])
2620 self.failIf(r["results"]["needs-rebalancing"])
2621 self.failUnless(r["results"]["recoverable"])
2622 d.addCallback(_got_json_good)
2624 d.addCallback(self.CHECK, "small", "t=check")
2625 def _got_html_small(res):
2626 self.failUnless("Literal files are always healthy" in res, res)
2627 self.failIf("Not Healthy" in res, res)
2628 d.addCallback(_got_html_small)
2629 d.addCallback(self.CHECK, "small", "t=check&return_to=somewhere")
2630 def _got_html_small_return_to(res):
2631 self.failUnless("Literal files are always healthy" in res, res)
2632 self.failIf("Not Healthy" in res, res)
2633 self.failUnless('<a href="somewhere">Return to parent directory'
2635 d.addCallback(_got_html_small_return_to)
2636 d.addCallback(self.CHECK, "small", "t=check&output=json")
2637 def _got_json_small(res):
2638 r = simplejson.loads(res)
2639 self.failUnlessEqual(r["storage-index"], "")
2640 self.failUnless(r["results"]["healthy"])
2641 d.addCallback(_got_json_small)
2643 d.addCallback(self.CHECK, "sick", "t=check")
2644 def _got_html_sick(res):
2645 self.failUnless("Not Healthy" in res, res)
2646 d.addCallback(_got_html_sick)
2647 d.addCallback(self.CHECK, "sick", "t=check&output=json")
2648 def _got_json_sick(res):
2649 r = simplejson.loads(res)
2650 self.failUnlessEqual(r["summary"],
2651 "Not Healthy: 9 shares (enc 3-of-10)")
2652 self.failIf(r["results"]["healthy"])
2653 self.failIf(r["results"]["needs-rebalancing"])
2654 self.failUnless(r["results"]["recoverable"])
2655 d.addCallback(_got_json_sick)
2657 d.addCallback(self.CHECK, "dead", "t=check")
2658 def _got_html_dead(res):
2659 self.failUnless("Not Healthy" in res, res)
2660 d.addCallback(_got_html_dead)
2661 d.addCallback(self.CHECK, "dead", "t=check&output=json")
2662 def _got_json_dead(res):
2663 r = simplejson.loads(res)
2664 self.failUnlessEqual(r["summary"],
2665 "Not Healthy: 1 shares (enc 3-of-10)")
2666 self.failIf(r["results"]["healthy"])
2667 self.failIf(r["results"]["needs-rebalancing"])
2668 self.failIf(r["results"]["recoverable"])
2669 d.addCallback(_got_json_dead)
2671 d.addCallback(self.CHECK, "corrupt", "t=check&verify=true")
2672 def _got_html_corrupt(res):
2673 self.failUnless("Not Healthy! : Unhealthy" in res, res)
2674 d.addCallback(_got_html_corrupt)
2675 d.addCallback(self.CHECK, "corrupt", "t=check&verify=true&output=json")
2676 def _got_json_corrupt(res):
2677 r = simplejson.loads(res)
2678 self.failUnless("Unhealthy: 9 shares (enc 3-of-10)" in r["summary"],
2680 self.failIf(r["results"]["healthy"])
2681 self.failUnless(r["results"]["recoverable"])
2682 self.failUnlessEqual(r["results"]["count-shares-good"], 9)
2683 self.failUnlessEqual(r["results"]["count-corrupt-shares"], 1)
2684 d.addCallback(_got_json_corrupt)
2686 d.addErrback(self.explain_web_error)
2689 def test_repair_html(self):
2690 self.basedir = "web/Grid/repair_html"
2692 c0 = self.g.clients[0]
2695 d = c0.upload(upload.Data(DATA, convergence=""))
2696 def _stash_uri(ur, which):
2697 self.uris[which] = ur.uri
2698 d.addCallback(_stash_uri, "good")
2699 d.addCallback(lambda ign:
2700 c0.upload(upload.Data(DATA+"1", convergence="")))
2701 d.addCallback(_stash_uri, "sick")
2702 d.addCallback(lambda ign:
2703 c0.upload(upload.Data(DATA+"2", convergence="")))
2704 d.addCallback(_stash_uri, "dead")
2705 def _stash_mutable_uri(n, which):
2706 self.uris[which] = n.get_uri()
2707 assert isinstance(self.uris[which], str)
2708 d.addCallback(lambda ign: c0.create_mutable_file(DATA+"3"))
2709 d.addCallback(_stash_mutable_uri, "corrupt")
2711 def _compute_fileurls(ignored):
2713 for which in self.uris:
2714 self.fileurls[which] = "uri/" + urllib.quote(self.uris[which])
2715 d.addCallback(_compute_fileurls)
2717 def _clobber_shares(ignored):
2718 good_shares = self.find_shares(self.uris["good"])
2719 self.failUnlessEqual(len(good_shares), 10)
2720 sick_shares = self.find_shares(self.uris["sick"])
2721 os.unlink(sick_shares[0][2])
2722 dead_shares = self.find_shares(self.uris["dead"])
2723 for i in range(1, 10):
2724 os.unlink(dead_shares[i][2])
2725 c_shares = self.find_shares(self.uris["corrupt"])
2726 cso = CorruptShareOptions()
2727 cso.stdout = StringIO()
2728 cso.parseOptions([c_shares[0][2]])
2730 d.addCallback(_clobber_shares)
2732 d.addCallback(self.CHECK, "good", "t=check&repair=true")
2733 def _got_html_good(res):
2734 self.failUnless("Healthy" in res, res)
2735 self.failIf("Not Healthy" in res, res)
2736 self.failUnless("No repair necessary" in res, res)
2737 d.addCallback(_got_html_good)
2739 d.addCallback(self.CHECK, "sick", "t=check&repair=true")
2740 def _got_html_sick(res):
2741 self.failUnless("Healthy : healthy" in res, res)
2742 self.failIf("Not Healthy" in res, res)
2743 self.failUnless("Repair successful" in res, res)
2744 d.addCallback(_got_html_sick)
2746 # repair of a dead file will fail, of course, but it isn't yet
2747 # clear how this should be reported. Right now it shows up as
2750 #d.addCallback(self.CHECK, "dead", "t=check&repair=true")
2751 #def _got_html_dead(res):
2753 # self.failUnless("Healthy : healthy" in res, res)
2754 # self.failIf("Not Healthy" in res, res)
2755 # self.failUnless("No repair necessary" in res, res)
2756 #d.addCallback(_got_html_dead)
2758 d.addCallback(self.CHECK, "corrupt", "t=check&verify=true&repair=true")
2759 def _got_html_corrupt(res):
2760 self.failUnless("Healthy : Healthy" in res, res)
2761 self.failIf("Not Healthy" in res, res)
2762 self.failUnless("Repair successful" in res, res)
2763 d.addCallback(_got_html_corrupt)
2765 d.addErrback(self.explain_web_error)
2768 def test_repair_json(self):
2769 self.basedir = "web/Grid/repair_json"
2771 c0 = self.g.clients[0]
2774 d = c0.upload(upload.Data(DATA+"1", convergence=""))
2775 def _stash_uri(ur, which):
2776 self.uris[which] = ur.uri
2777 d.addCallback(_stash_uri, "sick")
2779 def _compute_fileurls(ignored):
2781 for which in self.uris:
2782 self.fileurls[which] = "uri/" + urllib.quote(self.uris[which])
2783 d.addCallback(_compute_fileurls)
2785 def _clobber_shares(ignored):
2786 sick_shares = self.find_shares(self.uris["sick"])
2787 os.unlink(sick_shares[0][2])
2788 d.addCallback(_clobber_shares)
2790 d.addCallback(self.CHECK, "sick", "t=check&repair=true&output=json")
2791 def _got_json_sick(res):
2792 r = simplejson.loads(res)
2793 self.failUnlessEqual(r["repair-attempted"], True)
2794 self.failUnlessEqual(r["repair-successful"], True)
2795 self.failUnlessEqual(r["pre-repair-results"]["summary"],
2796 "Not Healthy: 9 shares (enc 3-of-10)")
2797 self.failIf(r["pre-repair-results"]["results"]["healthy"])
2798 self.failUnlessEqual(r["post-repair-results"]["summary"], "healthy")
2799 self.failUnless(r["post-repair-results"]["results"]["healthy"])
2800 d.addCallback(_got_json_sick)
2802 d.addErrback(self.explain_web_error)
2805 def test_deep_check(self):
2806 self.basedir = "web/Grid/deep_check"
2808 c0 = self.g.clients[0]
2812 d = c0.create_empty_dirnode()
2813 def _stash_root_and_create_file(n):
2815 self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri()) + "/"
2816 return n.add_file(u"good", upload.Data(DATA, convergence=""))
2817 d.addCallback(_stash_root_and_create_file)
2818 def _stash_uri(fn, which):
2819 self.uris[which] = fn.get_uri()
2820 d.addCallback(_stash_uri, "good")
2821 d.addCallback(lambda ign:
2822 self.rootnode.add_file(u"small",
2823 upload.Data("literal",
2825 d.addCallback(_stash_uri, "small")
2827 d.addCallback(self.CHECK, "root", "t=stream-deep-check")
2829 units = [simplejson.loads(line)
2830 for line in res.splitlines()
2832 self.failUnlessEqual(len(units), 3+1)
2833 # should be parent-first
2835 self.failUnlessEqual(u0["path"], [])
2836 self.failUnlessEqual(u0["type"], "directory")
2837 self.failUnlessEqual(u0["cap"], self.rootnode.get_uri())
2838 u0cr = u0["check-results"]
2839 self.failUnlessEqual(u0cr["results"]["count-shares-good"], 10)
2841 ugood = [u for u in units
2842 if u["type"] == "file" and u["path"] == [u"good"]][0]
2843 self.failUnlessEqual(ugood["cap"], self.uris["good"])
2844 ugoodcr = ugood["check-results"]
2845 self.failUnlessEqual(ugoodcr["results"]["count-shares-good"], 10)
2848 self.failUnlessEqual(stats["type"], "stats")
2850 self.failUnlessEqual(s["count-immutable-files"], 1)
2851 self.failUnlessEqual(s["count-literal-files"], 1)
2852 self.failUnlessEqual(s["count-directories"], 1)
2853 d.addCallback(_done)
2855 d.addErrback(self.explain_web_error)
2858 def test_deep_check_and_repair(self):
2859 self.basedir = "web/Grid/deep_check_and_repair"
2861 c0 = self.g.clients[0]
2865 d = c0.create_empty_dirnode()
2866 def _stash_root_and_create_file(n):
2868 self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri()) + "/"
2869 return n.add_file(u"good", upload.Data(DATA, convergence=""))
2870 d.addCallback(_stash_root_and_create_file)
2871 def _stash_uri(fn, which):
2872 self.uris[which] = fn.get_uri()
2873 d.addCallback(_stash_uri, "good")
2874 d.addCallback(lambda ign:
2875 self.rootnode.add_file(u"small",
2876 upload.Data("literal",
2878 d.addCallback(_stash_uri, "small")
2879 d.addCallback(lambda ign:
2880 self.rootnode.add_file(u"sick",
2881 upload.Data(DATA+"1",
2883 d.addCallback(_stash_uri, "sick")
2884 #d.addCallback(lambda ign:
2885 # self.rootnode.add_file(u"dead",
2886 # upload.Data(DATA+"2",
2888 #d.addCallback(_stash_uri, "dead")
2890 #d.addCallback(lambda ign: c0.create_mutable_file("mutable"))
2891 #d.addCallback(lambda fn: self.rootnode.set_node(u"corrupt", fn))
2892 #d.addCallback(_stash_uri, "corrupt")
2894 def _clobber_shares(ignored):
2895 good_shares = self.find_shares(self.uris["good"])
2896 self.failUnlessEqual(len(good_shares), 10)
2897 sick_shares = self.find_shares(self.uris["sick"])
2898 os.unlink(sick_shares[0][2])
2899 #dead_shares = self.find_shares(self.uris["dead"])
2900 #for i in range(1, 10):
2901 # os.unlink(dead_shares[i][2])
2903 #c_shares = self.find_shares(self.uris["corrupt"])
2904 #cso = CorruptShareOptions()
2905 #cso.stdout = StringIO()
2906 #cso.parseOptions([c_shares[0][2]])
2908 d.addCallback(_clobber_shares)
2910 d.addCallback(self.CHECK, "root", "t=stream-deep-check&repair=true")
2912 units = [simplejson.loads(line)
2913 for line in res.splitlines()
2915 self.failUnlessEqual(len(units), 4+1)
2916 # should be parent-first
2918 self.failUnlessEqual(u0["path"], [])
2919 self.failUnlessEqual(u0["type"], "directory")
2920 self.failUnlessEqual(u0["cap"], self.rootnode.get_uri())
2921 u0crr = u0["check-and-repair-results"]
2922 self.failUnlessEqual(u0crr["repair-attempted"], False)
2923 self.failUnlessEqual(u0crr["pre-repair-results"]["results"]["count-shares-good"], 10)
2925 ugood = [u for u in units
2926 if u["type"] == "file" and u["path"] == [u"good"]][0]
2927 self.failUnlessEqual(ugood["cap"], self.uris["good"])
2928 ugoodcrr = ugood["check-and-repair-results"]
2929 self.failUnlessEqual(u0crr["repair-attempted"], False)
2930 self.failUnlessEqual(u0crr["pre-repair-results"]["results"]["count-shares-good"], 10)
2932 usick = [u for u in units
2933 if u["type"] == "file" and u["path"] == [u"sick"]][0]
2934 self.failUnlessEqual(usick["cap"], self.uris["sick"])
2935 usickcrr = usick["check-and-repair-results"]
2936 self.failUnlessEqual(usickcrr["repair-attempted"], True)
2937 self.failUnlessEqual(usickcrr["repair-successful"], True)
2938 self.failUnlessEqual(usickcrr["pre-repair-results"]["results"]["count-shares-good"], 9)
2939 self.failUnlessEqual(usickcrr["post-repair-results"]["results"]["count-shares-good"], 10)
2942 self.failUnlessEqual(stats["type"], "stats")
2944 self.failUnlessEqual(s["count-immutable-files"], 2)
2945 self.failUnlessEqual(s["count-literal-files"], 1)
2946 self.failUnlessEqual(s["count-directories"], 1)
2947 d.addCallback(_done)
2949 d.addErrback(self.explain_web_error)
2952 def _count_leases(self, ignored, which):
2953 u = self.uris[which]
2954 shares = self.find_shares(u)
2956 for shnum, serverid, fn in shares:
2957 if u.startswith("URI:SSK") or u.startswith("URI:DIR2"):
2958 sf = MutableShareFile(fn)
2959 num_leases = len(sf.debug_get_leases())
2960 elif u.startswith("URI:CHK"):
2962 num_leases = len(list(sf.iter_leases()))
2964 raise RuntimeError("can't get leases on %s" % u)
2965 lease_counts.append( (fn, num_leases) )
2968 def _assert_leasecount(self, lease_counts, expected):
2969 for (fn, num_leases) in lease_counts:
2970 if num_leases != expected:
2971 self.fail("expected %d leases, have %d, on %s" %
2972 (expected, num_leases, fn))
2974 def test_add_lease(self):
2975 self.basedir = "web/Grid/add_lease"
2976 self.set_up_grid(num_clients=2)
2977 c0 = self.g.clients[0]
2980 d = c0.upload(upload.Data(DATA, convergence=""))
2981 def _stash_uri(ur, which):
2982 self.uris[which] = ur.uri
2983 d.addCallback(_stash_uri, "one")
2984 d.addCallback(lambda ign:
2985 c0.upload(upload.Data(DATA+"1", convergence="")))
2986 d.addCallback(_stash_uri, "two")
2987 def _stash_mutable_uri(n, which):
2988 self.uris[which] = n.get_uri()
2989 assert isinstance(self.uris[which], str)
2990 d.addCallback(lambda ign: c0.create_mutable_file(DATA+"2"))
2991 d.addCallback(_stash_mutable_uri, "mutable")
2993 def _compute_fileurls(ignored):
2995 for which in self.uris:
2996 self.fileurls[which] = "uri/" + urllib.quote(self.uris[which])
2997 d.addCallback(_compute_fileurls)
2999 d.addCallback(self._count_leases, "one")
3000 d.addCallback(self._assert_leasecount, 1)
3001 d.addCallback(self._count_leases, "two")
3002 d.addCallback(self._assert_leasecount, 1)
3003 d.addCallback(self._count_leases, "mutable")
3004 d.addCallback(self._assert_leasecount, 1)
3006 d.addCallback(self.CHECK, "one", "t=check") # no add-lease
3007 def _got_html_good(res):
3008 self.failUnless("Healthy" in res, res)
3009 self.failIf("Not Healthy" in res, res)
3010 d.addCallback(_got_html_good)
3012 d.addCallback(self._count_leases, "one")
3013 d.addCallback(self._assert_leasecount, 1)
3014 d.addCallback(self._count_leases, "two")
3015 d.addCallback(self._assert_leasecount, 1)
3016 d.addCallback(self._count_leases, "mutable")
3017 d.addCallback(self._assert_leasecount, 1)
3019 # this CHECK uses the original client, which uses the same
3020 # lease-secrets, so it will just renew the original lease
3021 d.addCallback(self.CHECK, "one", "t=check&add-lease=true")
3022 d.addCallback(_got_html_good)
3024 d.addCallback(self._count_leases, "one")
3025 d.addCallback(self._assert_leasecount, 1)
3026 d.addCallback(self._count_leases, "two")
3027 d.addCallback(self._assert_leasecount, 1)
3028 d.addCallback(self._count_leases, "mutable")
3029 d.addCallback(self._assert_leasecount, 1)
3031 # this CHECK uses an alternate client, which adds a second lease
3032 d.addCallback(self.CHECK, "one", "t=check&add-lease=true", clientnum=1)
3033 d.addCallback(_got_html_good)
3035 d.addCallback(self._count_leases, "one")
3036 d.addCallback(self._assert_leasecount, 2)
3037 d.addCallback(self._count_leases, "two")
3038 d.addCallback(self._assert_leasecount, 1)
3039 d.addCallback(self._count_leases, "mutable")
3040 d.addCallback(self._assert_leasecount, 1)
3042 d.addCallback(self.CHECK, "mutable", "t=check&add-lease=true")
3043 d.addCallback(_got_html_good)
3045 d.addCallback(self._count_leases, "one")
3046 d.addCallback(self._assert_leasecount, 2)
3047 d.addCallback(self._count_leases, "two")
3048 d.addCallback(self._assert_leasecount, 1)
3049 d.addCallback(self._count_leases, "mutable")
3050 d.addCallback(self._assert_leasecount, 1)
3052 d.addCallback(self.CHECK, "mutable", "t=check&add-lease=true",
3054 d.addCallback(_got_html_good)
3056 d.addCallback(self._count_leases, "one")
3057 d.addCallback(self._assert_leasecount, 2)
3058 d.addCallback(self._count_leases, "two")
3059 d.addCallback(self._assert_leasecount, 1)
3060 d.addCallback(self._count_leases, "mutable")
3061 d.addCallback(self._assert_leasecount, 2)
3063 d.addErrback(self.explain_web_error)
3066 def test_deep_add_lease(self):
3067 self.basedir = "web/Grid/deep_add_lease"
3068 self.set_up_grid(num_clients=2)
3069 c0 = self.g.clients[0]
3073 d = c0.create_empty_dirnode()
3074 def _stash_root_and_create_file(n):
3076 self.uris["root"] = n.get_uri()
3077 self.fileurls["root"] = "uri/" + urllib.quote(n.get_uri()) + "/"
3078 return n.add_file(u"one", upload.Data(DATA, convergence=""))
3079 d.addCallback(_stash_root_and_create_file)
3080 def _stash_uri(fn, which):
3081 self.uris[which] = fn.get_uri()
3082 d.addCallback(_stash_uri, "one")
3083 d.addCallback(lambda ign:
3084 self.rootnode.add_file(u"small",
3085 upload.Data("literal",
3087 d.addCallback(_stash_uri, "small")
3089 d.addCallback(lambda ign: c0.create_mutable_file("mutable"))
3090 d.addCallback(lambda fn: self.rootnode.set_node(u"mutable", fn))
3091 d.addCallback(_stash_uri, "mutable")
3093 d.addCallback(self.CHECK, "root", "t=stream-deep-check") # no add-lease
3095 units = [simplejson.loads(line)
3096 for line in res.splitlines()
3098 # root, one, small, mutable, stats
3099 self.failUnlessEqual(len(units), 4+1)
3100 d.addCallback(_done)
3102 d.addCallback(self._count_leases, "root")
3103 d.addCallback(self._assert_leasecount, 1)
3104 d.addCallback(self._count_leases, "one")
3105 d.addCallback(self._assert_leasecount, 1)
3106 d.addCallback(self._count_leases, "mutable")
3107 d.addCallback(self._assert_leasecount, 1)
3109 d.addCallback(self.CHECK, "root", "t=stream-deep-check&add-lease=true")
3110 d.addCallback(_done)
3112 d.addCallback(self._count_leases, "root")
3113 d.addCallback(self._assert_leasecount, 1)
3114 d.addCallback(self._count_leases, "one")
3115 d.addCallback(self._assert_leasecount, 1)
3116 d.addCallback(self._count_leases, "mutable")
3117 d.addCallback(self._assert_leasecount, 1)
3119 d.addCallback(self.CHECK, "root", "t=stream-deep-check&add-lease=true",
3121 d.addCallback(_done)
3123 d.addCallback(self._count_leases, "root")
3124 d.addCallback(self._assert_leasecount, 2)
3125 d.addCallback(self._count_leases, "one")
3126 d.addCallback(self._assert_leasecount, 2)
3127 d.addCallback(self._count_leases, "mutable")
3128 d.addCallback(self._assert_leasecount, 2)
3130 d.addErrback(self.explain_web_error)